Skip to main content

wgpu_graphics/
lib.rs

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
19/// Stores textures for text rendering.
20pub type GlyphCache<'a> =
21    graphics::glyph_cache::rusttype::GlyphCache<'a, TextureContext, Texture>;
22
23/// Input struct for the "colored" pipeline's vertex shader.
24#[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/// Input struct for the "textured" pipeline's vertex shader.
53#[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
87/// Stores `T` object for each Blend mode.
88struct 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    /// Returns `T` object for `blend`.
99    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
111/// Stores `T` object for each (Stencil, Blend) mode.
112struct 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    /// Creates a new `PsoStencil<T>`, using `f`, for all (Stencil, Blend) mode.
122    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    /// Returns `T` object for `stencil` and `blend`.
284    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/// Represents a texture.
296#[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/// Context required to create and update textures.
305#[derive(Clone)]
306pub struct TextureContext {
307    device: Arc<wgpu::Device>,
308    queue: Arc<wgpu::Queue>,
309}
310
311impl TextureContext {
312    /// Creates a new `TextureContext` from its parts.
313    pub fn from_parts(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
314        TextureContext { device, queue }
315    }
316}
317
318impl Texture {
319    /// Creates a `Texture` with image loading from `path`.
320    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    /// Creates a `Texture` with `img`.
338    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    /// Creates a [`BindGroupLayout`](`wgpu::BindGroupLayout`) for "textured" pipeline's fragment shader's binding.
348    // FIXME: Maybe should be moved out of `impl Texture`?
349    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/// Texture creation or update error.
379#[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;
554// The number of chunks to fill up before rendering.
555// Amount of memory used: `BUFFER_SIZE * CHUNKS * 4 * (2 + 4)`
556// `4` for bytes per f32, and `2 + 4` for position and color.
557const CHUNKS: usize = 100;
558const SOFT_BUFFER_LIMIT: usize = CHUNKS * BUFFER_SIZE;
559
560/// The resource needed for rendering 2D.
561pub 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    /// Creates a new `Wgpu2d`.
571    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    /// Performs 2D graphics operations and returns encoded commands.
699    ///
700    /// To actually draw on a window surface, you must [`submit`](`wgpu::Queue::submit`) the returned [`CommandBuffer`](`wgpu::CommandBuffer`).
701    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
718/// Graphics back-end.
719pub 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    /// Creates a new `WgpuGraphics`.
732    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    /// Performs 2D graphics operations and returns encoded commands.
773    ///
774    /// To actually draw on a window surface, you must [`submit`](`wgpu::Queue::submit`) the returned [`CommandBuffer`](`wgpu::CommandBuffer`).
775    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}