Skip to main content

fyrox_graphics_gl/
texture.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{server::GlGraphicsServer, ToGlConstant};
22use fyrox_graphics::{
23    error::FrameworkError,
24    gpu_texture::{
25        image_1d_size_bytes, image_2d_size_bytes, image_3d_size_bytes, CubeMapFace,
26        GpuTextureDescriptor, GpuTextureKind, GpuTextureTrait, PixelKind,
27    },
28};
29use glow::{HasContext, PixelUnpackData, COMPRESSED_RED_RGTC1, COMPRESSED_RG_RGTC2};
30use std::cell::Cell;
31use std::{
32    marker::PhantomData,
33    rc::{Rc, Weak},
34};
35
36impl ToGlConstant for GpuTextureKind {
37    fn into_gl(self) -> u32 {
38        match self {
39            Self::Line { .. } => glow::TEXTURE_1D,
40            Self::Rectangle { .. } => glow::TEXTURE_2D,
41            Self::Cube { .. } => glow::TEXTURE_CUBE_MAP,
42            Self::Volume { .. } => glow::TEXTURE_3D,
43        }
44    }
45}
46
47impl ToGlConstant for CubeMapFace {
48    fn into_gl(self) -> u32 {
49        match self {
50            Self::PositiveX => glow::TEXTURE_CUBE_MAP_POSITIVE_X,
51            Self::NegativeX => glow::TEXTURE_CUBE_MAP_NEGATIVE_X,
52            Self::PositiveY => glow::TEXTURE_CUBE_MAP_POSITIVE_Y,
53            Self::NegativeY => glow::TEXTURE_CUBE_MAP_NEGATIVE_Y,
54            Self::PositiveZ => glow::TEXTURE_CUBE_MAP_POSITIVE_Z,
55            Self::NegativeZ => glow::TEXTURE_CUBE_MAP_NEGATIVE_Z,
56        }
57    }
58}
59
60pub struct GlTexture {
61    state: Weak<GlGraphicsServer>,
62    texture: glow::Texture,
63    kind: Cell<GpuTextureKind>,
64    pixel_kind: Cell<PixelKind>,
65    size_bytes: Cell<usize>,
66    // Force compiler to not implement Send and Sync, because OpenGL is not thread-safe.
67    thread_mark: PhantomData<*const u8>,
68}
69
70const GL_COMPRESSED_RGB_S3TC_DXT1_EXT: u32 = 0x83F0;
71const GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: u32 = 0x83F1;
72const GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: u32 = 0x83F2;
73const GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: u32 = 0x83F3;
74
75pub struct PixelDescriptor {
76    pub data_type: u32,
77    pub format: u32,
78    pub internal_format: u32,
79    pub swizzle_mask: Option<[i32; 4]>,
80}
81
82impl From<PixelKind> for PixelDescriptor {
83    fn from(value: PixelKind) -> Self {
84        let (data_type, format, internal_format, swizzle_mask) = match value {
85            PixelKind::R32F => (glow::FLOAT, glow::RED, glow::R32F, None),
86            PixelKind::R32UI => (glow::UNSIGNED_INT, glow::RED_INTEGER, glow::R32UI, None),
87            PixelKind::R16F => (glow::FLOAT, glow::RED, glow::R16F, None),
88            PixelKind::RG16F => (glow::HALF_FLOAT, glow::RG, glow::RG16F, None),
89            PixelKind::D32F => (
90                glow::FLOAT,
91                glow::DEPTH_COMPONENT,
92                glow::DEPTH_COMPONENT32F,
93                None,
94            ),
95            PixelKind::D16 => (
96                glow::UNSIGNED_SHORT,
97                glow::DEPTH_COMPONENT,
98                glow::DEPTH_COMPONENT16,
99                None,
100            ),
101            PixelKind::D24S8 => (
102                glow::UNSIGNED_INT_24_8,
103                glow::DEPTH_STENCIL,
104                glow::DEPTH24_STENCIL8,
105                None,
106            ),
107            PixelKind::RGBA8 => (glow::UNSIGNED_BYTE, glow::RGBA, glow::RGBA8, None),
108            PixelKind::SRGBA8 => (glow::UNSIGNED_BYTE, glow::RGBA, glow::SRGB8_ALPHA8, None),
109            PixelKind::RGB8 => (glow::UNSIGNED_BYTE, glow::RGB, glow::RGB8, None),
110            PixelKind::SRGB8 => (glow::UNSIGNED_BYTE, glow::RGB, glow::SRGB8, None),
111            PixelKind::RG8 => (glow::UNSIGNED_BYTE, glow::RG, glow::RG8, None),
112            PixelKind::R8 => (glow::UNSIGNED_BYTE, glow::RED, glow::R8, None),
113            PixelKind::R8UI => (glow::UNSIGNED_BYTE, glow::RED_INTEGER, glow::R8UI, None),
114            PixelKind::BGRA8 => (glow::UNSIGNED_BYTE, glow::BGRA, glow::RGBA8, None),
115            PixelKind::BGR8 => (glow::UNSIGNED_BYTE, glow::BGR, glow::RGB8, None),
116            PixelKind::RG16 => (glow::UNSIGNED_SHORT, glow::RG, glow::RG16, None),
117            PixelKind::R16 => (glow::UNSIGNED_SHORT, glow::RED, glow::R16, None),
118            PixelKind::RGB16 => (glow::UNSIGNED_SHORT, glow::RGB, glow::RGB16, None),
119            PixelKind::RGBA16 => (glow::UNSIGNED_SHORT, glow::RGBA, glow::RGBA16, None),
120            PixelKind::RGB10A2 => (
121                glow::UNSIGNED_INT_2_10_10_10_REV,
122                glow::RGBA,
123                glow::RGB10_A2,
124                None,
125            ),
126            PixelKind::DXT1RGB => (0, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, None),
127            PixelKind::DXT1RGBA => (0, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, None),
128            PixelKind::DXT3RGBA => (0, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, None),
129            PixelKind::DXT5RGBA => (0, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, None),
130            PixelKind::R8RGTC => (0, 0, COMPRESSED_RED_RGTC1, None),
131            PixelKind::RG8RGTC => (0, 0, COMPRESSED_RG_RGTC2, None),
132            PixelKind::RGB32F => (glow::FLOAT, glow::RGB, glow::RGB32F, None),
133            PixelKind::RGBA32F => (glow::FLOAT, glow::RGBA, glow::RGBA32F, None),
134            PixelKind::RGBA16F => (glow::HALF_FLOAT, glow::RGBA, glow::RGBA16F, None),
135            PixelKind::RGB16F => (glow::HALF_FLOAT, glow::RGB, glow::RGB16F, None),
136            PixelKind::R11G11B10F => (glow::FLOAT, glow::RGB, glow::R11F_G11F_B10F, None),
137            PixelKind::L8 => (
138                glow::UNSIGNED_BYTE,
139                glow::RED,
140                glow::R8,
141                Some([
142                    glow::RED as i32,
143                    glow::RED as i32,
144                    glow::RED as i32,
145                    glow::ONE as i32,
146                ]),
147            ),
148            PixelKind::LA8 => (
149                glow::UNSIGNED_BYTE,
150                glow::RG,
151                glow::RG8,
152                Some([
153                    glow::RED as i32,
154                    glow::RED as i32,
155                    glow::RED as i32,
156                    glow::GREEN as i32,
157                ]),
158            ),
159            PixelKind::LA16 => (
160                glow::UNSIGNED_SHORT,
161                glow::RG,
162                glow::RG16,
163                Some([
164                    glow::RED as i32,
165                    glow::RED as i32,
166                    glow::RED as i32,
167                    glow::GREEN as i32,
168                ]),
169            ),
170            PixelKind::L16 => (
171                glow::UNSIGNED_SHORT,
172                glow::RED,
173                glow::R16,
174                Some([
175                    glow::RED as i32,
176                    glow::RED as i32,
177                    glow::RED as i32,
178                    glow::ONE as i32,
179                ]),
180            ),
181        };
182
183        PixelDescriptor {
184            data_type,
185            format,
186            internal_format,
187            swizzle_mask,
188        }
189    }
190}
191
192struct TempBinding {
193    server: Rc<GlGraphicsServer>,
194    unit: u32,
195    target: u32,
196}
197
198impl TempBinding {
199    fn new(server: Rc<GlGraphicsServer>, texture: &GlTexture) -> Self {
200        let unit = server
201            .free_texture_unit()
202            .expect("Texture units limit exceeded!");
203        let target = texture.kind.get().into_gl();
204        server.set_texture(unit, target, Some(texture.texture));
205        Self {
206            server,
207            unit,
208            target,
209        }
210    }
211
212    fn set_base_level(&mut self, level: usize) {
213        unsafe {
214            self.server
215                .gl
216                .tex_parameter_i32(self.target, glow::TEXTURE_BASE_LEVEL, level as i32);
217        }
218    }
219
220    fn set_max_level(&mut self, level: usize) {
221        unsafe {
222            self.server
223                .gl
224                .tex_parameter_i32(self.target, glow::TEXTURE_MAX_LEVEL, level as i32);
225        }
226    }
227
228    fn generate_mipmap(&mut self, width: usize, height: usize, depth: usize) {
229        unsafe {
230            self.set_base_level(0);
231            self.set_max_level(
232                ((width.max(height).max(depth) as f32).log2().floor() + 1.0) as usize,
233            );
234            self.server.gl.generate_mipmap(self.target);
235        }
236    }
237}
238
239impl Drop for TempBinding {
240    fn drop(&mut self) {
241        self.server.set_texture(self.unit, self.target, None);
242    }
243}
244
245impl GlTexture {
246    /// Creates new GPU texture of specified kind. Mip count must be at least 1, it means
247    /// that there is only main level of detail.
248    ///
249    /// # Data layout
250    ///
251    /// In case of Cube texture, `bytes` should contain all 6 cube faces ordered like so,
252    /// +X, -X, +Y, -Y, +Z, -Z. Cube mips must follow one after another.
253    ///
254    /// Produced texture can be used as render target for framebuffer, in this case `data`
255    /// parameter can be None.
256    ///
257    /// # Compressed textures
258    ///
259    /// For compressed textures data must contain all mips, where each mip must be 2 times
260    /// smaller than previous.
261    pub fn new(
262        server: &GlGraphicsServer,
263        mut desc: GpuTextureDescriptor,
264    ) -> Result<Self, FrameworkError> {
265        // Clamp the mip level values to sensible range to prevent weird behavior.
266        let actual_max_level = desc.mip_count.saturating_sub(1);
267        if desc.max_level > actual_max_level {
268            desc.max_level = actual_max_level;
269        }
270        if desc.base_level > desc.max_level {
271            desc.base_level = desc.max_level;
272        }
273        if desc.base_level > actual_max_level {
274            desc.base_level = actual_max_level;
275        }
276
277        unsafe {
278            let texture = server.gl.create_texture()?;
279
280            let result = Self {
281                state: server.weak(),
282                texture,
283                kind: desc.kind.into(),
284                pixel_kind: desc.pixel_kind.into(),
285                size_bytes: Cell::new(0),
286                thread_mark: PhantomData,
287            };
288
289            let byte_count =
290                result.set_data(desc.kind, desc.pixel_kind, desc.mip_count, desc.data)?;
291
292            server.memory_usage.borrow_mut().textures += byte_count;
293
294            let mut binding = result.make_temp_binding();
295            #[cfg(not(target_arch = "wasm32"))]
296            if server.gl.supports_debug() && server.named_objects.get() {
297                server
298                    .gl
299                    .object_label(glow::TEXTURE, texture.0.get(), Some(desc.name));
300            }
301            binding.set_base_level(desc.base_level);
302            binding.set_max_level(desc.max_level);
303
304            Ok(result)
305        }
306    }
307
308    pub(crate) fn generate_mipmap(&self) {
309        let (width, height, depth) = match self.kind.get() {
310            GpuTextureKind::Line { length } => (length, 1, 1),
311            GpuTextureKind::Rectangle { width, height } => (width, height, 1),
312            GpuTextureKind::Cube { size } => (size, size, 1),
313            GpuTextureKind::Volume {
314                width,
315                height,
316                depth,
317            } => (width, height, depth),
318        };
319        self.make_temp_binding()
320            .generate_mipmap(width, height, depth);
321    }
322
323    pub fn bind(&self, server: &GlGraphicsServer, sampler_index: u32) {
324        server.set_texture(sampler_index, self.kind.get().into_gl(), Some(self.texture));
325    }
326
327    fn make_temp_binding(&self) -> TempBinding {
328        let server = self.state.upgrade().unwrap();
329        TempBinding::new(server, self)
330    }
331
332    pub fn id(&self) -> glow::Texture {
333        self.texture
334    }
335}
336
337impl Drop for GlTexture {
338    fn drop(&mut self) {
339        if let Some(state) = self.state.upgrade() {
340            state.memory_usage.borrow_mut().textures -= self.size_bytes.get();
341            state.delete_texture(self.texture)
342        }
343    }
344}
345
346fn desired_byte_count(kind: GpuTextureKind, mip_count: usize, pixel_kind: PixelKind) -> usize {
347    let mut desired_byte_count = 0;
348
349    'mip_loop: for mip in 0..mip_count {
350        match kind {
351            GpuTextureKind::Line { length } => {
352                if let Some(length) = length.checked_shr(mip as u32) {
353                    desired_byte_count += image_1d_size_bytes(pixel_kind, length);
354                } else {
355                    break 'mip_loop;
356                }
357            }
358            GpuTextureKind::Rectangle { width, height } => {
359                if let (Some(width), Some(height)) = (
360                    width.checked_shr(mip as u32),
361                    height.checked_shr(mip as u32),
362                ) {
363                    desired_byte_count += image_2d_size_bytes(pixel_kind, width, height);
364                } else {
365                    break 'mip_loop;
366                }
367            }
368            GpuTextureKind::Cube { size } => {
369                if let Some(size) = size.checked_shr(mip as u32) {
370                    desired_byte_count += 6 * image_2d_size_bytes(pixel_kind, size, size);
371                } else {
372                    break 'mip_loop;
373                }
374            }
375            GpuTextureKind::Volume {
376                width,
377                height,
378                depth,
379            } => {
380                if let (Some(width), Some(height), Some(depth)) = (
381                    width.checked_shr(mip as u32),
382                    height.checked_shr(mip as u32),
383                    depth.checked_shr(mip as u32),
384                ) {
385                    desired_byte_count += image_3d_size_bytes(pixel_kind, width, height, depth);
386                } else {
387                    break 'mip_loop;
388                }
389            }
390        };
391    }
392
393    desired_byte_count
394}
395
396impl GpuTextureTrait for GlTexture {
397    fn set_data(
398        &self,
399        kind: GpuTextureKind,
400        pixel_kind: PixelKind,
401        mip_count: usize,
402        data: Option<&[u8]>,
403    ) -> Result<usize, FrameworkError> {
404        let mip_count = mip_count.max(1);
405
406        let desired_byte_count = desired_byte_count(kind, mip_count, pixel_kind);
407
408        if let Some(data) = data {
409            let actual_data_size = data.len();
410            if actual_data_size != desired_byte_count {
411                return Err(FrameworkError::InvalidTextureData {
412                    expected_data_size: desired_byte_count,
413                    actual_data_size,
414                });
415            }
416        }
417
418        self.size_bytes.set(desired_byte_count);
419
420        self.kind.set(kind);
421        self.pixel_kind.set(pixel_kind);
422
423        let mut temp_binding = self.make_temp_binding();
424        temp_binding.set_max_level(mip_count.saturating_sub(1));
425        let target = kind.into_gl();
426
427        unsafe {
428            let PixelDescriptor {
429                data_type,
430                format,
431                internal_format,
432                swizzle_mask,
433            } = PixelDescriptor::from(pixel_kind);
434
435            let is_compressed = pixel_kind.is_compressed();
436
437            if let Some(alignment) = pixel_kind.unpack_alignment() {
438                temp_binding
439                    .server
440                    .gl
441                    .pixel_store_i32(glow::UNPACK_ALIGNMENT, alignment);
442            }
443
444            if let Some(swizzle_mask) = swizzle_mask {
445                if temp_binding
446                    .server
447                    .gl
448                    .supported_extensions()
449                    .contains("GL_ARB_texture_swizzle")
450                {
451                    temp_binding.server.gl.tex_parameter_i32_slice(
452                        target,
453                        glow::TEXTURE_SWIZZLE_RGBA,
454                        &swizzle_mask,
455                    );
456                }
457            }
458
459            let mut mip_byte_offset = 0;
460            'mip_loop2: for mip in 0..mip_count {
461                match kind {
462                    GpuTextureKind::Line { length } => {
463                        if let Some(length) = length.checked_shr(mip as u32) {
464                            let size = image_1d_size_bytes(pixel_kind, length) as i32;
465                            let pixels = data.map(|data| {
466                                &data[mip_byte_offset..(mip_byte_offset + size as usize)]
467                            });
468
469                            if is_compressed {
470                                temp_binding.server.gl.compressed_tex_image_1d(
471                                    glow::TEXTURE_1D,
472                                    mip as i32,
473                                    internal_format as i32,
474                                    length as i32,
475                                    0,
476                                    size,
477                                    pixels.ok_or(FrameworkError::EmptyTextureData)?,
478                                );
479                            } else {
480                                temp_binding.server.gl.tex_image_1d(
481                                    glow::TEXTURE_1D,
482                                    mip as i32,
483                                    internal_format as i32,
484                                    length as i32,
485                                    0,
486                                    format,
487                                    data_type,
488                                    PixelUnpackData::Slice(pixels),
489                                );
490                            }
491
492                            mip_byte_offset += size as usize;
493                        } else {
494                            // No need to add degenerated mips (0x1, 0x2, 4x0, etc).
495                            break 'mip_loop2;
496                        }
497                    }
498                    GpuTextureKind::Rectangle { width, height } => {
499                        if let (Some(width), Some(height)) = (
500                            width.checked_shr(mip as u32),
501                            height.checked_shr(mip as u32),
502                        ) {
503                            let size = image_2d_size_bytes(pixel_kind, width, height) as i32;
504                            let pixels = data.map(|data| {
505                                &data[mip_byte_offset..(mip_byte_offset + size as usize)]
506                            });
507
508                            if is_compressed {
509                                temp_binding.server.gl.compressed_tex_image_2d(
510                                    glow::TEXTURE_2D,
511                                    mip as i32,
512                                    internal_format as i32,
513                                    width as i32,
514                                    height as i32,
515                                    0,
516                                    size,
517                                    pixels.ok_or(FrameworkError::EmptyTextureData)?,
518                                );
519                            } else {
520                                temp_binding.server.gl.tex_image_2d(
521                                    glow::TEXTURE_2D,
522                                    mip as i32,
523                                    internal_format as i32,
524                                    width as i32,
525                                    height as i32,
526                                    0,
527                                    format,
528                                    data_type,
529                                    PixelUnpackData::Slice(pixels),
530                                );
531                            }
532
533                            mip_byte_offset += size as usize;
534                        } else {
535                            // No need to add degenerated mips (0x1, 0x2, 4x0, etc).
536                            break 'mip_loop2;
537                        }
538                    }
539                    GpuTextureKind::Cube { size } => {
540                        if let Some(size) = size.checked_shr(mip as u32) {
541                            let bytes_per_face = image_2d_size_bytes(pixel_kind, size, size);
542
543                            for face in 0..6 {
544                                let begin = mip_byte_offset + face * bytes_per_face;
545                                let end = mip_byte_offset + (face + 1) * bytes_per_face;
546                                let face_pixels = data.map(|data| &data[begin..end]);
547
548                                if is_compressed {
549                                    temp_binding.server.gl.compressed_tex_image_2d(
550                                        glow::TEXTURE_CUBE_MAP_POSITIVE_X + face as u32,
551                                        mip as i32,
552                                        internal_format as i32,
553                                        size as i32,
554                                        size as i32,
555                                        0,
556                                        bytes_per_face as i32,
557                                        face_pixels.ok_or(FrameworkError::EmptyTextureData)?,
558                                    );
559                                } else {
560                                    temp_binding.server.gl.tex_image_2d(
561                                        glow::TEXTURE_CUBE_MAP_POSITIVE_X + face as u32,
562                                        mip as i32,
563                                        internal_format as i32,
564                                        size as i32,
565                                        size as i32,
566                                        0,
567                                        format,
568                                        data_type,
569                                        PixelUnpackData::Slice(face_pixels),
570                                    );
571                                }
572                            }
573
574                            mip_byte_offset += 6 * bytes_per_face;
575                        } else {
576                            // No need to add degenerated mips (0x1, 0x2, 4x0, etc).
577                            break 'mip_loop2;
578                        }
579                    }
580                    GpuTextureKind::Volume {
581                        width,
582                        height,
583                        depth,
584                    } => {
585                        if let (Some(width), Some(height), Some(depth)) = (
586                            width.checked_shr(mip as u32),
587                            height.checked_shr(mip as u32),
588                            depth.checked_shr(mip as u32),
589                        ) {
590                            let size = image_3d_size_bytes(pixel_kind, width, height, depth) as i32;
591                            let pixels = data.map(|data| {
592                                &data[mip_byte_offset..(mip_byte_offset + size as usize)]
593                            });
594
595                            if is_compressed {
596                                temp_binding.server.gl.compressed_tex_image_3d(
597                                    glow::TEXTURE_3D,
598                                    mip as i32,
599                                    internal_format as i32,
600                                    width as i32,
601                                    height as i32,
602                                    depth as i32,
603                                    0,
604                                    size,
605                                    pixels.ok_or(FrameworkError::EmptyTextureData)?,
606                                );
607                            } else {
608                                temp_binding.server.gl.tex_image_3d(
609                                    glow::TEXTURE_3D,
610                                    mip as i32,
611                                    internal_format as i32,
612                                    width as i32,
613                                    height as i32,
614                                    depth as i32,
615                                    0,
616                                    format,
617                                    data_type,
618                                    PixelUnpackData::Slice(pixels),
619                                );
620                            }
621
622                            mip_byte_offset += size as usize;
623                        } else {
624                            // No need to add degenerated mips (0x1, 0x2, 4x0, etc).
625                            break 'mip_loop2;
626                        }
627                    }
628                }
629            }
630        }
631
632        Ok(desired_byte_count)
633    }
634
635    fn kind(&self) -> GpuTextureKind {
636        self.kind.get()
637    }
638
639    fn pixel_kind(&self) -> PixelKind {
640        self.pixel_kind.get()
641    }
642}