Skip to content

Commit 688db93

Browse files
committed
feat(geometry): raster virtual visibility IDs
1 parent 6d1183d commit 688db93

8 files changed

Lines changed: 690 additions & 4 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
const BLOOM_VIRTUAL_MESH_SLOT_MASK: u32 = 0x000fffffu;
2+
const BLOOM_VIRTUAL_FLAG_DOUBLE_SIDED: u32 = 1u;
3+
const BLOOM_VIRTUAL_FLAG_ALPHA_MASKED: u32 = 2u;
4+
5+
struct GpuVirtualMeshEntry {
6+
mesh_id: u32,
7+
page_table_base: u32,
8+
page_count: u32,
9+
cluster_table_base: u32,
10+
cluster_count: u32,
11+
root_cluster_count: u32,
12+
page_stride_bytes: u32,
13+
vertex_encoding: u32,
14+
format_version: u32,
15+
flags: u32,
16+
reserved: vec2<u32>,
17+
};
18+
struct GpuVirtualClusterEntry {
19+
aabb_min_error: vec4<f32>,
20+
aabb_max_radius: vec4<f32>,
21+
sphere: vec4<f32>,
22+
normal_cone: vec4<f32>,
23+
identity: vec4<u32>,
24+
page_lod_counts: vec4<u32>,
25+
payload: vec4<u32>,
26+
relations: vec4<u32>,
27+
};
28+
struct GpuSelectedVirtualCluster {
29+
mesh_id: u32,
30+
instance_index: u32,
31+
cluster_index: u32,
32+
physical_slot: u32,
33+
lod_level: u32,
34+
triangle_count: u32,
35+
material_id: u32,
36+
flags: u32,
37+
};
38+
struct GpuVirtualInstance {
39+
model: mat4x4<f32>,
40+
normal_rows: array<vec4<f32>, 3>,
41+
instance_info: vec4<u32>,
42+
previous_model: mat4x4<f32>,
43+
model_tint: vec4<f32>,
44+
};
45+
struct GpuVirtualVisibilityFrame {
46+
view_projection: mat4x4<f32>,
47+
previous_view_projection: mat4x4<f32>,
48+
};
49+
struct VirtualMeshTable { records: array<GpuVirtualMeshEntry>, };
50+
struct VirtualClusterTable { records: array<GpuVirtualClusterEntry>, };
51+
struct VirtualSelectedTable { records: array<GpuSelectedVirtualCluster>, };
52+
struct VirtualInstanceTable { records: array<GpuVirtualInstance>, };
53+
54+
struct VirtualVisibilityVertexOut {
55+
@builtin(position) position: vec4<f32>,
56+
@location(0) @interpolate(flat) draw_index: u32,
57+
@location(1) @interpolate(flat) flags: u32,
58+
};
59+
60+
@group(0) @binding(0) var<storage, read> virtual_page_words: BloomVirtualRawWords;
61+
@group(0) @binding(1) var<storage, read> virtual_meshes: VirtualMeshTable;
62+
@group(0) @binding(2) var<storage, read> virtual_clusters: VirtualClusterTable;
63+
@group(0) @binding(3) var<storage, read> virtual_selected: VirtualSelectedTable;
64+
@group(0) @binding(4) var<storage, read> virtual_instances: VirtualInstanceTable;
65+
@group(0) @binding(5) var<uniform> virtual_frame: GpuVirtualVisibilityFrame;
66+
67+
fn bloom_invalid_virtual_vertex() -> VirtualVisibilityVertexOut {
68+
return VirtualVisibilityVertexOut(vec4<f32>(2.0, 2.0, 2.0, 1.0), 0u, 0u);
69+
}
70+
71+
@vertex
72+
fn vs_virtual_visibility(
73+
@builtin(vertex_index) corner: u32,
74+
@builtin(instance_index) selected_index: u32,
75+
) -> VirtualVisibilityVertexOut {
76+
if (selected_index >= arrayLength(&virtual_selected.records)) {
77+
return bloom_invalid_virtual_vertex();
78+
}
79+
let selection = virtual_selected.records[selected_index];
80+
if (selection.instance_index >= arrayLength(&virtual_instances.records)) {
81+
return bloom_invalid_virtual_vertex();
82+
}
83+
let instance = virtual_instances.records[selection.instance_index];
84+
if (instance.instance_info.x != selection.mesh_id) {
85+
return bloom_invalid_virtual_vertex();
86+
}
87+
let mesh_slot_plus_one = selection.mesh_id & BLOOM_VIRTUAL_MESH_SLOT_MASK;
88+
if (mesh_slot_plus_one == 0u) {
89+
return bloom_invalid_virtual_vertex();
90+
}
91+
let mesh_index = mesh_slot_plus_one - 1u;
92+
if (mesh_index >= arrayLength(&virtual_meshes.records)) {
93+
return bloom_invalid_virtual_vertex();
94+
}
95+
let mesh = virtual_meshes.records[mesh_index];
96+
if (mesh.mesh_id != selection.mesh_id || selection.cluster_index >= mesh.cluster_count) {
97+
return bloom_invalid_virtual_vertex();
98+
}
99+
let cluster_index = mesh.cluster_table_base + selection.cluster_index;
100+
if (cluster_index >= arrayLength(&virtual_clusters.records)) {
101+
return bloom_invalid_virtual_vertex();
102+
}
103+
let cluster = virtual_clusters.records[cluster_index];
104+
let corner_count = cluster.page_lod_counts.w * 3u;
105+
if (corner >= corner_count || selection.triangle_count != cluster.page_lod_counts.w) {
106+
return bloom_invalid_virtual_vertex();
107+
}
108+
let page_base = selection.physical_slot * mesh.page_stride_bytes;
109+
let local_vertex = bloom_virtual_load_local_index(page_base + cluster.payload.y + corner);
110+
if (local_vertex >= cluster.page_lod_counts.z) {
111+
return bloom_invalid_virtual_vertex();
112+
}
113+
let vertex_offset = page_base + cluster.payload.x + local_vertex * cluster.payload.z;
114+
let vertex = bloom_virtual_decode_vertex(
115+
vertex_offset,
116+
mesh.vertex_encoding,
117+
cluster.aabb_min_error.xyz,
118+
cluster.aabb_max_radius.xyz,
119+
);
120+
let world = instance.model * vec4<f32>(vertex.position, 1.0);
121+
return VirtualVisibilityVertexOut(
122+
virtual_frame.view_projection * world,
123+
selected_index,
124+
selection.flags,
125+
);
126+
}
127+
128+
@fragment
129+
fn fs_virtual_visibility(
130+
in: VirtualVisibilityVertexOut,
131+
@builtin(primitive_index) primitive_id: u32,
132+
@builtin(front_facing) front_facing: bool,
133+
) -> @location(0) vec2<u32> {
134+
// Masked clusters remain on compatibility rendering until this pass owns
135+
// the exact alpha-coverage texture/sampler and cutoff contract.
136+
if ((in.flags & BLOOM_VIRTUAL_FLAG_ALPHA_MASKED) != 0u) { discard; }
137+
if ((in.flags & BLOOM_VIRTUAL_FLAG_DOUBLE_SIDED) == 0u && !front_facing) { discard; }
138+
return bloom_encode_virtual_visibility(in.draw_index, primitive_id, front_facing);
139+
}

native/shared/src/renderer/formats.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use wgpu;
1212
// Depth texture helper
1313
// ============================================================
1414

15-
pub(super) const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
15+
pub(crate) const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
1616
/// Linear HDR format for the offscreen render target. The scene + sky
1717
/// + immediate-mode 3D passes write here in linear space; a final
1818
/// composite pass tonemaps to the sRGB surface format.

native/shared/src/virtual_geometry/draw_emission.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ impl GpuVirtualDrawEmitter {
222222
self.draw_capacity
223223
}
224224

225+
pub(super) const fn selector_id(&self) -> u64 {
226+
self.selector_id
227+
}
228+
225229
pub fn command_buffer(&self) -> &wgpu::Buffer {
226230
&self.command_buffer
227231
}

native/shared/src/virtual_geometry/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod draw_emission;
99
mod gpu_pool;
1010
mod residency;
1111
mod traversal;
12+
mod visibility;
1213

1314
pub use asset::{ArtifactIdentity, VirtualGeometryAsset, VirtualGeometryLoadError};
1415
pub use bloom_geometry_format::{
@@ -33,6 +34,9 @@ pub use traversal::{
3334
GpuVirtualPageRequest, GpuVirtualTraversalConfig, GpuVirtualTraversalCounters,
3435
VirtualGeometryTraversalDispatch, VirtualGeometryTraversalError, VirtualGeometryView,
3536
};
37+
pub use visibility::{
38+
GpuVirtualVisibilityFrame, GpuVirtualVisibilityRaster, VirtualGeometryVisibilityError,
39+
};
3640

3741
#[cfg(test)]
3842
mod tests;

native/shared/src/virtual_geometry/tests.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ const VIRTUAL_GEOMETRY_DECODE_PROBE_WGSL: &str =
1717
#[cfg(not(target_arch = "wasm32"))]
1818
#[path = "temporal_material_tests.rs"]
1919
mod temporal_material_tests;
20-
20+
#[cfg(not(target_arch = "wasm32"))]
21+
#[path = "virtual_visibility_tests.rs"]
22+
mod virtual_visibility_tests;
2123
fn push_u32(bytes: &mut Vec<u8>, value: u32) {
2224
bytes.extend_from_slice(&value.to_le_bytes());
2325
}
@@ -462,8 +464,9 @@ fn try_traversal_device() -> Option<(wgpu::Device, wgpu::Queue)> {
462464
.ok()?;
463465
let mut limits = wgpu::Limits::downlevel_defaults();
464466
limits.max_storage_buffers_per_shader_stage = 7;
465-
let optional_indirect =
466-
wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
467+
let optional_indirect = wgpu::Features::INDIRECT_FIRST_INSTANCE
468+
| wgpu::Features::MULTI_DRAW_INDIRECT_COUNT
469+
| wgpu::Features::PRIMITIVE_INDEX;
467470
pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
468471
label: Some("virtual_geometry_traversal_test_device"),
469472
required_limits: limits,

native/shared/src/virtual_geometry/traversal.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ impl GpuVirtualHierarchySelector {
435435
pub(super) const fn id(&self) -> u64 {
436436
self.id
437437
}
438+
439+
pub(super) const fn pool_id(&self) -> u64 {
440+
self.pool_id
441+
}
438442
}
439443

440444
fn binding(binding: u32, buffer: &wgpu::Buffer) -> wgpu::BindGroupEntry<'_> {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use super::*;
2+
use crate::renderer::visibility_buffer::{
3+
VisibilityDraw, VisibilityRecord, INVALID_DRAW_ID, VISIBILITY_FORMAT,
4+
};
5+
6+
#[test]
7+
fn raw_virtual_clusters_rasterize_namespaced_visibility_ids_on_the_real_gpu() {
8+
const WIDTH: u32 = 16;
9+
const HEIGHT: u32 = 16;
10+
const ROW_BYTES: u32 = 256;
11+
12+
let Some((device, queue)) = try_traversal_device() else {
13+
eprintln!("no GPU adapter — skipping virtual visibility raster oracle");
14+
return;
15+
};
16+
let required = wgpu::Features::PRIMITIVE_INDEX | wgpu::Features::INDIRECT_FIRST_INSTANCE;
17+
if !device.features().contains(required) {
18+
eprintln!("adapter lacks primitive-index/indirect-first-instance — skipping oracle");
19+
return;
20+
}
21+
22+
let mut pool = GpuVirtualGeometryPool::new(&device, gpu_config(5)).unwrap();
23+
let mesh = pool
24+
.register_mesh(&queue, hierarchy_asset(hierarchy_archive()))
25+
.unwrap();
26+
make_hierarchy_fully_resident(&mut pool, &queue, mesh);
27+
let selector = GpuVirtualHierarchySelector::new(&device, &pool, traversal_config()).unwrap();
28+
let emitter = GpuVirtualDrawEmitter::new(&device, &selector).unwrap();
29+
let raster = GpuVirtualVisibilityRaster::new(&device, &pool, &selector, &emitter).unwrap();
30+
let identity = [
31+
[1.0, 0.0, 0.0, 0.0],
32+
[0.0, 1.0, 0.0, 0.0],
33+
[0.0, 0.0, 1.0, 0.0],
34+
[0.0, 0.0, 0.0, 1.0],
35+
];
36+
raster
37+
.prepare_frame(
38+
&queue,
39+
GpuVirtualVisibilityFrame::new(identity, identity).unwrap(),
40+
)
41+
.unwrap();
42+
43+
let visibility = device.create_texture(&wgpu::TextureDescriptor {
44+
label: Some("virtual_visibility_oracle_ids"),
45+
size: wgpu::Extent3d {
46+
width: WIDTH,
47+
height: HEIGHT,
48+
depth_or_array_layers: 1,
49+
},
50+
mip_level_count: 1,
51+
sample_count: 1,
52+
dimension: wgpu::TextureDimension::D2,
53+
format: VISIBILITY_FORMAT,
54+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
55+
view_formats: &[],
56+
});
57+
let depth = device.create_texture(&wgpu::TextureDescriptor {
58+
label: Some("virtual_visibility_oracle_depth"),
59+
size: wgpu::Extent3d {
60+
width: WIDTH,
61+
height: HEIGHT,
62+
depth_or_array_layers: 1,
63+
},
64+
mip_level_count: 1,
65+
sample_count: 1,
66+
dimension: wgpu::TextureDimension::D2,
67+
format: crate::renderer::DEPTH_FORMAT,
68+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
69+
view_formats: &[],
70+
});
71+
let readback_source = device.create_buffer(&wgpu::BufferDescriptor {
72+
label: Some("virtual_visibility_oracle_copy"),
73+
size: u64::from(ROW_BYTES * HEIGHT),
74+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
75+
mapped_at_creation: false,
76+
});
77+
let visibility_view = visibility.create_view(&Default::default());
78+
let depth_view = depth.create_view(&Default::default());
79+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
80+
label: Some("virtual_visibility_oracle_encoder"),
81+
});
82+
selector
83+
.record(
84+
&queue,
85+
&mut encoder,
86+
&pool,
87+
&[GpuVirtualInstance::identity(mesh, 901)],
88+
traversal_view(50.0),
89+
)
90+
.unwrap();
91+
emitter.record(&queue, &mut encoder, &selector).unwrap();
92+
{
93+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
94+
label: Some("virtual_visibility_oracle_pass"),
95+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
96+
view: &visibility_view,
97+
resolve_target: None,
98+
depth_slice: None,
99+
ops: wgpu::Operations {
100+
load: wgpu::LoadOp::Clear(wgpu::Color {
101+
r: f64::from(u32::MAX),
102+
g: f64::from(u32::MAX),
103+
b: 0.0,
104+
a: 0.0,
105+
}),
106+
store: wgpu::StoreOp::Store,
107+
},
108+
})],
109+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
110+
view: &depth_view,
111+
depth_ops: Some(wgpu::Operations {
112+
load: wgpu::LoadOp::Clear(1.0),
113+
store: wgpu::StoreOp::Store,
114+
}),
115+
stencil_ops: None,
116+
}),
117+
timestamp_writes: None,
118+
occlusion_query_set: None,
119+
multiview_mask: None,
120+
});
121+
raster.draw_fixed_for_test(&mut pass, &emitter, 4).unwrap();
122+
}
123+
encoder.copy_texture_to_buffer(
124+
wgpu::TexelCopyTextureInfo {
125+
texture: &visibility,
126+
mip_level: 0,
127+
origin: wgpu::Origin3d::ZERO,
128+
aspect: wgpu::TextureAspect::All,
129+
},
130+
wgpu::TexelCopyBufferInfo {
131+
buffer: &readback_source,
132+
layout: wgpu::TexelCopyBufferLayout {
133+
offset: 0,
134+
bytes_per_row: Some(ROW_BYTES),
135+
rows_per_image: Some(HEIGHT),
136+
},
137+
},
138+
wgpu::Extent3d {
139+
width: WIDTH,
140+
height: HEIGHT,
141+
depth_or_array_layers: 1,
142+
},
143+
);
144+
queue.submit(std::iter::once(encoder.finish()));
145+
146+
let bytes = read_gpu_buffer(
147+
&device,
148+
&queue,
149+
&readback_source,
150+
u64::from(ROW_BYTES * HEIGHT),
151+
);
152+
let mut covered = 0usize;
153+
let mut background = 0usize;
154+
for y in 0..HEIGHT {
155+
for x in 0..WIDTH {
156+
let offset = (y * ROW_BYTES + x * 8) as usize;
157+
let words: &[u32] = bytemuck::cast_slice(&bytes[offset..offset + 8]);
158+
let record = VisibilityRecord {
159+
draw_id: words[0],
160+
primitive_and_face: words[1],
161+
};
162+
if record.draw_id == INVALID_DRAW_ID {
163+
background += 1;
164+
continue;
165+
}
166+
let Some((VisibilityDraw::Virtual(draw_index), primitive, _)) = record.decode_draw()
167+
else {
168+
panic!("virtual raster emitted a compatibility or invalid visibility ID");
169+
};
170+
assert!(draw_index < 4);
171+
assert_eq!(primitive, 0);
172+
covered += 1;
173+
}
174+
}
175+
assert!(covered > 0);
176+
assert!(background > 0);
177+
assert_eq!(
178+
raster.counted_submission_supported(),
179+
device
180+
.features()
181+
.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT)
182+
);
183+
}

0 commit comments

Comments
 (0)