1use crevice::std140::AsStd140;
2
3use crate::{
4 context::{Has, HasMut},
5 GameError, GameResult,
6};
7
8use super::{
9 gpu::arc::{ArcBindGroup, ArcBindGroupLayout},
10 internal_canvas::{screen_to_mat, InstanceArrayView, InternalCanvas},
11 BlendMode, Color, DrawParam, Drawable, GraphicsContext, Image, InstanceArray, Mesh, Rect,
12 Sampler, ScreenImage, Shader, ShaderParams, Text, WgpuContext, ZIndex,
13};
14use std::{collections::BTreeMap, sync::Arc};
15
16#[derive(Debug)]
27pub struct Canvas {
28 pub(crate) wgpu: Arc<WgpuContext>,
29 draws: BTreeMap<ZIndex, Vec<DrawCommand>>,
30 state: DrawState,
31 original_state: DrawState,
32 screen: Option<Rect>,
33 defaults: DefaultResources,
34
35 target: Image,
36 resolve: Option<Image>,
37 clear: Option<Color>,
38
39 pub(crate) queued_texts: Vec<(Text, mint::Point2<f32>, Option<Color>)>,
41}
42
43impl Canvas {
44 #[inline]
50 pub fn from_image(
51 gfx: &impl Has<GraphicsContext>,
52 image: Image,
53 clear: impl Into<Option<Color>>,
54 ) -> Self {
55 Canvas::new(gfx, image, None, clear.into())
56 }
57
58 #[inline]
60 pub fn from_screen_image(
61 gfx: &impl Has<GraphicsContext>,
62 image: &mut ScreenImage,
63 clear: impl Into<Option<Color>>,
64 ) -> Self {
65 let gfx = gfx.retrieve();
66 let image = image.image(gfx);
67 Canvas::from_image(gfx, image, clear)
68 }
69
70 #[inline]
74 pub fn from_msaa(
75 gfx: &impl Has<GraphicsContext>,
76 msaa_image: Image,
77 resolve: Image,
78 clear: impl Into<Option<Color>>,
79 ) -> Self {
80 Canvas::new(gfx, msaa_image, Some(resolve), clear.into())
81 }
82
83 #[inline]
85 pub fn from_screen_msaa(
86 gfx: &impl Has<GraphicsContext>,
87 msaa_image: &mut ScreenImage,
88 resolve: &mut ScreenImage,
89 clear: impl Into<Option<Color>>,
90 ) -> Self {
91 let msaa = msaa_image.image(gfx);
92 let resolve = resolve.image(gfx);
93 Canvas::from_msaa(gfx, msaa, resolve, clear)
94 }
95
96 pub fn from_frame(gfx: &impl Has<GraphicsContext>, clear: impl Into<Option<Color>>) -> Self {
100 let gfx = gfx.retrieve();
101 let samples = gfx.frame_msaa_image.as_ref().unwrap().samples();
103 let (target, resolve) = if samples > 1 {
104 (
105 gfx.frame_msaa_image.clone().unwrap(),
106 Some(gfx.frame_image.clone().unwrap()),
107 )
108 } else {
109 (gfx.frame_image.clone().unwrap(), None)
110 };
111 Canvas::new(gfx, target, resolve, clear.into())
112 }
113
114 fn new(
115 gfx: &impl Has<GraphicsContext>,
116 target: Image,
117 resolve: Option<Image>,
118 clear: Option<Color>,
119 ) -> Self {
120 let gfx = gfx.retrieve();
121
122 let defaults = DefaultResources::new(gfx);
123
124 let state = DrawState {
125 shader: default_shader(),
126 params: None,
127 text_shader: default_text_shader(),
128 text_params: None,
129 sampler: Sampler::default(),
130 blend_mode: BlendMode::ALPHA,
131 premul_text: true,
132 projection: glam::Mat4::IDENTITY.into(),
133 scissor_rect: (0, 0, target.width(), target.height()),
134 };
135
136 let screen = Rect {
137 x: 0.,
138 y: 0.,
139 w: target.width() as _,
140 h: target.height() as _,
141 };
142
143 let mut this = Canvas {
144 wgpu: gfx.wgpu.clone(),
145 draws: BTreeMap::new(),
146 state: state.clone(),
147 original_state: state,
148 screen: Some(screen),
149 defaults,
150
151 target,
152 resolve,
153 clear,
154
155 queued_texts: Vec::new(),
156 };
157
158 this.set_screen_coordinates(screen);
159
160 this
161 }
162
163 #[inline]
165 pub fn set_shader(&mut self, shader: &Shader) {
166 self.state.shader = shader.clone();
167 }
168
169 #[inline]
171 pub fn shader(&self) -> Shader {
172 self.state.shader.clone()
173 }
174
175 #[inline]
179 pub fn set_shader_params<Uniforms: AsStd140>(&mut self, params: &ShaderParams<Uniforms>) {
180 self.state.params = Some((
181 params.bind_group.clone().unwrap(),
182 params.layout.clone().unwrap(),
183 params.buffer_offset,
184 ));
185 }
186
187 #[inline]
189 pub fn set_text_shader(&mut self, shader: Shader) {
190 self.state.text_shader = shader;
191 }
192
193 #[inline]
195 pub fn text_shader(&self) -> Shader {
196 self.state.text_shader.clone()
197 }
198
199 #[inline]
203 pub fn set_text_shader_params<Uniforms: AsStd140>(
204 &mut self,
205 params: &ShaderParams<Uniforms>,
206 ) -> GameResult {
207 self.state.text_params = Some((
208 params.bind_group.clone().unwrap(),
209 params.layout.clone().unwrap(),
210 params.buffer_offset,
211 ));
212 Ok(())
213 }
214
215 #[inline]
217 pub fn set_default_shader(&mut self) {
218 self.state.shader = default_shader();
219 }
220
221 #[inline]
223 pub fn set_default_text_shader(&mut self) {
224 self.state.text_shader = default_text_shader();
225 }
226
227 #[inline]
231 pub fn set_sampler(&mut self, sampler: impl Into<Sampler>) {
232 self.state.sampler = sampler.into();
233 }
234
235 #[inline]
237 pub fn sampler(&self) -> Sampler {
238 self.state.sampler
239 }
240
241 #[inline]
245 pub fn set_default_sampler(&mut self) {
246 self.set_sampler(Sampler::default());
247 }
248
249 #[inline]
251 pub fn set_blend_mode(&mut self, blend_mode: BlendMode) {
252 self.state.blend_mode = blend_mode;
253 }
254
255 #[inline]
257 pub fn blend_mode(&self) -> BlendMode {
258 self.state.blend_mode
259 }
260
261 #[inline]
264 pub fn set_premultiplied_text(&mut self, premultiplied_text: bool) {
265 self.state.premul_text = premultiplied_text;
266 }
267
268 #[inline]
272 pub fn set_projection(&mut self, proj: impl Into<mint::ColumnMatrix4<f32>>) {
273 self.state.projection = proj.into();
274 self.screen = None;
275 }
276
277 #[inline]
279 pub fn projection(&self) -> mint::ColumnMatrix4<f32> {
280 self.state.projection
281 }
282
283 pub fn mul_projection(&mut self, transform: impl Into<mint::ColumnMatrix4<f32>>) {
285 self.set_projection(
286 glam::Mat4::from(transform.into()) * glam::Mat4::from(self.state.projection),
287 );
288 self.screen = None;
289 }
290
291 #[inline]
303 pub fn set_screen_coordinates(&mut self, rect: Rect) {
304 self.set_projection(screen_to_mat(rect));
305 self.screen = Some(rect);
306 }
307
308 #[inline]
312 pub fn screen_coordinates(&self) -> Option<Rect> {
313 self.screen
314 }
315
316 #[inline]
321 pub fn set_scissor_rect(&mut self, rect: Rect) -> GameResult {
322 if rect.w as u32 == 0 || rect.h as u32 == 0 {
323 return Err(GameError::RenderError(String::from(
324 "the scissor rectangle size must be larger than zero.",
325 )));
326 }
327
328 let image_size = (self.target.width(), self.target.height());
329 if rect.x as u32 >= image_size.0 || rect.y as u32 >= image_size.1 {
330 return Err(GameError::RenderError(String::from(
331 "the scissor rectangle cannot start outside the canvas image.",
332 )));
333 }
334
335 let rect_width = u32::min(image_size.0 - rect.x as u32, rect.w as u32);
337 let rect_height = u32::min(image_size.1 - rect.y as u32, rect.h as u32);
338
339 self.state.scissor_rect = (rect.x as u32, rect.y as u32, rect_width, rect_height);
340
341 Ok(())
342 }
343
344 #[inline]
346 pub fn scissor_rect(&self) -> Rect {
347 Rect::new(
348 self.state.scissor_rect.0 as f32,
349 self.state.scissor_rect.1 as f32,
350 self.state.scissor_rect.2 as f32,
351 self.state.scissor_rect.3 as f32,
352 )
353 }
354
355 #[inline]
358 pub fn set_default_scissor_rect(&mut self) {
359 self.state.scissor_rect = self.original_state.scissor_rect;
360 }
361
362 #[inline]
364 pub fn draw(&mut self, drawable: &impl Drawable, param: impl Into<DrawParam>) {
365 drawable.draw(self, param)
366 }
367
368 pub fn draw_textured_mesh(&mut self, mesh: Mesh, image: Image, param: impl Into<DrawParam>) {
372 self.push_draw(
373 Draw::Mesh {
374 mesh,
375 image,
376 scale: false,
377 },
378 param.into(),
379 );
380 }
381
382 pub fn draw_instanced_mesh(
387 &mut self,
388 mesh: Mesh,
389 instances: &InstanceArray,
390 param: impl Into<DrawParam>,
391 ) {
392 instances.flush_wgpu(&self.wgpu).unwrap(); self.push_draw(
394 Draw::MeshInstances {
395 mesh,
396 instances: InstanceArrayView::from_instances(instances).unwrap(),
397 scale: false,
398 },
399 param.into(),
400 );
401 }
402
403 #[inline]
405 pub fn finish(mut self, gfx: &mut impl HasMut<GraphicsContext>) -> GameResult {
406 let gfx = gfx.retrieve_mut();
407 self.finalize(gfx)
408 }
409
410 #[inline]
411 pub(crate) fn default_resources(&self) -> &DefaultResources {
412 &self.defaults
413 }
414
415 #[inline]
416 pub(crate) fn push_draw(&mut self, draw: Draw, param: DrawParam) {
417 self.draws.entry(param.z).or_default().push(DrawCommand {
418 state: self.state.clone(),
419 draw,
420 param,
421 });
422 }
423
424 fn finalize(&mut self, gfx: &mut GraphicsContext) -> GameResult {
425 let mut canvas = if let Some(resolve) = &self.resolve {
426 InternalCanvas::from_msaa(gfx, self.clear, &self.target, resolve)?
427 } else {
428 InternalCanvas::from_image(gfx, self.clear, &self.target)?
429 };
430
431 let mut state = self.state.clone();
432
433 canvas.set_shader(state.shader.clone());
435 if let Some((bind_group, layout, offset)) = &state.params {
436 canvas.set_shader_params(bind_group.clone(), layout.clone(), *offset);
437 }
438
439 canvas.set_text_shader(state.text_shader.clone());
440 if let Some((bind_group, layout, offset)) = &state.text_params {
441 canvas.set_text_shader_params(bind_group.clone(), layout.clone(), *offset);
442 }
443
444 canvas.set_sampler(state.sampler);
445 canvas.set_blend_mode(state.blend_mode);
446 canvas.set_projection(state.projection);
447
448 if state.scissor_rect.2 > 0 && state.scissor_rect.3 > 0 {
449 canvas.set_scissor_rect(state.scissor_rect);
450 }
451
452 for draws in self.draws.values() {
453 for draw in draws {
454 if draw.state.shader != state.shader {
457 canvas.set_shader(draw.state.shader.clone());
458 }
459
460 if draw.state.params != state.params {
461 if let Some((bind_group, layout, offset)) = &draw.state.params {
462 canvas.set_shader_params(bind_group.clone(), layout.clone(), *offset);
463 }
464 }
465
466 if draw.state.text_shader != state.text_shader {
467 canvas.set_text_shader(draw.state.text_shader.clone());
468 }
469
470 if draw.state.text_params != state.text_params {
471 if let Some((bind_group, layout, offset)) = &draw.state.text_params {
472 canvas.set_text_shader_params(bind_group.clone(), layout.clone(), *offset);
473 }
474 }
475
476 if draw.state.sampler != state.sampler {
477 canvas.set_sampler(draw.state.sampler);
478 }
479
480 if draw.state.blend_mode != state.blend_mode {
481 canvas.set_blend_mode(draw.state.blend_mode);
482 }
483
484 if draw.state.premul_text != state.premul_text {
485 canvas.set_premultiplied_text(draw.state.premul_text);
486 }
487
488 if draw.state.projection != state.projection {
489 canvas.set_projection(draw.state.projection);
490 }
491
492 if draw.state.scissor_rect != state.scissor_rect {
493 canvas.set_scissor_rect(draw.state.scissor_rect);
494 }
495
496 state = draw.state.clone();
497
498 match &draw.draw {
499 Draw::Mesh { mesh, image, scale } => {
500 canvas.draw_mesh(mesh, image, draw.param, *scale)
501 }
502 Draw::MeshInstances {
503 mesh,
504 instances,
505 scale,
506 } => canvas.draw_mesh_instances(mesh, instances, draw.param, *scale)?,
507 Draw::BoundedText { text } => canvas.draw_bounded_text(text, draw.param)?,
508 }
509 }
510 }
511
512 canvas.finish();
513
514 Ok(())
515 }
516}
517
518#[derive(Debug, Clone)]
519struct DrawState {
520 shader: Shader,
521 params: Option<(ArcBindGroup, ArcBindGroupLayout, u32)>,
522 text_shader: Shader,
523 text_params: Option<(ArcBindGroup, ArcBindGroupLayout, u32)>,
524 sampler: Sampler,
525 blend_mode: BlendMode,
526 premul_text: bool,
527 projection: mint::ColumnMatrix4<f32>,
528 scissor_rect: (u32, u32, u32, u32),
529}
530
531#[derive(Debug)]
532pub(crate) enum Draw {
533 Mesh {
534 mesh: Mesh,
535 image: Image,
536 scale: bool,
537 },
538 MeshInstances {
539 mesh: Mesh,
540 instances: InstanceArrayView,
541 scale: bool,
542 },
543 BoundedText {
544 text: Text,
545 },
546}
547
548#[derive(Debug)]
550struct DrawCommand {
551 state: DrawState,
552 param: DrawParam,
553 draw: Draw,
554}
555
556#[derive(Debug)]
557pub(crate) struct DefaultResources {
558 pub mesh: Mesh,
559 pub image: Image,
560}
561
562impl DefaultResources {
563 fn new(gfx: &GraphicsContext) -> Self {
564 let mesh = gfx.rect_mesh.clone();
565 let image = gfx.white_image.clone();
566
567 DefaultResources { mesh, image }
568 }
569}
570
571pub fn default_shader() -> Shader {
573 Shader {
574 fs_module: None,
575 vs_module: None,
576 }
577}
578
579pub fn default_text_shader() -> Shader {
581 Shader {
582 fs_module: None,
583 vs_module: None,
584 }
585}