1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
//! Image units, views into specific planes of textures
use crate::ToGlEnum;
use crate::gl;
use crate::texture;
use crate::texture::GetFormatError;

#[derive(Debug)]
/// Represents an error related to the use of an Image Unit
pub enum ImageUnitError {
    /// The texture does not contain a mipmap at the requested level
    NoMipmapAtLevel(u32),
    /// This type of texture is not layered
    LayeringNotSupported(texture::Dimensions),
    /// The layer requested is out of the bounds of this texture
    LayerOutOfBounds(u32),
    /// The format of the texture and the requested format are not compatible
    BadFormatClass(usize, usize),
    /// Error while trying to get the format of the passed texture
    GetFormat(GetFormatError),
}

impl std::fmt::Display for ImageUnitError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use self::ImageUnitError::*;

        let desc = match *self {
            NoMipmapAtLevel(level) => write!(f, "No mipmap level {} found", level),
            LayeringNotSupported(kind) => write!(f, "Layering is not supported with textures of dimensions {:?}", kind),
            LayerOutOfBounds(layer) => write!(f, "Request layer {} is out of bounds", layer),
            BadFormatClass(tbits, ibits) => write!(f, "Texture format has {} bits but image format has {} bits", tbits, ibits),
            GetFormat(error) => write!(f, "{}", error),
        };
        Ok(())
    }
}

impl std::error::Error for ImageUnitError {}


/// How we bind a texture to an image unit
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct ImageUnitBehavior {
    /// The mip level to bind
    pub level: u32,
    pub(crate) layer: Option<u32>,
    /// How the shader will access the image unit
    pub access: ImageUnitAccess,
    /// How the shader should interpret the image
    pub format: ImageUnitFormat,
}

impl Default for ImageUnitBehavior {
    #[inline]
    fn default() -> ImageUnitBehavior {
        ImageUnitBehavior {
            level: 0,
            layer: None,
            access: ImageUnitAccess::ReadWrite,
            format: ImageUnitFormat::R32I,
        }
    }
}

/// An image unit uniform marker
pub struct ImageUnit<'t, T: 't + core::ops::Deref<Target = crate::texture::TextureAny>>(pub &'t T, pub ImageUnitBehavior);

impl<'t, T: 't + core::ops::Deref<Target = crate::texture::TextureAny>> ImageUnit<'t, T> {
    /// Create a new marker
    pub fn new(texture: &'t T, format: ImageUnitFormat) -> Result<ImageUnit<'t, T>, ImageUnitError> {
        let tbits = texture.get_internal_format().unwrap().get_total_bits();
        if tbits != format.get_total_bits() {
            return Err(ImageUnitError::BadFormatClass(tbits, format.get_total_bits()))
        }

        Ok(ImageUnit(texture, ImageUnitBehavior {
	    format,
	    ..Default::default()
	}))
    }

    /// Set the mip level that will be bound
    pub fn set_level(mut self, level: u32) -> Result<Self, ImageUnitError> {
        self.0.mipmap(level).ok_or(ImageUnitError::NoMipmapAtLevel(level))?;
        self.1.level = level;
        Ok(self)
    }

    /// Sets the layer of the texture to bind, or None to disable layer binding
    // TODO: only implement this for texture types where layering makes sense
    pub fn set_layer(mut self, layer: Option<u32>) -> Result<Self, ImageUnitError> {
        if let Some(layer) = layer {
            match self.0.dimensions() {
                texture::Dimensions::Texture1d { width } =>
                    Err(ImageUnitError::LayeringNotSupported(self.0.dimensions())),
                texture::Dimensions::Texture2d { width, height } =>
                    Err(ImageUnitError::LayeringNotSupported(self.0.dimensions())),
                texture::Dimensions::Texture2dMultisample { width, height, samples } =>
                    Err(ImageUnitError::LayeringNotSupported(self.0.dimensions())),
                texture::Dimensions::Texture1dArray { width, array_size } =>
                    if layer >= array_size { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
                texture::Dimensions::Texture2dArray { width, height, array_size } =>
                    if layer >= array_size { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
                texture::Dimensions::Texture2dMultisampleArray { width, height, array_size, samples } =>
                    if layer >= array_size { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
                texture::Dimensions::Texture3d { width, height, depth } =>
                    if layer >= depth { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
                texture::Dimensions::Cubemap { dimension } =>
                    if layer >= 6 { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
                texture::Dimensions::CubemapArray { dimension, array_size } =>
                    if layer >= 6*array_size { Err(ImageUnitError::LayerOutOfBounds(layer))} else { Ok(()) },
            }?;
        }
        self.1.layer = layer;
        Ok(self)
    }

    /// State how the shader will access the image unit
    pub fn set_access(mut self, access: ImageUnitAccess) -> Self {
        self.1.access = access;
        self
    }

}

/// States how the shader will access the image unit
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ImageUnitAccess {
    /// The shader will only read from the image unit
    Read,
    /// The shader will only write to the image unit
    Write,
    /// The shader will perform both reads and writes to the image unit
    ReadWrite,
}

impl ToGlEnum for ImageUnitAccess {
    #[inline]
    fn to_glenum(&self) -> gl::types::GLenum {
        match *self {
            ImageUnitAccess::Read => gl::READ_ONLY,
            ImageUnitAccess::Write => gl::WRITE_ONLY,
            ImageUnitAccess::ReadWrite => gl::READ_WRITE,
        }
    }
}



/// How the shader should interpret the data in the image
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ImageUnitFormat {
    /// The image elements are 4-component 32 bit floating point
    RGBA32F,
    /// The image elements are 4-component 16 bit floating point
    RGBA16F,
    /// The image elements are 2-component 32 bit floating point
    RG32F,
    /// The image elements are 4-component 16 bit floating point
    RG16F,
    /// The image elements are 2 11-bit floats and 1 10-bit float
    R11FG11FB10F,
    /// The image elements are 1-component 32 bit floating point
    R32F,
    /// The image elements are 4-component 16 bit floating point
    R16F,

    /// The image elements are 4-component 32 bit unsigned integer
    RGBA32UI,
    /// The image elements are 4-component 16 bit unsigned integer
    RGBA16UI,
    /// The image elements have 3 10-bit unsigned integer components and 1 2-bit alpha component
    RGB10A2UI,
    /// The image elements are 4-component 8 bit unsigned integer
    RGBA8UI,
    /// The image elements are 2-component 32 bit unsigned integer
    RG32UI,
    /// The image elements are 2-component 16 bit unsigned integer
    RG16UI,
    /// The image elements are 2-component 8 bit unsigned integer
    RG8UI,
    /// The image elements are 1-component 32 bit unsigned integer
    R32UI,
    /// The image elements are 1-component 16 bit unsigned integer
    R16UI,
    /// The image elements are 1-component 8 bit unsigned integer
    R8UI,

    /// The image elements are 4-component 32 bit signed integer
    RGBA32I,
    /// The image elements are 4-component 16 bit signed integer
    RGBA16I,
    /// The image elements are 4-component 8 bit signed integer
    RGBA8I,
    /// The image elements are 2-component 32 bit signed integer
    RG32I,
    /// The image elements are 2-component 16 bit signed integer
    RG16I,
    /// The image elements are 2-component 8 bit signed integer
    RG8I,
    /// The image elements are 1-component 32 bit signed integer
    R32I,
    /// The image elements are 1-component 16 bit signed integer
    R16I,
    /// The image elements are 1-component 8 bit signed integer
    R8I,

    /// The image elements are 4-component 16 bit floating point
    RGBA16,
    /// The image elements are 3-component 10 bit floating point with 2 alpha bits
    RGB10A2,
    /// The image elements are 4-component 8 bit floating point
    RGBA8,
    /// The image elements are 2-component 16 bit floating point
    RG16,
    /// The image elements are 2-component 8 bit floating point
    RG8,
    /// The image elements are 1-component 16 bit floating point
    R16,
    /// The image elements are 1-component 8 bit floating point
    R8,

    /// The image elements are 4-component 16 bit floating point, normalized to the -1.0 to 1.0 range
    RGBA16snorm,
    /// The image elements are 4-component 8 bit floating point, normalized to the -1.0 to 1.0 range
    RGBA8snorm,
    /// The image elements are 2-component 16 bit floating point, normalized to the -1.0 to 1.0 range
    RG16snorm,
    /// The image elements are 2-component 8 bit floating point, normalized to the -1.0 to 1.0 range
    RG8snorm,
    /// The image elements are 1-component 16 bit floating point, normalized to the -1.0 to 1.0 range
    R16snorm,
    /// The image elements are 1-component 8 bit floating point, normalized to the -1.0 to 1.0 range
    R8snorm,
}

impl ImageUnitFormat {
    fn get_total_bits(&self) -> usize {
        match self {
            ImageUnitFormat::RGBA32F => 4*32,
            ImageUnitFormat::RGBA16F => 4*16,
            ImageUnitFormat::RG32F => 2*32,
            ImageUnitFormat::RG16F => 2*16,
            ImageUnitFormat::R11FG11FB10F => 11*2 + 10,
            ImageUnitFormat::R32F => 1*32,
            ImageUnitFormat::R16F => 1*16,

            ImageUnitFormat::RGBA32UI => 4*32,
            ImageUnitFormat::RGBA16UI => 4*16,
            ImageUnitFormat::RGB10A2UI => 3*10 + 2,
            ImageUnitFormat::RGBA8UI => 8*4,
            ImageUnitFormat::RG32UI => 2*32,
            ImageUnitFormat::RG16UI => 2*16,
            ImageUnitFormat::RG8UI => 2*8,
            ImageUnitFormat::R32UI => 1*32,
            ImageUnitFormat::R16UI => 1*16,
            ImageUnitFormat::R8UI => 1*8,

            ImageUnitFormat::RGBA32I => 4*32,
            ImageUnitFormat::RGBA16I => 2*32,
            ImageUnitFormat::RGBA8I => 4*8,
            ImageUnitFormat::RG32I => 2*32,
            ImageUnitFormat::RG16I => 2*16,
            ImageUnitFormat::RG8I => 2*8,
            ImageUnitFormat::R32I => 1*32,
            ImageUnitFormat::R16I => 1*16,
            ImageUnitFormat::R8I => 1*8,

            ImageUnitFormat::RGBA16 => 4*16,
            ImageUnitFormat::RGB10A2 => 3*10+2,
            ImageUnitFormat::RGBA8 => 4*8,
            ImageUnitFormat::RG16 => 2*16,
            ImageUnitFormat::RG8 => 2*8,
            ImageUnitFormat::R16 => 1*16,
            ImageUnitFormat::R8 => 1*8,

            ImageUnitFormat::RGBA16snorm => 4*16,
            ImageUnitFormat::RGBA8snorm => 4*8,
            ImageUnitFormat::RG16snorm => 2*16,
            ImageUnitFormat::RG8snorm => 2*8,
            ImageUnitFormat::R16snorm => 1*16,
            ImageUnitFormat::R8snorm => 1*8,
        }
    }
}

impl ToGlEnum for ImageUnitFormat {
    #[inline]
    fn to_glenum(&self) -> gl::types::GLenum {
        match *self {
            ImageUnitFormat::RGBA32F => gl::RGBA32F,
            ImageUnitFormat::RGBA16F => gl::RGBA16F,
            ImageUnitFormat::RG32F => gl::RG32F,
            ImageUnitFormat::RG16F => gl::RG16F,
            ImageUnitFormat::R11FG11FB10F => gl::R11F_G11F_B10F,
            ImageUnitFormat::R32F => gl::R32F,
            ImageUnitFormat::R16F => gl::R16F,

            ImageUnitFormat::RGBA32UI => gl::RGBA32UI,
            ImageUnitFormat::RGBA16UI => gl::RGBA16UI,
            ImageUnitFormat::RGB10A2UI => gl::RGB10_A2UI,
            ImageUnitFormat::RGBA8UI => gl::RGBA8UI,
            ImageUnitFormat::RG32UI => gl::RG32UI,
            ImageUnitFormat::RG16UI => gl::RG16UI,
            ImageUnitFormat::RG8UI => gl::RG8UI,
            ImageUnitFormat::R32UI => gl::R32UI,
            ImageUnitFormat::R16UI => gl::R16UI,
            ImageUnitFormat::R8UI => gl::R8UI,

            ImageUnitFormat::RGBA32I => gl::RGBA32I,
            ImageUnitFormat::RGBA16I => gl::RGBA16I,
            ImageUnitFormat::RGBA8I => gl::RGBA8I,
            ImageUnitFormat::RG32I => gl::RG32I,
            ImageUnitFormat::RG16I => gl::RG16I,
            ImageUnitFormat::RG8I => gl::RG8I,
            ImageUnitFormat::R32I => gl::R32I,
            ImageUnitFormat::R16I => gl::R16I,
            ImageUnitFormat::R8I => gl::R8I,

            ImageUnitFormat::RGBA16 => gl::RGBA16,
            ImageUnitFormat::RGB10A2 => gl::RGB10_A2,
            ImageUnitFormat::RGBA8 => gl::RGBA8,
            ImageUnitFormat::RG16 => gl::RG16,
            ImageUnitFormat::RG8 => gl::RG8,
            ImageUnitFormat::R16 => gl::R16,
            ImageUnitFormat::R8 => gl::R8,

            ImageUnitFormat::RGBA16snorm => gl::RGBA16_SNORM,
            ImageUnitFormat::RGBA8snorm => gl::RGBA8_SNORM,
            ImageUnitFormat::RG16snorm => gl::RG16_SNORM,
            ImageUnitFormat::RG8snorm => gl::RG8_SNORM,
            ImageUnitFormat::R16snorm => gl::R16_SNORM,
            ImageUnitFormat::R8snorm => gl::R8_SNORM,
        }
    }
}