1use pfx_bake::plate::{Depth, Plate, PlateCamera, anchor_blend};
2use pfx_gpu::{Gpu, GpuProfiler, wgpu};
3
4use pfx_bake::plate::read_plate;
5use pfx_load::scene::{Draw, PlateCasters, ProxyShape, Scene};
6
7use crate::frame::{Frame, Instance, Matrix, MeshData, MeshHandle};
8use crate::renderer::{Renderer, invert};
9use crate::shadow::light_basis;
10
11pub const SHADOW_SIZE: u32 = 2048;
12pub const DEPTH_BIAS: f32 = 0.0015;
13pub const ITERATIONS: u32 = 8;
14const UNIFORM_BYTES: usize = 7 * 64 + 15 * 16;
15
16pub const PLATE_WGSL: &str = r#"
17struct PlateUniform {
18 inverse: mat4x4f,
19 still_inverse: mat4x4f,
20 view_projection: mat4x4f,
21 current: mat4x4f,
22 previous: mat4x4f,
23 view: mat4x4f,
24 shadow: mat4x4f,
25 origin: vec4f,
26 forward: vec4f,
27 right: vec4f,
28 up: vec4f,
29 size: vec4f,
30 blend: vec4f,
31 eye: vec4f,
32 params: vec4f,
33 sun: vec4f,
34 extra: vec4f,
35 light_a: vec4f,
36 light_b: vec4f,
37 light_now: vec4f,
38 toward_a: vec4f,
39 toward_b: vec4f,
40}
41struct PlateCascades {
42 splits: vec4f,
43 filter_params: vec4f,
44 sun: vec4f,
45 texels: vec4f,
46 view_proj_0: mat4x4f,
47 view_proj_1: mat4x4f,
48 view_proj_2: mat4x4f,
49}
50@group(0) @binding(0) var<uniform> plate: PlateUniform;
51@group(0) @binding(1) var plate_color: texture_2d_array<f32>;
52@group(0) @binding(2) var plate_sun: texture_2d_array<f32>;
53@group(0) @binding(3) var plate_depth_codes: texture_2d<u32>;
54@group(0) @binding(4) var plate_depth_floats: texture_2d<f32>;
55@group(0) @binding(5) var plate_normal: texture_2d<u32>;
56@group(0) @binding(6) var plate_ids: texture_2d<u32>;
57@group(0) @binding(7) var plate_sampler: sampler;
58@group(0) @binding(8) var plate_shadow: texture_depth_2d;
59@group(0) @binding(9) var plate_compare: sampler_comparison;
60@group(0) @binding(10) var plate_transfer: texture_2d_array<f32>;
61@group(0) @binding(11) var plate_point: sampler;
62@group(1) @binding(0) var<uniform> cascades: PlateCascades;
63@group(1) @binding(1) var cascade_atlas: texture_depth_2d_array;
64
65struct PlateOut {
66 @location(0) color: vec4f,
67 @location(1) id: u32,
68 @location(2) velocity: vec2f,
69 @location(3) normal_roughness: vec4f,
70 @location(4) reactive: f32,
71 @builtin(frag_depth) depth: f32,
72}
73
74fn plate_inverse_at(texel: vec2i) -> f32 {
75 let size = vec2i(plate.size.xy);
76 let at = clamp(texel, vec2i(0), size - vec2i(1));
77 if (plate.forward.w < 0.5) {
78 let code = textureLoad(plate_depth_codes, at, 0).r;
79 return f32(code) / (65535.0 * plate.origin.w);
80 }
81 let depth = textureLoad(plate_depth_floats, at, 0).r;
82 return select(0.0, 1.0 / depth, depth > 0.0);
83}
84
85fn plate_inverse(position: vec2f) -> f32 {
86 let q = position - vec2f(0.5);
87 let base = vec2i(floor(q));
88 let f = q - floor(q);
89 var a: f32;
90 var b: f32;
91 var c: f32;
92 var d: f32;
93 if (plate.forward.w < 0.5) {
94 let codes = vec4f(textureGather(0, plate_depth_codes, plate_point, (vec2f(base) + vec2f(1.0)) / plate.size.xy)) / (65535.0 * plate.origin.w);
95 a = codes.w;
96 b = codes.z;
97 c = codes.x;
98 d = codes.y;
99 } else {
100 a = plate_inverse_at(base);
101 b = plate_inverse_at(base + vec2i(1, 0));
102 c = plate_inverse_at(base + vec2i(0, 1));
103 d = plate_inverse_at(base + vec2i(1, 1));
104 }
105 let lo = min(min(a, b), min(c, d));
106 let hi = max(max(a, b), max(c, d));
107 if (lo > 0.0 && hi <= lo * 1.03) {
108 return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
109 }
110 return plate_inverse_at(vec2i(floor(position)));
111}
112
113fn plate_texel(world: vec3f) -> vec3f {
114 let v = world - plate.origin.xyz;
115 let z = dot(v, plate.forward.xyz);
116 let r = plate.right.xyz;
117 let u = plate.up.xyz;
118 let a = dot(v, r) / (dot(r, r) * z);
119 let b = dot(v, u) / (dot(u, u) * z);
120 let x = a - plate.right.w;
121 let y = plate.up.w - b;
122 return vec3f((x + 1.0) * 0.5 * plate.size.x, (y + 1.0) * 0.5 * plate.size.y, z);
123}
124
125fn plate_along(eye: vec3f, direction: vec3f, depth: f32) -> f32 {
126 let offset = dot(eye - plate.origin.xyz, plate.forward.xyz);
127 return (depth - offset) / max(dot(direction, plate.forward.xyz), 1e-6);
128}
129
130struct PlateHit {
131 t: f32,
132 texel: vec2f,
133 inverse: f32,
134}
135
136fn plate_march(eye: vec3f, direction: vec3f, start: f32, steps: u32) -> PlateHit {
137 var t = start;
138 var hit = PlateHit(t, vec2f(0.0), 0.0);
139 for (var i = 0u; i < steps; i++) {
140 let at = plate_texel(eye + direction * t);
141 let inverse = plate_inverse(at.xy);
142 hit.texel = at.xy;
143 hit.inverse = inverse;
144 if (inverse <= 0.0) {
145 return hit;
146 }
147 let next = plate_along(eye, direction, 1.0 / inverse);
148 let settled = abs(next - t) <= 1e-5 * max(t, 1e-3);
149 t = next;
150 hit.t = t;
151 if (settled) {
152 break;
153 }
154 }
155 return hit;
156}
157
158fn plate_ray(inverse: mat4x4f, ndc: vec2f) -> vec3f {
159 let far = inverse * vec4f(ndc, 1.0, 1.0);
160 return normalize(far.xyz / far.w - plate.eye.xyz);
161}
162
163fn plate_start(eye: vec3f, direction: vec3f) -> f32 {
164 let far = plate_texel(eye + direction * 1e4);
165 let inverse = plate_inverse(far.xy);
166 return plate_along(eye, direction, select(1e4, 1.0 / inverse, inverse > 0.0));
167}
168
169fn plate_octahedral(packed: u32) -> vec3f {
170 let x = max(f32(bitcast<i32>(packed << 16u) >> 16u) / 32767.0, -1.0);
171 let y = max(f32(bitcast<i32>(packed) >> 16u) / 32767.0, -1.0);
172 var n = vec3f(x, y, 1.0 - abs(x) - abs(y));
173 if (n.z < 0.0) {
174 let flip = vec2f(select(-1.0, 1.0, n.x >= 0.0), select(-1.0, 1.0, n.y >= 0.0));
175 n = vec3f((1.0 - abs(n.yx)) * flip, n.z);
176 }
177 return normalize(n);
178}
179
180fn plate_shadowed(world: vec3f, normal: vec3f) -> f32 {
181 let offset = world + normal * plate.params.z * 1.5 + plate.sun.xyz * plate.params.z;
182 let clip = plate.shadow * vec4f(offset, 1.0);
183 let uv = vec2f(clip.x, -clip.y) * 0.5 + vec2f(0.5);
184 if (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0)) || clip.z >= 1.0) {
185 return 1.0;
186 }
187 let texel = 1.0 / f32(textureDimensions(plate_shadow).x);
188 var lit = 0.0;
189 for (var y = -1; y <= 1; y++) {
190 for (var x = -1; x <= 1; x++) {
191 let at = uv + vec2f(f32(x), f32(y)) * texel * 1.5;
192 lit += textureSampleCompareLevel(plate_shadow, plate_compare, at, clip.z);
193 }
194 }
195 return lit / 9.0;
196}
197
198fn plate_sunlit(world: vec3f, normal: vec3f) -> f32 {
199 if (cascades.splits.w <= 0.0) {
200 return 1.0;
201 }
202 let distance = length(world - plate.eye.xyz);
203 var layer = 0;
204 var matrix = cascades.view_proj_0;
205 var texel = cascades.texels.x;
206 if (distance > cascades.splits.y) {
207 layer = 1;
208 matrix = cascades.view_proj_1;
209 texel = cascades.texels.y;
210 }
211 if (distance > cascades.splits.z) {
212 layer = 2;
213 matrix = cascades.view_proj_2;
214 texel = cascades.texels.z;
215 }
216 let clip = matrix * vec4f(world + normal * texel * 1.5, 1.0);
217 let point = clip.xyz / clip.w;
218 let uv = point.xy * vec2f(0.5, -0.5) + vec2f(0.5);
219 if (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0)) || point.z < 0.0 || point.z > 1.0) {
220 return 1.0;
221 }
222 let size = vec2f(textureDimensions(cascade_atlas));
223 let centre = uv * size;
224 var lit = 0.0;
225 for (var y = -1; y <= 1; y++) {
226 for (var x = -1; x <= 1; x++) {
227 let at = clamp(vec2i(centre + vec2f(f32(x), f32(y))), vec2i(0), vec2i(size) - vec2i(1));
228 lit += select(0.0, 1.0, point.z - cascades.sun.w <= textureLoad(cascade_atlas, at, layer, 0));
229 }
230 }
231 return lit / 9.0;
232}
233
234@fragment fn fragment(@builtin(position) position: vec4f) -> PlateOut {
235 let ndc = vec2f(position.x * plate.size.z * 2.0 - 1.0, 1.0 - position.y * plate.size.w * 2.0);
236 let eye = plate.eye.xyz;
237 let steps = u32(plate.params.y);
238 let jittered = plate_ray(plate.inverse, ndc);
239 let hit = plate_march(eye, jittered, plate_start(eye, jittered), steps);
240 if (hit.inverse <= 0.0) {
241 discard;
242 }
243 let still = plate_ray(plate.still_inverse, ndc);
244 let seen = plate_march(eye, still, plate_along(eye, still, 1.0 / hit.inverse), 3u);
245 let world = eye + still * seen.t;
246 let uv = seen.texel / plate.size.xy;
247 let a = i32(plate.blend.x);
248 let b = i32(plate.blend.y);
249 let color_a = textureSampleLevel(plate_color, plate_sampler, uv, a, 0.0).rgb;
250 let color_b = textureSampleLevel(plate_color, plate_sampler, uv, b, 0.0).rgb;
251 var color = mix(color_a, color_b, plate.blend.z);
252 let texel = clamp(vec2i(floor(seen.texel)), vec2i(0), vec2i(plate.size.xy) - vec2i(1));
253 var normal = vec3f(0.0, 1.0, 0.0);
254 if (plate.extra.x > 0.5) {
255 normal = plate_octahedral(textureLoad(plate_normal, texel, 0).r);
256 }
257 if (plate.blend.w > 0.5) {
258 let share_a = textureSampleLevel(plate_sun, plate_sampler, uv, a, 0.0).rgb;
259 let share_b = textureSampleLevel(plate_sun, plate_sampler, uv, b, 0.0).rgb;
260 let unit_a = select(vec3f(0.0), share_a / plate.light_a.rgb, plate.light_a.rgb > vec3f(0.0));
261 let unit_b = select(vec3f(0.0), share_b / plate.light_b.rgb, plate.light_b.rgb > vec3f(0.0));
262 var share = mix(unit_a, unit_b, plate.blend.z) * plate.light_now.rgb;
263 let rest = max(mix(color_a - share_a, color_b - share_b, plate.blend.z), vec3f(0.0));
264 if (plate.sun.w > 0.5) {
265 share *= 1.0 - (1.0 - plate_shadowed(world, normal)) * plate.eye.w;
266 }
267 let weight = (1.0 - abs(1.0 - 2.0 * plate.blend.z)) * plate.extra.w;
268 if (weight > 0.0) {
269 let open_a = textureSampleLevel(plate_transfer, plate_sampler, uv, a, 0.0).rgb;
270 let open_b = textureSampleLevel(plate_transfer, plate_sampler, uv, b, 0.0).rgb;
271 let facing_a = max(dot(normal, plate.toward_a.xyz), 0.0);
272 let facing_b = max(dot(normal, plate.toward_b.xyz), 0.0);
273 let facing = max(dot(normal, plate.sun.xyz), 0.0);
274 let per_a = select(vec3f(0.0), open_a / plate.light_a.rgb, plate.light_a.rgb > vec3f(0.0));
275 let per_b = select(vec3f(0.0), open_b / plate.light_b.rgb, plate.light_b.rgb > vec3f(0.0));
276 let albedo = (per_a * facing_a + per_b * facing_b) / max(facing_a * facing_a + facing_b * facing_b, 1e-4);
277 let relit = albedo * facing * plate.light_now.rgb * plate_sunlit(world, normal);
278 share = mix(share, relit, weight);
279 }
280 color = rest + share;
281 }
282 var out: PlateOut;
283 out.color = vec4f(color, 1.0);
284 out.id = select(0u, textureLoad(plate_ids, texel, 0).r, plate.extra.y > 0.5);
285 let front = eye + jittered * hit.t * (1.0 + plate.params.x);
286 let clip = plate.view_projection * vec4f(front, 1.0);
287 out.depth = clamp(clip.z / clip.w, 0.0, 1.0);
288 let now = plate.current * vec4f(world, 1.0);
289 let before = plate.previous * vec4f(world, 1.0);
290 let moved = (before.xy / before.w - now.xy / now.w) * vec2f(0.5, -0.5);
291 out.velocity = select(moved, vec2f(0.0), abs(moved) < vec2f(1e-8));
292 out.normal_roughness = vec4f(normalize((plate.view * vec4f(normal, 0.0)).xyz), 1.0);
293 out.reactive = plate.extra.z;
294 return out;
295}
296"#;
297
298pub const PLATE_SHADOW_WGSL: &str = r#"
299@group(0) @binding(0) var<uniform> light: mat4x4f;
300struct Caster {
301 @location(0) position: vec3f,
302 @location(1) c0: vec4f,
303 @location(2) c1: vec4f,
304 @location(3) c2: vec4f,
305 @location(4) c3: vec4f,
306}
307@vertex fn vertex(input: Caster) -> @builtin(position) vec4f {
308 let model = mat4x4f(input.c0, input.c1, input.c2, input.c3);
309 return light * model * vec4f(input.position, 1.0);
310}
311"#;
312
313const FULLSCREEN: &str = "@vertex fn vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f { let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0)); return vec4f(xy[index], 0.0, 1.0); }";
314
315pub fn plate_source() -> String {
316 format!("{FULLSCREEN}\n{PLATE_WGSL}")
317}
318
319#[derive(Clone, Copy, Debug, PartialEq)]
320pub struct PlateSettings {
321 pub depth_bias: f32,
322 pub iterations: u32,
323 pub shadow_strength: f32,
324 pub temporal: bool,
325 pub relight: bool,
326}
327
328impl Default for PlateSettings {
329 fn default() -> Self {
330 Self {
331 depth_bias: DEPTH_BIAS,
332 iterations: ITERATIONS,
333 shadow_strength: 1.0,
334 temporal: false,
335 relight: true,
336 }
337 }
338}
339
340#[derive(Clone, Copy, Debug, PartialEq)]
341pub struct PlateInfo {
342 pub width: u32,
343 pub height: u32,
344 pub anchors: usize,
345 pub bytes_per_anchor: u64,
346 pub shared_bytes: u64,
347 pub camera: PlateCamera,
348 pub bounds: pfx_bake::plate::Bounds,
349}
350
351pub struct PlateFrame<'a> {
352 pub view_projection: Matrix,
353 pub jittered: Matrix,
354 pub previous: Matrix,
355 pub view: Matrix,
356 pub eye: [f32; 3],
357 pub toward_sun: [f32; 3],
358 pub sun_light: [f32; 3],
359 pub sun_up: bool,
360 pub instances: &'a [Instance],
361}
362
363pub struct Plates {
364 info: PlateInfo,
365 anchors: Vec<f32>,
366 lights: Vec<[f32; 3]>,
367 towards: Vec<[f32; 3]>,
368 has_transfer: bool,
369 cascades: wgpu::Buffer,
370 depth_near: f32,
371 float_depth: bool,
372 has_sun: bool,
373 has_normal: bool,
374 has_ids: bool,
375 hour: f32,
376 settings: PlateSettings,
377 uniform: wgpu::Buffer,
378 bind: wgpu::BindGroup,
379 kit: PlateKit,
380 shadow_view: wgpu::TextureView,
381 shadow_uniform: wgpu::Buffer,
382 shadow_bind: wgpu::BindGroup,
383 casters: wgpu::Buffer,
384 caster_capacity: u64,
385 shadow_texel: f32,
386 _textures: Vec<wgpu::Texture>,
387}
388
389fn upload(
390 gpu: &Gpu,
391 label: &str,
392 format: wgpu::TextureFormat,
393 size: [u32; 2],
394 layers: &[&[u8]],
395) -> wgpu::Texture {
396 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
397 label: Some(label),
398 size: wgpu::Extent3d {
399 width: size[0],
400 height: size[1],
401 depth_or_array_layers: layers.len() as u32,
402 },
403 mip_level_count: 1,
404 sample_count: 1,
405 dimension: wgpu::TextureDimension::D2,
406 format,
407 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
408 view_formats: &[],
409 });
410 let row = size[0] * format.block_copy_size(None).unwrap_or(4);
411 for (layer, bytes) in layers.iter().enumerate() {
412 gpu.queue.write_texture(
413 wgpu::TexelCopyTextureInfo {
414 texture: &texture,
415 mip_level: 0,
416 origin: wgpu::Origin3d {
417 x: 0,
418 y: 0,
419 z: layer as u32,
420 },
421 aspect: wgpu::TextureAspect::All,
422 },
423 bytes,
424 wgpu::TexelCopyBufferLayout {
425 offset: 0,
426 bytes_per_row: Some(row),
427 rows_per_image: Some(size[1]),
428 },
429 wgpu::Extent3d {
430 width: size[0],
431 height: size[1],
432 depth_or_array_layers: 1,
433 },
434 );
435 }
436 texture
437}
438
439fn layered(gpu: &Gpu, label: &str, size: [u32; 2], layers: Option<&[Vec<u32>]>) -> wgpu::Texture {
440 let format = wgpu::TextureFormat::Rgb9e5Ufloat;
441 match layers {
442 Some(layers) => {
443 let bytes: Vec<&[u8]> = layers
444 .iter()
445 .map(|layer| bytemuck::cast_slice(layer))
446 .collect();
447 upload(gpu, label, format, size, &bytes)
448 }
449 None => upload(gpu, label, format, [1, 1], &[&[0; 4]]),
450 }
451}
452
453fn single<T: bytemuck::Pod>(
454 gpu: &Gpu,
455 label: &str,
456 format: wgpu::TextureFormat,
457 size: [u32; 2],
458 values: Option<&[T]>,
459) -> wgpu::Texture {
460 match values {
461 Some(values) => upload(gpu, label, format, size, &[bytemuck::cast_slice(values)]),
462 None => {
463 let zero = vec![0; format.block_copy_size(None).unwrap_or(4) as usize];
464 upload(gpu, label, format, [1, 1], &[&zero])
465 }
466 }
467}
468
469fn array_view(texture: &wgpu::Texture) -> wgpu::TextureView {
470 texture.create_view(&wgpu::TextureViewDescriptor {
471 dimension: Some(wgpu::TextureViewDimension::D2Array),
472 ..Default::default()
473 })
474}
475
476fn layout_entry(binding: u32, ty: wgpu::BindingType) -> wgpu::BindGroupLayoutEntry {
477 wgpu::BindGroupLayoutEntry {
478 binding,
479 visibility: wgpu::ShaderStages::FRAGMENT,
480 ty,
481 count: None,
482 }
483}
484
485fn texture_entry(
486 binding: u32,
487 sample_type: wgpu::TextureSampleType,
488 dimension: wgpu::TextureViewDimension,
489) -> wgpu::BindGroupLayoutEntry {
490 layout_entry(
491 binding,
492 wgpu::BindingType::Texture {
493 sample_type,
494 view_dimension: dimension,
495 multisampled: false,
496 },
497 )
498}
499
500fn uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
501 layout_entry(
502 binding,
503 wgpu::BindingType::Buffer {
504 ty: wgpu::BufferBindingType::Uniform,
505 has_dynamic_offset: false,
506 min_binding_size: None,
507 },
508 )
509}
510
511pub fn composite_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
512 let unfilterable = wgpu::TextureSampleType::Float { filterable: false };
513 let filterable = wgpu::TextureSampleType::Float { filterable: true };
514 let flat = wgpu::TextureViewDimension::D2;
515 let array = wgpu::TextureViewDimension::D2Array;
516 vec![
517 uniform_entry(0),
518 texture_entry(1, filterable, array),
519 texture_entry(2, filterable, array),
520 texture_entry(3, wgpu::TextureSampleType::Uint, flat),
521 texture_entry(4, unfilterable, flat),
522 texture_entry(5, wgpu::TextureSampleType::Uint, flat),
523 texture_entry(6, wgpu::TextureSampleType::Uint, flat),
524 layout_entry(
525 7,
526 wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
527 ),
528 texture_entry(8, wgpu::TextureSampleType::Depth, flat),
529 layout_entry(
530 9,
531 wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
532 ),
533 texture_entry(10, filterable, array),
534 layout_entry(
535 11,
536 wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
537 ),
538 ]
539}
540
541pub fn cascade_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
542 vec![
543 uniform_entry(0),
544 texture_entry(
545 1,
546 wgpu::TextureSampleType::Depth,
547 wgpu::TextureViewDimension::D2Array,
548 ),
549 ]
550}
551
552#[derive(Clone)]
553pub struct PlateKit {
554 layout: wgpu::BindGroupLayout,
555 cascade_layout: wgpu::BindGroupLayout,
556 shadow_layout: wgpu::BindGroupLayout,
557 pipeline: wgpu::RenderPipeline,
558 shadow_pipeline: wgpu::RenderPipeline,
559 sampler: wgpu::Sampler,
560 point: wgpu::Sampler,
561 compare: wgpu::Sampler,
562}
563
564impl PlateKit {
565 pub fn new(gpu: &Gpu, cache: Option<&wgpu::PipelineCache>) -> Self {
566 let device = &gpu.device;
567 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
568 label: Some("plate"),
569 entries: &composite_entries(),
570 });
571 let cascade_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
572 label: Some("plate cascades"),
573 entries: &cascade_entries(),
574 });
575 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
576 label: Some("plate sampler"),
577 mag_filter: wgpu::FilterMode::Linear,
578 min_filter: wgpu::FilterMode::Linear,
579 ..Default::default()
580 });
581 let point = device.create_sampler(&wgpu::SamplerDescriptor {
582 label: Some("plate depth gather"),
583 ..Default::default()
584 });
585 let compare = device.create_sampler(&wgpu::SamplerDescriptor {
586 label: Some("plate shadow compare"),
587 mag_filter: wgpu::FilterMode::Linear,
588 min_filter: wgpu::FilterMode::Linear,
589 compare: Some(wgpu::CompareFunction::LessEqual),
590 ..Default::default()
591 });
592 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
593 label: Some("plate composite"),
594 source: wgpu::ShaderSource::Wgsl(plate_source().into()),
595 });
596 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
597 label: Some("plate composite"),
598 bind_group_layouts: &[Some(&layout), Some(&cascade_layout)],
599 immediate_size: 0,
600 });
601 let target = |format| {
602 Some(wgpu::ColorTargetState {
603 format,
604 blend: None,
605 write_mask: wgpu::ColorWrites::ALL,
606 })
607 };
608 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
609 label: Some("plate composite"),
610 layout: Some(&pipeline_layout),
611 vertex: wgpu::VertexState {
612 module: &module,
613 entry_point: Some("vertex"),
614 buffers: &[],
615 compilation_options: Default::default(),
616 },
617 fragment: Some(wgpu::FragmentState {
618 module: &module,
619 entry_point: Some("fragment"),
620 targets: &[
621 target(wgpu::TextureFormat::Rgba16Float),
622 target(wgpu::TextureFormat::R32Uint),
623 target(wgpu::TextureFormat::Rg16Float),
624 target(crate::frame::NORMAL_FORMAT),
625 target(wgpu::TextureFormat::R8Unorm),
626 ],
627 compilation_options: Default::default(),
628 }),
629 primitive: wgpu::PrimitiveState::default(),
630 depth_stencil: Some(wgpu::DepthStencilState {
631 format: wgpu::TextureFormat::Depth32Float,
632 depth_write_enabled: Some(true),
633 depth_compare: Some(wgpu::CompareFunction::Less),
634 stencil: Default::default(),
635 bias: Default::default(),
636 }),
637 multisample: Default::default(),
638 multiview_mask: None,
639 cache,
640 });
641 let shadow_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
642 label: Some("plate shadow"),
643 entries: &[wgpu::BindGroupLayoutEntry {
644 binding: 0,
645 visibility: wgpu::ShaderStages::VERTEX,
646 ty: wgpu::BindingType::Buffer {
647 ty: wgpu::BufferBindingType::Uniform,
648 has_dynamic_offset: false,
649 min_binding_size: None,
650 },
651 count: None,
652 }],
653 });
654 let shadow_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
655 label: Some("plate shadow"),
656 source: wgpu::ShaderSource::Wgsl(PLATE_SHADOW_WGSL.into()),
657 });
658 let shadow_pipeline_layout =
659 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
660 label: Some("plate shadow"),
661 bind_group_layouts: &[Some(&shadow_layout)],
662 immediate_size: 0,
663 });
664 let instance_attributes = [
665 wgpu::VertexAttribute {
666 format: wgpu::VertexFormat::Float32x4,
667 offset: 0,
668 shader_location: 1,
669 },
670 wgpu::VertexAttribute {
671 format: wgpu::VertexFormat::Float32x4,
672 offset: 16,
673 shader_location: 2,
674 },
675 wgpu::VertexAttribute {
676 format: wgpu::VertexFormat::Float32x4,
677 offset: 32,
678 shader_location: 3,
679 },
680 wgpu::VertexAttribute {
681 format: wgpu::VertexFormat::Float32x4,
682 offset: 48,
683 shader_location: 4,
684 },
685 ];
686 let shadow_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
687 label: Some("plate shadow"),
688 layout: Some(&shadow_pipeline_layout),
689 vertex: wgpu::VertexState {
690 module: &shadow_module,
691 entry_point: Some("vertex"),
692 buffers: &[
693 Some(wgpu::VertexBufferLayout {
694 array_stride: 12,
695 step_mode: wgpu::VertexStepMode::Vertex,
696 attributes: &[wgpu::VertexAttribute {
697 format: wgpu::VertexFormat::Float32x3,
698 offset: 0,
699 shader_location: 0,
700 }],
701 }),
702 Some(wgpu::VertexBufferLayout {
703 array_stride: 64,
704 step_mode: wgpu::VertexStepMode::Instance,
705 attributes: &instance_attributes,
706 }),
707 ],
708 compilation_options: Default::default(),
709 },
710 fragment: None,
711 primitive: wgpu::PrimitiveState {
712 cull_mode: None,
713 ..Default::default()
714 },
715 depth_stencil: Some(wgpu::DepthStencilState {
716 format: wgpu::TextureFormat::Depth32Float,
717 depth_write_enabled: Some(true),
718 depth_compare: Some(wgpu::CompareFunction::Less),
719 stencil: Default::default(),
720 bias: wgpu::DepthBiasState {
721 constant: 2,
722 slope_scale: 2.0,
723 clamp: 0.0,
724 },
725 }),
726 multisample: Default::default(),
727 multiview_mask: None,
728 cache,
729 });
730 Self {
731 layout,
732 cascade_layout,
733 shadow_layout,
734 pipeline,
735 shadow_pipeline,
736 sampler,
737 point,
738 compare,
739 }
740 }
741}
742
743impl Plates {
744 pub fn new(gpu: &Gpu, plate: &Plate, settings: PlateSettings) -> Result<Self, String> {
745 Self::with_kit(gpu, &PlateKit::new(gpu, None), plate, settings)
746 }
747
748 pub fn with_kit(
749 gpu: &Gpu,
750 kit: &PlateKit,
751 plate: &Plate,
752 settings: PlateSettings,
753 ) -> Result<Self, String> {
754 let manifest = &plate.manifest;
755 let size = [manifest.width, manifest.height];
756 let limit = gpu.device.limits().max_texture_dimension_2d;
757 if size[0] > limit || size[1] > limit {
758 return Err(format!(
759 "a {}x{} plate is larger than this device's {limit} texels",
760 size[0], size[1]
761 ));
762 }
763 let color = layered(gpu, "plate colour", size, Some(plate.color.as_slice()));
764 let sun = layered(gpu, "plate sun", size, plate.sun.as_deref());
765 let transfer = layered(gpu, "plate transfer", size, plate.transfer.as_deref());
766 let (inverse, float) = match &plate.depth {
767 Depth::Inverse(values) => (Some(values.as_slice()), None),
768 Depth::Float(values) => (None, Some(values.as_slice())),
769 };
770 let float_depth = float.is_some();
771 let codes = single(
772 gpu,
773 "plate depth",
774 wgpu::TextureFormat::R16Uint,
775 size,
776 inverse,
777 );
778 let floats = single(
779 gpu,
780 "plate depth",
781 wgpu::TextureFormat::R32Float,
782 size,
783 float,
784 );
785 let normal = single(
786 gpu,
787 "plate normal",
788 wgpu::TextureFormat::R32Uint,
789 size,
790 plate.normal.as_deref(),
791 );
792 let ids = single(
793 gpu,
794 "plate ids",
795 wgpu::TextureFormat::R16Uint,
796 size,
797 plate.id.as_deref(),
798 );
799 let shadow = gpu.device.create_texture(&wgpu::TextureDescriptor {
800 label: Some("plate shadow"),
801 size: wgpu::Extent3d {
802 width: SHADOW_SIZE,
803 height: SHADOW_SIZE,
804 depth_or_array_layers: 1,
805 },
806 mip_level_count: 1,
807 sample_count: 1,
808 dimension: wgpu::TextureDimension::D2,
809 format: wgpu::TextureFormat::Depth32Float,
810 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
811 view_formats: &[],
812 });
813 let shadow_view = shadow.create_view(&Default::default());
814 let device = &gpu.device;
815 let uniform = device.create_buffer(&wgpu::BufferDescriptor {
816 label: Some("plate uniform"),
817 size: UNIFORM_BYTES as u64,
818 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
819 mapped_at_creation: false,
820 });
821 let cascades = device.create_buffer(&wgpu::BufferDescriptor {
822 label: Some("plate cascades"),
823 size: crate::shadow::SAMPLE_UNIFORM_BYTES as u64,
824 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
825 mapped_at_creation: false,
826 });
827 let views = [
828 array_view(&color),
829 array_view(&sun),
830 codes.create_view(&Default::default()),
831 floats.create_view(&Default::default()),
832 normal.create_view(&Default::default()),
833 ids.create_view(&Default::default()),
834 array_view(&transfer),
835 ];
836 let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
837 label: Some("plate"),
838 layout: &kit.layout,
839 entries: &[
840 wgpu::BindGroupEntry {
841 binding: 0,
842 resource: uniform.as_entire_binding(),
843 },
844 wgpu::BindGroupEntry {
845 binding: 1,
846 resource: wgpu::BindingResource::TextureView(&views[0]),
847 },
848 wgpu::BindGroupEntry {
849 binding: 2,
850 resource: wgpu::BindingResource::TextureView(&views[1]),
851 },
852 wgpu::BindGroupEntry {
853 binding: 3,
854 resource: wgpu::BindingResource::TextureView(&views[2]),
855 },
856 wgpu::BindGroupEntry {
857 binding: 4,
858 resource: wgpu::BindingResource::TextureView(&views[3]),
859 },
860 wgpu::BindGroupEntry {
861 binding: 5,
862 resource: wgpu::BindingResource::TextureView(&views[4]),
863 },
864 wgpu::BindGroupEntry {
865 binding: 6,
866 resource: wgpu::BindingResource::TextureView(&views[5]),
867 },
868 wgpu::BindGroupEntry {
869 binding: 7,
870 resource: wgpu::BindingResource::Sampler(&kit.sampler),
871 },
872 wgpu::BindGroupEntry {
873 binding: 8,
874 resource: wgpu::BindingResource::TextureView(&shadow_view),
875 },
876 wgpu::BindGroupEntry {
877 binding: 9,
878 resource: wgpu::BindingResource::Sampler(&kit.compare),
879 },
880 wgpu::BindGroupEntry {
881 binding: 10,
882 resource: wgpu::BindingResource::TextureView(&views[6]),
883 },
884 wgpu::BindGroupEntry {
885 binding: 11,
886 resource: wgpu::BindingResource::Sampler(&kit.point),
887 },
888 ],
889 });
890 let shadow_uniform = device.create_buffer(&wgpu::BufferDescriptor {
891 label: Some("plate shadow light"),
892 size: 64,
893 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
894 mapped_at_creation: false,
895 });
896 let shadow_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
897 label: Some("plate shadow"),
898 layout: &kit.shadow_layout,
899 entries: &[wgpu::BindGroupEntry {
900 binding: 0,
901 resource: shadow_uniform.as_entire_binding(),
902 }],
903 });
904 let casters = device.create_buffer(&wgpu::BufferDescriptor {
905 label: Some("plate casters"),
906 size: 64 * 64,
907 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
908 mapped_at_creation: false,
909 });
910 gpu.queue.submit([]);
911 Ok(Self {
912 info: PlateInfo {
913 width: size[0],
914 height: size[1],
915 anchors: manifest.anchors.len(),
916 bytes_per_anchor: plate.bytes_per_anchor(),
917 shared_bytes: plate.shared_bytes(),
918 camera: manifest.camera,
919 bounds: manifest.bounds,
920 },
921 anchors: manifest.anchors.clone(),
922 lights: manifest.suns.iter().map(|sun| sun.light()).collect(),
923 towards: manifest.suns.iter().map(|sun| sun.direction).collect(),
924 has_transfer: plate.transfer.is_some(),
925 cascades,
926 depth_near: manifest.depth_near,
927 float_depth,
928 has_sun: plate.sun.is_some(),
929 has_normal: plate.normal.is_some(),
930 has_ids: plate.id.is_some(),
931 hour: manifest.anchors[0],
932 settings,
933 uniform,
934 kit: kit.clone(),
935 bind,
936 shadow_view,
937 shadow_uniform,
938 shadow_bind,
939 casters,
940 caster_capacity: 64,
941 shadow_texel: 0.0,
942 _textures: vec![color, sun, codes, floats, normal, ids, transfer, shadow],
943 })
944 }
945
946 pub fn info(&self) -> PlateInfo {
947 self.info
948 }
949
950 pub fn hour(&self) -> f32 {
951 self.hour
952 }
953
954 pub fn set_hour(&mut self, hour: f32) {
955 self.hour = hour;
956 }
957
958 pub fn blend(&self) -> (usize, usize, f32) {
959 anchor_blend(&self.anchors, self.hour)
960 }
961
962 pub fn settings(&self) -> PlateSettings {
963 self.settings
964 }
965
966 pub fn set_settings(&mut self, settings: PlateSettings) {
967 self.settings = settings;
968 }
969
970 pub fn light(&self, toward_sun: [f32; 3]) -> (Matrix, f32) {
971 let bounds = self.info.bounds;
972 let [right, up, forward] = light_basis(toward_sun);
973 let mut lo = [f32::INFINITY; 3];
974 let mut hi = [f32::NEG_INFINITY; 3];
975 for corner in 0..8 {
976 let point = [
977 if corner & 1 == 0 {
978 bounds.min[0]
979 } else {
980 bounds.max[0]
981 },
982 if corner & 2 == 0 {
983 bounds.min[1]
984 } else {
985 bounds.max[1]
986 },
987 if corner & 4 == 0 {
988 bounds.min[2]
989 } else {
990 bounds.max[2]
991 },
992 ];
993 for (k, axis) in [right, up, forward].iter().enumerate() {
994 let value = point[0] * axis[0] + point[1] * axis[1] + point[2] * axis[2];
995 lo[k] = lo[k].min(value);
996 hi[k] = hi[k].max(value);
997 }
998 }
999 let extent = (0..3)
1000 .map(|k| hi[k] - lo[k])
1001 .fold(0.0f32, f32::max)
1002 .max(1e-3);
1003 let margin = extent * 0.02;
1004 let near = lo[2] - extent * 2.0;
1005 let far = hi[2] + margin;
1006 let half = [
1007 ((hi[0] - lo[0]) * 0.5 + margin).max(1e-3),
1008 ((hi[1] - lo[1]) * 0.5 + margin).max(1e-3),
1009 ];
1010 let centre = [(hi[0] + lo[0]) * 0.5, (hi[1] + lo[1]) * 0.5];
1011 let range = far - near;
1012 let row = |axis: [f32; 3], scale: f32| axis.map(|v| v * scale);
1013 let r = row(right, 1.0 / half[0]);
1014 let u = row(up, 1.0 / half[1]);
1015 let f = row(forward, 1.0 / range);
1016 let matrix = [
1017 [r[0], u[0], f[0], 0.0],
1018 [r[1], u[1], f[1], 0.0],
1019 [r[2], u[2], f[2], 0.0],
1020 [
1021 -centre[0] / half[0],
1022 -centre[1] / half[1],
1023 -near / range,
1024 1.0,
1025 ],
1026 ];
1027 let texel = 2.0 * half[0].max(half[1]) / SHADOW_SIZE as f32;
1028 (matrix, texel)
1029 }
1030
1031 fn uniform_bytes(
1032 &self,
1033 frame: &PlateFrame<'_>,
1034 light: Matrix,
1035 casters: bool,
1036 size: [u32; 2],
1037 ) -> Result<Vec<u8>, String> {
1038 let inverse = invert(frame.jittered).ok_or("camera projection is singular")?;
1039 let still = invert(frame.view_projection).ok_or("camera projection is singular")?;
1040 let (a, b, t) = self.blend();
1041 let camera = self.info.camera;
1042 let mut floats: Vec<f32> = Vec::with_capacity(UNIFORM_BYTES / 4);
1043 for matrix in [
1044 inverse,
1045 still,
1046 frame.jittered,
1047 frame.view_projection,
1048 frame.previous,
1049 frame.view,
1050 light,
1051 ] {
1052 floats.extend(matrix.iter().flatten());
1053 }
1054 floats.extend([
1055 camera.origin[0],
1056 camera.origin[1],
1057 camera.origin[2],
1058 self.depth_near,
1059 camera.forward[0],
1060 camera.forward[1],
1061 camera.forward[2],
1062 if self.float_depth { 1.0 } else { 0.0 },
1063 camera.right[0],
1064 camera.right[1],
1065 camera.right[2],
1066 camera.shift[0],
1067 camera.up[0],
1068 camera.up[1],
1069 camera.up[2],
1070 camera.shift[1],
1071 self.info.width as f32,
1072 self.info.height as f32,
1073 1.0 / size[0] as f32,
1074 1.0 / size[1] as f32,
1075 a as f32,
1076 b as f32,
1077 t,
1078 if self.has_sun { 1.0 } else { 0.0 },
1079 frame.eye[0],
1080 frame.eye[1],
1081 frame.eye[2],
1082 self.settings.shadow_strength,
1083 self.settings.depth_bias,
1084 self.settings.iterations.max(1) as f32,
1085 self.shadow_texel,
1086 0.0,
1087 frame.toward_sun[0],
1088 frame.toward_sun[1],
1089 frame.toward_sun[2],
1090 if casters && frame.sun_up { 1.0 } else { 0.0 },
1091 if self.has_normal { 1.0 } else { 0.0 },
1092 if self.has_ids { 1.0 } else { 0.0 },
1093 if self.settings.temporal { 0.0 } else { 1.0 },
1094 if self.has_transfer && self.settings.relight {
1095 1.0
1096 } else {
1097 0.0
1098 },
1099 ]);
1100 for light in [self.lights[a], self.lights[b], frame.sun_light] {
1101 floats.extend([light[0], light[1], light[2], 0.0]);
1102 }
1103 for toward in [self.towards[a], self.towards[b]] {
1104 floats.extend([toward[0], toward[1], toward[2], 0.0]);
1105 }
1106 Ok(bytemuck::cast_slice(&floats).to_vec())
1107 }
1108
1109 pub fn encode(
1110 &mut self,
1111 live: &Frame,
1112 sun: (&crate::shadow::Shadows, &crate::shadow::Fit),
1113 reactive: &wgpu::TextureView,
1114 encoder: &mut wgpu::CommandEncoder,
1115 frame: &PlateFrame<'_>,
1116 mut profiler: Option<&mut GpuProfiler>,
1117 ) -> Result<bool, String> {
1118 let gpu = &live.gpu;
1119 let targets = &live.targets;
1120 let mesh = |instance: &Instance| live.shadow_mesh(instance.mesh);
1121 let casting: Vec<&Instance> = frame
1122 .instances
1123 .iter()
1124 .filter(|instance| instance.casts_shadow && mesh(instance).is_some())
1125 .collect();
1126 let shadows = self.has_sun && frame.sun_up && !casting.is_empty();
1127 let (light, texel) = self.light(frame.toward_sun);
1128 self.shadow_texel = texel;
1129 if shadows {
1130 let bytes: Vec<u8> = casting
1131 .iter()
1132 .flat_map(|instance| {
1133 bytemuck::cast_slice::<f32, u8>(instance.model.as_flattened()).to_vec()
1134 })
1135 .collect();
1136 let needed = casting.len() as u64;
1137 if needed > self.caster_capacity {
1138 self.caster_capacity = needed.next_power_of_two();
1139 self.casters = gpu.device.create_buffer(&wgpu::BufferDescriptor {
1140 label: Some("plate casters"),
1141 size: 64 * self.caster_capacity,
1142 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1143 mapped_at_creation: false,
1144 });
1145 }
1146 gpu.queue.write_buffer(&self.casters, 0, &bytes);
1147 gpu.queue.write_buffer(
1148 &self.shadow_uniform,
1149 0,
1150 bytemuck::cast_slice(light.as_flattened()),
1151 );
1152 let timing = profiler
1153 .as_deref_mut()
1154 .and_then(|p| p.pass("plate shadows"));
1155 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1156 multiview_mask: None,
1157 label: Some("plate shadows"),
1158 color_attachments: &[],
1159 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1160 view: &self.shadow_view,
1161 depth_ops: Some(wgpu::Operations {
1162 load: wgpu::LoadOp::Clear(1.0),
1163 store: wgpu::StoreOp::Store,
1164 }),
1165 stencil_ops: None,
1166 }),
1167 timestamp_writes: profiler.as_deref().and_then(|p| p.render_writes(timing)),
1168 occlusion_query_set: None,
1169 });
1170 pass.set_pipeline(&self.kit.shadow_pipeline);
1171 pass.set_bind_group(0, &self.shadow_bind, &[]);
1172 pass.set_vertex_buffer(1, self.casters.slice(..));
1173 for (index, instance) in casting.iter().enumerate() {
1174 let Some((positions, indices, count)) = mesh(instance) else {
1175 continue;
1176 };
1177 pass.set_vertex_buffer(0, positions.slice(..));
1178 pass.set_index_buffer(indices.slice(..), wgpu::IndexFormat::Uint32);
1179 let at = index as u32;
1180 pass.draw_indexed(0..count, 0, at..at + 1);
1181 }
1182 }
1183 let bytes = self.uniform_bytes(frame, light, shadows, live.viewport())?;
1184 gpu.queue.write_buffer(&self.uniform, 0, &bytes);
1185 gpu.queue
1186 .write_buffer(&self.cascades, 0, &sun.0.sample_uniform(sun.1));
1187 let cascade_bind = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
1188 label: Some("plate cascades"),
1189 layout: &self.kit.cascade_layout,
1190 entries: &[
1191 wgpu::BindGroupEntry {
1192 binding: 0,
1193 resource: self.cascades.as_entire_binding(),
1194 },
1195 wgpu::BindGroupEntry {
1196 binding: 1,
1197 resource: wgpu::BindingResource::TextureView(sun.0.atlas_view()),
1198 },
1199 ],
1200 });
1201 let timing = profiler
1202 .as_deref_mut()
1203 .and_then(|p| p.pass("plate composite"));
1204 let load = |view| {
1205 Some(wgpu::RenderPassColorAttachment {
1206 view,
1207 resolve_target: None,
1208 depth_slice: None,
1209 ops: wgpu::Operations {
1210 load: wgpu::LoadOp::Load,
1211 store: wgpu::StoreOp::Store,
1212 },
1213 })
1214 };
1215 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1216 multiview_mask: None,
1217 label: Some("plate composite"),
1218 color_attachments: &[
1219 load(&targets.hdr.view),
1220 load(&targets.ids_view),
1221 load(&targets.velocity_view),
1222 load(&targets.normal_roughness_view),
1223 Some(wgpu::RenderPassColorAttachment {
1224 view: reactive,
1225 resolve_target: None,
1226 depth_slice: None,
1227 ops: wgpu::Operations {
1228 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1229 store: wgpu::StoreOp::Store,
1230 },
1231 }),
1232 ],
1233 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1234 view: &targets.depth_view,
1235 depth_ops: Some(wgpu::Operations {
1236 load: wgpu::LoadOp::Load,
1237 store: wgpu::StoreOp::Store,
1238 }),
1239 stencil_ops: None,
1240 }),
1241 timestamp_writes: profiler.as_deref().and_then(|p| p.render_writes(timing)),
1242 occlusion_query_set: None,
1243 });
1244 crate::viewport::apply(&mut pass, live.viewport());
1245 pass.set_pipeline(&self.kit.pipeline);
1246 pass.set_bind_group(0, &self.bind, &[]);
1247 pass.set_bind_group(1, &cascade_bind, &[]);
1248 pass.draw(0..3, 0..1);
1249 Ok(shadows)
1250 }
1251}
1252
1253pub struct PlateMode {
1254 plated: bool,
1255 dynamic: Vec<bool>,
1256 casters: PlateCasters,
1257 proxies: Vec<Instance>,
1258 proxy_meshes: Vec<MeshHandle>,
1259}
1260
1261impl Default for PlateMode {
1262 fn default() -> Self {
1263 Self {
1264 plated: false,
1265 dynamic: Vec::new(),
1266 casters: PlateCasters::Meshes,
1267 proxies: Vec::new(),
1268 proxy_meshes: Vec::new(),
1269 }
1270 }
1271}
1272
1273fn unit_cube() -> (Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<u32>) {
1274 let mut positions = Vec::new();
1275 let mut normals = Vec::new();
1276 let mut indices = Vec::new();
1277 for axis in 0..3 {
1278 for side in [-1.0f32, 1.0] {
1279 let mut normal = [0.0; 3];
1280 normal[axis] = side;
1281 let u = (axis + 1) % 3;
1282 let v = (axis + 2) % 3;
1283 let base = positions.len() as u32;
1284 for (a, b) in [(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)] {
1285 let mut corner = [0.0; 3];
1286 corner[axis] = side * 0.5;
1287 corner[u] = a;
1288 corner[v] = b;
1289 positions.push(corner);
1290 normals.push(normal);
1291 }
1292 if side > 0.0 {
1293 indices.extend([base, base + 1, base + 2, base, base + 2, base + 3]);
1294 } else {
1295 indices.extend([base, base + 2, base + 1, base, base + 3, base + 2]);
1296 }
1297 }
1298 }
1299 (positions, normals, indices)
1300}
1301
1302fn hull(points: &[[f32; 3]], triangles: &[[u32; 3]]) -> (Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<u32>) {
1303 let mut positions = Vec::new();
1304 let mut normals = Vec::new();
1305 let mut indices = Vec::new();
1306 for triangle in triangles {
1307 let [a, b, c] = triangle.map(|corner| points[corner as usize]);
1308 let e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
1309 let e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
1310 let n = [
1311 e1[1] * e2[2] - e1[2] * e2[1],
1312 e1[2] * e2[0] - e1[0] * e2[2],
1313 e1[0] * e2[1] - e1[1] * e2[0],
1314 ];
1315 let length = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt().max(1e-12);
1316 let base = positions.len() as u32;
1317 for corner in [a, b, c] {
1318 positions.push(corner);
1319 normals.push(n.map(|v| v / length));
1320 }
1321 indices.extend([base, base + 1, base + 2]);
1322 }
1323 (positions, normals, indices)
1324}
1325
1326fn upload_shape(
1327 renderer: &mut Renderer,
1328 (positions, normals, indices): (Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<u32>),
1329) -> Result<MeshHandle, String> {
1330 let count = positions.len();
1331 let tangents = vec![[1.0, 0.0, 0.0, 1.0]; count];
1332 let uvs = vec![[0.0; 2]; count];
1333 renderer.upload_mesh(MeshData {
1334 positions: &positions,
1335 normals: &normals,
1336 tangents: &tangents,
1337 uvs: &uvs,
1338 uvs1: None,
1339 alpha: None,
1340 indices: &indices,
1341 })
1342}
1343
1344impl PlateMode {
1345 pub fn load(
1346 &mut self,
1347 renderer: &mut Renderer,
1348 scene: &Scene,
1349 reload: bool,
1350 ) -> Result<(), String> {
1351 self.dynamic = (0..scene.objects.len())
1352 .map(|index| scene.dynamic(index))
1353 .collect();
1354 if !reload {
1355 return Ok(());
1356 }
1357 self.release_proxies(renderer)?;
1358 let settings = renderer.plates().map(Plates::settings).unwrap_or_default();
1359 match &scene.plates {
1360 Some(plates) if plates.manifest.is_some() => {
1361 let plate = read_plate(&plates.dir)?;
1362 renderer.set_plates(Some(&plate), settings)?;
1363 renderer.set_plate_hour(scene.sun_or_dark().hour);
1364 self.plated = true;
1365 self.casters = plates.casters;
1366 if let (PlateCasters::Proxies, Some(proxies)) = (plates.casters, &plates.proxies) {
1367 let mut cube = None;
1368 for proxy in &proxies.items {
1369 let (mesh, model) = match &proxy.shape {
1370 ProxyShape::Box { model, .. } => {
1371 let mesh = match cube {
1372 Some(mesh) => mesh,
1373 None => {
1374 let mesh = upload_shape(renderer, unit_cube())?;
1375 self.proxy_meshes.push(mesh);
1376 cube = Some(mesh);
1377 mesh
1378 }
1379 };
1380 (mesh, *model)
1381 }
1382 ProxyShape::Hull { points, triangles } => {
1383 let mesh = upload_shape(renderer, hull(points, triangles))?;
1384 self.proxy_meshes.push(mesh);
1385 (mesh, pfx_load::scene::IDENTITY)
1386 }
1387 };
1388 self.proxies.push(Instance::new(mesh, model, 0, proxy.id));
1389 }
1390 }
1391 }
1392 _ => {
1393 renderer.set_plates(None, settings)?;
1394 renderer.set_plate_casters(&[]);
1395 self.plated = false;
1396 }
1397 }
1398 Ok(())
1399 }
1400
1401 pub fn plated(&self) -> bool {
1402 self.plated
1403 }
1404
1405 pub fn baked(&self, draw: &Draw) -> bool {
1406 self.plated
1407 && !draw
1408 .owner
1409 .is_some_and(|owner| self.dynamic.get(owner).copied().unwrap_or(false))
1410 }
1411
1412 pub fn cast(&self, renderer: &mut Renderer, stand_ins: &[Instance]) -> Result<(), String> {
1413 if !self.plated {
1414 return Ok(());
1415 }
1416 match self.casters {
1417 PlateCasters::Meshes => renderer.set_plate_casters(stand_ins),
1418 PlateCasters::Proxies => renderer.set_plate_casters(&self.proxies),
1419 PlateCasters::None => renderer.set_plate_casters(&[]),
1420 }
1421 Ok(())
1422 }
1423
1424 fn release_proxies(&mut self, renderer: &mut Renderer) -> Result<(), String> {
1425 self.proxies.clear();
1426 for mesh in self.proxy_meshes.drain(..) {
1427 renderer.release_mesh(mesh)?;
1428 }
1429 Ok(())
1430 }
1431
1432 pub fn release(mut self, renderer: &mut Renderer) -> Result<(), String> {
1433 self.release_proxies(renderer)
1434 }
1435}
1436
1437#[cfg(test)]
1438mod tests {
1439 use super::*;
1440
1441 #[test]
1442 fn the_plate_shaders_validate() {
1443 for source in [plate_source(), PLATE_SHADOW_WGSL.to_string()] {
1444 let module = naga::front::wgsl::parse_str(&source).unwrap();
1445 naga::valid::Validator::new(
1446 naga::valid::ValidationFlags::all(),
1447 naga::valid::Capabilities::all(),
1448 )
1449 .validate(&module)
1450 .unwrap();
1451 }
1452 }
1453
1454 #[test]
1455 fn the_uniform_layout_matches_the_shader() {
1456 let module = naga::front::wgsl::parse_str(&plate_source()).unwrap();
1457 let (_, ty) = module
1458 .types
1459 .iter()
1460 .find(|(_, ty)| ty.name.as_deref() == Some("PlateUniform"))
1461 .unwrap();
1462 let naga::TypeInner::Struct { span, .. } = ty.inner else {
1463 panic!("a struct");
1464 };
1465 assert_eq!(span as usize, UNIFORM_BYTES);
1466 }
1467}