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::FilterMode::Linear,
464 Filter::Nearest => wgpu::FilterMode::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 push_constant_ranges: &[],
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: false,
608 depth_compare: 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 fragment: Some(wgpu::FragmentState {
618 module: &colored_shader_module,
619 entry_point: Some("fs_main"),
620 targets: &[Some(wgpu::ColorTargetState {
621 format: config.format,
622 blend,
623 write_mask: wgpu::ColorWrites::ALL,
624 })],
625 compilation_options: Default::default(),
626 }),
627 multiview: None,
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: &[&textured_bind_group_layout],
637 push_constant_ranges: &[],
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: false,
666 depth_compare: 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 fragment: Some(wgpu::FragmentState {
676 module: &textured_shader_module,
677 entry_point: Some("fs_main"),
678 targets: &[Some(wgpu::ColorTargetState {
679 format: config.format,
680 blend,
681 write_mask: wgpu::ColorWrites::ALL,
682 })],
683 compilation_options: Default::default(),
684 }),
685 multiview: None,
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 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
795 depth_slice: None,
796 view: output_view,
797 resolve_target: None,
798 ops: wgpu::Operations {
799 load: wgpu::LoadOp::Load,
800 store: StoreOp::Store,
801 },
802 })],
803 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
804 view: &self.stencil_view,
805 depth_ops: None,
806 stencil_ops: Some(wgpu::Operations {
807 load: wgpu::LoadOp::Load,
808 store: StoreOp::Store,
809 }),
810 }),
811 occlusion_query_set: None,
812 timestamp_writes: None,
813 });
814
815 render_pass.set_blend_constant(wgpu::Color::WHITE);
816
817 let vertex_buffer =
818 self.wgpu2d
819 .device
820 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
821 label: Some("Vertex Buffer"),
822 contents: bytemuck::cast_slice(colored_inputs),
823 usage: wgpu::BufferUsages::VERTEX,
824 });
825
826 let (pipeline, stencil_val) = self
827 .wgpu2d
828 .colored_render_pipelines
829 .stencil_blend(draw_state.stencil, draw_state.blend);
830
831 let [x, y, width, height] = match draw_state.scissor {
832 Some(rect) => rect,
833 None => [0, 0, self.width, self.height],
834 };
835 render_pass.set_pipeline(pipeline);
836 render_pass.set_scissor_rect(x, y, width, height);
837 if let Some(stencil_val) = stencil_val {
838 render_pass.set_stencil_reference(stencil_val as u32);
839 }
840
841 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
842 render_pass.draw(0..colored_inputs.len() as u32, 0..1);
843
844 self.wgpu2d.colored_data.clear();
845 }
846
847 fn command_textured(&mut self) {
848 let texture = &self.texture.as_ref().unwrap();
849 let draw_state = &self.draw_state;
850 let textured_inputs = &*self.wgpu2d.textured_data;
851 let output_view = self.output_view;
852 let encoder = &mut self.command_encoder;
853
854 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
855 label: Some("Colored Render Pass"),
856 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
857 depth_slice: None,
858 view: output_view,
859 resolve_target: None,
860 ops: wgpu::Operations {
861 load: wgpu::LoadOp::Load,
862 store: StoreOp::Store,
863 },
864 })],
865 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
866 view: &self.stencil_view,
867 depth_ops: None,
868 stencil_ops: Some(wgpu::Operations {
869 load: wgpu::LoadOp::Load,
870 store: StoreOp::Store,
871 }),
872 }),
873 occlusion_query_set: None,
874 timestamp_writes: None,
875 });
876
877 render_pass.set_blend_constant(wgpu::Color::WHITE);
878
879 let vertex_buffer =
880 self.wgpu2d
881 .device
882 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
883 label: Some("Vertex Buffer"),
884 contents: bytemuck::cast_slice(textured_inputs),
885 usage: wgpu::BufferUsages::VERTEX,
886 });
887
888 let (pipeline, stencil_val) = self
889 .wgpu2d
890 .textured_render_pipelines
891 .stencil_blend(draw_state.stencil, draw_state.blend);
892
893 let [x, y, width, height] = match draw_state.scissor {
894 Some(rect) => rect,
895 None => [0, 0, self.width, self.height],
896 };
897 render_pass.set_pipeline(pipeline);
898 render_pass.set_scissor_rect(x, y, width, height);
899 if let Some(stencil_val) = stencil_val {
900 render_pass.set_stencil_reference(stencil_val as u32);
901 }
902
903 render_pass.set_bind_group(0, Some(&texture.bind_group), &[]);
904 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
905 render_pass.draw(0..textured_inputs.len() as u32, 0..1);
906
907 self.wgpu2d.textured_data.clear();
908 }
909}
910
911impl<'a> Graphics for WgpuGraphics<'a> {
912 type Texture = Texture;
913
914 fn clear_color(&mut self, color: Color) {
915 if self.wgpu2d.colored_data.len() > 0 {
916 self.command_colored();
917 }
918 if self.wgpu2d.textured_data.len() > 0 {
919 self.command_textured();
920 }
921
922 let output_view = self.output_view;
923 let color_load = wgpu::LoadOp::Clear(to_wgpu_color(color));
924 let encoder = &mut self.command_encoder;
925 let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
926 label: Some("Clear Color Render Pass"),
927 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
928 depth_slice: None,
929 view: output_view,
930 resolve_target: None,
931 ops: wgpu::Operations {
932 load: color_load,
933 store: StoreOp::Store,
934 },
935 })],
936 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
937 view: &self.stencil_view,
938 depth_ops: None,
939 stencil_ops: Some(wgpu::Operations {
940 load: wgpu::LoadOp::Load,
941 store: StoreOp::Store,
942 }),
943 }),
944 occlusion_query_set: None,
945 timestamp_writes: None,
946 });
947 }
948
949 fn clear_stencil(&mut self, value: u8) {
950 if self.wgpu2d.colored_data.len() > 0 {
951 self.command_colored();
952 }
953 if self.wgpu2d.textured_data.len() > 0 {
954 self.command_textured();
955 }
956
957 let output_view = self.output_view;
958 let stencil_load = wgpu::LoadOp::Clear(value as u32);
959 let encoder = &mut self.command_encoder;
960 let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
961 label: Some("Clear Stencil Render Pass"),
962 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
963 depth_slice: None,
964 view: output_view,
965 resolve_target: None,
966 ops: wgpu::Operations {
967 load: wgpu::LoadOp::Load,
968 store: StoreOp::Store,
969 },
970 })],
971 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
972 view: &self.stencil_view,
973 depth_ops: None,
974 stencil_ops: Some(wgpu::Operations {
975 load: stencil_load,
976 store: StoreOp::Store,
977 }),
978 }),
979 occlusion_query_set: None,
980 timestamp_writes: None,
981 });
982 }
983
984 fn tri_list<F>(&mut self, draw_state: &DrawState, &color: &[f32; 4], mut f: F)
985 where
986 F: FnMut(&mut dyn FnMut(&[[f32; 2]])),
987 {
988 if self.wgpu2d.colored_data.len() > 0 {
989 let flush = self.wgpu2d.colored_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT ||
990 draw_state != &self.draw_state;
991 if flush {self.command_colored()}
992 }
993 if self.wgpu2d.textured_data.len() > 0 {
994 self.command_textured();
995 }
996
997 self.draw_state = *draw_state;
998 f(&mut |positions| {
999 if self.wgpu2d.colored_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT {
1000 self.command_colored();
1001 }
1002 self.wgpu2d.colored_data.extend(positions
1003 .iter()
1004 .map(|&position| ColoredPipelineInput { position, color }));
1005 })
1006 }
1007
1008 fn tri_list_c<F>(&mut self, draw_state: &DrawState, mut f: F)
1009 where
1010 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 4]])),
1011 {
1012 if self.wgpu2d.colored_data.len() > 0 {
1013 let flush = self.wgpu2d.colored_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT ||
1014 draw_state != &self.draw_state;
1015 if flush {self.command_colored()}
1016 }
1017 if self.wgpu2d.textured_data.len() > 0 {
1018 self.command_textured();
1019 }
1020
1021 self.draw_state = *draw_state;
1022 f(&mut |positions, colors| {
1023 if self.wgpu2d.colored_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT {
1024 self.command_colored();
1025 }
1026 self.wgpu2d.colored_data.extend(positions
1027 .iter()
1028 .zip(colors.iter())
1029 .map(|(&position, &color)| ColoredPipelineInput { position, color }));
1030 });
1031 }
1032
1033 fn tri_list_uv<F>(
1034 &mut self,
1035 draw_state: &DrawState,
1036 &color: &[f32; 4],
1037 texture: &Texture,
1038 mut f: F,
1039 ) where
1040 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]])),
1041 {
1042 if self.wgpu2d.colored_data.len() > 0 {
1043 self.command_colored();
1044 }
1045 if self.wgpu2d.textured_data.len() > 0 {
1046 let flush = self.wgpu2d.textured_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT ||
1047 draw_state != &self.draw_state;
1048 if flush {self.command_textured()}
1049 else if let Some(prev_texture) = self.texture.as_ref() {
1050 if texture != prev_texture {
1051 self.command_textured();
1052 }
1053 }
1054 }
1055
1056 self.texture = Some(texture.clone());
1057 self.draw_state = *draw_state;
1058 f(&mut |xys, uvs| {
1059 if self.wgpu2d.textured_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT {
1060 self.command_textured();
1061 }
1062 self.wgpu2d.textured_data.extend(xys
1063 .iter()
1064 .zip(uvs.iter())
1065 .map(|(&xy, &uv)| TexturedPipelineInput { xy, uv, color }));
1066 })
1067 }
1068
1069 fn tri_list_uv_c<F>(&mut self, draw_state: &DrawState, texture: &Texture, mut f: F)
1070 where
1071 F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]], &[[f32; 4]])),
1072 {
1073 if self.wgpu2d.colored_data.len() > 0 {
1074 self.command_colored();
1075 }
1076 if self.wgpu2d.textured_data.len() > 0 {
1077 let flush = self.wgpu2d.textured_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT ||
1078 draw_state != &self.draw_state;
1079 if flush {self.command_textured()}
1080 else if let Some(prev_texture) = self.texture.as_ref() {
1081 if texture != prev_texture {
1082 self.command_textured();
1083 }
1084 }
1085 }
1086
1087 self.texture = Some(texture.clone());
1088 self.draw_state = *draw_state;
1089 f(&mut |xys, uvs, colors| {
1090 if self.wgpu2d.textured_data.len() + BUFFER_SIZE >= SOFT_BUFFER_LIMIT {
1091 self.command_textured();
1092 }
1093 self.wgpu2d.textured_data.extend(xys
1094 .iter()
1095 .zip(uvs.iter())
1096 .zip(colors.iter())
1097 .map(|((&xy, &uv), &color)| TexturedPipelineInput { xy, uv, color }));
1098 })
1099 }
1100}
1101
1102fn to_wgpu_color(color: Color) -> wgpu::Color {
1103 wgpu::Color {
1104 r: color[0] as f64,
1105 g: color[1] as f64,
1106 b: color[2] as f64,
1107 a: color[3] as f64,
1108 }
1109}