blade_renderer.rs

  1// Doing `if let` gives you nice scoping with passes/encoders
  2#![allow(irrefutable_let_patterns)]
  3
  4use super::{BladeBelt, BladeBeltDescriptor};
  5use crate::{PrimitiveBatch, Quad, Scene, Shadow};
  6use bytemuck::{Pod, Zeroable};
  7
  8use blade_graphics as gpu;
  9use std::sync::Arc;
 10
 11const SURFACE_FRAME_COUNT: u32 = 3;
 12const MAX_FRAME_TIME_MS: u32 = 1000;
 13
 14#[repr(C)]
 15#[derive(Clone, Copy, Pod, Zeroable)]
 16struct GlobalParams {
 17    viewport_size: [f32; 2],
 18    pad: [u32; 2],
 19}
 20
 21#[derive(blade_macros::ShaderData)]
 22struct ShaderQuadsData {
 23    globals: GlobalParams,
 24    b_quads: gpu::BufferPiece,
 25}
 26
 27#[derive(blade_macros::ShaderData)]
 28struct ShaderShadowsData {
 29    globals: GlobalParams,
 30    b_shadows: gpu::BufferPiece,
 31}
 32
 33struct BladePipelines {
 34    quads: gpu::RenderPipeline,
 35    shadows: gpu::RenderPipeline,
 36}
 37
 38impl BladePipelines {
 39    fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
 40        let shader = gpu.create_shader(gpu::ShaderDesc {
 41            source: include_str!("shaders.wgsl"),
 42        });
 43        shader.check_struct_size::<Quad>();
 44        shader.check_struct_size::<Shadow>();
 45        let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
 46        let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
 47        Self {
 48            quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
 49                name: "quads",
 50                data_layouts: &[&quads_layout],
 51                vertex: shader.at("vs_quad"),
 52                primitive: gpu::PrimitiveState {
 53                    topology: gpu::PrimitiveTopology::TriangleStrip,
 54                    ..Default::default()
 55                },
 56                depth_stencil: None,
 57                fragment: shader.at("fs_quad"),
 58                color_targets: &[gpu::ColorTargetState {
 59                    format: surface_format,
 60                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
 61                    write_mask: gpu::ColorWrites::default(),
 62                }],
 63            }),
 64            shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
 65                name: "shadows",
 66                data_layouts: &[&shadows_layout],
 67                vertex: shader.at("vs_shadow"),
 68                primitive: gpu::PrimitiveState {
 69                    topology: gpu::PrimitiveTopology::TriangleStrip,
 70                    ..Default::default()
 71                },
 72                depth_stencil: None,
 73                fragment: shader.at("fs_shadow"),
 74                color_targets: &[gpu::ColorTargetState {
 75                    format: surface_format,
 76                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
 77                    write_mask: gpu::ColorWrites::default(),
 78                }],
 79            }),
 80        }
 81    }
 82}
 83
 84pub struct BladeRenderer {
 85    gpu: Arc<gpu::Context>,
 86    command_encoder: gpu::CommandEncoder,
 87    last_sync_point: Option<gpu::SyncPoint>,
 88    pipelines: BladePipelines,
 89    instance_belt: BladeBelt,
 90    viewport_size: gpu::Extent,
 91}
 92
 93impl BladeRenderer {
 94    pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
 95        let surface_format = gpu.resize(gpu::SurfaceConfig {
 96            size,
 97            usage: gpu::TextureUsage::TARGET,
 98            frame_count: SURFACE_FRAME_COUNT,
 99        });
100        let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
101            name: "main",
102            buffer_count: 2,
103        });
104        let pipelines = BladePipelines::new(&gpu, surface_format);
105        let instance_belt = BladeBelt::new(BladeBeltDescriptor {
106            memory: gpu::Memory::Shared,
107            min_chunk_size: 0x1000,
108        });
109        Self {
110            gpu,
111            command_encoder,
112            last_sync_point: None,
113            pipelines,
114            instance_belt,
115            viewport_size: size,
116        }
117    }
118
119    fn wait_for_gpu(&mut self) {
120        if let Some(last_sp) = self.last_sync_point.take() {
121            if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
122                panic!("GPU hung");
123            }
124        }
125    }
126
127    pub fn destroy(&mut self) {
128        self.wait_for_gpu();
129        self.instance_belt.destroy(&self.gpu);
130        self.gpu.destroy_command_encoder(&mut self.command_encoder);
131    }
132
133    pub fn resize(&mut self, size: gpu::Extent) {
134        self.wait_for_gpu();
135        self.gpu.resize(gpu::SurfaceConfig {
136            size,
137            usage: gpu::TextureUsage::TARGET,
138            frame_count: SURFACE_FRAME_COUNT,
139        });
140        self.viewport_size = size;
141    }
142
143    pub fn draw(&mut self, scene: &Scene) {
144        let frame = self.gpu.acquire_frame();
145        self.command_encoder.start();
146        self.command_encoder.init_texture(frame.texture());
147
148        let globals = GlobalParams {
149            viewport_size: [
150                self.viewport_size.width as f32,
151                self.viewport_size.height as f32,
152            ],
153            pad: [0; 2],
154        };
155
156        if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
157            colors: &[gpu::RenderTarget {
158                view: frame.texture_view(),
159                init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
160                finish_op: gpu::FinishOp::Store,
161            }],
162            depth_stencil: None,
163        }) {
164            for batch in scene.batches() {
165                match batch {
166                    PrimitiveBatch::Quads(quads) => {
167                        let instances = self.instance_belt.alloc_data(quads, &self.gpu);
168                        let mut encoder = pass.with(&self.pipelines.quads);
169                        encoder.bind(
170                            0,
171                            &ShaderQuadsData {
172                                globals,
173                                b_quads: instances,
174                            },
175                        );
176                        encoder.draw(0, 4, 0, quads.len() as u32);
177                    }
178                    PrimitiveBatch::Shadows(shadows) => {
179                        let instances = self.instance_belt.alloc_data(shadows, &self.gpu);
180                        let mut encoder = pass.with(&self.pipelines.shadows);
181                        encoder.bind(
182                            0,
183                            &ShaderShadowsData {
184                                globals,
185                                b_shadows: instances,
186                            },
187                        );
188                        encoder.draw(0, 4, 0, shadows.len() as u32);
189                    }
190                    _ => continue,
191                }
192            }
193        }
194
195        self.command_encoder.present(frame);
196        let sync_point = self.gpu.submit(&mut self.command_encoder);
197        self.instance_belt.flush(&sync_point);
198        self.wait_for_gpu();
199        self.last_sync_point = Some(sync_point);
200    }
201}