nightshade-renderer 0.56.0

GPU-driven wgpu renderer with a built-in frame graph.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
use wgpu::util::DeviceExt;

use super::types::{
    AreaLightData, AreaUniforms, ClusterUniforms, LightData, LightGrid, MAX_AREA_LIGHTS,
    MAX_LIGHTS_PER_CLUSTER, MaterialData, SkinnedBoundsInput, SkinnedCullObject,
    SkinnedCullUniforms, SkinnedDrawIndexedIndirect, SkinnedObjectData, TOTAL_CLUSTERS,
};

const INITIAL_LIGHTS: usize = 32;
const INITIAL_OBJECTS: usize = 100;
const INITIAL_COMMANDS: usize = 64;

const INITIAL_MATERIALS: usize = 100;

pub(super) struct SkinnedWorldGpuBuffers {
    pub materials_buffer: wgpu::Buffer,
    pub materials_buffer_size: usize,
    pub lights_buffer: wgpu::Buffer,
    pub lights_buffer_size: usize,
    pub object_buffer: wgpu::Buffer,
    pub object_buffer_size: usize,
    pub custom_data_buffer: wgpu::Buffer,
    pub custom_data_buffer_size: usize,
    pub cluster_uniforms_buffer: wgpu::Buffer,
    pub light_grid_buffer: wgpu::Buffer,
    pub light_grid_reset_buffer: wgpu::Buffer,
    pub light_indices_buffer: wgpu::Buffer,
    pub cull_objects_buffer: wgpu::Buffer,
    pub cull_objects_buffer_size: usize,
    pub bounds_inputs_buffer: wgpu::Buffer,
    pub bounds_inputs_buffer_size: usize,
    pub indirect_commands_buffer: wgpu::Buffer,
    pub indirect_commands_buffer_size: usize,
    pub visible_indices_buffer: wgpu::Buffer,
    pub visible_indices_buffer_size: usize,
    pub cull_uniforms_buffer: wgpu::Buffer,
    pub instance_bind_group: wgpu::BindGroup,
    pub cull_bind_group: wgpu::BindGroup,
    pub cluster_bounds_bind_group: wgpu::BindGroup,
    pub cluster_assign_bind_group: Option<wgpu::BindGroup>,
    pub area_lights_buffer: wgpu::Buffer,
    pub area_uniforms_buffer: wgpu::Buffer,
    pub dense_capacity_buffer: wgpu::Buffer,
    pub dense_capacity_size: usize,
    pub command_map_buffer: wgpu::Buffer,
    pub command_map_size: usize,
    pub batch_meta_buffer: wgpu::Buffer,
    pub batch_bind_group: Option<wgpu::BindGroup>,
}

impl SkinnedWorldGpuBuffers {
    pub fn new(
        device: &wgpu::Device,
        instance_bind_group_layout: &wgpu::BindGroupLayout,
        cull_bind_group_layout: &wgpu::BindGroupLayout,
        cluster_bounds_bind_group_layout: &wgpu::BindGroupLayout,
        cluster_bounds_buffer: &wgpu::Buffer,
        joint_buffers: (&wgpu::Buffer, &wgpu::Buffer),
        morph_displacement_buffer: &wgpu::Buffer,
    ) -> Self {
        let (joint_matrices_buffer, prev_joint_matrices_buffer) = joint_buffers;
        let cull_objects_buffer_size = INITIAL_OBJECTS;
        let cull_objects_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Cull Objects Buffer (Per-World)"),
            size: (std::mem::size_of::<SkinnedCullObject>() * cull_objects_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let bounds_inputs_buffer_size = INITIAL_OBJECTS;
        let bounds_inputs_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Bounds Inputs Buffer (Per-World)"),
            size: (std::mem::size_of::<SkinnedBoundsInput>() * bounds_inputs_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let indirect_commands_buffer_size = INITIAL_COMMANDS;
        let indirect_commands_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Indirect Commands Buffer (Per-World)"),
            size: (std::mem::size_of::<SkinnedDrawIndexedIndirect>()
                * indirect_commands_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let visible_indices_buffer_size = INITIAL_OBJECTS;
        let visible_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Visible Indices Buffer (Per-World)"),
            size: (std::mem::size_of::<u32>() * visible_indices_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let cull_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Cull Uniforms Buffer (Per-World)"),
            size: std::mem::size_of::<SkinnedCullUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let materials_buffer_size = INITIAL_MATERIALS;
        let materials_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Materials Buffer (Per-World)"),
            size: (std::mem::size_of::<MaterialData>() * materials_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let lights_buffer_size = INITIAL_LIGHTS;
        let lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Lights Buffer (Per-World)"),
            size: (std::mem::size_of::<LightData>() * lights_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let object_buffer_size = INITIAL_OBJECTS;
        let object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Object Buffer (Per-World)"),
            size: (std::mem::size_of::<SkinnedObjectData>() * object_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let custom_data_buffer_size = INITIAL_OBJECTS;
        let custom_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Custom Data Buffer (Per-World)"),
            size: (std::mem::size_of::<[f32; 4]>() * custom_data_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let cluster_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Cluster Uniforms Buffer (Per-World)"),
            size: std::mem::size_of::<ClusterUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let light_grid_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Light Grid Buffer (Per-World)"),
            size: (std::mem::size_of::<LightGrid>() * TOTAL_CLUSTERS as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let light_grid_reset_data: Vec<LightGrid> = vec![
            LightGrid {
                offset: 0,
                count: 0
            };
            TOTAL_CLUSTERS as usize
        ];
        let light_grid_reset_buffer =
            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                label: Some("Skinned Light Grid Reset Buffer (Per-World)"),
                contents: bytemuck::cast_slice(&light_grid_reset_data),
                usage: wgpu::BufferUsages::COPY_SRC,
            });

        let light_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Light Indices Buffer (Per-World)"),
            size: (std::mem::size_of::<u32>()
                * TOTAL_CLUSTERS as usize
                * MAX_LIGHTS_PER_CLUSTER as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let area_lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Area Lights Buffer (Per-World)"),
            size: (std::mem::size_of::<AreaLightData>() * MAX_AREA_LIGHTS) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let area_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Area Uniforms Buffer (Per-World)"),
            size: std::mem::size_of::<AreaUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Skinned Mesh Instance Bind Group (Per-World)"),
            layout: instance_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: materials_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: joint_matrices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: custom_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: morph_displacement_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: light_grid_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: light_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: cluster_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 10,
                    resource: area_lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 11,
                    resource: area_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 12,
                    resource: prev_joint_matrices_buffer.as_entire_binding(),
                },
            ],
        });

        let cull_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Skinned Mesh Cull Bind Group (Per-World)"),
            layout: cull_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: cull_objects_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: indirect_commands_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: cull_uniforms_buffer.as_entire_binding(),
                },
            ],
        });

        let cluster_bounds_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Skinned Mesh Cluster Bounds Bind Group (Per-World)"),
            layout: cluster_bounds_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: cluster_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: cluster_bounds_buffer.as_entire_binding(),
                },
            ],
        });

        let dense_capacity_size = 128;
        let dense_capacity_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Dense Capacity Buffer (Per-World)"),
            size: (std::mem::size_of::<u32>() * dense_capacity_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let command_map_size = 128;
        let command_map_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Command Map Buffer (Per-World)"),
            size: (std::mem::size_of::<u32>() * command_map_size) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let batch_meta_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Skinned Mesh Batch Meta Buffer (Per-World)"),
            size: (std::mem::size_of::<u32>() * 2) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        Self {
            materials_buffer,
            materials_buffer_size,
            lights_buffer,
            lights_buffer_size,
            object_buffer,
            object_buffer_size,
            custom_data_buffer,
            custom_data_buffer_size,
            cluster_uniforms_buffer,
            light_grid_buffer,
            light_grid_reset_buffer,
            light_indices_buffer,
            cull_objects_buffer,
            cull_objects_buffer_size,
            bounds_inputs_buffer,
            bounds_inputs_buffer_size,
            indirect_commands_buffer,
            indirect_commands_buffer_size,
            visible_indices_buffer,
            visible_indices_buffer_size,
            cull_uniforms_buffer,
            instance_bind_group,
            cull_bind_group,
            cluster_bounds_bind_group,
            cluster_assign_bind_group: None,
            area_lights_buffer,
            area_uniforms_buffer,
            dense_capacity_buffer,
            dense_capacity_size,
            command_map_buffer,
            command_map_size,
            batch_meta_buffer,
            batch_bind_group: None,
        }
    }
}

/// A run of same-mesh skinned instances that share one instanced draw
/// call. `first_instance` indexes the object/custom-data buffers, which
/// are ordered so that each group's instances are contiguous.
#[derive(Clone, Copy)]
pub(super) struct SkinnedDrawGroup {
    pub mesh_id: u32,
    pub first_instance: u32,
    pub instance_count: u32,
}

pub(super) struct SkinnedWorldRenderState {
    pub skinned_entities: Vec<crate::entity::RenderEntity>,
    pub opaque_draw_groups: Vec<SkinnedDrawGroup>,
    pub transparent_draw_groups: Vec<SkinnedDrawGroup>,
    pub cached_cull_objects: Vec<super::types::SkinnedCullObject>,
    pub cached_bounds_inputs: Vec<(usize, usize, f32)>,
    /// The last-built indirect commands with zeroed instance counts. The CPU
    /// batching path re-uploads these every frame so the cull shader's
    /// `atomicAdd` never accumulates across frames; the GPU batching path
    /// zeroes counts with its clear dispatch instead.
    pub cached_indirect_commands: Vec<super::types::SkinnedDrawIndexedIndirect>,
    pub cached_materials_generation: u64,
    pub cached_skinned_objects_generation: u64,

    pub last_used_frame: u64,

    pub gpu_buffers: Option<SkinnedWorldGpuBuffers>,

    pub ibl_brdf_lut_view: Option<wgpu::TextureView>,
    pub ibl_irradiance_view: Option<wgpu::TextureView>,
    pub ibl_prefiltered_view: Option<wgpu::TextureView>,
    pub ibl_irradiance_b_view: Option<wgpu::TextureView>,
    pub ibl_prefiltered_b_view: Option<wgpu::TextureView>,
    pub ibl_blend_factor: f32,
}

impl SkinnedWorldRenderState {
    pub fn new() -> Self {
        Self {
            skinned_entities: Vec::new(),
            opaque_draw_groups: Vec::new(),
            transparent_draw_groups: Vec::new(),
            cached_cull_objects: Vec::new(),
            cached_bounds_inputs: Vec::new(),
            cached_indirect_commands: Vec::new(),
            cached_materials_generation: u64::MAX,
            cached_skinned_objects_generation: u64::MAX,
            last_used_frame: 0,
            gpu_buffers: None,
            ibl_brdf_lut_view: None,
            ibl_irradiance_view: None,
            ibl_prefiltered_view: None,
            ibl_irradiance_b_view: None,
            ibl_prefiltered_b_view: None,
            ibl_blend_factor: 0.0,
        }
    }
}

impl Default for SkinnedWorldRenderState {
    fn default() -> Self {
        Self::new()
    }
}