mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 03:51:08 +00:00
* claim: #2888 * fix: preserve AO depth in a portable integer texture (#2888)
This commit is contained in:
committed by
GitHub
parent
c5129a45d7
commit
511faedcfa
@@ -29,7 +29,7 @@ fn near_z(c: vec4<f32>) -> f32 {
|
||||
}
|
||||
|
||||
@group(1) @binding(2) var acc_t: texture_2d<f32>;
|
||||
@group(2) @binding(0) var ao_depth: texture_depth_2d;
|
||||
@group(2) @binding(0) var ao_depth: texture_2d<u32>;
|
||||
|
||||
fn clipped(p: vec3<f32>) -> bool {
|
||||
return length(u.clip.xyz) > 0.5 && dot(u.clip.xyz, p) > u.clip.w;
|
||||
@@ -141,11 +141,19 @@ fn solid(i: TOut, front: bool) -> vec4<f32> {
|
||||
}
|
||||
|
||||
// A single-sample opaque depth map, independent of the window's MSAA and its UI offsets.
|
||||
// Preserve the depth's bits in R32Uint: GLSL cannot textureLoad a depth texture,
|
||||
// and float colour attachments are not available on every GL adapter.
|
||||
struct AoDepth {
|
||||
@builtin(frag_depth) depth: f32,
|
||||
@location(0) value: u32,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_ao_depth(i: TOut, @builtin(front_facing) front: bool) -> @builtin(frag_depth) f32 {
|
||||
fn fs_ao_depth(i: TOut, @builtin(front_facing) front: bool) -> AoDepth {
|
||||
if (clipped(i.wp) || length(i.n) < 0.5) { discard; }
|
||||
if (!front && length(u.clip.xyz) > 0.5) { return cap_depth(i.wp, i.pos.z); }
|
||||
return i.pos.z;
|
||||
var depth = i.pos.z;
|
||||
if (!front && length(u.clip.xyz) > 0.5) { depth = cap_depth(i.wp, i.pos.z); }
|
||||
return AoDepth(depth, bitcast<u32>(depth));
|
||||
}
|
||||
|
||||
// Deterministic cosine-weighted hemisphere samples. Projecting the sample and a point one
|
||||
@@ -180,7 +188,7 @@ fn occlusion(i: TOut) -> f32 {
|
||||
let ndc = q.xyz / q.w;
|
||||
let uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
||||
if (any(uv < vec2<f32>(0.0)) || any(uv >= vec2<f32>(1.0)) || ndc.z < 0.0 || ndc.z > 1.0) { continue; }
|
||||
let d0 = textureLoad(ao_depth, vec2<i32>(uv * vec2<f32>(size)), 0);
|
||||
let d0 = bitcast<f32>(textureLoad(ao_depth, vec2<i32>(uv * vec2<f32>(size)), 0).x);
|
||||
let zs = select(ndc.z, 1.0 - ndc.z, flip);
|
||||
let zn = select(near.z / near.w, 1.0 - near.z / near.w, flip);
|
||||
let d = select(d0, 1.0 - d0, flip);
|
||||
|
||||
+40
-20
@@ -2,6 +2,9 @@
|
||||
//! depth is drawn once into a single-sample depth map the lit fragment samples for occlusion.
|
||||
//! The map is independent of the window's MSAA, and the depth runs the same way as the
|
||||
//! viewport's own buffer (see [`super::depth_compare`] and [`super::far_depth`]).
|
||||
//! Depth testing chooses the nearest surface; an R32Uint colour attachment stores that depth's
|
||||
//! bits for portable integer texel reads. GLSL cannot load from a depth texture, and float
|
||||
//! colour attachments are not available on every GL adapter.
|
||||
|
||||
use super::{Batches, color_layout, depth_compare, far_depth, planned_layout, tri_layout};
|
||||
|
||||
@@ -9,29 +12,34 @@ pub(super) struct Resources {
|
||||
pub layout: wgpu::BindGroupLayout,
|
||||
pub bind: wgpu::BindGroup,
|
||||
depth: wgpu::TextureView,
|
||||
values: wgpu::TextureView,
|
||||
size: [u32; 2],
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
planned_pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
fn texture(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, size: [u32; 2]) -> (wgpu::TextureView, wgpu::BindGroup) {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("sc_ao_depth"),
|
||||
size: wgpu::Extent3d { width: size[0], height: size[1], depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
let depth = texture.create_view(&Default::default());
|
||||
fn texture(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, size: [u32; 2]) -> (wgpu::TextureView, wgpu::TextureView, wgpu::BindGroup) {
|
||||
let texture = |label, format, usage| {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d { width: size[0], height: size[1], depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage,
|
||||
view_formats: &[],
|
||||
})
|
||||
};
|
||||
let depth = texture("sc_ao_depth", wgpu::TextureFormat::Depth32Float, wgpu::TextureUsages::RENDER_ATTACHMENT).create_view(&Default::default());
|
||||
let values = texture("sc_ao_values", wgpu::TextureFormat::R32Uint, wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
|
||||
.create_view(&Default::default());
|
||||
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("sc_ao_depth"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&depth) }],
|
||||
entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&values) }],
|
||||
});
|
||||
(depth, bind)
|
||||
(depth, values, bind)
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
@@ -42,7 +50,7 @@ impl Resources {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
sample_type: wgpu::TextureSampleType::Uint,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
@@ -78,7 +86,11 @@ impl Resources {
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module,
|
||||
entry_point: Some("fs_ao_depth"),
|
||||
targets: &[],
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::R32Uint,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::RED,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
multiview_mask: None,
|
||||
@@ -87,8 +99,8 @@ impl Resources {
|
||||
};
|
||||
let planned_pipeline = pipeline(true);
|
||||
let pipeline = pipeline(false);
|
||||
let (depth, bind) = texture(device, &layout, [1, 1]);
|
||||
Self { layout, bind, depth, size: [1, 1], pipeline, planned_pipeline }
|
||||
let (depth, values, bind) = texture(device, &layout, [1, 1]);
|
||||
Self { layout, bind, depth, values, size: [1, 1], pipeline, planned_pipeline }
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
@@ -105,7 +117,7 @@ impl Resources {
|
||||
// Bound memory at large window sizes; normalized coordinates also handle downsampling.
|
||||
let size = target_size(enabled, size_px, device.limits().max_texture_dimension_2d);
|
||||
if size != self.size {
|
||||
(self.depth, self.bind) = texture(device, &self.layout, size);
|
||||
(self.depth, self.values, self.bind) = texture(device, &self.layout, size);
|
||||
self.size = size;
|
||||
}
|
||||
if !enabled {
|
||||
@@ -113,7 +125,15 @@ impl Resources {
|
||||
}
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("sc_ao_prepass"),
|
||||
color_attachments: &[],
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.values,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color { r: f64::from(far_depth(reversed).to_bits()), g: 0.0, b: 0.0, a: 0.0 }),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth,
|
||||
depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(far_depth(reversed)), store: wgpu::StoreOp::Store }),
|
||||
|
||||
@@ -39,6 +39,39 @@ fn session(script: &[(&str, serde_json::Value)]) -> Session {
|
||||
s
|
||||
}
|
||||
|
||||
/// #2888: compiling the viewport on GL used to reject AO's depth textureLoad, even
|
||||
/// with AO disabled. Select GL explicitly so a working Vulkan adapter cannot hide it.
|
||||
#[test]
|
||||
fn opengl_viewport_pipelines_render_a_model() {
|
||||
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
|
||||
desc.backends = wgpu::Backends::GL;
|
||||
let instance = wgpu::Instance::new(desc);
|
||||
let adapter = match pollster::block_on(instance.request_adapter(&Default::default())) {
|
||||
Ok(adapter) => adapter,
|
||||
Err(e) => {
|
||||
let required = std::env::var_os("SOLIDCRAFT_REQUIRE_GPU").is_some() && wgpu::Backends::from_env() == Some(wgpu::Backends::GL);
|
||||
assert!(!required, "OpenGL is required here: {e}");
|
||||
eprintln!("skipping the OpenGL regression: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
assert_eq!(adapter.get_info().backend, wgpu::Backend::Gl);
|
||||
eprintln!("OpenGL regression: {:?}", adapter.get_info());
|
||||
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
required_features: solidcraft_gpu::depth_features(&adapter),
|
||||
required_limits: wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()),
|
||||
..Default::default()
|
||||
}))
|
||||
.unwrap();
|
||||
let c = Capturer::new(&adapter, device, queue).unwrap();
|
||||
let s = session(&[("solid.box", json!({"length": 40, "width": 30, "height": 20}))]);
|
||||
let cam = view::home_camera(&s);
|
||||
let img = c.rgba(&view::scene_with_guides(&s, &cam, None, Guides { grid: false, axes: false }), &cam, 160, 100).unwrap();
|
||||
let empty = c.rgba(&Default::default(), &cam, 160, 100).unwrap();
|
||||
let changed = pixels(&img).zip(pixels(&empty)).filter(|(a, b)| a != b).count();
|
||||
assert!(changed > 500, "the model covers only {changed} pixels");
|
||||
}
|
||||
|
||||
/// Appearances show as the viewport shows them: a body's colour, a face's own look.
|
||||
#[test]
|
||||
fn pictures_show_appearances() {
|
||||
|
||||
Reference in New Issue
Block a user