1use graphics::{
4 draw_state::{Blend, Stencil},
5 types::Color,
6 Context, DrawState, Graphics, Viewport,
7};
8use std::{
9 fmt::{self, Display, Formatter},
10 path::Path,
11};
12use wgpu::util::DeviceExt;
13use wgpu::StoreOp;
14
15pub use graphics::ImageSize;
16pub use texture::*;
17
18pub type GlyphCache<'a> =
20 graphics::glyph_cache::rusttype::GlyphCache<'a, TextureContext<'a>, Texture>;
21
22#[repr(C)]
24#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
25struct ColoredPipelineInput {
26 position: [f32; 2],
27 color: [f32; 4],
28}
29
30impl ColoredPipelineInput {
31 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
32 wgpu::VertexBufferLayout {
33 array_stride: std::mem::size_of::<ColoredPipelineInput>() as wgpu::BufferAddress,
34 step_mode: wgpu::VertexStepMode::Vertex,
35 attributes: &[
36 wgpu::VertexAttribute {
37 offset: 0,
38 shader_location: 0,
39 format: wgpu::VertexFormat::Float32x2,
40 },
41 wgpu::VertexAttribute {
42 offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
43 shader_location: 1,
44 format: wgpu::VertexFormat::Float32x4,
45 },
46 ],
47 }
48 }
49}
50
51#[repr(C)]
53#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
54struct TexturedPipelineInput {
55 xy: [f32; 2],
56 uv: [f32; 2],
57 color: [f32; 4],
58}
59
60impl TexturedPipelineInput {
61 fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
62 wgpu::VertexBufferLayout {
63 array_stride: std::mem::size_of::<TexturedPipelineInput>() as wgpu::BufferAddress,
64 step_mode: wgpu::VertexStepMode::Vertex,
65 attributes: &[
66 wgpu::VertexAttribute {
67 offset: 0,
68 shader_location: 0,
69 format: wgpu::VertexFormat::Float32x2,
70 },
71 wgpu::VertexAttribute {
72 offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
73 shader_location: 1,
74 format: wgpu::VertexFormat::Float32x2,
75 },
76 wgpu::VertexAttribute {
77 offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
78 shader_location: 2,
79 format: wgpu::VertexFormat::Float32x4,
80 },
81 ],
82 }
83 }
84}
85
86struct PsoBlend<T> {
88 none: T,
89 alpha: T,
90 add: T,
91 lighter: T,
92 multiply: T,
93 invert: T,
94}
95
96impl<T> PsoBlend<T> {
97 fn blend(&self, blend: Option<Blend>) -> &T {
99 match blend {
100 None => &self.none,
101 Some(Blend::Alpha) => &self.alpha,
102 Some(Blend::Add) => &self.add,
103 Some(Blend::Lighter) => &self.lighter,
104 Some(Blend::Multiply) => &self.multiply,
105 Some(Blend::Invert) => &self.invert,
106 }
107 }
108}
109
110struct PsoStencil<T> {
112 none: PsoBlend<T>,
113 clip: PsoBlend<T>,
114 inside: PsoBlend<T>,
115 outside: PsoBlend<T>,
116 increment: PsoBlend<T>,
117}
118
119impl<T> PsoStencil<T> {
120 fn new<F>(mut f: F) -> PsoStencil<T>
122 where
123 F: FnMut(Option<wgpu::BlendState>, wgpu::StencilState) -> T,
124 {
125 use wgpu::{
126 BlendComponent, BlendFactor, BlendOperation, BlendState, CompareFunction,
127 StencilFaceState, StencilOperation, StencilState,
128 };
129
130 let stencil_none = StencilState {
131 front: StencilFaceState::IGNORE,
132 back: StencilFaceState::IGNORE,
133 read_mask: 0,
134 write_mask: 0,
135 };
136 let stencil_clip = StencilState {
137 front: StencilFaceState {
138 compare: CompareFunction::Never,
139 fail_op: StencilOperation::Replace,
140 ..Default::default()
141 },
142 back: StencilFaceState {
143 compare: CompareFunction::Never,
144 fail_op: StencilOperation::Replace,
145 ..Default::default()
146 },
147 read_mask: 255,
148 write_mask: 255,
149 };
150 let stencil_inside = StencilState {
151 front: StencilFaceState {
152 compare: CompareFunction::Equal,
153 ..Default::default()
154 },
155 back: StencilFaceState {
156 compare: CompareFunction::Equal,
157 ..Default::default()
158 },
159 read_mask: 255,
160 write_mask: 255,
161 };
162 let stencil_outside = StencilState {
163 front: StencilFaceState {
164 compare: CompareFunction::NotEqual,
165 ..Default::default()
166 },
167 back: StencilFaceState {
168 compare: CompareFunction::NotEqual,
169 ..Default::default()
170 },
171 read_mask: 255,
172 write_mask: 255,
173 };
174 let stencil_increment = StencilState {
175 front: StencilFaceState {
176 compare: CompareFunction::Never,
177 fail_op: StencilOperation::IncrementClamp,
178 ..Default::default()
179 },
180 back: StencilFaceState {
181 compare: CompareFunction::Never,
182 fail_op: StencilOperation::IncrementClamp,
183 ..Default::default()
184 },
185 read_mask: 255,
186 write_mask: 255,
187 };
188
189 let blend_add = BlendState {
190 color: BlendComponent {
191 src_factor: BlendFactor::One,
192 dst_factor: BlendFactor::One,
193 operation: BlendOperation::Add,
194 },
195 alpha: BlendComponent {
196 src_factor: BlendFactor::One,
197 dst_factor: BlendFactor::One,
198 operation: BlendOperation::Add,
199 },
200 };
201 let blend_lighter = BlendState {
202 color: BlendComponent {
203 src_factor: BlendFactor::SrcAlpha,
204 dst_factor: BlendFactor::One,
205 operation: BlendOperation::Add,
206 },
207 alpha: BlendComponent {
208 src_factor: BlendFactor::Zero,
209 dst_factor: BlendFactor::One,
210 operation: BlendOperation::Add,
211 },
212 };
213 let blend_multiply = BlendState {
214 color: BlendComponent {
215 src_factor: BlendFactor::Dst,
216 dst_factor: BlendFactor::Zero,
217 operation: BlendOperation::Add,
218 },
219 alpha: BlendComponent {
220 src_factor: BlendFactor::DstAlpha,
221 dst_factor: BlendFactor::Zero,
222 operation: BlendOperation::Add,
223 },
224 };
225 let blend_invert = BlendState {
226 color: BlendComponent {
227 src_factor: BlendFactor::Constant,
228 dst_factor: BlendFactor::Src,
229 operation: BlendOperation::Subtract,
230 },
231 alpha: BlendComponent {
232 src_factor: BlendFactor::Zero,
233 dst_factor: BlendFactor::One,
234 operation: BlendOperation::Add,
235 },
236 };
237
238 PsoStencil {
239 none: PsoBlend {
240 none: f(None, stencil_none.clone()),
241 alpha: f(Some(BlendState::ALPHA_BLENDING), stencil_none.clone()),
242 add: f(Some(blend_add), stencil_none.clone()),
243 lighter: f(Some(blend_lighter), stencil_none.clone()),
244 multiply: f(Some(blend_multiply), stencil_none.clone()),
245 invert: f(Some(blend_invert), stencil_none),
246 },
247 clip: PsoBlend {
248 none: f(None, stencil_clip.clone()),
249 alpha: f(Some(BlendState::ALPHA_BLENDING), stencil_clip.clone()),
250 add: f(Some(blend_add), stencil_clip.clone()),
251 lighter: f(Some(blend_lighter), stencil_clip.clone()),
252 multiply: f(Some(blend_multiply), stencil_clip.clone()),
253 invert: f(Some(blend_invert), stencil_clip),
254 },
255 inside: PsoBlend {
256 none: f(None, stencil_inside.clone()),
257 alpha: f(Some(BlendState::ALPHA_BLENDING), stencil_inside.clone()),
258 add: f(Some(blend_add), stencil_inside.clone()),
259 lighter: f(Some(blend_lighter), stencil_inside.clone()),
260 multiply: f(Some(blend_multiply), stencil_inside.clone()),
261 invert: f(Some(blend_invert), stencil_inside),
262 },
263 outside: PsoBlend {
264 none: f(None, stencil_outside.clone()),
265 alpha: f(Some(BlendState::ALPHA_BLENDING), stencil_outside.clone()),
266 add: f(Some(blend_add), stencil_outside.clone()),
267 lighter: f(Some(blend_lighter), stencil_outside.clone()),
268 multiply: f(Some(blend_multiply), stencil_outside.clone()),
269 invert: f(Some(blend_invert), stencil_outside),
270 },
271 increment: PsoBlend {
272 none: f(None, stencil_increment.clone()),
273 alpha: f(Some(BlendState::ALPHA_BLENDING), stencil_increment.clone()),
274 add: f(Some(blend_add), stencil_increment.clone()),
275 lighter: f(Some(blend_lighter), stencil_increment.clone()),
276 multiply: f(Some(blend_multiply), stencil_increment.clone()),
277 invert: f(Some(blend_invert), stencil_increment),
278 },
279 }
280 }
281
282 fn stencil_blend(&self, stencil: Option<Stencil>, blend: Option<Blend>) -> (&T, Option<u8>) {
284 match stencil {
285 None => (self.none.blend(blend), None),
286 Some(Stencil::Clip(val)) => (self.clip.blend(blend), Some(val)),
287 Some(Stencil::Inside(val)) => (self.inside.blend(blend), Some(val)),
288 Some(Stencil::Outside(val)) => (self.outside.blend(blend), Some(val)),
289 Some(Stencil::Increment) => (self.increment.blend(blend), None),
290 }
291 }
292}
293
294pub struct Texture {
296 texture: wgpu::Texture,
297 bind_group: wgpu::BindGroup,
298 width: u32,
299 height: u32,
300}
301
302pub struct TextureContext<'a> {
304 device: &'a wgpu::Device,
305 queue: &'a wgpu::Queue,
306}
307
308impl<'a> TextureContext<'a> {
309 pub fn from_parts(device: &'a wgpu::Device, queue: &'a wgpu::Queue) -> Self {
311 TextureContext { device, queue }
312 }
313}
314
315impl Texture {
316 pub fn from_path<'a, P>(
318 context: &mut TextureContext<'a>,
319 path: P,
320 settings: &TextureSettings,
321 ) -> Result<Self, TextureError>
322 where
323 P: AsRef<Path>,
324 {
325 let img = image::open(path).map_err(TextureError::ImageError)?;
326 let img = match img {
327 image::DynamicImage::ImageRgba8(img) => img,
328 img => img.to_rgba8(),
329 };
330
331 Texture::from_image(context, &img, settings)
332 }
333
334 pub fn from_image<'a>(
336 context: &mut TextureContext<'a>,
337 img: &image::RgbaImage,
338 settings: &TextureSettings,
339 ) -> Result<Self, TextureError> {
340 let (width, height) = img.dimensions();
341 CreateTexture::create(context, Format::Rgba8, img, [width, height], settings)
342 }
343
344 fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
347 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
348 label: Some("Texture Bind Group Layout"),
349 entries: &[
350 wgpu::BindGroupLayoutEntry {
351 binding: 0,
352 visibility: wgpu::ShaderStages::FRAGMENT,
353 ty: wgpu::BindingType::Texture {
354 multisampled: false,
355 view_dimension: wgpu::TextureViewDimension::D2,
356 sample_type: wgpu::TextureSampleType::Float { filterable: true },
357 },
358 count: None,
359 },
360 wgpu::BindGroupLayoutEntry {
361 binding: 1,
362 visibility: wgpu::ShaderStages::FRAGMENT,
363 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
364 count: None,
365 },
366 ],
367 })
368 }
369}
370
371impl<'a> TextureOp<TextureContext<'a>> for Texture {
372 type Error = TextureError;
373}
374
375#[derive(Debug)]
377pub enum TextureError {
378 ImageError(image::error::ImageError),
379}
380
381impl Display for TextureError {
382 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
383 match self {
384 TextureError::ImageError(e) => write!(f, "Error loading image: {}", e),
385 }
386 }
387}
388
389#[allow(clippy::float_cmp)]
390impl<'a> CreateTexture<TextureContext<'a>> for Texture {
391 fn create<S: Into<[u32; 2]>>(
392 TextureContext { device, queue }: &mut TextureContext<'a>,
393 _format: Format,
394 memory: &[u8],
395 size: S,
396 settings: &TextureSettings,
397 ) -> Result<Self, TextureError> {
398 let [width, height] = size.into();
399 let texture_size = wgpu::Extent3d {
400 width,
401 height,
402 depth_or_array_layers: 1,
403 };
404
405 let texture = device.create_texture(&wgpu::TextureDescriptor {
406 label: Some("Diffuse Texture"),
407 size: texture_size,
408 mip_level_count: 1,
409 sample_count: 1,
410 dimension: wgpu::TextureDimension::D2,
411 format: wgpu::TextureFormat::Rgba8UnormSrgb,
412 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
413 view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
414 });
415
416 queue.write_texture(
417 wgpu::TexelCopyTextureInfoBase {
418 texture: &texture,
419 mip_level: 0,
420 origin: wgpu::Origin3d::ZERO,
421 aspect: wgpu::TextureAspect::All,
422 },
423 memory,
424 wgpu::TexelCopyBufferLayout {
425 offset: 0,
426 bytes_per_row: Some(4 * width),
427 rows_per_image: Some(height),
428 },
429 texture_size,
430 );
431
432 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
433 label: Some("Texture View"),
434 ..Default::default()
435 });
436
437 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
438 address_mode_u: match settings.get_wrap_u() {
439 Wrap::ClampToEdge => wgpu::AddressMode::ClampToEdge,
440 Wrap::Repeat => wgpu::AddressMode::Repeat,
441 Wrap::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
442 Wrap::ClampToBorder => wgpu::AddressMode::ClampToBorder,
443 },
444 address_mode_v: match settings.get_wrap_v() {
445 Wrap::ClampToEdge => wgpu::AddressMode::ClampToEdge,
446 Wrap::Repeat => wgpu::AddressMode::Repeat,
447 Wrap::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
448 Wrap::ClampToBorder => wgpu::AddressMode::ClampToBorder,
449 },
450 address_mode_w: wgpu::AddressMode::ClampToEdge,
451 mag_filter: match settings.get_mag() {
452 Filter::Linear => wgpu::FilterMode::Linear,
453 Filter::Nearest => wgpu::FilterMode::Nearest,
454 },
455 min_filter: match settings.get_min() {
456 Filter::Linear => wgpu::FilterMode::Linear,
457 Filter::Nearest => wgpu::FilterMode::Nearest,
458 },
459 mipmap_filter: match settings.get_mipmap() {
460 Filter::Linear => wgpu::FilterMode::Linear,
461 Filter::Nearest => wgpu::FilterMode::Nearest,
462 },
463 border_color: if settings.get_border_color() == [0.0; 4] {
464 Some(wgpu::SamplerBorderColor::TransparentBlack)
465 } else if settings.get_border_color() == [0.0, 0.0, 0.0, 1.0] {
466 Some(wgpu::SamplerBorderColor::OpaqueBlack)
467 } else if settings.get_border_color() == [1.0; 4] {
468 Some(wgpu::SamplerBorderColor::OpaqueWhite)
469 } else {
470 None
471 },
472 ..Default::default()
473 });
474
475 let bind_group_layout = Texture::create_bind_group_layout(device);
476
477 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
478 label: Some("Texture Bind Group"),
479 layout: &bind_group_layout,
480 entries: &[
481 wgpu::BindGroupEntry {
482 binding: 0,
483 resource: wgpu::BindingResource::TextureView(&texture_view),
484 },
485 wgpu::BindGroupEntry {
486 binding: 1,
487 resource: wgpu::BindingResource::Sampler(&sampler),
488 },
489 ],
490 });
491
492 Ok(Self {
493 texture,
494 bind_group,
495 width,
496 height,
497 })
498 }
499}
500
501impl<'a> UpdateTexture<TextureContext<'a>> for Texture {
502 fn update<O, S>(
503 &mut self,
504 TextureContext { queue, .. }: &mut TextureContext<'a>,
505 _format: Format,
506 memory: &[u8],
507 offset: O,
508 size: S,
509 ) -> Result<(), TextureError>
510 where
511 O: Into<[u32; 2]>,
512 S: Into<[u32; 2]>,
513 {
514 let Texture { ref texture, .. } = self;
515 let [x, y] = offset.into();
516 let [width, height] = size.into();
517
518 let origin = wgpu::Origin3d { x, y, z: 0 };
519 let size = wgpu::Extent3d {
520 width,
521 height,
522 depth_or_array_layers: 1,
523 };
524
525 queue.write_texture(
526 wgpu::TexelCopyTextureInfoBase {
527 texture,
528 mip_level: 0,
529 origin,
530 aspect: wgpu::TextureAspect::All,
531 },
532 memory,
533 wgpu::TexelCopyBufferLayout {
534 offset: 0,
535 bytes_per_row: Some(4 * width),
536 rows_per_image: Some(height),
537 },
538 size,
539 );
540 Ok(())
541 }
542}
543
544impl ImageSize for Texture {
545 fn get_size(&self) -> (u32, u32) {
546 (self.width, self.height)
547 }
548}
549
550pub struct Wgpu2d<'a> {
552 device: &'a wgpu::Device,
553 colored_render_pipelines: PsoStencil<wgpu::RenderPipeline>,
554 textured_render_pipelines: PsoStencil<wgpu::RenderPipeline>,
555}
556
557impl<'a> Wgpu2d<'a> {
558 pub fn new<'b>(device: &'a wgpu::Device, config: &'b wgpu::SurfaceConfiguration) -> Self {
560 let colored_pipeline_layout =
561 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
562 label: Some("Colored Pipeline Layout"),
563 bind_group_layouts: &[],
564 push_constant_ranges: &[],
565 });
566
567 let colored_shader_module =
568 device.create_shader_module(wgpu::include_wgsl!("colored.wgsl"));
569
570 let colored_render_pipelines = PsoStencil::new(|blend, stencil| {
571 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
572 cache: None,
573 label: Some("Colored Render Pipeline"),
574 layout: Some(&colored_pipeline_layout),
575 vertex: wgpu::VertexState {
576 module: &colored_shader_module,
577 entry_point: Some("vs_main"),
578 buffers: &[ColoredPipelineInput::desc()],
579 compilation_options: Default::default(),
580 },
581 primitive: wgpu::PrimitiveState {
582 topology: wgpu::PrimitiveTopology::TriangleList,
583 strip_index_format: None,
584 front_face: wgpu::FrontFace::Ccw,
585 cull_mode: None,
586 unclipped_depth: true,
587 polygon_mode: wgpu::PolygonMode::Fill,
588 conservative: false,
589 },
590 depth_stencil: Some(wgpu::DepthStencilState {
591 format: wgpu::TextureFormat::Depth24PlusStencil8,
592 depth_write_enabled: false,
593 depth_compare: wgpu::CompareFunction::Always,
594 stencil,
595 bias: wgpu::DepthBiasState::default(),
596 }),
597 multisample: wgpu::MultisampleState {
598 count: 1,
599 mask: !0,
600 alpha_to_coverage_enabled: false,
601 },
602 fragment: Some(wgpu::FragmentState {
603 module: &colored_shader_module,
604 entry_point: Some("fs_main"),
605 targets: &[Some(wgpu::ColorTargetState {
606 format: config.format,
607 blend,
608 write_mask: wgpu::ColorWrites::ALL,
609 })],
610 compilation_options: Default::default(),
611 }),
612 multiview: None,
613 })
614 });
615
616 let textured_bind_group_layout = Texture::create_bind_group_layout(device);
617
618 let textured_pipeline_layout =
619 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
620 label: Some("Textured Pipeline Layout"),
621 bind_group_layouts: &[&textured_bind_group_layout],
622 push_constant_ranges: &[],
623 });
624
625 let textured_shader_module =
626 device.create_shader_module(wgpu::include_wgsl!("textured.wgsl"));
627
628 let textured_render_pipelines = PsoStencil::new(|blend, stencil| {
629 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
630 cache: None,
631 label: Some("Textured Render Pipeline"),
632 layout: Some(&textured_pipeline_layout),
633 vertex: wgpu::VertexState {
634 module: &textured_shader_module,
635 entry_point: Some("vs_main"),
636 buffers: &[TexturedPipelineInput::desc()],
637 compilation_options: Default::default(),
638 },
639 primitive: wgpu::PrimitiveState {
640 topology: wgpu::PrimitiveTopology::TriangleList,
641 strip_index_format: None,
642 front_face: wgpu::FrontFace::Ccw,
643 cull_mode: None,
644 unclipped_depth: true,
645 polygon_mode: wgpu::PolygonMode::Fill,
646 conservative: false,
647 },
648 depth_stencil: Some(wgpu::DepthStencilState {
649 format: wgpu::TextureFormat::Depth24PlusStencil8,
650 depth_write_enabled: false,
651 depth_compare: wgpu::CompareFunction::Always,
652 stencil,
653 bias: wgpu::DepthBiasState::default(),
654 }),
655 multisample: wgpu::MultisampleState {
656 count: 1,
657 mask: !0,
658 alpha_to_coverage_enabled: false,
659 },
660 fragment: Some(wgpu::FragmentState {
661 module: &textured_shader_module,
662 entry_point: Some("fs_main"),
663 targets: &[Some(wgpu::ColorTargetState {
664 format: config.format,
665 blend,
666 write_mask: wgpu::ColorWrites::ALL,
667 })],
668 compilation_options: Default::default(),
669 }),
670 multiview: None,
671 })
672 });
673
674 Self {
675 device,
676 colored_render_pipelines,
677 textured_render_pipelines,
678 }
679 }
680
681 pub fn draw<F>(
685 &mut self,
686 device: &wgpu::Device,
687 config: &wgpu::SurfaceConfiguration,
688 output_view: &wgpu::TextureView,
689 viewport: Viewport,
690 f: F,
691 ) -> wgpu::CommandBuffer
692 where
693 F: FnOnce(Context, &mut WgpuGraphics),
694 {
695 let mut g = WgpuGraphics::new(self, config);
696 let c = Context::new_viewport(viewport);
697 f(c, &mut g);
698 g.draw(device, output_view)
699 }
700}
701
702pub struct WgpuGraphics<'a> {
704 wgpu2d: &'a Wgpu2d<'a>,
705 width: u32,
706 height: u32,
707 color_format: wgpu::TextureFormat,
708 clear_color: Option<Color>,
709 clear_stencil: Option<u8>,
710 stencil_view: wgpu::TextureView,
711 render_bundles: Vec<(
712 &'a wgpu::RenderPipeline,
713 Option<[u32; 4]>,
714 Option<u8>,
715 wgpu::RenderBundle,
716 )>,
717}
718
719impl<'a> WgpuGraphics<'a> {
720 pub fn new(wgpu2d: &'a Wgpu2d<'a>, config: &wgpu::SurfaceConfiguration) -> Self {
722 let size = wgpu::Extent3d {
723 width: config.width,
724 height: config.height,
725 depth_or_array_layers: 1,
726 };
727 let stencil = wgpu2d.device.create_texture(&wgpu::TextureDescriptor {
728 label: Some("Stencil Texture"),
729 size,
730 mip_level_count: 1,
731 sample_count: 1,
732 dimension: wgpu::TextureDimension::D2,
733 format: wgpu::TextureFormat::Depth24PlusStencil8,
734 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
735 view_formats: &[wgpu::TextureFormat::Depth24PlusStencil8],
736 });
737 let stencil_view = stencil.create_view(&wgpu::TextureViewDescriptor {
738 label: Some("Stencil Texture View"),
739 ..Default::default()
740 });
741 Self {
742 wgpu2d,
743 width: config.width,
744 height: config.height,
745 color_format: config.format,
746 clear_color: None,
747 clear_stencil: None,
748 stencil_view,
749 render_bundles: vec![],
750 }
751 }
752
753 pub fn draw(
757 self,
758 device: &wgpu::Device,
759 output_view: &wgpu::TextureView,
760 ) -> wgpu::CommandBuffer {
761 let color_load = match self.clear_color {
762 Some(c) => wgpu::LoadOp::Clear(to_wgpu_color(c)),
763 None => wgpu::LoadOp::Load,
764 };
765 let stencil_load = match self.clear_stencil {
766 Some(s) => wgpu::LoadOp::Clear(s as u32),
767 None => wgpu::LoadOp::Load,
768 };
769
770 encode(device, |encoder| {
771 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
772 label: Some("Render Pass"),
773 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
774 depth_slice: None,
775 view: output_view,
776 resolve_target: None,
777 ops: wgpu::Operations {
778 load: color_load,
779 store: StoreOp::Store,
780 },
781 })],
782 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
783 view: &self.stencil_view,
784 depth_ops: None,
785 stencil_ops: Some(wgpu::Operations {
786 load: stencil_load,
787 store: StoreOp::Store,
788 }),
789 }),
790 occlusion_query_set: None,
791 timestamp_writes: None,
792 });
793
794 render_pass.set_blend_constant(wgpu::Color::WHITE);
795
796 for &(pipeline, scissor, stencil_val, ref render_bundle) in &self.render_bundles {
797 let [x, y, width, height] = match scissor {
798 Some(rect) => rect,
799 None => [0, 0, self.width, self.height],
800 };
801 render_pass.set_pipeline(pipeline);
806
807 render_pass.set_scissor_rect(x, y, width, height);
808 if let Some(stencil_val) = stencil_val {
809 render_pass.set_stencil_reference(stencil_val as u32);
810 }
811 render_pass.execute_bundles(std::iter::once(render_bundle));
812 }
813 })
814 }
815
816 fn bundle<'b, F>(&self, device: &'b wgpu::Device, f: F) -> wgpu::RenderBundle
817 where
818 F: FnOnce(&mut dyn wgpu::util::RenderEncoder<'b>),
819 {
820 let mut render_bundle_encoder =
821 device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
822 label: Some("Render Bundle Encoder"),
823 color_formats: &[Some(self.color_format)],
824 depth_stencil: Some(wgpu::RenderBundleDepthStencil {
825 format: wgpu::TextureFormat::Depth24PlusStencil8,
826 depth_read_only: true,
827 stencil_read_only: false,
828 }),
829 sample_count: 1,
830 multiview: None,
831 });
832
833 f(&mut render_bundle_encoder);
834
835 render_bundle_encoder.finish(&wgpu::RenderBundleDescriptor {
836 label: Some("Render Bundle"),
837 })
838 }
839
840 fn bundle_colored(&mut self, colored_inputs: &[ColoredPipelineInput], draw_state: &DrawState) {
841 let vertex_buffer =
842 self.wgpu2d
843 .device
844 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
845 label: Some("Vertex Buffer"),
846 contents: bytemuck::cast_slice(colored_inputs),
847 usage: wgpu::BufferUsages::VERTEX,
848 });
849
850 let (pipeline, stencil_val) = self
851 .wgpu2d
852 .colored_render_pipelines
853 .stencil_blend(draw_state.stencil, draw_state.blend);
854
855 let render_bundle = self.bundle(self.wgpu2d.device, |render_encoder| {
856 render_encoder.set_pipeline(pipeline);
857 render_encoder.set_vertex_buffer(0, vertex_buffer.slice(..));
858 render_encoder.draw(0..colored_inputs.len() as u32, 0..1);
859 });
860
861 self.render_bundles
862 .push((pipeline, draw_state.scissor, stencil_val, render_bundle));
863 }
864
865 fn bundle_textured(
866 &mut self,
867 textured_inputs: &[TexturedPipelineInput],
868 texture: &Texture,
869 draw_state: &DrawState,
870 ) {
871 let vertex_buffer =
872 self.wgpu2d
873 .device
874 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
875 label: Some("Vertex Buffer"),
876 contents: bytemuck::cast_slice(textured_inputs),
877 usage: wgpu::BufferUsages::VERTEX,
878 });
879
880 let (pipeline, stencil_val) = self
881 .wgpu2d
882 .textured_render_pipelines
883 .stencil_blend(draw_state.stencil, draw_state.blend);
884
885 let render_bundle = self.bundle(self.wgpu2d.device, |render_encoder| {
886 render_encoder.set_pipeline(pipeline);
887 render_encoder.set_bind_group(0, Some(&texture.bind_group), &[]);
888 render_encoder.set_vertex_buffer(0, vertex_buffer.slice(..));
889 render_encoder.draw(0..textured_inputs.len() as u32, 0..1);
890 });
891
892 self.render_bundles
893 .push((pipeline, draw_state.scissor, stencil_val, render_bundle));
894 }
895}
896
897impl<'a> Graphics for WgpuGraphics<'a> {
898 type Texture = Texture;
899
900 fn clear_color(&mut self, color: Color) {
901 self.clear_color = Some(color);
902 self.render_bundles
903 .retain(|&(_, _, stencil_val, _)| stencil_val.is_some());
904 }
905
906 fn clear_stencil(&mut self, value: u8) {
907 self.clear_stencil = Some(value);
908 self.render_bundles
909 .retain(|&(_, _, stencil_val, _)| stencil_val.is_none());
910 }
911
912 fn tri_list<F>(&mut self, draw_state: &DrawState, &color: &[f32; 4], mut f: F)
913 where
914 F: FnMut(&mut dyn FnMut(&[[f32; 2]])),
915 {
916 f(&mut |positions| {
917 let pipeline_inputs = positions
918 .iter()
919 .map(|&position| ColoredPipelineInput { position, color })
920 .collect::<Vec<_>>();
921
922 self.bundle_colored(&pipeline_inputs, draw_state);
923 });
924 }
925
926 fn tri_list_c<F>(&mut self, draw_state: &DrawState, mut f: F)
927 where
928 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 4]])),
929 {
930 f(&mut |positions, colors| {
931 let pipeline_inputs = positions
932 .iter()
933 .zip(colors.iter())
934 .map(|(&position, &color)| ColoredPipelineInput { position, color })
935 .collect::<Vec<_>>();
936
937 self.bundle_colored(&pipeline_inputs, draw_state);
938 });
939 }
940
941 fn tri_list_uv<F>(
942 &mut self,
943 draw_state: &DrawState,
944 &color: &[f32; 4],
945 texture: &Texture,
946 mut f: F,
947 ) where
948 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]])),
949 {
950 f(&mut |xys, uvs| {
951 let pipeline_inputs = xys
952 .iter()
953 .zip(uvs.iter())
954 .map(|(&xy, &uv)| TexturedPipelineInput { xy, uv, color })
955 .collect::<Vec<_>>();
956
957 self.bundle_textured(&pipeline_inputs, texture, draw_state);
958 })
959 }
960
961 fn tri_list_uv_c<F>(&mut self, draw_state: &DrawState, texture: &Texture, mut f: F)
962 where
963 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]], &[[f32; 4]])),
964 {
965 f(&mut |xys, uvs, colors| {
966 let pipeline_inputs = xys
967 .iter()
968 .zip(uvs.iter())
969 .zip(colors.iter())
970 .map(|((&xy, &uv), &color)| TexturedPipelineInput { xy, uv, color })
971 .collect::<Vec<_>>();
972
973 self.bundle_textured(&pipeline_inputs, texture, draw_state);
974 })
975 }
976}
977
978fn encode<F>(device: &wgpu::Device, f: F) -> wgpu::CommandBuffer
979where
980 F: FnOnce(&mut wgpu::CommandEncoder),
981{
982 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
983 label: Some("Command Encoder"),
984 });
985
986 f(&mut encoder);
987
988 encoder.finish()
989}
990
991fn to_wgpu_color(color: Color) -> wgpu::Color {
992 wgpu::Color {
993 r: color[0] as f64,
994 g: color[1] as f64,
995 b: color[2] as f64,
996 a: color[3] as f64,
997 }
998}