@bornengine/engine 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- package/src/world/version.ts +114 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
//! Froxel light clustering — task #23 of the architecture audit.
|
|
2
|
+
//!
|
|
3
|
+
//! The 8+256 light-cap raise removed the capability ceiling but left
|
|
4
|
+
//! the scene shader paying O(live point lights) per fragment. This
|
|
5
|
+
//! module restores O(cluster lights): a compute pass assigns the point
|
|
6
|
+
//! lights (read from the same lighting UBO the shaders already use) to
|
|
7
|
+
//! a 16×9×24 view-frustum froxel grid each frame, and a clustered
|
|
8
|
+
//! variant of the scene shader loops only its froxel's index list.
|
|
9
|
+
//!
|
|
10
|
+
//! Backend split, by capability rather than cfg: storage buffers in
|
|
11
|
+
//! fragment shaders don't exist on WebGL2, so [`FroxelPass::supported`]
|
|
12
|
+
//! gates on the device limits. Unsupported backends keep the plain
|
|
13
|
+
//! count-driven loop (the semantic reference — the clustered path must
|
|
14
|
+
//! match it exactly, which the many_point_lights golden enforces).
|
|
15
|
+
//!
|
|
16
|
+
//! Memory: counts 3456×4 B ≈ 14 KB; index list 3456×256×4 B ≈ 3.5 MB
|
|
17
|
+
//! (256 = worst-case every light in one froxel — exact parity with the
|
|
18
|
+
//! reference loop, no truncation).
|
|
19
|
+
|
|
20
|
+
use wgpu::util::DeviceExt;
|
|
21
|
+
|
|
22
|
+
pub(super) const GRID_X: u32 = 16;
|
|
23
|
+
pub(super) const GRID_Y: u32 = 9;
|
|
24
|
+
pub(super) const GRID_Z: u32 = 24;
|
|
25
|
+
pub(super) const CLUSTER_COUNT: u32 = GRID_X * GRID_Y * GRID_Z;
|
|
26
|
+
pub(super) const MAX_LIGHTS_PER_CLUSTER: u32 = 256;
|
|
27
|
+
|
|
28
|
+
/// Uniform parameters shared by the assignment compute pass and the
|
|
29
|
+
/// clustered fragment loop. Layout mirrored in WGSL below and in the
|
|
30
|
+
/// fragment include.
|
|
31
|
+
#[repr(C)]
|
|
32
|
+
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
33
|
+
pub(super) struct FroxelParams {
|
|
34
|
+
/// View matrix (world → view) for light-position transform.
|
|
35
|
+
pub view: [[f32; 4]; 4],
|
|
36
|
+
/// x = grid_x, y = grid_y, z = grid_z, w = live point-light count.
|
|
37
|
+
pub grid: [u32; 4],
|
|
38
|
+
/// x = znear, y = zfar, z = log(zfar/znear), w = unused.
|
|
39
|
+
pub depth_range: [f32; 4],
|
|
40
|
+
/// x = 1/tile_w_px, y = 1/tile_h_px (fragment tile lookup),
|
|
41
|
+
/// z = p22, w = p32 (depth linearization, same convention as Hi-Z).
|
|
42
|
+
pub screen: [f32; 4],
|
|
43
|
+
/// Inverse projection — froxel corner reconstruction in the
|
|
44
|
+
/// assignment pass.
|
|
45
|
+
pub inv_proj: [[f32; 4]; 4],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ASSIGN_SHADER: &str = "
|
|
49
|
+
struct FroxelParams {
|
|
50
|
+
view: mat4x4<f32>,
|
|
51
|
+
grid: vec4<u32>,
|
|
52
|
+
depth_range: vec4<f32>,
|
|
53
|
+
screen: vec4<f32>,
|
|
54
|
+
inv_proj: mat4x4<f32>,
|
|
55
|
+
};
|
|
56
|
+
struct PointLight { position: vec4<f32>, color: vec4<f32> };
|
|
57
|
+
struct Lights {
|
|
58
|
+
// Mirrors the tail of the Lighting UBO relevant here. The host
|
|
59
|
+
// binds a dedicated compact UBO (positions+ranges only would do,
|
|
60
|
+
// but reusing PointLight keeps one struct).
|
|
61
|
+
count: vec4<f32>,
|
|
62
|
+
lights: array<PointLight, 256>,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
@group(0) @binding(0) var<uniform> p: FroxelParams;
|
|
66
|
+
@group(0) @binding(1) var<uniform> l: Lights;
|
|
67
|
+
@group(0) @binding(2) var<storage, read_write> cluster_counts: array<u32>;
|
|
68
|
+
@group(0) @binding(3) var<storage, read_write> cluster_indices: array<u32>;
|
|
69
|
+
|
|
70
|
+
// View-space Z of slice boundary k (logarithmic distribution).
|
|
71
|
+
fn slice_z(k: u32) -> f32 {
|
|
72
|
+
let t = f32(k) / f32(p.grid.z);
|
|
73
|
+
return p.depth_range.x * exp(t * p.depth_range.z);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@compute @workgroup_size(4, 4, 4)
|
|
77
|
+
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
78
|
+
if (gid.x >= p.grid.x || gid.y >= p.grid.y || gid.z >= p.grid.z) { return; }
|
|
79
|
+
let cluster = gid.x + gid.y * p.grid.x + gid.z * p.grid.x * p.grid.y;
|
|
80
|
+
|
|
81
|
+
// Froxel AABB in view space: reconstruct the tile's corner rays on
|
|
82
|
+
// the near plane of the projection and scale to the slice depths.
|
|
83
|
+
// NDC tile extents:
|
|
84
|
+
let x0 = (f32(gid.x) / f32(p.grid.x)) * 2.0 - 1.0;
|
|
85
|
+
let x1 = (f32(gid.x + 1u) / f32(p.grid.x)) * 2.0 - 1.0;
|
|
86
|
+
// NDC y is up; tile row 0 is the TOP of the screen.
|
|
87
|
+
let y1 = 1.0 - (f32(gid.y) / f32(p.grid.y)) * 2.0;
|
|
88
|
+
let y0 = 1.0 - (f32(gid.y + 1u) / f32(p.grid.y)) * 2.0;
|
|
89
|
+
|
|
90
|
+
// Unproject the four corners at an arbitrary depth and normalize to
|
|
91
|
+
// rays through the camera (view space, looking down -Z).
|
|
92
|
+
var mn = vec3<f32>( 1e30, 1e30, 1e30);
|
|
93
|
+
var mx = vec3<f32>(-1e30, -1e30, -1e30);
|
|
94
|
+
let z_near_s = slice_z(gid.z);
|
|
95
|
+
let z_far_s = slice_z(gid.z + 1u);
|
|
96
|
+
for (var cx = 0u; cx < 2u; cx++) {
|
|
97
|
+
for (var cy = 0u; cy < 2u; cy++) {
|
|
98
|
+
let nx = select(x0, x1, cx == 1u);
|
|
99
|
+
let ny = select(y0, y1, cy == 1u);
|
|
100
|
+
let h = p.inv_proj * vec4<f32>(nx, ny, 0.5, 1.0);
|
|
101
|
+
let dir = h.xyz / h.w; // a point on the ray (view space)
|
|
102
|
+
let ray = dir / max(-dir.z, 1e-6); // scale so z == -1
|
|
103
|
+
// corner at both slice depths (view z is negative forward)
|
|
104
|
+
let a = ray * z_near_s;
|
|
105
|
+
let b = ray * z_far_s;
|
|
106
|
+
mn = min(mn, min(vec3<f32>(a.xy, -z_near_s), vec3<f32>(b.xy, -z_far_s)));
|
|
107
|
+
mx = max(mx, max(vec3<f32>(a.xy, -z_near_s), vec3<f32>(b.xy, -z_far_s)));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Sphere/AABB tests against every live light.
|
|
112
|
+
var count = 0u;
|
|
113
|
+
let n = u32(l.count.x);
|
|
114
|
+
let base = cluster * 256u;
|
|
115
|
+
for (var i = 0u; i < n; i++) {
|
|
116
|
+
let pos_w = l.lights[i].position;
|
|
117
|
+
let pos_v = (p.view * vec4<f32>(pos_w.xyz, 1.0)).xyz;
|
|
118
|
+
let r = pos_w.w;
|
|
119
|
+
let closest = clamp(pos_v, mn, mx);
|
|
120
|
+
let d = pos_v - closest;
|
|
121
|
+
if (dot(d, d) <= r * r) {
|
|
122
|
+
cluster_indices[base + count] = i;
|
|
123
|
+
count++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
cluster_counts[cluster] = count;
|
|
127
|
+
}
|
|
128
|
+
";
|
|
129
|
+
|
|
130
|
+
/// The fragment-side replacement for the plain point-light loop, plus
|
|
131
|
+
/// the bindings it needs. Spliced into SCENE_SHADER between the
|
|
132
|
+
/// BEGIN/END-POINT-LIGHT-LOOP markers by [`clustered_scene_shader`].
|
|
133
|
+
const CLUSTERED_BINDINGS: &str = "
|
|
134
|
+
struct FroxelParams {
|
|
135
|
+
view: mat4x4<f32>,
|
|
136
|
+
grid: vec4<u32>,
|
|
137
|
+
depth_range: vec4<f32>,
|
|
138
|
+
screen: vec4<f32>,
|
|
139
|
+
inv_proj: mat4x4<f32>,
|
|
140
|
+
};
|
|
141
|
+
@group(1) @binding(10) var<uniform> froxel: FroxelParams;
|
|
142
|
+
@group(1) @binding(11) var<storage, read> cluster_counts: array<u32>;
|
|
143
|
+
@group(1) @binding(12) var<storage, read> cluster_indices: array<u32>;
|
|
144
|
+
";
|
|
145
|
+
|
|
146
|
+
const CLUSTERED_LOOP: &str = "
|
|
147
|
+
// Froxel-clustered point lights: identical shading math to the
|
|
148
|
+
// reference loop, restricted to this fragment's cluster list.
|
|
149
|
+
let view_z = -froxel.screen.w / (in.clip_position.z + froxel.screen.z);
|
|
150
|
+
let slice = clamp(
|
|
151
|
+
u32(log(max(view_z, froxel.depth_range.x) / froxel.depth_range.x)
|
|
152
|
+
/ froxel.depth_range.z * f32(froxel.grid.z)),
|
|
153
|
+
0u, froxel.grid.z - 1u);
|
|
154
|
+
let tile_x = min(u32(in.clip_position.x * froxel.screen.x), froxel.grid.x - 1u);
|
|
155
|
+
let tile_y = min(u32(in.clip_position.y * froxel.screen.y), froxel.grid.y - 1u);
|
|
156
|
+
let cluster = tile_x + tile_y * froxel.grid.x + slice * froxel.grid.x * froxel.grid.y;
|
|
157
|
+
let cl_count = cluster_counts[cluster];
|
|
158
|
+
let cl_base = cluster * 256u;
|
|
159
|
+
for (var ci = 0u; ci < cl_count; ci++) {
|
|
160
|
+
let pl = lighting.point_lights[cluster_indices[cl_base + ci]];
|
|
161
|
+
let to_light = pl.position.xyz - in.world_pos;
|
|
162
|
+
let dist = length(to_light);
|
|
163
|
+
let range = pl.position.w;
|
|
164
|
+
if (dist < range && dist > 0.0) {
|
|
165
|
+
let l = to_light / dist;
|
|
166
|
+
let atten = 1.0 - (dist / range);
|
|
167
|
+
let atten2 = atten * atten;
|
|
168
|
+
lit += shade_pbr(n, v, l, pl.color.rgb, pl.color.w * atten2,
|
|
169
|
+
base_color, metallic, roughness);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
";
|
|
173
|
+
|
|
174
|
+
/// Build the clustered SCENE_SHADER variant from the canonical source.
|
|
175
|
+
pub(super) fn clustered_scene_shader(source: &str) -> String {
|
|
176
|
+
let begin = source
|
|
177
|
+
.find("// BEGIN-POINT-LIGHT-LOOP")
|
|
178
|
+
.expect("scene shader missing BEGIN-POINT-LIGHT-LOOP marker");
|
|
179
|
+
let end_marker = "// END-POINT-LIGHT-LOOP";
|
|
180
|
+
let end = source.find(end_marker).expect("scene shader missing END marker") + end_marker.len();
|
|
181
|
+
format!(
|
|
182
|
+
"{}{}{}{}",
|
|
183
|
+
CLUSTERED_BINDINGS,
|
|
184
|
+
&source[..begin],
|
|
185
|
+
CLUSTERED_LOOP,
|
|
186
|
+
&source[end..]
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// The three entries appended to `lighting_layout` (group 1) when the
|
|
191
|
+
/// device supports the clustered path. Pipelines whose shaders don't
|
|
192
|
+
/// reference them (SHADER_3D's pipeline_3d) are unaffected — extra
|
|
193
|
+
/// layout entries are legal as long as the bind group provides them.
|
|
194
|
+
pub(super) fn extra_lighting_layout_entries() -> [wgpu::BindGroupLayoutEntry; 3] {
|
|
195
|
+
let storage_ro = wgpu::BindingType::Buffer {
|
|
196
|
+
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
|
197
|
+
has_dynamic_offset: false,
|
|
198
|
+
min_binding_size: None,
|
|
199
|
+
};
|
|
200
|
+
[
|
|
201
|
+
wgpu::BindGroupLayoutEntry {
|
|
202
|
+
binding: 10,
|
|
203
|
+
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
204
|
+
ty: wgpu::BindingType::Buffer {
|
|
205
|
+
ty: wgpu::BufferBindingType::Uniform,
|
|
206
|
+
has_dynamic_offset: false,
|
|
207
|
+
min_binding_size: None,
|
|
208
|
+
},
|
|
209
|
+
count: None,
|
|
210
|
+
},
|
|
211
|
+
wgpu::BindGroupLayoutEntry { binding: 11, visibility: wgpu::ShaderStages::FRAGMENT, ty: storage_ro, count: None },
|
|
212
|
+
wgpu::BindGroupLayoutEntry { binding: 12, visibility: wgpu::ShaderStages::FRAGMENT, ty: storage_ro, count: None },
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
pub struct FroxelPass {
|
|
217
|
+
pub assign_pipeline: wgpu::ComputePipeline,
|
|
218
|
+
pub assign_layout: wgpu::BindGroupLayout,
|
|
219
|
+
pub params_buffer: wgpu::Buffer,
|
|
220
|
+
/// Compact point-light UBO for the compute pass (count + 256 lights).
|
|
221
|
+
pub lights_buffer: wgpu::Buffer,
|
|
222
|
+
pub counts_buffer: wgpu::Buffer,
|
|
223
|
+
pub indices_buffer: wgpu::Buffer,
|
|
224
|
+
assign_bg: wgpu::BindGroup,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
impl FroxelPass {
|
|
228
|
+
/// Storage buffers must be available in BOTH compute and fragment
|
|
229
|
+
/// stages (WebGL2 has neither). `BLOOM_DISABLE_FROXEL=1` forces the
|
|
230
|
+
/// reference loop — used to (re)generate the clustered-parity
|
|
231
|
+
/// golden and to bisect suspected clustering bugs in the field.
|
|
232
|
+
pub fn supported(device: &wgpu::Device) -> bool {
|
|
233
|
+
if std::env::var_os("BLOOM_DISABLE_FROXEL").is_some_and(|v| v == "1") {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
let l = device.limits();
|
|
237
|
+
l.max_storage_buffers_per_shader_stage >= 2
|
|
238
|
+
&& l.max_storage_buffer_binding_size as u64
|
|
239
|
+
>= (CLUSTER_COUNT * MAX_LIGHTS_PER_CLUSTER * 4) as u64
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
pub fn new(device: &wgpu::Device) -> Self {
|
|
243
|
+
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
244
|
+
label: Some("froxel_assign_shader"),
|
|
245
|
+
source: wgpu::ShaderSource::Wgsl(ASSIGN_SHADER.into()),
|
|
246
|
+
});
|
|
247
|
+
let assign_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
248
|
+
label: Some("froxel_assign_layout"),
|
|
249
|
+
entries: &[
|
|
250
|
+
wgpu::BindGroupLayoutEntry {
|
|
251
|
+
binding: 0,
|
|
252
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
253
|
+
ty: wgpu::BindingType::Buffer {
|
|
254
|
+
ty: wgpu::BufferBindingType::Uniform,
|
|
255
|
+
has_dynamic_offset: false,
|
|
256
|
+
min_binding_size: None,
|
|
257
|
+
},
|
|
258
|
+
count: None,
|
|
259
|
+
},
|
|
260
|
+
wgpu::BindGroupLayoutEntry {
|
|
261
|
+
binding: 1,
|
|
262
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
263
|
+
ty: wgpu::BindingType::Buffer {
|
|
264
|
+
ty: wgpu::BufferBindingType::Uniform,
|
|
265
|
+
has_dynamic_offset: false,
|
|
266
|
+
min_binding_size: None,
|
|
267
|
+
},
|
|
268
|
+
count: None,
|
|
269
|
+
},
|
|
270
|
+
wgpu::BindGroupLayoutEntry {
|
|
271
|
+
binding: 2,
|
|
272
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
273
|
+
ty: wgpu::BindingType::Buffer {
|
|
274
|
+
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
|
275
|
+
has_dynamic_offset: false,
|
|
276
|
+
min_binding_size: None,
|
|
277
|
+
},
|
|
278
|
+
count: None,
|
|
279
|
+
},
|
|
280
|
+
wgpu::BindGroupLayoutEntry {
|
|
281
|
+
binding: 3,
|
|
282
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
283
|
+
ty: wgpu::BindingType::Buffer {
|
|
284
|
+
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
|
285
|
+
has_dynamic_offset: false,
|
|
286
|
+
min_binding_size: None,
|
|
287
|
+
},
|
|
288
|
+
count: None,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
293
|
+
label: Some("froxel_assign_pl"),
|
|
294
|
+
bind_group_layouts: &[Some(&assign_layout)],
|
|
295
|
+
..Default::default()
|
|
296
|
+
});
|
|
297
|
+
let assign_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
|
298
|
+
label: Some("froxel_assign_pipeline"),
|
|
299
|
+
layout: Some(&pl),
|
|
300
|
+
module: &shader,
|
|
301
|
+
entry_point: Some("cs_main"),
|
|
302
|
+
compilation_options: Default::default(),
|
|
303
|
+
cache: None,
|
|
304
|
+
});
|
|
305
|
+
let params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
306
|
+
label: Some("froxel_params"),
|
|
307
|
+
contents: &[0u8; std::mem::size_of::<FroxelParams>()],
|
|
308
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
309
|
+
});
|
|
310
|
+
// count vec4 + 256 lights × 2 vec4
|
|
311
|
+
let lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
312
|
+
label: Some("froxel_lights"),
|
|
313
|
+
size: 16 + 256 * 32,
|
|
314
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
315
|
+
mapped_at_creation: false,
|
|
316
|
+
});
|
|
317
|
+
let counts_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
318
|
+
label: Some("froxel_counts"),
|
|
319
|
+
size: (CLUSTER_COUNT * 4) as u64,
|
|
320
|
+
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
|
321
|
+
mapped_at_creation: false,
|
|
322
|
+
});
|
|
323
|
+
let indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
324
|
+
label: Some("froxel_indices"),
|
|
325
|
+
size: (CLUSTER_COUNT * MAX_LIGHTS_PER_CLUSTER * 4) as u64,
|
|
326
|
+
usage: wgpu::BufferUsages::STORAGE,
|
|
327
|
+
mapped_at_creation: false,
|
|
328
|
+
});
|
|
329
|
+
let assign_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
330
|
+
label: Some("froxel_assign_bg"),
|
|
331
|
+
layout: &assign_layout,
|
|
332
|
+
entries: &[
|
|
333
|
+
wgpu::BindGroupEntry { binding: 0, resource: params_buffer.as_entire_binding() },
|
|
334
|
+
wgpu::BindGroupEntry { binding: 1, resource: lights_buffer.as_entire_binding() },
|
|
335
|
+
wgpu::BindGroupEntry { binding: 2, resource: counts_buffer.as_entire_binding() },
|
|
336
|
+
wgpu::BindGroupEntry { binding: 3, resource: indices_buffer.as_entire_binding() },
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
Self {
|
|
340
|
+
assign_pipeline,
|
|
341
|
+
assign_layout,
|
|
342
|
+
params_buffer,
|
|
343
|
+
lights_buffer,
|
|
344
|
+
counts_buffer,
|
|
345
|
+
indices_buffer,
|
|
346
|
+
assign_bg,
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/// The bind-group entries matching [`extra_lighting_layout_entries`],
|
|
351
|
+
/// appended to every lighting bind group the renderer builds.
|
|
352
|
+
pub(super) fn extra_lighting_bind_entries(&self) -> [wgpu::BindGroupEntry<'_>; 3] {
|
|
353
|
+
[
|
|
354
|
+
wgpu::BindGroupEntry { binding: 10, resource: self.params_buffer.as_entire_binding() },
|
|
355
|
+
wgpu::BindGroupEntry { binding: 11, resource: self.counts_buffer.as_entire_binding() },
|
|
356
|
+
wgpu::BindGroupEntry { binding: 12, resource: self.indices_buffer.as_entire_binding() },
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Record the per-frame assignment dispatch. The caller uploads
|
|
361
|
+
/// params + lights first (see Renderer::record_froxel_assign).
|
|
362
|
+
pub fn record(&self, encoder: &mut wgpu::CommandEncoder) {
|
|
363
|
+
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
|
364
|
+
label: Some("froxel_assign_pass"),
|
|
365
|
+
timestamp_writes: None,
|
|
366
|
+
});
|
|
367
|
+
pass.set_pipeline(&self.assign_pipeline);
|
|
368
|
+
pass.set_bind_group(0, &self.assign_bg, &[]);
|
|
369
|
+
pass.dispatch_workgroups(GRID_X / 4, GRID_Y.div_ceil(4), GRID_Z / 4);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
impl super::Renderer {
|
|
374
|
+
/// Upload froxel params + the compact light list and dispatch the
|
|
375
|
+
/// assignment pass. Runs every 3D frame on supported devices —
|
|
376
|
+
/// even with zero lights, so `cluster_counts` never carries stale
|
|
377
|
+
/// data from a previous frame's camera.
|
|
378
|
+
pub(super) fn record_froxel_assign(&mut self, encoder: &mut wgpu::CommandEncoder) {
|
|
379
|
+
let Some(froxel) = &self.froxel else { return };
|
|
380
|
+
|
|
381
|
+
let proj = self.current_proj_matrix;
|
|
382
|
+
let p22 = proj[2][2];
|
|
383
|
+
let p32 = proj[3][2];
|
|
384
|
+
// Same linearization as Hi-Z: view_z(depth) = -p32/(depth + p22),
|
|
385
|
+
// positive forward. Evaluate at depth 0 and 1; min/max makes
|
|
386
|
+
// this hold for reversed-Z too, and the clamps keep an
|
|
387
|
+
// infinite-far projection (division by ~0) finite.
|
|
388
|
+
let z_at = |d: f32| -p32 / (d + p22);
|
|
389
|
+
let (z0, z1) = (z_at(0.0), z_at(1.0));
|
|
390
|
+
let znear = z0.min(z1).max(1e-3);
|
|
391
|
+
let zfar = z0.max(z1).clamp(znear * 1.001, 1e9);
|
|
392
|
+
|
|
393
|
+
// clip_position.xy is in render-target pixels — the HDR scene
|
|
394
|
+
// pass runs at render_extent (render_scale-aware), not surface
|
|
395
|
+
// size.
|
|
396
|
+
let (rw, rh) = self.render_extent();
|
|
397
|
+
let n = (self.lighting_uniforms.point_light_count[0] as u32)
|
|
398
|
+
.min(MAX_LIGHTS_PER_CLUSTER);
|
|
399
|
+
let params = FroxelParams {
|
|
400
|
+
view: self.current_view_matrix,
|
|
401
|
+
grid: [GRID_X, GRID_Y, GRID_Z, n],
|
|
402
|
+
depth_range: [znear, zfar, (zfar / znear).ln(), 0.0],
|
|
403
|
+
screen: [
|
|
404
|
+
GRID_X as f32 / rw.max(1) as f32,
|
|
405
|
+
GRID_Y as f32 / rh.max(1) as f32,
|
|
406
|
+
p22,
|
|
407
|
+
p32,
|
|
408
|
+
],
|
|
409
|
+
inv_proj: self.current_inv_proj_matrix,
|
|
410
|
+
};
|
|
411
|
+
self.queue.write_buffer(&froxel.params_buffer, 0, bytemuck::bytes_of(¶ms));
|
|
412
|
+
let count = [n as f32, 0.0, 0.0, 0.0_f32];
|
|
413
|
+
self.queue.write_buffer(&froxel.lights_buffer, 0, bytemuck::bytes_of(&count));
|
|
414
|
+
self.queue.write_buffer(
|
|
415
|
+
&froxel.lights_buffer,
|
|
416
|
+
16,
|
|
417
|
+
bytemuck::cast_slice(&self.lighting_uniforms.point_lights),
|
|
418
|
+
);
|
|
419
|
+
froxel.record(encoder);
|
|
420
|
+
}
|
|
421
|
+
}
|