1use glam::*;
4
5use wgpu::util::DeviceExt;
6
7mod atlas;
8#[cfg(feature = "text")]
9pub mod font;
10#[cfg(feature = "text")]
11mod text;
12
13type Cache = std::collections::HashMap<u64, wgpu::Texture>;
14
15pub type Color = rgb::Rgba<u8>;
17
18#[cfg(feature = "text")]
19pub use text::Label;
20
21struct Sprite<'a> {
22    texture: &'a dyn Texture,
23    src_offset: IVec2,
24    src_size: UVec2,
25    src_layer: u32,
26    transform: Affine2,
27    tint: Color,
28}
29
30enum Command<'a> {
31    Sprite(Sprite<'a>),
32    #[cfg(feature = "text")]
33    Text(text::Section),
34}
35
36pub struct Canvas<'a> {
38    commands: Vec<Command<'a>>,
39}
40
41pub trait Drawable<'a>
43where
44    Self: Sized + Clone,
45{
46    fn draw(&self, canvas: &mut Canvas<'a>, tint: Color, transform: glam::Affine2);
48
49    fn tinted(&self, tint: Color) -> impl Drawable<'a> {
51        Tinted {
52            drawable: self.clone(),
53            tint,
54        }
55    }
56}
57
58#[cfg(feature = "text")]
59impl<'a> Drawable<'a> for text::Label {
60    fn draw(&self, canvas: &mut Canvas<'a>, tint: Color, transform: glam::Affine2) {
61        canvas.commands.push(Command::Text(text::Section {
62            label: self.clone(),
63            transform,
64            tint,
65        }));
66    }
67}
68
69#[derive(Debug, Clone, Copy)]
70struct Rect {
71    offset: IVec2,
72    size: UVec2,
73}
74
75impl Rect {
76    fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
77        Self {
78            offset: IVec2::new(x, y),
79            size: UVec2::new(width, height),
80        }
81    }
82    const fn left(&self) -> i32 {
83        self.offset.x
84    }
85    const fn top(&self) -> i32 {
86        self.offset.y
87    }
88    const fn right(&self) -> i32 {
89        self.offset.x + self.size.x as i32
90    }
91    const fn bottom(&self) -> i32 {
92        self.offset.y + self.size.y as i32
93    }
94}
95
96pub trait Texture {
100    fn size(&self) -> wgpu::Extent3d;
102
103    fn upload_to_wgpu(&self, device: &wgpu::Device, queue: &wgpu::Queue, cache: &mut Cache);
107
108    fn get_wgpu_texture<'a>(&'a self, cache: &'a Cache) -> Option<&'a wgpu::Texture>;
112}
113
114pub struct Image {
118    id: u64,
119    pixels: Vec<u8>,
120    desc: wgpu::TextureDescriptor<'static>,
121}
122
123impl Image {
124    pub fn new(pixels: Vec<u8>, desc: wgpu::TextureDescriptor<'static>) -> Self {
126        static IMAGE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
127        Self {
128            id: IMAGE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
129            pixels,
130            desc,
131        }
132    }
133}
134
135impl Texture for Image {
136    fn size(&self) -> wgpu::Extent3d {
137        self.desc.size
138    }
139
140    fn upload_to_wgpu(&self, device: &wgpu::Device, queue: &wgpu::Queue, cache: &mut Cache) {
141        cache.entry(self.id).or_insert_with(|| {
142            device.create_texture_with_data(
143                queue,
144                &self.desc,
145                wgpu::util::TextureDataOrder::default(),
146                &self.pixels,
147            )
148        });
149    }
150
151    fn get_wgpu_texture<'a>(&'a self, cache: &'a Cache) -> Option<&'a wgpu::Texture> {
152        cache.get(&self.id)
153    }
154}
155
156impl Texture for wgpu::Texture {
157    fn size(&self) -> wgpu::Extent3d {
158        self.size()
159    }
160
161    fn upload_to_wgpu(&self, _device: &wgpu::Device, _queue: &wgpu::Queue, _cache: &mut Cache) {}
162
163    fn get_wgpu_texture<'a>(&'a self, _cache: &'a Cache) -> Option<&'a wgpu::Texture> {
164        Some(self)
165    }
166}
167
168pub struct TextureSlice<'a, T> {
170    texture: &'a T,
171    layer: u32,
172    rect: Rect,
173}
174
175impl<'a, T> Clone for TextureSlice<'a, T> {
176    fn clone(&self) -> Self {
177        Self {
178            texture: self.texture,
179            layer: self.layer,
180            rect: self.rect,
181        }
182    }
183}
184
185impl<'a, T> Copy for TextureSlice<'a, T> {}
186
187impl<'a, T> TextureSlice<'a, T>
188where
189    T: Texture,
190{
191    pub fn from_layer(texture: &'a T, layer: u32) -> Option<Self> {
193        let size = texture.size();
194        if layer >= size.depth_or_array_layers {
195            return None;
196        }
197        Some(Self {
198            texture,
199            layer,
200            rect: Rect::new(0, 0, size.width, size.height),
201        })
202    }
203
204    pub fn slice(&self, offset: glam::IVec2, size: glam::UVec2) -> Option<Self> {
210        let rect = Rect {
211            offset: self.rect.offset + offset,
212            size,
213        };
214        if rect.left() < self.rect.left()
215            || rect.right() > self.rect.right()
216            || rect.top() < self.rect.top()
217            || rect.bottom() > self.rect.bottom()
218        {
219            return None;
220        }
221        Some(Self {
222            texture: self.texture,
223            layer: self.layer,
224            rect,
225        })
226    }
227
228    pub fn size(&self) -> glam::UVec2 {
230        self.rect.size
231    }
232}
233
234impl<'a, T> Drawable<'a> for TextureSlice<'a, T>
235where
236    T: Texture,
237{
238    fn draw(&self, canvas: &mut Canvas<'a>, tint: Color, transform: glam::Affine2) {
239        canvas.commands.push(Command::Sprite(Sprite {
240            transform,
241            tint,
242            texture: self.texture,
243            src_offset: self.rect.offset,
244            src_size: self.rect.size,
245            src_layer: self.layer,
246        }));
247    }
248}
249
250#[derive(Clone)]
251struct Tinted<T> {
252    drawable: T,
253    tint: Color,
254}
255
256impl<'a, T> Drawable<'a> for Tinted<T>
257where
258    T: Drawable<'a>,
259{
260    fn draw(&self, canvas: &mut Canvas<'a>, tint: Color, transform: glam::Affine2) {
261        self.drawable.draw(
262            canvas,
263            Color::new(
264                ((tint.r as u16 * self.tint.r as u16) / 0xff) as u8,
265                ((tint.g as u16 * self.tint.g as u16) / 0xff) as u8,
266                ((tint.b as u16 * self.tint.b as u16) / 0xff) as u8,
267                ((tint.a as u16 * self.tint.a as u16) / 0xff) as u8,
268            ),
269            transform,
270        );
271    }
272}
273
274impl<'a> Canvas<'a> {
275    pub fn new() -> Self {
276        Self { commands: vec![] }
277    }
278
279    #[inline]
281    pub fn draw(&mut self, drawable: impl Drawable<'a>, transform: glam::Affine2) {
282        drawable.draw(self, Color::new(0xff, 0xff, 0xff, 0xff), transform);
283    }
284}
285
286pub struct Renderer {
288    renderer: spright::Renderer,
289    cache: Cache,
290    #[cfg(feature = "text")]
291    text_sprite_maker: text::SpriteMaker,
292}
293
294#[derive(thiserror::Error, Debug)]
296pub enum Error {
297    #[error("out of glylph atlas space")]
299    OutOfGlyphAtlasSpace,
300}
301
302impl Renderer {
303    pub fn new(device: &wgpu::Device, texture_format: wgpu::TextureFormat) -> Self {
305        Self {
306            renderer: spright::Renderer::new(device, texture_format),
307            cache: Cache::new(),
308            #[cfg(feature = "text")]
309            text_sprite_maker: text::SpriteMaker::new(device),
310        }
311    }
312
313    pub fn prepare(
315        &mut self,
316        device: &wgpu::Device,
317        queue: &wgpu::Queue,
318        font_system: &mut cosmic_text::FontSystem,
319        target_size: wgpu::Extent3d,
320        canvas: &Canvas,
321    ) -> Result<(), Error> {
322        let mut staged = vec![];
323
324        enum Staged<'a> {
325            Sprite(spright::batch::Sprite<'a>),
326            TextSprite(text::TextSprite),
327        }
328
329        for cmd in canvas.commands.iter() {
330            if let Command::Sprite(sprite) = cmd {
331                sprite
332                    .texture
333                    .upload_to_wgpu(device, queue, &mut self.cache);
334            }
335        }
336
337        for cmd in canvas.commands.iter() {
338            match cmd {
339                Command::Sprite(sprite) => {
340                    staged.push(Staged::Sprite(spright::batch::Sprite {
341                        texture: sprite.texture.get_wgpu_texture(&self.cache).unwrap(),
342                        src_offset: sprite.src_offset,
343                        src_size: sprite.src_size,
344                        src_layer: sprite.src_layer,
345                        transform: sprite.transform,
346                        tint: sprite.tint,
347                    }));
348                }
349                Command::Text(section) => {
350                    staged.extend(
351                        self.text_sprite_maker
352                            .make(device, queue, font_system, §ion.label, section.tint)
353                            .ok_or(Error::OutOfGlyphAtlasSpace)?
354                            .into_iter()
355                            .map(|s| {
356                                Staged::TextSprite(text::TextSprite {
357                                    transform: section.transform * s.transform,
358                                    ..s
359                                })
360                            }),
361                    );
362                }
363            }
364        }
365
366        self.renderer.prepare(
367            device,
368            queue,
369            target_size,
370            &spright::batch::batch(
371                &staged
372                    .into_iter()
373                    .map(|staged| match staged {
374                        Staged::Sprite(sprite) => sprite,
375                        Staged::TextSprite(text_sprite) => spright::batch::Sprite {
376                            texture: if text_sprite.is_mask {
377                                self.text_sprite_maker.mask_texture()
378                            } else {
379                                self.text_sprite_maker.color_texture()
380                            },
381                            src_offset: text_sprite.offset,
382                            src_size: text_sprite.size,
383                            src_layer: 0,
384                            tint: text_sprite.tint,
385                            transform: text_sprite.transform,
386                        },
387                    })
388                    .collect::<Vec<_>>(),
389            ),
390        );
391
392        #[cfg(feature = "text")]
393        self.text_sprite_maker.flush(queue);
394
395        Ok(())
396    }
397
398    pub fn render<'rpass>(&'rpass self, rpass: &'rpass mut wgpu::RenderPass<'rpass>) {
400        self.renderer.render(rpass);
401    }
402}