Merge pull request #10502 from arslanarm/shaderpacks/dimension-folder-gbuffers

Shader packs: terrain without a program in the pack is drawn by nothing
This commit is contained in:
Kacper authored and GitHub committed 2026-11-06 09:11:25 +01:00
commit 63db515796
3 files changed
+131 -3

No files matched your search

@@ -40,6 +40,9 @@ mod higher_shadowcolor_tests;
#[cfg(test)]
mod single_side_tests;
#[cfg(test)]
mod undrawn_terrain_tests;
#[cfg(test)]
mod shadow_faces_tests;
@@ -558,6 +561,10 @@ pub struct PackPost {
terrain_cutout: Option<Pass>,
/// What draws translucent terrain (`gbuffers_water` or its fallback), after `deferred`.
water: Option<Pass>,
/// The pack has no program for solid and cutout terrain, or for translucent terrain:
/// nothing draws it (`horizon_shaders::Post::terrain_undrawn`).
terrain_undrawn: bool,
water_undrawn: bool,
/// What draws the sky (`gbuffers_skybasic` or its fallback), before terrain, and its vertices.
sky: Option<Sky>,
/// What draws entities and block entities, after terrain, and their vertices.
@@ -2300,6 +2307,7 @@ impl PackPost {
cull: g.cull,
});
let split = post.terrain.is_some() && post.terrain_cutout.is_some();
let (terrain_undrawn, water_undrawn) = (post.terrain_undrawn, post.water_undrawn);
let mut terrain = post
.terrain
.zip(terrain_geometry.as_ref())
@@ -2846,6 +2854,8 @@ impl PackPost {
terrain,
terrain_cutout,
water,
terrain_undrawn,
water_undrawn,
sky,
entities,
clouds,
@@ -4073,9 +4083,9 @@ impl PackPost {
})
}
/// Whether the pack draws terrain itself.
/// Whether the pack draws terrain itself, or has nothing draw it.
pub fn has_terrain(&self) -> bool {
self.terrain.is_some() || self.terrain_cutout.is_some()
self.terrain.is_some() || self.terrain_cutout.is_some() || self.terrain_undrawn
}
/// Whether the pack's shadow pass draws terrain (`shadow_view`).
@@ -4103,7 +4113,7 @@ impl PackPost {
/// Whether the pack draws translucent terrain itself.
pub fn has_water(&self) -> bool {
self.water.is_some()
self.water.is_some() || self.water_undrawn
}
/// The names of the programs that run, in order.
@@ -0,0 +1,49 @@
//! A pack with no program for terrain draws none (#10455): Iris 1.11.7 drew no terrain, solid
//! or translucent, into any buffer or depth with probe packs of ours having no gbuffers
//! programs. The game hands the pack its terrain, so it doesn't draw it either. The programs
//! here are original and cut down.
use super::destination_tests::Fixture;
use std::io::{Cursor, Write};
const VERTEX: &str = "#version 120\nvoid main() { gl_Position = ftransform(); }\n";
const WHITE: &str =
"#version 120\n/* DRAWBUFFERS:0 */\nvoid main() { gl_FragData[0] = vec4(1.0); }\n";
/// A pack with `programs` (each drawing white into colortex0) loaded for a game that can hand it
/// terrain: whether it takes solid and translucent terrain from the game.
fn takes_terrain(programs: &[&str]) -> Option<(bool, bool)> {
let gpu = Fixture::new()?;
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
for name in programs {
for (ext, source) in [("vsh", VERTEX), ("fsh", WHITE)] {
zip.start_file(
format!("shaders/{name}.{ext}"),
zip::write::SimpleFileOptions::default(),
)
.unwrap();
zip.write_all(source.as_bytes()).unwrap();
}
}
let pack = horizon_shaders::Pack::from_zip_bytes(
"undrawn-terrain",
zip.finish().unwrap().into_inner(),
)
.unwrap();
let mut macros = horizon_shaders::Macros::standard();
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
let inputs = horizon_shaders::GameInputs {
terrain: Some(Default::default()),
..Default::default()
};
let post = horizon_shaders::load_post(&pack, "", &properties, &macros, &inputs);
let post = gpu.try_build(post).ok()?;
Some((post.has_terrain(), post.has_water()))
}
#[test]
fn a_pack_without_terrain_programs_takes_terrain_from_the_game() {
let Some(taken) = takes_terrain(&["composite"]) else {
return;
};
assert_eq!(taken, (true, true));
}
+69
View File
@@ -426,6 +426,14 @@ pub struct Post {
pub terrain_cutout: Option<PostProgram>,
/// What draws translucent terrain (water, stained glass) after deferred.
pub water: Option<PostProgram>,
/// The pack has no program for solid and cutout terrain (none of `gbuffers_terrain`'s
/// chain, nor `_solid` or `_cutout`), or for translucent terrain (none of
/// `gbuffers_water`'s): nothing draws that terrain, into no buffer nor depth. Iris 1.11.7
/// drew no terrain with our probe packs having no gbuffers programs, at the root or in a
/// dimension's folder (#10455). A program that is there but doesn't load still leaves its
/// terrain to the game.
pub terrain_undrawn: bool,
pub water_undrawn: bool,
/// What draws the sky (disc, sunset, stars, void) before terrain.
pub sky: Option<PostProgram>,
/// What draws the sun and moon, between the sunset and the stars.
@@ -639,6 +647,8 @@ pub fn load_post(
terrain: None,
terrain_cutout: None,
water: None,
terrain_undrawn: false,
water_undrawn: false,
sky: None,
sky_textured: None,
entities: None,
@@ -939,6 +949,19 @@ pub fn load_post(
// gbuffers_water falls back to gbuffers_terrain's chain (2.3).
let water = gbuffers(&WATER, Group::Translucent, terrain);
loader.post.notes.extend(lone);
loader.post.terrain_undrawn = !has_solid && !has_cutout;
loader.post.water_undrawn = !WATER.iter().any(|n| loader.has(n));
for (undrawn, part) in [
(loader.post.terrain_undrawn, "terrain"),
(loader.post.water_undrawn, "translucent terrain"),
] {
if undrawn {
loader
.post
.notes
.push(format!("no program for {part}; nothing draws it"));
}
}
loader.post.terrain = solid;
loader.post.water = water.map(|mut p| {
p.blend = blend(properties, &p.name).unwrap_or(Some(TRANSLUCENT_BLEND));
@@ -1673,6 +1696,52 @@ mod tests {
);
}
/// #10455, with probe packs of ours in Iris 1.11.7: a pack (or the dimension folder it
/// uses) with no gbuffers programs drew no terrain, solid or translucent, into any buffer or
/// depth, and the root folder's gbuffers programs didn't draw the Nether's terrain when
/// `world-1` had none. A `gbuffers_water` alone draws translucent terrain only (Horizon's
/// choice; not measured in Iris).
#[test]
fn a_folder_without_terrain_programs_draws_no_terrain() {
const VSH: &str = "#version 120\nvoid main() { gl_Position = ftransform(); }";
const FSH: &str =
"#version 120\n/* DRAWBUFFERS:0 */\nvoid main() { gl_FragData[0] = vec4(1.0); }";
let pack = Pack::from_zip_bytes(
"original-undrawn-terrain",
crate::pack::tests::zip(&[
("shaders/gbuffers_basic.vsh", VSH),
("shaders/gbuffers_basic.fsh", FSH),
("shaders/world-1/composite.vsh", VSH),
("shaders/world-1/composite.fsh", FSH),
("shaders/world1/gbuffers_water.vsh", VSH),
("shaders/world1/gbuffers_water.fsh", FSH),
]),
)
.unwrap();
let inputs = GameInputs {
terrain: Some(GbuffersInputs::default()),
..Default::default()
};
let load = |folder: &str| {
let post = load_post(
&pack,
folder,
&Properties::default(),
&Macros::standard(),
&inputs,
);
(
post.terrain.is_some(),
post.water.is_some(),
post.terrain_undrawn,
post.water_undrawn,
)
};
assert_eq!(load(""), (true, true, false, false));
assert_eq!(load("world-1"), (false, false, true, true));
assert_eq!(load("world1"), (false, true, true, false));
}
#[test]
fn named_dimension_folder_reaches_the_program_loader() {
let pack = Pack::from_zip_bytes(