diff --git a/crates/horizon-client-render/src/gfx/pack_post.rs b/crates/horizon-client-render/src/gfx/pack_post.rs index d1da190e61..1430bb38cb 100644 --- a/crates/horizon-client-render/src/gfx/pack_post.rs +++ b/crates/horizon-client-render/src/gfx/pack_post.rs @@ -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, /// What draws translucent terrain (`gbuffers_water` or its fallback), after `deferred`. water: Option, + /// 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, /// 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. diff --git a/crates/horizon-client-render/src/gfx/pack_post/undrawn_terrain_tests.rs b/crates/horizon-client-render/src/gfx/pack_post/undrawn_terrain_tests.rs new file mode 100644 index 0000000000..78bdb4c0f7 --- /dev/null +++ b/crates/horizon-client-render/src/gfx/pack_post/undrawn_terrain_tests.rs @@ -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, ¯os, &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)); +} diff --git a/crates/horizon-shaders/src/post.rs b/crates/horizon-shaders/src/post.rs index 6afaffd9b7..edadc92a77 100644 --- a/crates/horizon-shaders/src/post.rs +++ b/crates/horizon-shaders/src/post.rs @@ -426,6 +426,14 @@ pub struct Post { pub terrain_cutout: Option, /// What draws translucent terrain (water, stained glass) after deferred. pub water: Option, + /// 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, /// 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(