wgpu_graphics/
lib.rs

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