mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #10175 from vnsmoreira/fix-translucent-color-feedback
Fix shader-pack color-buffer reads in translucent draws
This commit is contained in:
2 files changed
+168
-4
No files matched your search
@@ -55,6 +55,9 @@ mod custom_type_tests;
|
||||
#[cfg(test)]
|
||||
mod sky_depth_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod translucent_color_tests;
|
||||
|
||||
/// The texture behind a pack sampler name.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Source {
|
||||
@@ -1273,8 +1276,9 @@ fn bindings(
|
||||
let mut reads = 0u16;
|
||||
let mut shadow_reads = 0u16;
|
||||
let watershadow = interface.samplers.iter().any(|s| s.name == "watershadow");
|
||||
// A gbuffers pass draws into its buffers and the depth buffer: reading them as well would
|
||||
// be a feedback loop (undefined in GL, refused by wgpu), so those read a blank texture.
|
||||
// Opaque gbuffers draws cannot sample their own attachments. Translucent draws can
|
||||
// read colortex4 and above from the preceding deferred stage; those reads use a copy
|
||||
// taken before the draw, so the sampled texture is never also an attachment.
|
||||
let in_place = match geometry {
|
||||
Some(_) => buffers.iter().fold(0u16, |m, &b| m | 1 << b),
|
||||
None => 0,
|
||||
@@ -1384,7 +1388,9 @@ fn bindings(
|
||||
Source::Depth => geometry.is_some(),
|
||||
// Translucent terrain reads the copy; opaque terrain would read itself.
|
||||
Source::Depth1 | Source::Depth2 => group == Group::Gbuffers,
|
||||
Source::Color(n) => in_place & 1 << n != 0,
|
||||
Source::Color(n) => {
|
||||
(group != Group::Translucent || n < 4) && in_place & 1 << n != 0
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let mut sample_type = match src {
|
||||
@@ -1865,7 +1871,13 @@ fn build_pass(
|
||||
ids: geometry.is_some_and(|g| g.ids.is_some()),
|
||||
groups: HashMap::new(),
|
||||
portal_groups: HashMap::new(),
|
||||
copies: image_buffers(&images) & reads,
|
||||
copies: (image_buffers(&images)
|
||||
| if p.group == Group::Translucent {
|
||||
written
|
||||
} else {
|
||||
0
|
||||
})
|
||||
& reads,
|
||||
reads: reads | image_buffers(&images),
|
||||
buffers: !interface.buffers.is_empty(),
|
||||
images,
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
//! A translucent draw reads the preceding scene even when it writes the same color buffer.
|
||||
//! Original programs and independently calculated colors; no shader-pack assets.
|
||||
use super::depth_test::{Gpu, SIZE, VERTEX, fixture_frame};
|
||||
use super::*;
|
||||
use crate::hud::HudBatch;
|
||||
use wgpu::{TextureFormat as T, util::DeviceExt};
|
||||
|
||||
#[test]
|
||||
fn translucent_draws_sample_the_color_written_by_deferred() {
|
||||
let Some(gpu) = Gpu::new() else { return };
|
||||
let positions: [[f32; 3]; 6] = [
|
||||
[-1.0, -1.0, 0.0],
|
||||
[1.0, -1.0, 0.0],
|
||||
[1.0, 1.0, 0.0],
|
||||
[-1.0, -1.0, 0.0],
|
||||
[1.0, 1.0, 0.0],
|
||||
[-1.0, 1.0, 0.0],
|
||||
];
|
||||
let vertices = gpu
|
||||
.device
|
||||
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Original translucent quad"),
|
||||
contents: bytemuck::cast_slice(&positions),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
const ATTRS: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![0=>Float32x3];
|
||||
let layouts = [Some(wgpu::VertexBufferLayout {
|
||||
array_stride: 12,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &ATTRS,
|
||||
})];
|
||||
// Odd and even numbers of deferred passes leave the current scene on different sides.
|
||||
// Exercise two buffer numbers and the legacy sampler aliases too.
|
||||
for (buffer, sampler) in [(4, "colortex4"), (7, "gaux4")] {
|
||||
for stages in [1, 2] {
|
||||
for blended in [false, true] {
|
||||
let mut files = vec![
|
||||
("deferred.vsh".into(), VERTEX.into()),
|
||||
(
|
||||
"deferred.fsh".into(),
|
||||
format!(
|
||||
"#version 120\n/* RENDERTARGETS: {buffer} */\nvoid main(){{gl_FragData[0]=vec4(0.25,0.5,0.75,1.0);}}"
|
||||
),
|
||||
),
|
||||
(
|
||||
"gbuffers_water.vsh".into(),
|
||||
"#version 120\nvoid main(){gl_Position=gl_Vertex;}".into(),
|
||||
),
|
||||
(
|
||||
"gbuffers_water.fsh".into(),
|
||||
format!(
|
||||
"#version 120\n/* RENDERTARGETS: {buffer} */\nuniform sampler2D {sampler};\nvoid main(){{gl_FragData[0]=vec4(texture2D({sampler},vec2(0.5)).rgb+vec3(0.125,0.0,0.0),0.5);}}"
|
||||
),
|
||||
),
|
||||
("final.vsh".into(), VERTEX.into()),
|
||||
(
|
||||
"final.fsh".into(),
|
||||
format!(
|
||||
"#version 120\nuniform sampler2D colortex{buffer};varying vec2 uv;\nvoid main(){{gl_FragColor=vec4(texture2D(colortex{buffer},uv).rgb,1.0);}}"
|
||||
),
|
||||
),
|
||||
(
|
||||
"shaders.properties".into(),
|
||||
format!(
|
||||
"blend.gbuffers_water={}\n",
|
||||
if blended {
|
||||
"SRC_ALPHA ONE_MINUS_SRC_ALPHA ONE ONE_MINUS_SRC_ALPHA"
|
||||
} else {
|
||||
"off"
|
||||
}
|
||||
),
|
||||
),
|
||||
];
|
||||
if stages == 2 {
|
||||
files.extend([
|
||||
("deferred1.vsh".into(), VERTEX.into()),
|
||||
("deferred1.fsh".into(), format!(
|
||||
"#version 120\n/* RENDERTARGETS: {buffer} */\nvoid main(){{gl_FragData[0]=vec4(0.5,0.75,0.25,1.0);}}"
|
||||
)),
|
||||
]);
|
||||
}
|
||||
let color = gpu.texture(T::Rgba8Unorm);
|
||||
let depth = gpu.texture(T::Depth32Float);
|
||||
gpu.clear(&color, &depth, 0.0);
|
||||
let mut post = gpu.load(
|
||||
files,
|
||||
World {
|
||||
atlas: None,
|
||||
material_maps: None,
|
||||
lightmap: color.clone(),
|
||||
terrain: Some(Geometry {
|
||||
buffers: &layouts,
|
||||
table: None,
|
||||
depth_write: true,
|
||||
atlas: None,
|
||||
material_maps: false,
|
||||
ids: None,
|
||||
cull: false,
|
||||
}),
|
||||
block_ids: &[],
|
||||
sprite_rects: &[],
|
||||
celestial: None,
|
||||
gui_size: SIZE,
|
||||
clouds: None,
|
||||
weather: false,
|
||||
hand: false,
|
||||
},
|
||||
horizon_shaders::GameInputs {
|
||||
terrain: Some(horizon_shaders::GbuffersInputs {
|
||||
declarations: "layout(location=0)in vec3 fixture_position;\n".into(),
|
||||
body: "hz_Vertex=vec4(fixture_position,1.0);\n".into(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(post.has_water(), "{:?}", post.notes);
|
||||
let mut expected = if stages == 1 {
|
||||
[0.25f32, 0.5, 0.75]
|
||||
} else {
|
||||
[0.5, 0.75, 0.25]
|
||||
};
|
||||
expected[0] += if blended { 0.0625 } else { 0.125 };
|
||||
let expected = expected.map(|c| (c * 255.0).round() as u8);
|
||||
let hud = HudBatch::new(SIZE);
|
||||
for frame in 0..2 {
|
||||
let pixels = gpu.render(
|
||||
&mut post,
|
||||
&color,
|
||||
&depth,
|
||||
&fixture_frame(&hud),
|
||||
&mut |rp, list, _, _| {
|
||||
if list == TerrainList::Translucent {
|
||||
rp.set_vertex_buffer(0, vertices.slice(..));
|
||||
rp.draw(0..6, 0..1);
|
||||
}
|
||||
},
|
||||
);
|
||||
for p in pixels {
|
||||
assert!(
|
||||
p[..3]
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.all(|(&a, b)| a.abs_diff(b) <= 2),
|
||||
"buffer {buffer}, {stages} deferred stages, blended {blended}, frame {frame}: {p:?}, expected {expected:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user