1use 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#[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
34pub 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
47pub 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#[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 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 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 }
195}