@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,429 @@
|
|
|
1
|
+
//! Hi-Z occlusion culling — coarse max-depth grid with async readback.
|
|
2
|
+
//!
|
|
3
|
+
//! The engine already builds a linear-depth Hi-Z pyramid for SSAO/SSR,
|
|
4
|
+
//! but that chain is min-reduced (nearest depth — what ray marching
|
|
5
|
+
//! wants). Occlusion needs the opposite bound: a node is provably hidden
|
|
6
|
+
//! only if its nearest point is farther than the FARTHEST depth across
|
|
7
|
+
//! its whole screen footprint. So this module adds one small compute
|
|
8
|
+
//! reduce: Hi-Z mip 0 → a 64×64 max-depth grid, copied to a mappable
|
|
9
|
+
//! buffer and read back asynchronously.
|
|
10
|
+
//!
|
|
11
|
+
//! The CPU test runs one frame late (against last frame's grid and last
|
|
12
|
+
//! frame's view-projection) — the standard latency trade that avoids any
|
|
13
|
+
//! GPU stall. Every uncertain case resolves to "visible": no grid yet,
|
|
14
|
+
//! corner behind the near plane, footprint off the captured screen,
|
|
15
|
+
//! depth within the safety margin. Disocclusion artifacts from camera
|
|
16
|
+
//! cuts last exactly one frame.
|
|
17
|
+
//!
|
|
18
|
+
//! `bloom_set_occlusion_culling(0/1)` exposes the kill switch to games;
|
|
19
|
+
//! default on.
|
|
20
|
+
|
|
21
|
+
use wgpu::util::DeviceExt;
|
|
22
|
+
|
|
23
|
+
pub(super) const GRID_W: u32 = 64;
|
|
24
|
+
pub(super) const GRID_H: u32 = 64;
|
|
25
|
+
// 64 texels * 4 bytes = 256 bytes per row — exactly wgpu's
|
|
26
|
+
// COPY_BYTES_PER_ROW_ALIGNMENT, so the readback needs no padding.
|
|
27
|
+
const ROW_BYTES: u32 = GRID_W * 4;
|
|
28
|
+
|
|
29
|
+
const REDUCE_SHADER: &str = "
|
|
30
|
+
struct Params {
|
|
31
|
+
// xy = source (hiz mip0) size, zw = tile size in source texels
|
|
32
|
+
size: vec4<u32>,
|
|
33
|
+
};
|
|
34
|
+
@group(0) @binding(0) var<uniform> u: Params;
|
|
35
|
+
@group(0) @binding(1) var src_tex: texture_2d<f32>;
|
|
36
|
+
@group(0) @binding(2) var dst_tex: texture_storage_2d<r32float, write>;
|
|
37
|
+
|
|
38
|
+
@compute @workgroup_size(8, 8, 1)
|
|
39
|
+
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
40
|
+
if (gid.x >= 64u || gid.y >= 64u) { return; }
|
|
41
|
+
let base = vec2<u32>(gid.x * u.size.z, gid.y * u.size.w);
|
|
42
|
+
var m: f32 = 0.0;
|
|
43
|
+
for (var ty: u32 = 0u; ty < u.size.w; ty = ty + 1u) {
|
|
44
|
+
for (var tx: u32 = 0u; tx < u.size.z; tx = tx + 1u) {
|
|
45
|
+
let p = base + vec2<u32>(tx, ty);
|
|
46
|
+
if (p.x < u.size.x && p.y < u.size.y) {
|
|
47
|
+
m = max(m, textureLoad(src_tex, vec2<i32>(p), 0).r);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
textureStore(dst_tex, vec2<i32>(gid.xy), vec4<f32>(m, 0.0, 0.0, 0.0));
|
|
52
|
+
}
|
|
53
|
+
";
|
|
54
|
+
|
|
55
|
+
struct Readback {
|
|
56
|
+
buffer: wgpu::Buffer,
|
|
57
|
+
/// capture VP for the data this buffer holds
|
|
58
|
+
vp: [[f32; 4]; 4],
|
|
59
|
+
/// copy submitted, map_async issued, result not yet collected
|
|
60
|
+
in_flight: bool,
|
|
61
|
+
map_done: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub struct OcclusionCuller {
|
|
65
|
+
pipeline: wgpu::ComputePipeline,
|
|
66
|
+
layout: wgpu::BindGroupLayout,
|
|
67
|
+
uniform: wgpu::Buffer,
|
|
68
|
+
grid_tex: wgpu::Texture,
|
|
69
|
+
grid_view: wgpu::TextureView,
|
|
70
|
+
bg_cache: Option<wgpu::BindGroup>,
|
|
71
|
+
readbacks: [Readback; 2],
|
|
72
|
+
parity: usize,
|
|
73
|
+
/// most recent completed grid
|
|
74
|
+
grid: Vec<f32>,
|
|
75
|
+
grid_valid: bool,
|
|
76
|
+
grid_vp: [[f32; 4]; 4],
|
|
77
|
+
pub enabled: bool,
|
|
78
|
+
/// EN-057 — false when no rasterized scene node exists to consume the
|
|
79
|
+
/// culling verdicts (e.g. a scene whose only nodes are gi_only proxies:
|
|
80
|
+
/// they never draw, so the reduce + readback benefited zero draws every
|
|
81
|
+
/// frame). Set per frame by the engine from the scene graph; defaults to
|
|
82
|
+
/// true so hosts that never call the setter keep today's behaviour.
|
|
83
|
+
has_consumers: bool,
|
|
84
|
+
/// set when record() ran this frame so after_submit() knows to map
|
|
85
|
+
recorded_this_frame: bool,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
impl OcclusionCuller {
|
|
89
|
+
pub fn new(device: &wgpu::Device) -> Self {
|
|
90
|
+
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
91
|
+
label: Some("occlusion_reduce_shader"),
|
|
92
|
+
source: wgpu::ShaderSource::Wgsl(REDUCE_SHADER.into()),
|
|
93
|
+
});
|
|
94
|
+
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
95
|
+
label: Some("occlusion_reduce_layout"),
|
|
96
|
+
entries: &[
|
|
97
|
+
wgpu::BindGroupLayoutEntry {
|
|
98
|
+
binding: 0,
|
|
99
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
100
|
+
ty: wgpu::BindingType::Buffer {
|
|
101
|
+
ty: wgpu::BufferBindingType::Uniform,
|
|
102
|
+
has_dynamic_offset: false,
|
|
103
|
+
min_binding_size: None,
|
|
104
|
+
},
|
|
105
|
+
count: None,
|
|
106
|
+
},
|
|
107
|
+
wgpu::BindGroupLayoutEntry {
|
|
108
|
+
binding: 1,
|
|
109
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
110
|
+
ty: wgpu::BindingType::Texture {
|
|
111
|
+
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
|
112
|
+
view_dimension: wgpu::TextureViewDimension::D2,
|
|
113
|
+
multisampled: false,
|
|
114
|
+
},
|
|
115
|
+
count: None,
|
|
116
|
+
},
|
|
117
|
+
wgpu::BindGroupLayoutEntry {
|
|
118
|
+
binding: 2,
|
|
119
|
+
visibility: wgpu::ShaderStages::COMPUTE,
|
|
120
|
+
ty: wgpu::BindingType::StorageTexture {
|
|
121
|
+
access: wgpu::StorageTextureAccess::WriteOnly,
|
|
122
|
+
format: wgpu::TextureFormat::R32Float,
|
|
123
|
+
view_dimension: wgpu::TextureViewDimension::D2,
|
|
124
|
+
},
|
|
125
|
+
count: None,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
130
|
+
label: Some("occlusion_reduce_pl"),
|
|
131
|
+
bind_group_layouts: &[Some(&layout)],
|
|
132
|
+
..Default::default()
|
|
133
|
+
});
|
|
134
|
+
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
|
135
|
+
label: Some("occlusion_reduce_pipeline"),
|
|
136
|
+
layout: Some(&pl),
|
|
137
|
+
module: &shader,
|
|
138
|
+
entry_point: Some("cs_main"),
|
|
139
|
+
compilation_options: Default::default(),
|
|
140
|
+
cache: None,
|
|
141
|
+
});
|
|
142
|
+
let uniform = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
143
|
+
label: Some("occlusion_reduce_uniform"),
|
|
144
|
+
contents: &[0u8; 16],
|
|
145
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
146
|
+
});
|
|
147
|
+
let grid_tex = device.create_texture(&wgpu::TextureDescriptor {
|
|
148
|
+
label: Some("occlusion_grid"),
|
|
149
|
+
size: wgpu::Extent3d { width: GRID_W, height: GRID_H, depth_or_array_layers: 1 },
|
|
150
|
+
mip_level_count: 1,
|
|
151
|
+
sample_count: 1,
|
|
152
|
+
dimension: wgpu::TextureDimension::D2,
|
|
153
|
+
format: wgpu::TextureFormat::R32Float,
|
|
154
|
+
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC,
|
|
155
|
+
view_formats: &[],
|
|
156
|
+
});
|
|
157
|
+
let grid_view = grid_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
|
158
|
+
let mk_readback = || Readback {
|
|
159
|
+
buffer: device.create_buffer(&wgpu::BufferDescriptor {
|
|
160
|
+
label: Some("occlusion_readback"),
|
|
161
|
+
size: (ROW_BYTES * GRID_H) as u64,
|
|
162
|
+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
163
|
+
mapped_at_creation: false,
|
|
164
|
+
}),
|
|
165
|
+
vp: [[0.0; 4]; 4],
|
|
166
|
+
in_flight: false,
|
|
167
|
+
map_done: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
|
168
|
+
};
|
|
169
|
+
Self {
|
|
170
|
+
pipeline,
|
|
171
|
+
layout,
|
|
172
|
+
uniform,
|
|
173
|
+
grid_tex,
|
|
174
|
+
grid_view,
|
|
175
|
+
bg_cache: None,
|
|
176
|
+
readbacks: [mk_readback(), mk_readback()],
|
|
177
|
+
parity: 0,
|
|
178
|
+
grid: vec![0.0; (GRID_W * GRID_H) as usize],
|
|
179
|
+
grid_valid: false,
|
|
180
|
+
grid_vp: [[0.0; 4]; 4],
|
|
181
|
+
enabled: true,
|
|
182
|
+
has_consumers: true,
|
|
183
|
+
recorded_this_frame: false,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// EN-057 — tell the culler whether any rasterized consumer exists this
|
|
188
|
+
/// frame. Going consumer-less also invalidates the grid, so if a
|
|
189
|
+
/// consumer appears later the interim frames read the conservative
|
|
190
|
+
/// "potentially visible" answer (test_aabb on an invalid grid) instead
|
|
191
|
+
/// of a stale capture — the gate cannot cost a wrongly-culled draw by
|
|
192
|
+
/// construction.
|
|
193
|
+
pub fn set_has_consumers(&mut self, has: bool) {
|
|
194
|
+
if !has {
|
|
195
|
+
self.grid_valid = false;
|
|
196
|
+
}
|
|
197
|
+
self.has_consumers = has;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Drop cached bind group (call when the Hi-Z chain is reallocated,
|
|
201
|
+
/// e.g. on resize / render-scale change).
|
|
202
|
+
pub fn invalidate_bindings(&mut self) {
|
|
203
|
+
self.bg_cache = None;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Collect any finished readback. Call once per frame before culling.
|
|
207
|
+
pub fn poll(&mut self, device: &wgpu::Device) {
|
|
208
|
+
use std::sync::atomic::Ordering;
|
|
209
|
+
// Non-blocking pump so map_async callbacks make progress even on
|
|
210
|
+
// frames where nothing else polls the device.
|
|
211
|
+
let _ = device.poll(wgpu::PollType::Poll);
|
|
212
|
+
for rb in &mut self.readbacks {
|
|
213
|
+
if rb.in_flight && rb.map_done.load(Ordering::Acquire) {
|
|
214
|
+
{
|
|
215
|
+
let n = self.grid.len();
|
|
216
|
+
let view = rb.buffer.slice(..).get_mapped_range();
|
|
217
|
+
let floats: &[f32] = bytemuck::cast_slice(&view);
|
|
218
|
+
self.grid.copy_from_slice(&floats[..n]);
|
|
219
|
+
}
|
|
220
|
+
rb.buffer.unmap();
|
|
221
|
+
rb.in_flight = false;
|
|
222
|
+
rb.map_done.store(false, Ordering::Release);
|
|
223
|
+
self.grid_vp = rb.vp;
|
|
224
|
+
self.grid_valid = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Record the reduce + copy for this frame's grid capture.
|
|
230
|
+
/// `src` is Hi-Z mip 0 (linear |view_z|, sky = 10000) of `src_size`.
|
|
231
|
+
pub fn record(
|
|
232
|
+
&mut self,
|
|
233
|
+
device: &wgpu::Device,
|
|
234
|
+
queue: &wgpu::Queue,
|
|
235
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
236
|
+
src: &wgpu::TextureView,
|
|
237
|
+
src_size: (u32, u32),
|
|
238
|
+
vp: [[f32; 4]; 4],
|
|
239
|
+
) {
|
|
240
|
+
self.recorded_this_frame = false;
|
|
241
|
+
if !self.enabled || !self.has_consumers {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
let rb = &mut self.readbacks[self.parity];
|
|
245
|
+
if rb.in_flight {
|
|
246
|
+
// Previous capture still in flight (GPU more than a frame
|
|
247
|
+
// behind) — skip; the grid just stays one frame staler.
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
let tile_w = src_size.0.div_ceil(GRID_W).max(1);
|
|
251
|
+
let tile_h = src_size.1.div_ceil(GRID_H).max(1);
|
|
252
|
+
let params: [u32; 4] = [src_size.0, src_size.1, tile_w, tile_h];
|
|
253
|
+
queue.write_buffer(&self.uniform, 0, bytemuck::cast_slice(¶ms));
|
|
254
|
+
|
|
255
|
+
if self.bg_cache.is_none() {
|
|
256
|
+
self.bg_cache = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
257
|
+
label: Some("occlusion_reduce_bg"),
|
|
258
|
+
layout: &self.layout,
|
|
259
|
+
entries: &[
|
|
260
|
+
wgpu::BindGroupEntry { binding: 0, resource: self.uniform.as_entire_binding() },
|
|
261
|
+
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(src) },
|
|
262
|
+
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&self.grid_view) },
|
|
263
|
+
],
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
{
|
|
267
|
+
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
|
268
|
+
label: Some("occlusion_reduce_pass"),
|
|
269
|
+
timestamp_writes: None,
|
|
270
|
+
});
|
|
271
|
+
pass.set_pipeline(&self.pipeline);
|
|
272
|
+
pass.set_bind_group(0, self.bg_cache.as_ref().unwrap(), &[]);
|
|
273
|
+
pass.dispatch_workgroups(GRID_W / 8, GRID_H / 8, 1);
|
|
274
|
+
}
|
|
275
|
+
encoder.copy_texture_to_buffer(
|
|
276
|
+
wgpu::TexelCopyTextureInfo {
|
|
277
|
+
texture: &self.grid_tex,
|
|
278
|
+
mip_level: 0,
|
|
279
|
+
origin: wgpu::Origin3d::ZERO,
|
|
280
|
+
aspect: wgpu::TextureAspect::All,
|
|
281
|
+
},
|
|
282
|
+
wgpu::TexelCopyBufferInfo {
|
|
283
|
+
buffer: &rb.buffer,
|
|
284
|
+
layout: wgpu::TexelCopyBufferLayout {
|
|
285
|
+
offset: 0,
|
|
286
|
+
bytes_per_row: Some(ROW_BYTES),
|
|
287
|
+
rows_per_image: Some(GRID_H),
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
wgpu::Extent3d { width: GRID_W, height: GRID_H, depth_or_array_layers: 1 },
|
|
291
|
+
);
|
|
292
|
+
rb.vp = vp;
|
|
293
|
+
self.recorded_this_frame = true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Issue the async map for the capture recorded this frame. Call
|
|
297
|
+
/// after queue.submit() of the encoder passed to record().
|
|
298
|
+
pub fn after_submit(&mut self) {
|
|
299
|
+
if !self.recorded_this_frame {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let rb = &mut self.readbacks[self.parity];
|
|
303
|
+
let done = rb.map_done.clone();
|
|
304
|
+
rb.in_flight = true;
|
|
305
|
+
rb.buffer.slice(..).map_async(wgpu::MapMode::Read, move |res| {
|
|
306
|
+
if res.is_ok() {
|
|
307
|
+
done.store(true, std::sync::atomic::Ordering::Release);
|
|
308
|
+
}
|
|
309
|
+
// On error the buffer stays flagged in-flight until the next
|
|
310
|
+
// successful cycle on the other parity; culling simply keeps
|
|
311
|
+
// using the older grid.
|
|
312
|
+
});
|
|
313
|
+
self.parity = 1 - self.parity;
|
|
314
|
+
self.recorded_this_frame = false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Conservative visibility test for a world-space AABB against the
|
|
318
|
+
/// last completed grid. `true` = potentially visible (draw it).
|
|
319
|
+
pub fn test_aabb(&self, wmin: [f32; 3], wmax: [f32; 3]) -> bool {
|
|
320
|
+
if !self.enabled || !self.grid_valid {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
let vp = &self.grid_vp;
|
|
324
|
+
let mut uv_min = [f32::MAX, f32::MAX];
|
|
325
|
+
let mut uv_max = [f32::MIN, f32::MIN];
|
|
326
|
+
let mut nearest = f32::MAX;
|
|
327
|
+
for ix in 0..2 {
|
|
328
|
+
for iy in 0..2 {
|
|
329
|
+
for iz in 0..2 {
|
|
330
|
+
let x = if ix == 0 { wmin[0] } else { wmax[0] };
|
|
331
|
+
let y = if iy == 0 { wmin[1] } else { wmax[1] };
|
|
332
|
+
let z = if iz == 0 { wmin[2] } else { wmax[2] };
|
|
333
|
+
let cw = vp[0][3] * x + vp[1][3] * y + vp[2][3] * z + vp[3][3];
|
|
334
|
+
if cw <= 0.05 {
|
|
335
|
+
// corner at/behind the captured near plane —
|
|
336
|
+
// can't bound the footprint; play safe
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
let cx = vp[0][0] * x + vp[1][0] * y + vp[2][0] * z + vp[3][0];
|
|
340
|
+
let cy = vp[0][1] * x + vp[1][1] * y + vp[2][1] * z + vp[3][1];
|
|
341
|
+
let u = (cx / cw) * 0.5 + 0.5;
|
|
342
|
+
let v = 1.0 - ((cy / cw) * 0.5 + 0.5);
|
|
343
|
+
uv_min[0] = uv_min[0].min(u);
|
|
344
|
+
uv_min[1] = uv_min[1].min(v);
|
|
345
|
+
uv_max[0] = uv_max[0].max(u);
|
|
346
|
+
uv_max[1] = uv_max[1].max(v);
|
|
347
|
+
nearest = nearest.min(cw);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Fully outside the captured view → last frame's depth says
|
|
352
|
+
// nothing about it. (Current-frame frustum culling handles
|
|
353
|
+
// actual offscreen-ness.)
|
|
354
|
+
if uv_max[0] <= 0.0 || uv_min[0] >= 1.0 || uv_max[1] <= 0.0 || uv_min[1] >= 1.0 {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
// Expand by one texel for footprint conservatism, clamp to grid.
|
|
358
|
+
let tx0 = ((uv_min[0] * GRID_W as f32) as i32 - 1).clamp(0, GRID_W as i32 - 1) as usize;
|
|
359
|
+
let tx1 = ((uv_max[0] * GRID_W as f32) as i32 + 1).clamp(0, GRID_W as i32 - 1) as usize;
|
|
360
|
+
let ty0 = ((uv_min[1] * GRID_H as f32) as i32 - 1).clamp(0, GRID_H as i32 - 1) as usize;
|
|
361
|
+
let ty1 = ((uv_max[1] * GRID_H as f32) as i32 + 1).clamp(0, GRID_H as i32 - 1) as usize;
|
|
362
|
+
let mut grid_max = 0.0f32;
|
|
363
|
+
for ty in ty0..=ty1 {
|
|
364
|
+
for tx in tx0..=tx1 {
|
|
365
|
+
grid_max = grid_max.max(self.grid[ty * GRID_W as usize + tx]);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Margin: 2% relative + 0.1 absolute absorbs linearization and
|
|
369
|
+
// one frame of camera motion for typical scenes.
|
|
370
|
+
nearest <= grid_max * 1.02 + 0.1
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#[cfg(test)]
|
|
375
|
+
mod tests {
|
|
376
|
+
use super::*;
|
|
377
|
+
|
|
378
|
+
fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> {
|
|
379
|
+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
380
|
+
backends: wgpu::Backends::all(),
|
|
381
|
+
..wgpu::InstanceDescriptor::new_without_display_handle()
|
|
382
|
+
});
|
|
383
|
+
let adapter =
|
|
384
|
+
pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default()))
|
|
385
|
+
.ok()?;
|
|
386
|
+
pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// VP looking down -Z from the origin in the engine's column-major
|
|
390
|
+
/// convention (vp[col][row]): clip.w = -z, x/y pass through.
|
|
391
|
+
fn look_down_neg_z() -> [[f32; 4]; 4] {
|
|
392
|
+
let mut m = [[0.0f32; 4]; 4];
|
|
393
|
+
m[0][0] = 1.0;
|
|
394
|
+
m[1][1] = 1.0;
|
|
395
|
+
m[2][2] = -1.0;
|
|
396
|
+
m[2][3] = -1.0; // w = -z
|
|
397
|
+
m
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
#[test]
|
|
401
|
+
fn occluded_and_visible_cases() {
|
|
402
|
+
let Some((device, _queue)) = try_device() else {
|
|
403
|
+
eprintln!("skip: no GPU adapter in this environment");
|
|
404
|
+
return;
|
|
405
|
+
};
|
|
406
|
+
let mut c = OcclusionCuller::new(&device);
|
|
407
|
+
// Grid: farthest visible surface everywhere is at depth 10.
|
|
408
|
+
c.grid.fill(10.0);
|
|
409
|
+
c.grid_valid = true;
|
|
410
|
+
c.grid_vp = look_down_neg_z();
|
|
411
|
+
|
|
412
|
+
// Box fully behind that wall (depth 20..21, small footprint).
|
|
413
|
+
assert!(
|
|
414
|
+
!c.test_aabb([-0.1, -0.1, -21.0], [0.1, 0.1, -20.0]),
|
|
415
|
+
"box behind a full-screen depth-10 wall should be culled"
|
|
416
|
+
);
|
|
417
|
+
// Box in front of the wall (depth 5).
|
|
418
|
+
assert!(c.test_aabb([-0.1, -0.1, -5.2], [0.1, 0.1, -5.0]));
|
|
419
|
+
// Box straddling the near plane — must play safe.
|
|
420
|
+
assert!(c.test_aabb([-0.1, -0.1, -20.0], [0.1, 0.1, 1.0]));
|
|
421
|
+
// Without a valid grid, never cull.
|
|
422
|
+
c.grid_valid = false;
|
|
423
|
+
assert!(c.test_aabb([-0.1, -0.1, -21.0], [0.1, 0.1, -20.0]));
|
|
424
|
+
// Disabled culler never culls.
|
|
425
|
+
c.grid_valid = true;
|
|
426
|
+
c.enabled = false;
|
|
427
|
+
assert!(c.test_aabb([-0.1, -0.1, -21.0], [0.1, 0.1, -20.0]));
|
|
428
|
+
}
|
|
429
|
+
}
|