renderling/
atlas.rs

1//! Texture atlas.
2//!
3//! All images are packed into an atlas at staging time.
4//! Texture descriptors describe where in the atlas an image is,
5//! and how it should sample pixels. These descriptors are packed into a buffer
6//! on the GPU. This keeps the number of texture binds to a minimum (one, in most cases).
7//!
8//! ## NOTE:
9//! `Atlas` is a temporary work around until we can use bindless techniques
10//! on web.
11//!
12//! `Atlas` is only available on CPU. Not available in shaders.
13use crabslab::SlabItem;
14
15#[cfg(cpu)]
16mod atlas_image;
17#[cfg(cpu)]
18pub use atlas_image::*;
19#[cfg(cpu)]
20mod cpu;
21#[cfg(cpu)]
22pub use cpu::*;
23
24pub mod shader;
25
26/// Method of addressing the edges of a texture.
27#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
28#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem)]
29pub struct TextureModes {
30    pub s: TextureAddressMode,
31    pub t: TextureAddressMode,
32}
33
34/// Infinitely wrap the input between 0.0 and 1.0.
35///
36/// Only handles `input` >= 0.0.
37pub fn repeat(mut input: f32) -> f32 {
38    let gto = input >= 1.0;
39    input %= 1.0;
40    if gto && input == 0.0 {
41        1.0
42    } else {
43        input
44    }
45}
46
47/// Clamp the input between 0.0 and 1.0.
48pub fn clamp(input: f32) -> f32 {
49    if input > 1.0 {
50        1.0 - f32::EPSILON
51    } else if input < 0.0 {
52        0.0 + f32::EPSILON
53    } else {
54        input
55    }
56}
57
58/// How edges should be handled in texture addressing/wrapping.
59#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
60#[repr(u32)]
61#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem)]
62pub enum TextureAddressMode {
63    #[default]
64    ClampToEdge,
65    Repeat,
66    MirroredRepeat,
67}
68
69impl core::fmt::Display for TextureAddressMode {
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        f.write_str(match *self {
72            TextureAddressMode::ClampToEdge => "clamp to edge",
73            TextureAddressMode::Repeat => "repeat",
74            TextureAddressMode::MirroredRepeat => "mirrored repeat",
75        })
76    }
77}
78
79impl TextureAddressMode {
80    /// Wrap the given s/t coord into a pixel index according to texture
81    /// addressing.
82    pub fn wrap(&self, input: f32) -> f32 {
83        match self {
84            TextureAddressMode::Repeat => {
85                let sign = if input >= 0.0 { 1.0f32 } else { -1.0 };
86                let input = repeat(input.abs());
87                if sign > 0.0 {
88                    input
89                } else {
90                    1.0 - input
91                }
92            }
93            TextureAddressMode::MirroredRepeat => {
94                let sign = if input >= 0.0 { 1.0f32 } else { -1.0 };
95                let i = input.abs();
96                let flip = i as u32 % 2 == 0;
97                let t = repeat(i);
98                if sign > 0.0 {
99                    if flip {
100                        t
101                    } else {
102                        1.0 - t
103                    }
104                } else if flip {
105                    1.0 - t
106                } else {
107                    t
108                }
109            }
110            _ => clamp(input),
111        }
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use glam::{UVec2, UVec3, Vec2, Vec3Swizzles, Vec4Swizzles};
118
119    use crate::atlas::shader::AtlasTextureDescriptor;
120
121    use super::*;
122
123    #[test]
124    fn can_repeat() {
125        assert_eq!(0.0, TextureAddressMode::Repeat.wrap(0.0));
126        assert_eq!(1.0, TextureAddressMode::Repeat.wrap(2.0));
127        assert_eq!(1.0, TextureAddressMode::Repeat.wrap(3.0));
128    }
129
130    #[test]
131    /// Tests that clip coordinates can be converted into texture coords within
132    /// a specific `AtlasTexture`, and back again.
133    fn constrain_clip_coords_sanity() {
134        let atlas_texture = AtlasTextureDescriptor {
135            offset_px: UVec2::splat(0),
136            size_px: UVec2::splat(800),
137            layer_index: 0,
138            frame_index: 0,
139            modes: TextureModes {
140                s: TextureAddressMode::ClampToEdge,
141                t: TextureAddressMode::ClampToEdge,
142            },
143        };
144        let atlas_size = UVec3::new(1024, 1024, 4);
145        let corners @ [tl, tr, br, bl] = [
146            crate::math::CLIP_SPACE_COORD_QUAD_CCW_TL,
147            crate::math::CLIP_SPACE_COORD_QUAD_CCW_TR,
148            crate::math::CLIP_SPACE_COORD_QUAD_CCW_BR,
149            crate::math::CLIP_SPACE_COORD_QUAD_CCW_BL,
150        ]
151        .map(|coord| {
152            atlas_texture.constrain_clip_coords_to_texture_space(coord.xy(), atlas_size.xy())
153        });
154        log::info!("uv_corners: {corners:#?}");
155
156        let clip_br = crate::math::CLIP_SPACE_COORD_QUAD_CCW_BR.xy();
157        log::info!("clip_br: {clip_br}");
158        let input_uv_br = (clip_br * Vec2::new(1.0, -1.0) + Vec2::splat(1.0)) * Vec2::splat(0.5);
159        log::info!("input_uv_br: {input_uv_br}");
160        assert_eq!(Vec2::ONE, input_uv_br, "incorrect uv");
161
162        let d = 800.0 / 1024.0;
163        assert_eq!(Vec2::splat(0.0), tl, "incorrect tl");
164        assert_eq!(Vec2::new(d, 0.0), tr, "incorrect tr");
165        assert_eq!(Vec2::new(d, d), br, "incorrect br");
166        assert_eq!(Vec2::new(0.0, d), bl, "incorrect bl");
167
168        let corners = [
169            crate::math::CLIP_SPACE_COORD_QUAD_CCW_TL,
170            crate::math::CLIP_SPACE_COORD_QUAD_CCW_TR,
171            crate::math::CLIP_SPACE_COORD_QUAD_CCW_BR,
172            crate::math::CLIP_SPACE_COORD_QUAD_CCW_BL,
173        ]
174        .map(|coord| atlas_texture.constrain_clip_coords(coord.xy(), atlas_size.xy()));
175        log::info!("clip_corners: {corners:#?}");
176        //     [
177        //     Vec2(
178        //         -1.0,
179        //         1.0,
180        //     ),
181        //     Vec2(
182        //         0.5625,
183        //         1.0,
184        //     ),
185        //     Vec2(
186        //         0.5625,
187        //         -0.5625,
188        //     ),
189        //     Vec2(
190        //         -1.0,
191        //         -0.5625,
192        //     ),
193        // ]
194    }
195}