Skip to main content

agg_gui_wgpu/
pipelines.rs

1//! All `wgpu::RenderPipeline` objects and associated `wgpu::BindGroupLayout`
2//! resources created once at startup.
3//!
4//! `WgpuPipelines` is created in `WgpuGfxCtx::new()` and stored for the
5//! lifetime of the context.  Every pipeline, bind-group layout, and shared
6//! sampler lives here.
7//!
8//! Uniform data is NOT stored here — each draw command in `end_frame` creates
9//! a small `wgpu::Buffer` via `device.create_buffer_init()` so that multiple
10//! draw commands of the same pipeline type in one frame do not overwrite each
11//! other's uniforms.
12
13use std::mem::size_of;
14
15use bytemuck::{Pod, Zeroable};
16
17use crate::shaders::{
18    AA_SOLID_WGSL, AA_TEXTURE_WGSL, GRADIENT_WGSL, LAYER_WGSL, LCB_FLATTEN_WGSL, LCB_WGSL,
19    LCD_WGSL, SOLID_WGSL, TEXT_GRAY_WGSL, TEX_DOWNSAMPLE_3X_WGSL, TEX_DOWNSAMPLE_4X_WGSL, TEX_WGSL,
20};
21
22// ---------------------------------------------------------------------------
23// Uniform structs (byte layouts must exactly match WGSL structs in shaders.rs)
24// ---------------------------------------------------------------------------
25
26/// 32-byte uniform block for the solid and AA-solid pipelines.
27#[repr(C)]
28#[derive(Clone, Copy, Debug, Pod, Zeroable)]
29pub(crate) struct SolidUniforms {
30    pub resolution: [f32; 2],
31    pub _pad: [f32; 2],
32    pub color: [f32; 4],
33}
34const _: () = assert!(size_of::<SolidUniforms>() == 32);
35
36/// 32-byte uniform block for the AA-texture pipeline (agg-sharp port).
37/// Same layout as `SolidUniforms` but kept as a separate type so the
38/// pipeline plumbing can read intent at a glance.
39#[repr(C)]
40#[derive(Clone, Copy, Debug, Pod, Zeroable)]
41pub(crate) struct AaTexUniforms {
42    pub resolution: [f32; 2],
43    pub _pad: [f32; 2],
44    pub color: [f32; 4],
45}
46const _: () = assert!(size_of::<AaTexUniforms>() == 32);
47
48/// 32-byte uniform block for the textured-quad pipeline. `tint` is
49/// a per-draw RGBA multiplier so callers can fade image blits — set
50/// `[1.0, 1.0, 1.0, alpha]` for a straight alpha fade, or zero out
51/// channels for a quick recolor. The 4×4-box downsample pipeline
52/// reuses the same layout and simply doesn't read `tint`.
53#[repr(C)]
54#[derive(Clone, Copy, Debug, Pod, Zeroable)]
55pub(crate) struct TexUniforms {
56    pub resolution: [f32; 2],
57    pub _pad: [f32; 2],
58    pub tint: [f32; 4],
59}
60const _: () = assert!(size_of::<TexUniforms>() == 32);
61
62/// 48-byte uniform block for the layer-composite pipeline.
63#[repr(C)]
64#[derive(Clone, Copy, Debug, Pod, Zeroable)]
65pub(crate) struct LayerUniforms {
66    pub resolution: [f32; 2],
67    pub alpha: f32,
68    pub mask_enabled: u32,
69    pub layer_size: [f32; 2],
70    pub mask_radius: f32,
71    pub _pad0: f32,
72    pub mask_rect: [f32; 4],
73}
74const _: () = assert!(size_of::<LayerUniforms>() == 48);
75
76/// 32-byte uniform block for the LCD subpixel-text pipeline.
77#[repr(C)]
78#[derive(Clone, Copy, Debug, Pod, Zeroable)]
79pub(crate) struct LcdUniforms {
80    pub resolution: [f32; 2],
81    pub channel: u32,
82    pub _pad: u32,
83    pub color: [f32; 4],
84}
85const _: () = assert!(size_of::<LcdUniforms>() == 32);
86
87/// 16-byte uniform block for the LCD backbuffer composite pipeline.
88///
89/// `global_alpha` folds the context's active alpha into the premultiplied
90/// blit so a direct `set_global_alpha` fades cached LCD text — matching the
91/// Textured and LcdMask paths, which already carry it.  Occupies the slot
92/// that used to be dead padding, so the struct is still 16 bytes.
93#[repr(C)]
94#[derive(Clone, Copy, Debug, Pod, Zeroable)]
95pub(crate) struct LcbUniforms {
96    pub resolution: [f32; 2],
97    pub channel: u32,
98    pub global_alpha: f32,
99}
100const _: () = assert!(size_of::<LcbUniforms>() == 16);
101
102// ---------------------------------------------------------------------------
103// Blend states
104// ---------------------------------------------------------------------------
105
106/// Standard 2-D composite: src-alpha / one-minus-src-alpha for colour, with
107/// premultiplied-style alpha accumulation so the same pipelines work both
108/// against the surface (alpha stays pinned at 1) and against transparent layer
109/// textures (alpha builds up correctly so the layer composite has something
110/// to alpha-blend against).
111///
112/// Earlier wired this with `alpha = { Zero, One }` to "preserve framebuffer
113/// alpha", which is fine on the surface but leaves layer textures stuck at
114/// alpha=0 — the composite then degenerates into pure additive blending and
115/// every windowed widget renders as washed-out white.  `One / OMSA` is a
116/// fixed-point at 1 on the surface AND accumulates correctly on a layer.
117const BLEND_STANDARD: wgpu::BlendState = wgpu::BlendState {
118    color: wgpu::BlendComponent {
119        src_factor: wgpu::BlendFactor::SrcAlpha,
120        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
121        operation: wgpu::BlendOperation::Add,
122    },
123    alpha: wgpu::BlendComponent {
124        src_factor: wgpu::BlendFactor::One,
125        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
126        operation: wgpu::BlendOperation::Add,
127    },
128};
129
130/// Premultiplied src-over for layer compositing and LCB backbuffer blits.
131const BLEND_PREMUL: wgpu::BlendState = wgpu::BlendState {
132    color: wgpu::BlendComponent {
133        src_factor: wgpu::BlendFactor::One,
134        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
135        operation: wgpu::BlendOperation::Add,
136    },
137    alpha: wgpu::BlendComponent {
138        src_factor: wgpu::BlendFactor::One,
139        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
140        operation: wgpu::BlendOperation::Add,
141    },
142};
143
144// ---------------------------------------------------------------------------
145// WgpuPipelines
146// ---------------------------------------------------------------------------
147
148/// All render pipelines, bind-group layouts, and shared samplers.
149///
150/// Created once per [`crate::WgpuGfxCtx`] and reused for the lifetime of the
151/// context.  The struct is publicly visible so library primitives like
152/// [`crate::ssaa::SsaaFramebuffer::blit_to`] can take a reference and reuse
153/// the shared 2-D textured-quad pipeline; the fields themselves stay
154/// `pub(crate)` so external code can hold a `&WgpuPipelines` but cannot
155/// reach in and rebuild individual pipelines.
156pub struct WgpuPipelines {
157    // ── Solid colour ─────────────────────────────────────────────────────────
158    pub solid_pipeline: wgpu::RenderPipeline,
159    pub solid_bgl: wgpu::BindGroupLayout,
160
161    // ── AA solid colour (per-vertex alpha from tess2 halo strip) ─────────────
162    pub aa_solid_pipeline: wgpu::RenderPipeline,
163    pub aa_solid_bgl: wgpu::BindGroupLayout,
164
165    // ── AA texture (agg-sharp `Graphics2DGpu` port; 1024-wide α-step texture).
166    pub aa_texture_pipeline: wgpu::RenderPipeline,
167    pub aa_texture_bgl0: wgpu::BindGroupLayout,
168    pub aa_texture_bgl1: wgpu::BindGroupLayout,
169
170    // ── Gradient (linear + radial, SVG spread modes) ──────────────────────────
171    pub gradient_pipeline: wgpu::RenderPipeline,
172    pub gradient_bgl0: wgpu::BindGroupLayout,
173    pub gradient_bgl1: wgpu::BindGroupLayout,
174
175    // ── Textured quad (image blit, Label backbuffer) ──────────────────────────
176    pub tex_pipeline: wgpu::RenderPipeline,
177    pub tex_bgl0: wgpu::BindGroupLayout,
178    pub tex_bgl1: wgpu::BindGroupLayout,
179
180    // ── 3×3-box downsample (SSAA at 3× linear) ─────────────────────────────
181    /// Same vertex / bind groups as `tex_pipeline`; fragment shader runs
182    /// 9 point-aligned taps at {-1, 0, +1}² texel offsets for an exact 3×3
183    /// box average.  Used by the cube widget at the 9× SSAA setting where
184    /// a single bilinear minification would drop 5 of 9 source texels per
185    /// output pixel (visually indistinguishable from no AA).
186    pub tex_downsample_3x_pipeline: wgpu::RenderPipeline,
187
188    // ── 4×4-box downsample (SSAA at 4× linear) ─────────────────────────────
189    /// Same vertex / bind groups as `tex_pipeline`; fragment shader runs
190    /// 4 bilinear taps in a 2×2 quadrant grid for an exact 4×4 box average.
191    /// Used by the cube widget at the highest SSAA setting where a single
192    /// bilinear minification would drop 12 of 16 source texels per output.
193    pub tex_downsample_4x_pipeline: wgpu::RenderPipeline,
194
195    // ── Layer composite (SDF rounded-corner mask in fragment shader) ──────────
196    pub layer_pipeline: wgpu::RenderPipeline,
197    pub layer_bgl0: wgpu::BindGroupLayout,
198    pub layer_bgl1: wgpu::BindGroupLayout,
199
200    // ── LCD subpixel text — three pipelines, one per colour channel ───────────
201    pub lcd_r: wgpu::RenderPipeline,
202    pub lcd_g: wgpu::RenderPipeline,
203    pub lcd_b: wgpu::RenderPipeline,
204    pub lcd_bgl0: wgpu::BindGroupLayout,
205    pub lcd_bgl1: wgpu::BindGroupLayout,
206
207    // ── LCD backbuffer composite — three pipelines, premultiplied blend ───────
208    pub lcb_r: wgpu::RenderPipeline,
209    pub lcb_g: wgpu::RenderPipeline,
210    pub lcb_b: wgpu::RenderPipeline,
211    pub lcb_bgl0: wgpu::BindGroupLayout,
212    pub lcb_bgl1: wgpu::BindGroupLayout,
213
214    // ── Inside-layer text (single-pass, alpha-writing) ────────────────────────
215    /// Grayscale coverage text quad — reuses the LCD bind-group layouts but
216    /// writes ALL colour channels + alpha in one pass.  Routed to when text is
217    /// drawn inside a compositing layer so the layer texture gets correct alpha
218    /// (the 3-pass LCD pipelines never write alpha → washed-out glyphs on pop).
219    pub text_gray: wgpu::RenderPipeline,
220    /// Two-plane backbuffer flatten quad — reuses the LCB bind-group layouts,
221    /// collapses the per-channel planes into a single premultiplied sample in
222    /// one alpha-writing pass.  Inside-layer companion to `lcb_r/g/b`.
223    pub lcb_flatten: wgpu::RenderPipeline,
224
225    // ── Shared samplers ───────────────────────────────────────────────────────
226    /// Nearest-neighbour — used for LCD mask textures and Label backbuffers.
227    pub nearest_sampler: wgpu::Sampler,
228    /// Bilinear — used for images, layers, and gradient ramp textures.
229    pub linear_sampler: wgpu::Sampler,
230}
231
232impl WgpuPipelines {
233    /// Construct all pipelines.  Panics if any WGSL shader fails to compile —
234    /// a startup crash is preferable to a silent runtime black screen.
235    ///
236    /// `sample_count` bakes into every pipeline's `MultisampleState`.  Must
237    /// match the sample count of every render-pass color attachment those
238    /// pipelines target — both the surface MSAA buffer and any layer MSAA
239    /// buffers.  `1` disables MSAA.
240    pub(crate) fn new(
241        device: &wgpu::Device,
242        surface_format: wgpu::TextureFormat,
243        sample_count: u32,
244    ) -> Self {
245        // ── Samplers ─────────────────────────────────────────────────────────
246        let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
247            label: Some("nearest"),
248            address_mode_u: wgpu::AddressMode::ClampToEdge,
249            address_mode_v: wgpu::AddressMode::ClampToEdge,
250            address_mode_w: wgpu::AddressMode::ClampToEdge,
251            mag_filter: wgpu::FilterMode::Nearest,
252            min_filter: wgpu::FilterMode::Nearest,
253            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
254            ..Default::default()
255        });
256        let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
257            label: Some("linear"),
258            address_mode_u: wgpu::AddressMode::ClampToEdge,
259            address_mode_v: wgpu::AddressMode::ClampToEdge,
260            address_mode_w: wgpu::AddressMode::ClampToEdge,
261            mag_filter: wgpu::FilterMode::Linear,
262            min_filter: wgpu::FilterMode::Linear,
263            mipmap_filter: wgpu::MipmapFilterMode::Linear,
264            ..Default::default()
265        });
266
267        // ── Shader modules ───────────────────────────────────────────────────
268        let solid_sm = mk_shader(device, "solid", SOLID_WGSL);
269        let aa_solid_sm = mk_shader(device, "aa_solid", AA_SOLID_WGSL);
270        let gradient_sm = mk_shader(device, "gradient", GRADIENT_WGSL);
271        let aa_texture_sm = mk_shader(device, "aa_texture", AA_TEXTURE_WGSL);
272        let tex_sm = mk_shader(device, "tex", TEX_WGSL);
273        let layer_sm = mk_shader(device, "layer", LAYER_WGSL);
274        let lcd_sm = mk_shader(device, "lcd", LCD_WGSL);
275        let lcb_sm = mk_shader(device, "lcb", LCB_WGSL);
276
277        // ── Bind group layouts ───────────────────────────────────────────────
278        let solid_bgl = mk_uniform_bgl(device, "solid", size_of::<SolidUniforms>() as u64);
279        let aa_solid_bgl = mk_uniform_bgl(device, "aa_solid", size_of::<SolidUniforms>() as u64);
280        let gradient_bgl0 = mk_uniform_bgl(
281            device,
282            "gradient0",
283            size_of::<crate::gradient::GradientUniforms>() as u64,
284        );
285        let gradient_bgl1 = mk_tex1_bgl(device, "gradient1");
286        let aa_texture_bgl0 =
287            mk_uniform_bgl(device, "aa_texture0", size_of::<AaTexUniforms>() as u64);
288        let aa_texture_bgl1 = mk_tex1_bgl(device, "aa_texture1");
289        let tex_bgl0 = mk_uniform_bgl(device, "tex0", size_of::<TexUniforms>() as u64);
290        let tex_bgl1 = mk_tex1_bgl(device, "tex1");
291        let layer_bgl0 = mk_uniform_bgl(device, "layer0", size_of::<LayerUniforms>() as u64);
292        let layer_bgl1 = mk_tex1_bgl(device, "layer1");
293        let lcd_bgl0 = mk_uniform_bgl(device, "lcd0", size_of::<LcdUniforms>() as u64);
294        let lcd_bgl1 = mk_tex1_bgl(device, "lcd1");
295        let lcb_bgl0 = mk_uniform_bgl(device, "lcb0", size_of::<LcbUniforms>() as u64);
296        let lcb_bgl1 = mk_tex2_bgl(device, "lcb1");
297
298        // ── Pipeline layouts ─────────────────────────────────────────────────
299        let solid_pl = mk_layout(device, "solid", &[&solid_bgl]);
300        let aa_solid_pl = mk_layout(device, "aa_solid", &[&aa_solid_bgl]);
301        let gradient_pl = mk_layout(device, "gradient", &[&gradient_bgl0, &gradient_bgl1]);
302        let aa_texture_pl = mk_layout(device, "aa_texture", &[&aa_texture_bgl0, &aa_texture_bgl1]);
303        let tex_pl = mk_layout(device, "tex", &[&tex_bgl0, &tex_bgl1]);
304        let layer_pl = mk_layout(device, "layer", &[&layer_bgl0, &layer_bgl1]);
305        let lcd_pl = mk_layout(device, "lcd", &[&lcd_bgl0, &lcd_bgl1]);
306        let lcb_pl = mk_layout(device, "lcb", &[&lcb_bgl0, &lcb_bgl1]);
307
308        // ── Shared vertex attribute slices ───────────────────────────────────
309        // Defined once here so that every VertexBufferLayout below can borrow them
310        // without moving them (they outlive all the pipeline create calls below).
311        let pos2_attrs = [wgpu::VertexAttribute {
312            format: wgpu::VertexFormat::Float32x2,
313            offset: 0,
314            shader_location: 0,
315        }];
316        let pos2_alpha_attrs = [
317            wgpu::VertexAttribute {
318                format: wgpu::VertexFormat::Float32x2,
319                offset: 0,
320                shader_location: 0,
321            },
322            wgpu::VertexAttribute {
323                format: wgpu::VertexFormat::Float32,
324                offset: 8,
325                shader_location: 1,
326            },
327        ];
328        let pos2_uv2_attrs = [
329            wgpu::VertexAttribute {
330                format: wgpu::VertexFormat::Float32x2,
331                offset: 0,
332                shader_location: 0,
333            },
334            wgpu::VertexAttribute {
335                format: wgpu::VertexFormat::Float32x2,
336                offset: 8,
337                shader_location: 1,
338            },
339        ];
340
341        // Convenience closures — return a fresh VertexBufferLayout borrowing the
342        // attribute arrays above.  Closures rather than values avoid requiring
343        // VertexBufferLayout to be Copy (it only derives Clone).
344        let vbl_pos2 = || wgpu::VertexBufferLayout {
345            array_stride: 8,
346            step_mode: wgpu::VertexStepMode::Vertex,
347            attributes: &pos2_attrs,
348        };
349        let vbl_pos2_alpha = || wgpu::VertexBufferLayout {
350            array_stride: 12,
351            step_mode: wgpu::VertexStepMode::Vertex,
352            attributes: &pos2_alpha_attrs,
353        };
354        let vbl_pos2_uv2 = || wgpu::VertexBufferLayout {
355            array_stride: 16,
356            step_mode: wgpu::VertexStepMode::Vertex,
357            attributes: &pos2_uv2_attrs,
358        };
359
360        // ── Render pipelines ─────────────────────────────────────────────────
361        let solid_pipeline = build_pipeline(
362            device,
363            "solid",
364            &solid_pl,
365            &solid_sm,
366            &solid_sm,
367            &[vbl_pos2()],
368            surface_format,
369            Some(BLEND_STANDARD),
370            wgpu::ColorWrites::ALL,
371            sample_count,
372        );
373        let aa_solid_pipeline = build_pipeline(
374            device,
375            "aa_solid",
376            &aa_solid_pl,
377            &aa_solid_sm,
378            &aa_solid_sm,
379            &[vbl_pos2_alpha()],
380            surface_format,
381            Some(BLEND_STANDARD),
382            wgpu::ColorWrites::ALL,
383            sample_count,
384        );
385        // Premultiplied blend matches agg-sharp's
386        // `gl.BlendFunc(One, OneMinusSrcAlpha)` in `Graphics2DGpu.PreRender`
387        // — the shader emits `(color * α, α)` and the blend composites it
388        // correctly into the surface.
389        let aa_texture_pipeline = build_pipeline(
390            device,
391            "aa_texture",
392            &aa_texture_pl,
393            &aa_texture_sm,
394            &aa_texture_sm,
395            &[vbl_pos2_uv2()],
396            surface_format,
397            Some(BLEND_PREMUL),
398            wgpu::ColorWrites::ALL,
399            sample_count,
400        );
401        let gradient_pipeline = build_pipeline(
402            device,
403            "gradient",
404            &gradient_pl,
405            &gradient_sm,
406            &gradient_sm,
407            &[vbl_pos2_alpha()],
408            surface_format,
409            Some(BLEND_STANDARD),
410            wgpu::ColorWrites::ALL,
411            sample_count,
412        );
413        let tex_pipeline = build_pipeline(
414            device,
415            "tex",
416            &tex_pl,
417            &tex_sm,
418            &tex_sm,
419            &[vbl_pos2_uv2()],
420            surface_format,
421            Some(BLEND_STANDARD),
422            wgpu::ColorWrites::ALL,
423            sample_count,
424        );
425        let tex_ds3_sm = mk_shader(device, "tex_downsample_3x", TEX_DOWNSAMPLE_3X_WGSL);
426        let tex_downsample_3x_pipeline = build_pipeline(
427            device,
428            "tex_downsample_3x",
429            &tex_pl,
430            &tex_ds3_sm,
431            &tex_ds3_sm,
432            &[vbl_pos2_uv2()],
433            surface_format,
434            Some(BLEND_STANDARD),
435            wgpu::ColorWrites::ALL,
436            sample_count,
437        );
438        let tex_ds4_sm = mk_shader(device, "tex_downsample_4x", TEX_DOWNSAMPLE_4X_WGSL);
439        let tex_downsample_4x_pipeline = build_pipeline(
440            device,
441            "tex_downsample_4x",
442            &tex_pl,
443            &tex_ds4_sm,
444            &tex_ds4_sm,
445            &[vbl_pos2_uv2()],
446            surface_format,
447            Some(BLEND_STANDARD),
448            wgpu::ColorWrites::ALL,
449            sample_count,
450        );
451        let layer_pipeline = build_pipeline(
452            device,
453            "layer",
454            &layer_pl,
455            &layer_sm,
456            &layer_sm,
457            &[vbl_pos2_uv2()],
458            surface_format,
459            Some(BLEND_PREMUL),
460            wgpu::ColorWrites::ALL,
461            sample_count,
462        );
463        let lcd_r = build_pipeline(
464            device,
465            "lcd_r",
466            &lcd_pl,
467            &lcd_sm,
468            &lcd_sm,
469            &[vbl_pos2_uv2()],
470            surface_format,
471            Some(BLEND_STANDARD),
472            wgpu::ColorWrites::RED,
473            sample_count,
474        );
475        let lcd_g = build_pipeline(
476            device,
477            "lcd_g",
478            &lcd_pl,
479            &lcd_sm,
480            &lcd_sm,
481            &[vbl_pos2_uv2()],
482            surface_format,
483            Some(BLEND_STANDARD),
484            wgpu::ColorWrites::GREEN,
485            sample_count,
486        );
487        let lcd_b = build_pipeline(
488            device,
489            "lcd_b",
490            &lcd_pl,
491            &lcd_sm,
492            &lcd_sm,
493            &[vbl_pos2_uv2()],
494            surface_format,
495            Some(BLEND_STANDARD),
496            wgpu::ColorWrites::BLUE,
497            sample_count,
498        );
499        let lcb_r = build_pipeline(
500            device,
501            "lcb_r",
502            &lcb_pl,
503            &lcb_sm,
504            &lcb_sm,
505            &[vbl_pos2_uv2()],
506            surface_format,
507            Some(BLEND_PREMUL),
508            wgpu::ColorWrites::RED,
509            sample_count,
510        );
511        let lcb_g = build_pipeline(
512            device,
513            "lcb_g",
514            &lcb_pl,
515            &lcb_sm,
516            &lcb_sm,
517            &[vbl_pos2_uv2()],
518            surface_format,
519            Some(BLEND_PREMUL),
520            wgpu::ColorWrites::GREEN,
521            sample_count,
522        );
523        let lcb_b = build_pipeline(
524            device,
525            "lcb_b",
526            &lcb_pl,
527            &lcb_sm,
528            &lcb_sm,
529            &[vbl_pos2_uv2()],
530            surface_format,
531            Some(BLEND_PREMUL),
532            wgpu::ColorWrites::BLUE,
533            sample_count,
534        );
535
536        // Inside-layer text: single-pass, alpha-writing.  `text_gray` reuses the
537        // LCD layout (`lcd_pl`) + a straight-alpha blend; `lcb_flatten` reuses
538        // the LCB layout (`lcb_pl`) + a premultiplied blend.  Both write ALL
539        // channels so a transparent layer accumulates correct alpha.
540        let text_gray_sm = mk_shader(device, "text_gray", TEXT_GRAY_WGSL);
541        let text_gray = build_pipeline(
542            device,
543            "text_gray",
544            &lcd_pl,
545            &text_gray_sm,
546            &text_gray_sm,
547            &[vbl_pos2_uv2()],
548            surface_format,
549            Some(BLEND_STANDARD),
550            wgpu::ColorWrites::ALL,
551            sample_count,
552        );
553        let lcb_flatten_sm = mk_shader(device, "lcb_flatten", LCB_FLATTEN_WGSL);
554        let lcb_flatten = build_pipeline(
555            device,
556            "lcb_flatten",
557            &lcb_pl,
558            &lcb_flatten_sm,
559            &lcb_flatten_sm,
560            &[vbl_pos2_uv2()],
561            surface_format,
562            Some(BLEND_PREMUL),
563            wgpu::ColorWrites::ALL,
564            sample_count,
565        );
566
567        Self {
568            solid_pipeline,
569            solid_bgl,
570            aa_solid_pipeline,
571            aa_solid_bgl,
572            aa_texture_pipeline,
573            aa_texture_bgl0,
574            aa_texture_bgl1,
575            gradient_pipeline,
576            gradient_bgl0,
577            gradient_bgl1,
578            tex_pipeline,
579            tex_bgl0,
580            tex_bgl1,
581            tex_downsample_3x_pipeline,
582            tex_downsample_4x_pipeline,
583            layer_pipeline,
584            layer_bgl0,
585            layer_bgl1,
586            lcd_r,
587            lcd_g,
588            lcd_b,
589            lcd_bgl0,
590            lcd_bgl1,
591            lcb_r,
592            lcb_g,
593            lcb_b,
594            lcb_bgl0,
595            lcb_bgl1,
596            text_gray,
597            lcb_flatten,
598            nearest_sampler,
599            linear_sampler,
600        }
601    }
602}
603
604// ---------------------------------------------------------------------------
605// Private helpers
606// ---------------------------------------------------------------------------
607
608fn mk_shader(device: &wgpu::Device, label: &str, src: &str) -> wgpu::ShaderModule {
609    device.create_shader_module(wgpu::ShaderModuleDescriptor {
610        label: Some(label),
611        source: wgpu::ShaderSource::Wgsl(src.into()),
612    })
613}
614
615/// Bind-group layout with a single uniform buffer at binding 0, visible to
616/// both vertex and fragment stages.
617fn mk_uniform_bgl(device: &wgpu::Device, label: &str, size: u64) -> wgpu::BindGroupLayout {
618    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
619        label: Some(label),
620        entries: &[wgpu::BindGroupLayoutEntry {
621            binding: 0,
622            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
623            ty: wgpu::BindingType::Buffer {
624                ty: wgpu::BufferBindingType::Uniform,
625                has_dynamic_offset: false,
626                min_binding_size: wgpu::BufferSize::new(size),
627            },
628            count: None,
629        }],
630    })
631}
632
633/// Bind-group layout for group 1 with one `texture_2d<f32>` (binding 0) and
634/// one filtering sampler (binding 1).
635fn mk_tex1_bgl(device: &wgpu::Device, label: &str) -> wgpu::BindGroupLayout {
636    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
637        label: Some(label),
638        entries: &[
639            wgpu::BindGroupLayoutEntry {
640                binding: 0,
641                visibility: wgpu::ShaderStages::FRAGMENT,
642                ty: wgpu::BindingType::Texture {
643                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
644                    view_dimension: wgpu::TextureViewDimension::D2,
645                    multisampled: false,
646                },
647                count: None,
648            },
649            wgpu::BindGroupLayoutEntry {
650                binding: 1,
651                visibility: wgpu::ShaderStages::FRAGMENT,
652                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
653                count: None,
654            },
655        ],
656    })
657}
658
659/// Bind-group layout for the LCB pipeline's group 1: two `texture_2d<f32>`
660/// (bindings 0 and 1, colour plane + alpha plane) and one sampler (binding 2).
661fn mk_tex2_bgl(device: &wgpu::Device, label: &str) -> wgpu::BindGroupLayout {
662    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
663        label: Some(label),
664        entries: &[
665            wgpu::BindGroupLayoutEntry {
666                binding: 0,
667                visibility: wgpu::ShaderStages::FRAGMENT,
668                ty: wgpu::BindingType::Texture {
669                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
670                    view_dimension: wgpu::TextureViewDimension::D2,
671                    multisampled: false,
672                },
673                count: None,
674            },
675            wgpu::BindGroupLayoutEntry {
676                binding: 1,
677                visibility: wgpu::ShaderStages::FRAGMENT,
678                ty: wgpu::BindingType::Texture {
679                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
680                    view_dimension: wgpu::TextureViewDimension::D2,
681                    multisampled: false,
682                },
683                count: None,
684            },
685            wgpu::BindGroupLayoutEntry {
686                binding: 2,
687                visibility: wgpu::ShaderStages::FRAGMENT,
688                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
689                count: None,
690            },
691        ],
692    })
693}
694
695fn mk_layout(
696    device: &wgpu::Device,
697    label: &str,
698    bgls: &[&wgpu::BindGroupLayout],
699) -> wgpu::PipelineLayout {
700    let opt: Vec<Option<&wgpu::BindGroupLayout>> = bgls.iter().copied().map(Some).collect();
701    device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
702        label: Some(label),
703        bind_group_layouts: &opt,
704        immediate_size: 0,
705    })
706}
707
708/// Create a `RenderPipeline` from a single shader module (vs and fs are the
709/// same module), a single vertex buffer slot, a blend state, and a colour
710/// write mask.  All 2D pipelines use `TriangleList` topology with no depth
711/// test, no culling, and no MSAA.
712fn build_pipeline(
713    device: &wgpu::Device,
714    label: &str,
715    layout: &wgpu::PipelineLayout,
716    vs: &wgpu::ShaderModule,
717    fs: &wgpu::ShaderModule,
718    vertex_buffers: &[wgpu::VertexBufferLayout<'_>],
719    surface_format: wgpu::TextureFormat,
720    blend: Option<wgpu::BlendState>,
721    write_mask: wgpu::ColorWrites,
722    sample_count: u32,
723) -> wgpu::RenderPipeline {
724    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
725        label: Some(label),
726        layout: Some(layout),
727        vertex: wgpu::VertexState {
728            module: vs,
729            entry_point: Some("vs_main"),
730            compilation_options: Default::default(),
731            buffers: vertex_buffers,
732        },
733        fragment: Some(wgpu::FragmentState {
734            module: fs,
735            entry_point: Some("fs_main"),
736            compilation_options: Default::default(),
737            targets: &[Some(wgpu::ColorTargetState {
738                format: surface_format,
739                blend,
740                write_mask,
741            })],
742        }),
743        primitive: wgpu::PrimitiveState {
744            topology: wgpu::PrimitiveTopology::TriangleList,
745            cull_mode: None,
746            ..Default::default()
747        },
748        depth_stencil: None,
749        multisample: wgpu::MultisampleState {
750            count: sample_count,
751            mask: !0,
752            alpha_to_coverage_enabled: false,
753        },
754        multiview_mask: None,
755        cache: None,
756    })
757}