Skip to main content

pfx_live/
plates.rs

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}