Skip to main content

pfx_live/flat/
sprites.rs

1use wgpu::util::DeviceExt;
2
3pub const SPRITE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
6pub enum SpriteFilter {
7    #[default]
8    Linear,
9    Nearest,
10}
11
12#[derive(Clone, Debug, PartialEq)]
13pub struct SpriteImage {
14    pub width: u32,
15    pub height: u32,
16    pub rgba: Vec<u8>,
17    pub filter: SpriteFilter,
18}
19
20impl SpriteImage {
21    pub fn new(
22        width: u32,
23        height: u32,
24        rgba: Vec<u8>,
25        filter: SpriteFilter,
26    ) -> Result<Self, String> {
27        if width == 0 || height == 0 {
28            return Err("a sprite atlas needs a size".into());
29        }
30        if rgba.len() != width as usize * height as usize * 4 {
31            return Err(format!(
32                "a {width}x{height} sprite atlas needs {} bytes, not {}",
33                width as usize * height as usize * 4,
34                rgba.len()
35            ));
36        }
37        Ok(Self {
38            width,
39            height,
40            rgba,
41            filter,
42        })
43    }
44
45    fn texel(&self, x: i64, y: i64) -> [f32; 4] {
46        let x = x.clamp(0, i64::from(self.width) - 1) as usize;
47        let y = y.clamp(0, i64::from(self.height) - 1) as usize;
48        let at = (y * self.width as usize + x) * 4;
49        std::array::from_fn(|channel| f32::from(self.rgba[at + channel]) / 255.0)
50    }
51
52    pub fn sample(&self, uv: [f32; 2]) -> [f32; 4] {
53        let x = uv[0] * self.width as f32;
54        let y = uv[1] * self.height as f32;
55        match self.filter {
56            SpriteFilter::Nearest => self.texel(x.floor() as i64, y.floor() as i64),
57            SpriteFilter::Linear => {
58                let fx = x - 0.5;
59                let fy = y - 0.5;
60                let x0 = fx.floor();
61                let y0 = fy.floor();
62                let tx = fx - x0;
63                let ty = fy - y0;
64                let (x0, y0) = (x0 as i64, y0 as i64);
65                let a = self.texel(x0, y0);
66                let b = self.texel(x0 + 1, y0);
67                let c = self.texel(x0, y0 + 1);
68                let d = self.texel(x0 + 1, y0 + 1);
69                std::array::from_fn(|k| {
70                    let top = a[k] + (b[k] - a[k]) * tx;
71                    let bottom = c[k] + (d[k] - c[k]) * tx;
72                    top + (bottom - top) * ty
73                })
74            }
75        }
76    }
77}
78
79pub struct FlatSprites {
80    pub image: SpriteImage,
81    _texture: wgpu::Texture,
82    pub(crate) view: wgpu::TextureView,
83    pub(crate) sampler: wgpu::Sampler,
84}
85
86impl FlatSprites {
87    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, image: SpriteImage) -> Self {
88        let texture = device.create_texture_with_data(
89            queue,
90            &wgpu::TextureDescriptor {
91                label: Some("flat sprites"),
92                size: wgpu::Extent3d {
93                    width: image.width,
94                    height: image.height,
95                    depth_or_array_layers: 1,
96                },
97                mip_level_count: 1,
98                sample_count: 1,
99                dimension: wgpu::TextureDimension::D2,
100                format: SPRITE_FORMAT,
101                usage: wgpu::TextureUsages::TEXTURE_BINDING,
102                view_formats: &[],
103            },
104            wgpu::util::TextureDataOrder::LayerMajor,
105            &image.rgba,
106        );
107        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
108        let sampler = sampler(device, image.filter);
109        Self {
110            image,
111            _texture: texture,
112            view,
113            sampler,
114        }
115    }
116
117    pub fn view(&self) -> &wgpu::TextureView {
118        &self.view
119    }
120}
121
122pub(crate) fn sampler(device: &wgpu::Device, filter: SpriteFilter) -> wgpu::Sampler {
123    let filter = match filter {
124        SpriteFilter::Linear => wgpu::FilterMode::Linear,
125        SpriteFilter::Nearest => wgpu::FilterMode::Nearest,
126    };
127    device.create_sampler(&wgpu::SamplerDescriptor {
128        label: Some("flat sprites"),
129        address_mode_u: wgpu::AddressMode::ClampToEdge,
130        address_mode_v: wgpu::AddressMode::ClampToEdge,
131        mag_filter: filter,
132        min_filter: filter,
133        mipmap_filter: wgpu::MipmapFilterMode::Nearest,
134        ..Default::default()
135    })
136}