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