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::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;
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                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    /// 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            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}