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}