mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #9084 from arslanarm/shaderpacks/raw-texture-defaults
🤖 Agent: I pushed19b0fd00and27d625d8: merged main and filled `Resources.packed` (#9090), `Interface.initial_values` and `Resources.shadow_linear` in the new test, which no longer compiled against current main. Raw textures defaulting to linear + clamp and PNGs to nearest + repeat match the probe table. Nothing else found.
This commit is contained in:
3 files changed
+198
-14
No files matched your search
@@ -1195,9 +1195,18 @@ fn bindings(
|
||||
let released = pack.released.get();
|
||||
let custom = pack_textures::find(textures, group, &s.ty, &s.name).filter(|&i| {
|
||||
let t = &textures[i];
|
||||
t.stage != pack_textures::stage(group)
|
||||
|| released.0 != t.stage
|
||||
|| horizon_shaders::buffer_index(&t.name).is_none_or(|b| released.1 & 1 << b == 0)
|
||||
// A PNG under `texture.<stage>.<name>` whose name is no sampler of the game's
|
||||
// isn't bound: the sampler reads as if it had no texture. Iris 1.11.7 with a probe
|
||||
// pack of ours (#9064) bound it over `gaux2` and `noisetex` but not over names of its
|
||||
// own, where a raw texture was bound; this rule is a fit to those cases.
|
||||
let unbound = t.png
|
||||
&& t.stage != horizon_shaders::EVERY_STAGE
|
||||
&& source(&s.name, false, true, false) == Source::Dummy;
|
||||
!unbound
|
||||
&& (t.stage != pack_textures::stage(group)
|
||||
|| released.0 != t.stage
|
||||
|| horizon_shaders::buffer_index(&t.name)
|
||||
.is_none_or(|b| released.1 & 1 << b == 0))
|
||||
});
|
||||
let image = pack
|
||||
.images
|
||||
@@ -4779,6 +4788,7 @@ mod tests {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
blur: false,
|
||||
clamp: false,
|
||||
png: false,
|
||||
image: pack_textures::Image::Atlas,
|
||||
}];
|
||||
let interface = horizon_shaders::Interface {
|
||||
@@ -4840,6 +4850,102 @@ mod tests {
|
||||
assert!(reads(Group::Final) == Source::Color(0));
|
||||
}
|
||||
|
||||
/// Iris 1.11.7 (a probe pack of ours, #9064) didn't bind a PNG under
|
||||
/// `texture.<stage>.<name>` when `name` was a name of the pack's own, no sampler of the
|
||||
/// game's; a raw texture there, a PNG over `gaux2` or `noisetex`, and a `customTexture` PNG
|
||||
/// were bound.
|
||||
#[test]
|
||||
fn a_png_under_a_name_of_its_own_is_not_bound() {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else {
|
||||
return;
|
||||
};
|
||||
let Ok((device, _queue)) = pollster::block_on(adapter.request_device(&Default::default()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let texture = |stage: &str, name: &str, png: bool| pack_textures::PackTexture {
|
||||
stage: stage.into(),
|
||||
name: name.into(),
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
blur: !png,
|
||||
clamp: !png,
|
||||
png,
|
||||
image: pack_textures::Image::Atlas,
|
||||
};
|
||||
let textures = [
|
||||
texture("composite", "tp", true),
|
||||
texture("composite", "tr", false),
|
||||
texture("composite", "gaux2", true),
|
||||
texture("composite", "noisetex", true),
|
||||
texture(horizon_shaders::EVERY_STAGE, "cp", true),
|
||||
];
|
||||
let names = ["tp", "tr", "gaux2", "noisetex", "cp"];
|
||||
let interface = horizon_shaders::Interface {
|
||||
uniform_size: 48,
|
||||
uniforms: Vec::new(),
|
||||
samplers: names
|
||||
.iter()
|
||||
.zip(1..)
|
||||
.map(|(name, k)| horizon_shaders::Sampler {
|
||||
name: (*name).into(),
|
||||
ty: "sampler2D".into(),
|
||||
binding: 2 * k - 1,
|
||||
})
|
||||
.collect(),
|
||||
outputs: vec![0],
|
||||
draw: false,
|
||||
images: Vec::new(),
|
||||
buffers: Vec::new(),
|
||||
initial_values: vec![0; 48],
|
||||
};
|
||||
let buffers = pack_images::Buffers::new(
|
||||
&device,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
wgpu::ShaderStages::FRAGMENT,
|
||||
);
|
||||
let stand_ins = std::cell::RefCell::new(Vec::new());
|
||||
let pack = Resources {
|
||||
textures: &textures,
|
||||
images: &[],
|
||||
left_out: &[],
|
||||
buffers: &buffers,
|
||||
indices: None,
|
||||
gpu: Gpu::of(&adapter, device.features()),
|
||||
storable: 0,
|
||||
rgb: 0,
|
||||
released: std::cell::Cell::new(("", 0)),
|
||||
stand_ins: &stand_ins,
|
||||
shadow_linear: 0,
|
||||
packed: [None; BUFFERS],
|
||||
};
|
||||
let (_, samplers, ..) = bindings(
|
||||
device.features(),
|
||||
&interface,
|
||||
"final",
|
||||
Group::Final,
|
||||
&[0],
|
||||
None,
|
||||
&[T::Rgba8Unorm; BUFFERS],
|
||||
None,
|
||||
&mut Vec::new(),
|
||||
&pack,
|
||||
)
|
||||
.expect("the pass is kept");
|
||||
let read: Vec<Source> = samplers.iter().map(|b| b.source).collect();
|
||||
assert!(
|
||||
read == [
|
||||
Source::Dummy,
|
||||
Source::Atlas,
|
||||
Source::Atlas,
|
||||
Source::Atlas,
|
||||
Source::Atlas
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Iris 1.11.7 runs a program whose image no `image.*` gives, and its loads read zero
|
||||
/// (observed with a probe pack). Its pass is kept, the image bound to a stand-in of its
|
||||
/// declared format, zero again before each pass even after one stored into it.
|
||||
|
||||
@@ -17,6 +17,8 @@ pub(super) struct PackTexture {
|
||||
pub(super) sample_type: wgpu::TextureSampleType,
|
||||
pub(super) blur: bool,
|
||||
pub(super) clamp: bool,
|
||||
/// Decoded from a PNG, the pack's or the game's (not a raw dump, not a generated texture).
|
||||
pub(super) png: bool,
|
||||
pub(super) image: Image,
|
||||
}
|
||||
|
||||
@@ -30,7 +32,7 @@ pub(super) fn upload(
|
||||
let features = device.features();
|
||||
let mut out = Vec::new();
|
||||
for t in textures {
|
||||
let (image, dimension, sample_type, blur, clamp) = match t.source {
|
||||
let (image, dimension, sample_type, blur, clamp, png) = match t.source {
|
||||
TextureSource::Image(texture) => {
|
||||
let (view, sample_type) = match create(device, queue, &texture, features) {
|
||||
Ok(created) => created,
|
||||
@@ -50,6 +52,7 @@ pub(super) fn upload(
|
||||
sample_type,
|
||||
texture.blur,
|
||||
texture.clamp,
|
||||
!texture.raw,
|
||||
)
|
||||
}
|
||||
TextureSource::Resource(location) => {
|
||||
@@ -66,7 +69,14 @@ pub(super) fn upload(
|
||||
continue;
|
||||
};
|
||||
let float = wgpu::TextureSampleType::Float { filterable: true };
|
||||
(image, wgpu::TextureViewDimension::D2, float, true, true)
|
||||
(
|
||||
image,
|
||||
wgpu::TextureViewDimension::D2,
|
||||
float,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
};
|
||||
out.push(PackTexture {
|
||||
@@ -76,6 +86,7 @@ pub(super) fn upload(
|
||||
sample_type,
|
||||
blur,
|
||||
clamp,
|
||||
png,
|
||||
image,
|
||||
});
|
||||
}
|
||||
@@ -285,6 +296,7 @@ mod tests {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
blur: false,
|
||||
clamp: false,
|
||||
png: false,
|
||||
image: Image::Lightmap,
|
||||
}
|
||||
}
|
||||
@@ -297,6 +309,7 @@ mod tests {
|
||||
texels,
|
||||
blur: false,
|
||||
clamp: false,
|
||||
raw: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,12 @@ pub struct Texture {
|
||||
/// The internal format the pack asked for.
|
||||
pub format: Format,
|
||||
pub texels: Texels,
|
||||
/// From the `.mcmeta` beside it: linear filtering, and clamping instead of repeating.
|
||||
/// Linear filtering, and clamping instead of repeating: on for a raw texture and off for a
|
||||
/// PNG, unless the `.mcmeta` beside it says otherwise.
|
||||
pub blur: bool,
|
||||
pub clamp: bool,
|
||||
/// A raw dump rather than a PNG.
|
||||
pub raw: bool,
|
||||
}
|
||||
|
||||
/// Texel values, `components` per texel: 1, 2 or 4 (three widen to four, alpha 1).
|
||||
@@ -150,7 +153,8 @@ pub fn resolve_resources(
|
||||
};
|
||||
let meta = read(namespace, &format!("{path}.mcmeta"));
|
||||
let text = meta.as_deref().map(String::from_utf8_lossy);
|
||||
(texture.blur, texture.clamp) = mcmeta(text.as_deref());
|
||||
let (blur, clamp) = mcmeta(text.as_deref());
|
||||
(texture.blur, texture.clamp) = (blur.unwrap_or(false), clamp.unwrap_or(false));
|
||||
t.source = TextureSource::Image(texture);
|
||||
true
|
||||
});
|
||||
@@ -181,17 +185,19 @@ fn source(pack: &Pack, value: &str) -> Result<TextureSource, String> {
|
||||
} else {
|
||||
raw(&bytes, &rest)?
|
||||
};
|
||||
texture.blur = blur;
|
||||
texture.clamp = clamp;
|
||||
// A PNG is nearest and repeating, a raw texture linear and clamped (Iris 1.11.7 with a probe
|
||||
// pack of ours, #9064); each key the `.mcmeta` has overrides its own default only.
|
||||
texture.blur = blur.unwrap_or(texture.raw);
|
||||
texture.clamp = clamp.unwrap_or(texture.raw);
|
||||
Ok(TextureSource::Image(texture))
|
||||
}
|
||||
|
||||
/// `{"texture": {"blur": true, "clamp": false}}`: both off when missing.
|
||||
fn mcmeta(text: Option<&str>) -> (bool, bool) {
|
||||
/// `{"texture": {"blur": true, "clamp": false}}`: each flag, or `None` when it is missing.
|
||||
fn mcmeta(text: Option<&str>) -> (Option<bool>, Option<bool>) {
|
||||
let flag = |key: &str| {
|
||||
text.and_then(|t| t.split_once(&format!("\"{key}\"")))
|
||||
.map(|(_, after)| after.trim_start().trim_start_matches(':').trim_start())
|
||||
.is_some_and(|v| v.starts_with("true"))
|
||||
.map(|v| v.starts_with("true"))
|
||||
};
|
||||
(flag("blur"), flag("clamp"))
|
||||
}
|
||||
@@ -233,6 +239,7 @@ pub fn decode_png(bytes: &[u8]) -> Option<Texture> {
|
||||
texels: Texels::Float(rgba),
|
||||
blur: false,
|
||||
clamp: false,
|
||||
raw: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,8 +354,9 @@ fn raw(bytes: &[u8], words: &[&str]) -> Result<Texture, String> {
|
||||
size,
|
||||
format,
|
||||
texels,
|
||||
blur: false,
|
||||
clamp: false,
|
||||
blur: true,
|
||||
clamp: true,
|
||||
raw: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -543,6 +551,63 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Raw textures are linear and clamped, PNGs nearest and repeating, and each key of a
|
||||
/// `.mcmeta` overrides its own default only (Iris 1.11.7 with a probe pack of ours, #9064).
|
||||
#[test]
|
||||
fn raw_textures_are_linear_and_clamped_unless_their_mcmeta_says_otherwise() {
|
||||
let mut png = Vec::new();
|
||||
{
|
||||
let mut e = png::Encoder::new(&mut png, 1, 1);
|
||||
e.set_color(png::ColorType::Rgba);
|
||||
e.set_depth(png::BitDepth::Eight);
|
||||
e.write_header()
|
||||
.unwrap()
|
||||
.write_image_data(&[1, 2, 3, 4])
|
||||
.unwrap();
|
||||
}
|
||||
let texel = [1u8, 2, 3, 4];
|
||||
let files = [
|
||||
("shaders/a.dat", texel.as_slice()),
|
||||
("shaders/b.dat", texel.as_slice()),
|
||||
("shaders/b.dat.mcmeta", b"{\"texture\": {\"blur\": true}}"),
|
||||
("shaders/c.dat", texel.as_slice()),
|
||||
("shaders/c.dat.mcmeta", b"{\"texture\": {\"clamp\": false}}"),
|
||||
("shaders/d.dat", texel.as_slice()),
|
||||
("shaders/d.dat.mcmeta", b"{\"texture\": {\"blur\": false}}"),
|
||||
("shaders/p.png", png.as_slice()),
|
||||
("shaders/q.png", png.as_slice()),
|
||||
("shaders/q.png.mcmeta", b"{\"texture\": {\"blur\": true}}"),
|
||||
];
|
||||
let pack = Pack::from_zip_bytes("P.zip", zip(&files)).unwrap();
|
||||
let raw = "TEXTURE_2D RGBA8 1 1 RGBA UNSIGNED_BYTE";
|
||||
let properties = format!(
|
||||
"customTexture.a = a.dat {raw}\ncustomTexture.b = b.dat {raw}\n\
|
||||
texture.composite.c = c.dat {raw}\ntexture.composite.d = d.dat {raw}\n\
|
||||
customTexture.p = p.png\ntexture.composite.gaux2 = q.png\n"
|
||||
);
|
||||
let (textures, notes) =
|
||||
load_textures(&pack, &Properties::parse(&properties, &Macros::standard()));
|
||||
assert!(notes.is_empty(), "{notes:?}");
|
||||
let found: Vec<(&str, bool, bool)> = textures
|
||||
.iter()
|
||||
.map(|t| match &t.source {
|
||||
TextureSource::Image(i) => (t.name.as_str(), i.blur, i.clamp),
|
||||
s => panic!("{s:?}"),
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
found,
|
||||
[
|
||||
("a", true, true),
|
||||
("b", true, true),
|
||||
("c", true, false),
|
||||
("d", false, true),
|
||||
("p", false, false),
|
||||
("gaux2", true, false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// A texture may name one of the game's (`minecraft:textures/environment/clouds.png`): it is
|
||||
/// read from the game's resources with its `.mcmeta`. The generated lightmap stays for the
|
||||
/// renderer; a missing one, or a location reaching outside the resources, is dropped.
|
||||
|
||||
Reference in new issue
Block a user