mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #7460 from arslanarm/shaderpacks/composite-scale
🤖 Agent: queued at ec8cebc6a8. A scaled pass loads its attachments instead of clearing them and draws into a whole-pixel viewport, so its buffers keep what lies outside it. Unscaled programs keep [1,0,0] and draw as before. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
This commit is contained in:
2 files changed
+136
-2
No files matched your search
@@ -380,6 +380,9 @@ struct Pass {
|
||||
image_copies: u64,
|
||||
/// The pack's images a compute pass's loads read as they were before it: copied first.
|
||||
snapshots: u64,
|
||||
/// `scale.<program>` (scale, offset x, offset y): the pass draws only that part of its
|
||||
/// buffers and keeps the rest.
|
||||
viewport: Option<[f32; 3]>,
|
||||
}
|
||||
|
||||
impl Pass {
|
||||
@@ -892,6 +895,20 @@ fn target(format: wgpu::TextureFormat) -> Option<wgpu::ColorTargetState> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The viewport of `scale.<program>` (scale, offset x, offset y) on buffers of `size`, in whole
|
||||
/// pixels, at least one and inside them. Buffer rows are GL's, so the offsets count from GL's
|
||||
/// bottom-left corner as they are.
|
||||
fn viewport(size: [u32; 2], [scale, x, y]: [f32; 3]) -> [f32; 4] {
|
||||
let [w, h] = size.map(|n| n as f32);
|
||||
let (x, y) = ((x * w).floor().min(w - 1.0), (y * h).floor().min(h - 1.0));
|
||||
[
|
||||
x,
|
||||
y,
|
||||
(scale * w).floor().clamp(1.0, w - x),
|
||||
(scale * h).floor().clamp(1.0, h - y),
|
||||
]
|
||||
}
|
||||
|
||||
fn attach(
|
||||
view: &wgpu::TextureView,
|
||||
ops: wgpu::Operations<wgpu::Color>,
|
||||
@@ -1555,6 +1572,7 @@ fn build_pass(
|
||||
compute: Vec::new(),
|
||||
sink,
|
||||
geometry: p.geometry_triangles,
|
||||
viewport: (!final_pass && p.scale != [1.0, 0.0, 0.0]).then_some(p.scale),
|
||||
image_copies,
|
||||
snapshots: 0,
|
||||
})
|
||||
@@ -1688,6 +1706,7 @@ fn build_compute(
|
||||
geometry: 0,
|
||||
image_copies,
|
||||
snapshots,
|
||||
viewport: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3990,18 +4009,27 @@ impl PackPost {
|
||||
.map(|t| &t[usize::from(self.main & 1 << i == 0)])
|
||||
};
|
||||
let mut attachments: Vec<Option<wgpu::RenderPassColorAttachment>> = Vec::new();
|
||||
// A scaled pass keeps what its buffers hold outside its viewport.
|
||||
let ops = if pass.viewport.is_some() {
|
||||
wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}
|
||||
} else {
|
||||
overwrite
|
||||
};
|
||||
if final_pass {
|
||||
attachments.push(attach(screen, overwrite));
|
||||
shown = true;
|
||||
} else if pass.sink {
|
||||
attachments.push(attach(&targets.sink, overwrite));
|
||||
attachments.push(attach(&targets.sink, ops));
|
||||
} else {
|
||||
for &(location, buffer) in &pass.writes {
|
||||
let l = location as usize;
|
||||
if attachments.len() <= l {
|
||||
attachments.resize(l + 1, None);
|
||||
}
|
||||
attachments[l] = view(buffer).and_then(|v| attach(v, overwrite));
|
||||
attachments[l] = view(buffer).and_then(|v| attach(v, ops));
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -4018,6 +4046,10 @@ impl PackPost {
|
||||
{
|
||||
rp.set_bind_group(4, g, &[]);
|
||||
}
|
||||
if let Some(scale) = pass.viewport {
|
||||
let [x, y, w, h] = viewport(out, scale);
|
||||
rp.set_viewport(x, y, w, h, 0.0, 1.0);
|
||||
}
|
||||
rp.draw(0..6, 0..1);
|
||||
}
|
||||
self.main ^= pass.flip;
|
||||
@@ -4273,6 +4305,27 @@ mod tests {
|
||||
assert_eq!(&read.slice(..).get_mapped_range().unwrap()[..], &[0; 4]);
|
||||
}
|
||||
|
||||
/// `scale.composite2 = 0.25 0.5 0.25` on a 1280×720 picture: Iris 1.11.7 draws x 640..960
|
||||
/// and GL y 180..360 (a probe pack of our own). Rows are GL's here, so that is rows 180..360.
|
||||
#[test]
|
||||
fn a_scaled_pass_draws_its_part_from_gl_s_corner() {
|
||||
assert_eq!(
|
||||
viewport([1280, 720], [0.25, 0.5, 0.25]),
|
||||
[640.0, 180.0, 320.0, 180.0]
|
||||
);
|
||||
assert_eq!(
|
||||
viewport([1280, 720], [0.5, 0.0, 0.0]),
|
||||
[0.0, 0.0, 640.0, 360.0]
|
||||
);
|
||||
// Whole pixels, at least one, and never past the buffer's edge.
|
||||
assert_eq!(viewport([5, 3], [0.5, 0.0, 0.0]), [0.0, 0.0, 2.0, 1.0]);
|
||||
assert_eq!(viewport([4, 4], [0.01, 0.0, 0.0]), [0.0, 0.0, 1.0, 1.0]);
|
||||
assert_eq!(
|
||||
viewport([100, 100], [1.0, 0.5, 0.75]),
|
||||
[50.0, 75.0, 50.0, 25.0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_formats_map_to_renderable_ones() {
|
||||
let f = |name| texture_format(Format::parse(name).unwrap(), wgpu::Features::empty());
|
||||
|
||||
@@ -226,6 +226,9 @@ pub struct PostProgram {
|
||||
/// A geometry stage folded into the vertex stage (`translate_geometry`): drawn non-indexed,
|
||||
/// six vertices per quad, with this many instances. 0 without one.
|
||||
pub geometry_triangles: u32,
|
||||
/// `scale.<name>`: (scale, offset x, offset y), the part of its buffers a program draws, as
|
||||
/// fractions of their size from GL's bottom-left corner; [1, 0, 0] (all of them) without.
|
||||
pub scale: [f32; 3],
|
||||
}
|
||||
|
||||
/// `size.buffer.<buffer>`: a size of its own, relative to the render size (floats) or in
|
||||
@@ -951,6 +954,7 @@ impl Loader<'_> {
|
||||
mipmaps: 0,
|
||||
compute,
|
||||
geometry_triangles: 0,
|
||||
scale: FULL,
|
||||
});
|
||||
}
|
||||
if !enabled(
|
||||
@@ -1031,6 +1035,7 @@ impl Loader<'_> {
|
||||
Some(PostProgram {
|
||||
buffer_blends: buffer_blends(self.properties, &name),
|
||||
flips: flips(self.properties, &name),
|
||||
scale: scale(self.properties, &name, group),
|
||||
name,
|
||||
group,
|
||||
program: Some(program),
|
||||
@@ -1130,6 +1135,7 @@ impl Loader<'_> {
|
||||
Some(PostProgram {
|
||||
buffer_blends: buffer_blends(self.properties, &name),
|
||||
flips: flips(self.properties, &name),
|
||||
scale: scale(self.properties, &name, group),
|
||||
name,
|
||||
group,
|
||||
program: Some(program),
|
||||
@@ -1270,6 +1276,29 @@ fn buffer_blends(properties: &Properties, program: &str) -> Vec<(u32, Option<Ble
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A program drawing all of its buffers.
|
||||
const FULL: [f32; 3] = [1.0, 0.0, 0.0];
|
||||
|
||||
/// `scale.<program>=<scale> [<offsetX> <offsetY>]` of a deferred or composite program (OptiFine's
|
||||
/// "Composite render scale"); prepare programs are treated the same, unverified. Iris 1.11.7
|
||||
/// applies the offsets too (a probe pack of our own, composite). A value that is not a scale in (0, 1] with offsets in [0, 1) draws all of them.
|
||||
fn scale(properties: &Properties, program: &str, group: Group) -> [f32; 3] {
|
||||
if !matches!(group, Group::Prepare | Group::Deferred | Group::Composite) {
|
||||
return FULL;
|
||||
}
|
||||
let Some(value) = properties.get(&format!("scale.{program}")) else {
|
||||
return FULL;
|
||||
};
|
||||
let numbers: Option<Vec<f32>> = value.split_whitespace().map(|n| n.parse().ok()).collect();
|
||||
let fraction = |s: f32| s > 0.0 && s <= 1.0;
|
||||
let offset = |o: f32| (0.0..1.0).contains(&o);
|
||||
match numbers.as_deref() {
|
||||
Some(&[s]) if fraction(s) => [s, 0.0, 0.0],
|
||||
Some(&[s, x, y]) if fraction(s) && offset(x) && offset(y) => [s, x, y],
|
||||
_ => FULL,
|
||||
}
|
||||
}
|
||||
|
||||
/// `flip.<program>.<buffer>=<bool>` keys of one program.
|
||||
fn flips(properties: &Properties, program: &str) -> Vec<(u32, bool)> {
|
||||
let prefix = format!("flip.{program}.");
|
||||
@@ -1525,6 +1554,58 @@ mod tests {
|
||||
assert!(post.programs.iter().any(|p| p.name == "composite3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_keys_give_composite_style_programs_a_viewport() {
|
||||
let names = [
|
||||
"deferred",
|
||||
"composite",
|
||||
"composite1",
|
||||
"composite2",
|
||||
"composite3",
|
||||
"final",
|
||||
];
|
||||
let files: Vec<(String, &str)> = names
|
||||
.iter()
|
||||
.flat_map(|n| {
|
||||
[
|
||||
(format!("shaders/{n}.vsh"), VSH),
|
||||
(format!("shaders/{n}.fsh"), FSH),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
let files: Vec<(&str, &str)> = files.iter().map(|(n, t)| (n.as_str(), *t)).collect();
|
||||
let pack = Pack::from_zip_bytes("t", zip(&files)).unwrap();
|
||||
let properties = Properties::parse(
|
||||
"scale.deferred = 0.5\nscale.composite = 0.25 0.5 0.25\nscale.composite1 = 2\n\
|
||||
scale.composite2 = 0.5 0.5\nscale.final = 0.5\n",
|
||||
&Macros::default(),
|
||||
);
|
||||
let post = load_post(
|
||||
&pack,
|
||||
"",
|
||||
&properties,
|
||||
&Macros::standard(),
|
||||
&GameInputs::default(),
|
||||
);
|
||||
let scales: Vec<(&str, [f32; 3])> = post
|
||||
.programs
|
||||
.iter()
|
||||
.map(|p| (p.name.as_str(), p.scale))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
scales,
|
||||
[
|
||||
("deferred", [0.5, 0.0, 0.0]),
|
||||
("composite", [0.25, 0.5, 0.25]),
|
||||
// Not a scale, an offset missing, a program that draws the screen: all of it.
|
||||
("composite1", FULL),
|
||||
("composite2", FULL),
|
||||
("composite3", FULL),
|
||||
("final", FULL),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_declarations_get_their_format_once() {
|
||||
let csh = |decl: &str| {
|
||||
|
||||
Reference in new issue
Block a user