mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Merge pull request #11264 from senjinthedragon/hand-translucent
Shader packs: gbuffers_hand_water for translucent held items
This commit is contained in:
3 files changed
+272
-14
No files matched your search
@@ -54,8 +54,12 @@ pub struct PackHand {
|
||||
slots: wgpu::Buffer,
|
||||
pub slot_group: wgpu::BindGroup,
|
||||
stride: u64,
|
||||
/// This frame's runs: first quad, quads, and the dynamic offset of their slot.
|
||||
/// This frame's runs: first quad, quads, and the dynamic offset of their slot. The opaque
|
||||
/// quads come first in the buffer, then the translucent ones (`translucent_runs`), which a
|
||||
/// pack's `gbuffers_hand_water` draws after deferred.
|
||||
pub runs: Vec<(u32, u32, u32)>,
|
||||
pub translucent_runs: Vec<(u32, u32, u32)>,
|
||||
order: Vec<Vertex>,
|
||||
}
|
||||
|
||||
impl PackHand {
|
||||
@@ -120,6 +124,8 @@ impl PackHand {
|
||||
slot_group,
|
||||
stride,
|
||||
runs: Vec::new(),
|
||||
translucent_runs: Vec::new(),
|
||||
order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,17 +172,25 @@ impl PackHand {
|
||||
) {
|
||||
self.quads = (hand.len() / 4) as u32;
|
||||
self.runs.clear();
|
||||
self.translucent_runs.clear();
|
||||
if self.quads == 0 {
|
||||
return;
|
||||
}
|
||||
let split = split(hand, items);
|
||||
self.order = split.order;
|
||||
let mut contents = Vec::new();
|
||||
for (start, quads, id) in runs(self.quads, items) {
|
||||
let slot = (self.runs.len() as u64).min(SLOTS - 1);
|
||||
if slot as usize == self.runs.len() {
|
||||
contents.extend_from_slice(&draw_slot(id));
|
||||
for (i, &(first, quads, id, translucent)) in split.runs.iter().enumerate() {
|
||||
let slot = (i as u64).min(SLOTS - 1);
|
||||
if slot == i as u64 {
|
||||
contents.extend_from_slice(&draw_slot(id, translucent));
|
||||
contents.resize(((slot + 1) * self.stride) as usize, 0);
|
||||
}
|
||||
self.runs.push((start, quads, (slot * self.stride) as u32));
|
||||
let run = (first, quads, (slot * self.stride) as u32);
|
||||
if translucent {
|
||||
self.translucent_runs.push(run);
|
||||
} else {
|
||||
self.runs.push(run);
|
||||
}
|
||||
}
|
||||
queue.write_buffer(&self.slots, 0, &contents);
|
||||
if self.quads > self.capacity {
|
||||
@@ -185,7 +199,7 @@ impl PackHand {
|
||||
Self::vertex_buffer(device, &self.layout, &self.uniform, self.capacity);
|
||||
self.indices = super::pack_entities::quad_indices(device, self.capacity);
|
||||
}
|
||||
queue.write_buffer(&self.vertices, 0, bytemuck::cast_slice(hand));
|
||||
queue.write_buffer(&self.vertices, 0, bytemuck::cast_slice(&self.order));
|
||||
let matrices = [projection, projection.inverse()].map(|m| m.to_cols_array());
|
||||
queue.write_buffer(&self.uniform, 0, bytemuck::cast_slice(&matrices));
|
||||
}
|
||||
@@ -198,11 +212,62 @@ impl PackHand {
|
||||
/// The hand's runs a frame keeps slots for.
|
||||
const SLOTS: u64 = 8;
|
||||
|
||||
/// `HzDraw` of a hand run: `renderStage` the solid hand's, `blockEntityId` -1 as outside block
|
||||
/// entities, and `currentRenderedItemId`.
|
||||
fn draw_slot(id: i32) -> [u8; SLOT as usize] {
|
||||
/// The hand's quads reordered, opaque first and translucent after (each in their order), and
|
||||
/// their runs: first quad, quads, `currentRenderedItemId`, translucent.
|
||||
struct Split {
|
||||
order: Vec<Vertex>,
|
||||
runs: Vec<(u32, u32, i32, bool)>,
|
||||
}
|
||||
|
||||
fn split(hand: &[Vertex], items: &[(u32, i32)]) -> Split {
|
||||
let quads = (hand.len() / 4) as u32;
|
||||
let mut ids = vec![0; quads as usize];
|
||||
for (start, n, id) in runs(quads, items) {
|
||||
ids[start as usize..(start + n) as usize].fill(id);
|
||||
}
|
||||
let kind = |q: usize| translucent_quad(&hand[q * 4..q * 4 + 4]);
|
||||
let opaque: Vec<usize> = (0..quads as usize).filter(|&q| !kind(q)).collect();
|
||||
let clear: Vec<usize> = (0..quads as usize).filter(|&q| kind(q)).collect();
|
||||
let mut out = Split {
|
||||
order: Vec::with_capacity(hand.len()),
|
||||
runs: Vec::new(),
|
||||
};
|
||||
for &q in opaque.iter().chain(&clear) {
|
||||
out.order.extend_from_slice(&hand[q * 4..q * 4 + 4]);
|
||||
}
|
||||
for (translucent, list, base) in [(false, &opaque, 0), (true, &clear, opaque.len())] {
|
||||
let mut i = 0;
|
||||
while i < list.len() {
|
||||
let id = ids[list[i]];
|
||||
let mut n = 1;
|
||||
while i + n < list.len() && ids[list[i + n]] == id {
|
||||
n += 1;
|
||||
}
|
||||
out.runs
|
||||
.push(((base + i) as u32, n as u32, id, translucent));
|
||||
i += n;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A held item's quad drawn from a translucent sheet (`item_render`: its overlay layer).
|
||||
fn translucent_quad(quad: &[Vertex]) -> bool {
|
||||
matches!(
|
||||
quad[0].layer & 0xffff,
|
||||
super::TRANSLUCENT_ITEM_OVERLAY | super::TRANSLUCENT_GLINT_ITEM_OVERLAY
|
||||
)
|
||||
}
|
||||
|
||||
/// `HzDraw` of a hand run: `renderStage` the solid or translucent hand's, `blockEntityId` -1 as
|
||||
/// outside block entities, and `currentRenderedItemId`.
|
||||
fn draw_slot(id: i32, translucent: bool) -> [u8; SLOT as usize] {
|
||||
let mut bytes = [0u8; SLOT as usize];
|
||||
let stage = horizon_shaders::render_stage("HAND_SOLID") as i32;
|
||||
let stage = horizon_shaders::render_stage(if translucent {
|
||||
"HAND_TRANSLUCENT"
|
||||
} else {
|
||||
"HAND_SOLID"
|
||||
}) as i32;
|
||||
bytes[0..4].copy_from_slice(&stage.to_le_bytes());
|
||||
bytes[8..12].copy_from_slice(&(-1i32).to_le_bytes());
|
||||
bytes[12..16].copy_from_slice(&id.to_le_bytes());
|
||||
@@ -233,6 +298,42 @@ fn runs(quads: u32, items: &[(u32, i32)]) -> Vec<(u32, u32, i32)> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The arm, then an item whose middle quad is translucent, then a second item: the opaque
|
||||
/// quads keep their order first, the translucent one comes after them, and each run keeps
|
||||
/// its item's id.
|
||||
#[test]
|
||||
fn translucent_quads_go_after_the_opaque_ones() {
|
||||
let quad = |layer: u32, tag: f32| {
|
||||
[Vertex {
|
||||
layer,
|
||||
pos: [tag, 0.0, 0.0],
|
||||
..bytemuck::Zeroable::zeroed()
|
||||
}; 4]
|
||||
};
|
||||
let glass = super::super::TRANSLUCENT_ITEM_OVERLAY;
|
||||
let hand: Vec<Vertex> = [
|
||||
quad(0, 0.),
|
||||
quad(0, 1.),
|
||||
quad(glass, 2.),
|
||||
quad(0, 3.),
|
||||
quad(0, 4.),
|
||||
]
|
||||
.concat();
|
||||
// The arm is quad 0; item 7 quads 1..4; item 9 quad 4.
|
||||
let split = split(&hand, &[(4, 7), (16, 9)]);
|
||||
let order: Vec<f32> = split.order.chunks(4).map(|q| q[0].pos[0]).collect();
|
||||
assert_eq!(order, [0., 1., 3., 4., 2.]);
|
||||
assert_eq!(
|
||||
split.runs,
|
||||
[
|
||||
(0, 1, 0, false),
|
||||
(1, 2, 7, false),
|
||||
(3, 1, 9, false),
|
||||
(4, 1, 7, true)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Each item's quads draw with its id; geometry before the first item is the arm's (0).
|
||||
#[test]
|
||||
fn runs_split_the_hand_by_item() {
|
||||
|
||||
@@ -614,6 +614,8 @@ pub struct PackPost {
|
||||
/// What draws the first-person hand (`gbuffers_hand` or its fallback), after the opaque
|
||||
/// world, and its vertices.
|
||||
hand: Option<(Pass, super::pack_hand::PackHand)>,
|
||||
/// `gbuffers_hand_water` (or its fallback): the hand's translucent quads, after deferred.
|
||||
hand_water: Option<Pass>,
|
||||
/// The pre-translucent copy: `depthtex1`, or `depthtex2` without a shader hand.
|
||||
depth1: bool,
|
||||
/// A program after the hand reads `depthtex2`: it is copied before the hand.
|
||||
@@ -2586,6 +2588,20 @@ impl PackPost {
|
||||
};
|
||||
build(p, Some(&g)).map(|pass| (pass, geometry))
|
||||
});
|
||||
// Translucent held items, drawn from the hand's own buffer.
|
||||
let hand_water = post.hand_water.and_then(|p| {
|
||||
let (_, geometry) = hand.as_ref()?;
|
||||
let g = Geometry {
|
||||
buffers: &entity_buffers,
|
||||
table: Some(&geometry.layout),
|
||||
depth_write: true,
|
||||
atlas: world.celestial.clone(),
|
||||
material_maps: false,
|
||||
ids: None,
|
||||
cull: false,
|
||||
};
|
||||
build(p, Some(&g))
|
||||
});
|
||||
let weather = post
|
||||
.weather
|
||||
.zip(weather_geometry.as_ref())
|
||||
@@ -2706,6 +2722,9 @@ impl PackPost {
|
||||
if let Some((h, _)) = &hand {
|
||||
notes.push(format!("the hand draws with {}", h.name));
|
||||
}
|
||||
if let Some(h) = &hand_water {
|
||||
notes.push(format!("translucent held items draw with {}", h.name));
|
||||
}
|
||||
if let Some([w, _]) = &weather {
|
||||
notes.push(format!("rain and snow draw with {}", w.name));
|
||||
}
|
||||
@@ -2759,6 +2778,7 @@ impl PackPost {
|
||||
.chain(&lines)
|
||||
.chain(weather.iter().flatten())
|
||||
.chain(hand.iter().map(|h| &h.0))
|
||||
.chain(&hand_water)
|
||||
};
|
||||
let shadow_targets = shadow_formats.map(|f| {
|
||||
let colors = every_pass().fold(0u16, |m, p| {
|
||||
@@ -2812,6 +2832,7 @@ impl PackPost {
|
||||
.chain(&water)
|
||||
.chain(weather.iter().take(1).flatten())
|
||||
.chain(hand.iter().map(|h| &h.0))
|
||||
.chain(&hand_water)
|
||||
.find(touched)
|
||||
.map(|_| true)
|
||||
.or_else(|| passes.iter().find(touched).map(|_| true));
|
||||
@@ -2991,6 +3012,7 @@ impl PackPost {
|
||||
weather_draw: None,
|
||||
weather_generation: 0,
|
||||
hand,
|
||||
hand_water,
|
||||
depth1,
|
||||
depth2,
|
||||
shadow_targets,
|
||||
@@ -3969,8 +3991,14 @@ impl PackPost {
|
||||
rp.set_bind_group(1, &geometry.group, &[]);
|
||||
rp.set_vertex_buffer(0, geometry.vertices());
|
||||
rp.set_index_buffer(geometry.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
// The arm and each held item: their own `currentRenderedItemId`.
|
||||
for &(first, quads, offset) in &geometry.runs {
|
||||
// The arm and each held item: their own `currentRenderedItemId`. Translucent
|
||||
// quads wait for `gbuffers_hand_water` when the pack has one.
|
||||
let late = self.hand_water.is_some();
|
||||
let runs = geometry
|
||||
.runs
|
||||
.iter()
|
||||
.chain(geometry.translucent_runs.iter().filter(|_| !late));
|
||||
for &(first, quads, offset) in runs {
|
||||
if pass.draw {
|
||||
rp.set_bind_group(2, &geometry.slot_group, &[offset]);
|
||||
}
|
||||
@@ -4097,9 +4125,53 @@ impl PackPost {
|
||||
terrain,
|
||||
);
|
||||
self.draw_entities(device, queue, encoder, size, true);
|
||||
self.draw_hand_water(device, queue, encoder, size);
|
||||
self.draw_weather(device, queue, encoder, size);
|
||||
}
|
||||
|
||||
/// The hand's translucent quads (uploaded by `draw_hand` this frame), with
|
||||
/// `gbuffers_hand_water`.
|
||||
fn draw_hand_water(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
size: [u32; 2],
|
||||
) {
|
||||
let Some(mut pass) = self.hand_water.take() else {
|
||||
return;
|
||||
};
|
||||
let hand = self.hand.take();
|
||||
if let Some((_, geometry)) = hand
|
||||
.as_ref()
|
||||
.filter(|(_, g)| g.quads > 0 && !g.translucent_runs.is_empty())
|
||||
{
|
||||
let group = self.bind(device, queue, encoder, &mut pass, size);
|
||||
{
|
||||
let mut rp = self.begin(encoder, &pass);
|
||||
rp.set_pipeline(pass.render());
|
||||
rp.set_bind_group(0, &group, &[]);
|
||||
if pass.buffers
|
||||
&& let Some(g) = &self.buffers.group
|
||||
{
|
||||
rp.set_bind_group(4, g, &[]);
|
||||
}
|
||||
rp.set_bind_group(1, &geometry.group, &[]);
|
||||
rp.set_vertex_buffer(0, geometry.vertices());
|
||||
rp.set_index_buffer(geometry.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for &(first, quads, offset) in &geometry.translucent_runs {
|
||||
if pass.draw {
|
||||
rp.set_bind_group(2, &geometry.slot_group, &[offset]);
|
||||
}
|
||||
rp.draw_indexed(first * 6..(first + quads) * 6, 0, 0..1);
|
||||
}
|
||||
}
|
||||
self.mips_valid &= !pass.written;
|
||||
}
|
||||
self.hand = hand;
|
||||
self.hand_water = Some(pass);
|
||||
}
|
||||
|
||||
/// The rain, then the snow, `set_weather` gave for this frame, with `gbuffers_weather`.
|
||||
fn draw_weather(
|
||||
&mut self,
|
||||
@@ -4275,6 +4347,7 @@ impl PackPost {
|
||||
.chain(&mut self.lines)
|
||||
.chain(self.weather.iter_mut().flatten())
|
||||
.chain(self.hand.iter_mut().map(|h| &mut h.0))
|
||||
.chain(&mut self.hand_water)
|
||||
.chain(&mut self.shadow_terrain)
|
||||
.chain(&mut self.shadow_water)
|
||||
.chain(&mut self.shadow_comp);
|
||||
|
||||
@@ -185,6 +185,15 @@ const HAND: [&str; 4] = [
|
||||
"gbuffers_basic",
|
||||
];
|
||||
|
||||
/// `gbuffers_hand_water` and its fallbacks (2.3): translucent held items, after deferred.
|
||||
const HAND_WATER: [&str; 5] = [
|
||||
"gbuffers_hand_water",
|
||||
"gbuffers_hand",
|
||||
"gbuffers_textured_lit",
|
||||
"gbuffers_textured",
|
||||
"gbuffers_basic",
|
||||
];
|
||||
|
||||
/// `gbuffers_weather` and its fallbacks (2.3).
|
||||
const WEATHER: [&str; 4] = [
|
||||
"gbuffers_weather",
|
||||
@@ -467,6 +476,8 @@ pub struct Post {
|
||||
pub rain_depth: bool,
|
||||
/// What draws the first-person hand and held items.
|
||||
pub hand: Option<PostProgram>,
|
||||
/// What draws their translucent parts (stained glass, ice...), after deferred and blended.
|
||||
pub hand_water: Option<PostProgram>,
|
||||
/// `clouds=off`: no clouds at all, neither the game's nor the pack's program for them.
|
||||
pub clouds_off: bool,
|
||||
/// Some program reads a shadow buffer, or `shadow.enabled=true`: the shadow pass runs (2.5).
|
||||
@@ -671,6 +682,7 @@ pub fn load_post(
|
||||
.get("rain.depth")
|
||||
.is_none_or(|v| v.trim() != "false"),
|
||||
hand: None,
|
||||
hand_water: None,
|
||||
clouds_off: false,
|
||||
shadow_pass: false,
|
||||
shadow: None,
|
||||
@@ -889,6 +901,15 @@ pub fn load_post(
|
||||
p.blend = blend(properties, &p.name).flatten();
|
||||
p
|
||||
});
|
||||
// Translucent held items draw after deferred as translucent terrain does, and blend.
|
||||
let hand_water = inputs
|
||||
.hand
|
||||
.as_ref()
|
||||
.and_then(|i| gbuffers(&HAND_WATER, Group::Translucent, i))
|
||||
.map(|mut p| {
|
||||
p.blend = blend(properties, &p.name).unwrap_or(Some(TRANSLUCENT_BLEND));
|
||||
p
|
||||
});
|
||||
if let Some(terrain) = &inputs.terrain {
|
||||
// Cutout terrain's alpha test, unless `alphaTest.*` says otherwise (3.2): alpha above
|
||||
// 0.5, which `alphaTestRef` reads. Iris 1.11.7 with probe packs of ours (#9307), through
|
||||
@@ -998,6 +1019,7 @@ pub fn load_post(
|
||||
loader.post.lines = lines;
|
||||
loader.post.weather = weather;
|
||||
loader.post.hand = hand;
|
||||
loader.post.hand_water = hand_water;
|
||||
let flag = |key: &str| properties.get(key).map(|v| v.trim() == "true");
|
||||
loader.post.translucent_late = flag("separateEntityDraws").unwrap_or(false);
|
||||
// `after` when the pack has a deferred program, else `mixed`; ours are all translucent, so
|
||||
@@ -1043,6 +1065,7 @@ fn load_shadow(
|
||||
&post.clouds,
|
||||
&post.weather,
|
||||
&post.hand,
|
||||
&post.hand_water,
|
||||
];
|
||||
// A program's compute stages count as its other stages do: Iris 1.11.7 drew the shadow map
|
||||
// for a probe pack of ours whose only reader of it was `prepare.csh` (#10490).
|
||||
@@ -2986,6 +3009,57 @@ blend.composite1.colortex3 = ONE ONE ONE ONE
|
||||
);
|
||||
}
|
||||
|
||||
/// A pack's own `gbuffers_hand_water` draws the translucent held items: after deferred,
|
||||
/// blended, beside its opaque `gbuffers_hand`.
|
||||
#[test]
|
||||
fn translucent_held_items_draw_with_gbuffers_hand_water() {
|
||||
let vsh = "#version 120\nvarying vec2 uv;\nvoid main() {\n gl_Position = ftransform();\n uv = gl_MultiTexCoord0.xy;\n}\n";
|
||||
let fsh = "#version 120\nuniform sampler2D texture;\nvarying vec2 uv;\nvoid main() {\n gl_FragData[0] = texture2D(texture, uv);\n}\n";
|
||||
let pack = Pack::from_zip_bytes(
|
||||
"P.zip",
|
||||
zip(&[
|
||||
("shaders/gbuffers_hand.vsh", vsh),
|
||||
("shaders/gbuffers_hand.fsh", fsh),
|
||||
("shaders/gbuffers_hand_water.vsh", vsh),
|
||||
("shaders/gbuffers_hand_water.fsh", fsh),
|
||||
]),
|
||||
)
|
||||
.unwrap();
|
||||
let hand = crate::GbuffersInputs {
|
||||
declarations: "layout(location = 0) in vec3 hz_v_pos;".into(),
|
||||
body: " hz_Vertex = vec4(hz_v_pos, 1.0);\n hz_Color = vec4(1.0);\n".into(),
|
||||
..Default::default()
|
||||
};
|
||||
let inputs = GameInputs {
|
||||
hand: Some(hand),
|
||||
..Default::default()
|
||||
};
|
||||
let post = load_post(
|
||||
&pack,
|
||||
"",
|
||||
&Properties::default(),
|
||||
&Macros::standard(),
|
||||
&inputs,
|
||||
);
|
||||
let (opaque, translucent) = (post.hand.unwrap(), post.hand_water.unwrap());
|
||||
assert_eq!(
|
||||
(opaque.name.as_str(), opaque.blend),
|
||||
("gbuffers_hand", None)
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
translucent.name.as_str(),
|
||||
translucent.group,
|
||||
translucent.blend
|
||||
),
|
||||
(
|
||||
"gbuffers_hand_water",
|
||||
Group::Translucent,
|
||||
Some(TRANSLUCENT_BLEND)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn water_falls_back_to_terrain_and_blends() {
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/packs/terrain-test");
|
||||
@@ -3072,12 +3146,22 @@ blend.composite1.colortex3 = ONE ONE ONE ONE
|
||||
(clouds.name.as_str(), clouds.blend),
|
||||
("gbuffers_textured", Some(TRANSLUCENT_BLEND))
|
||||
);
|
||||
// The hand falls back to gbuffers_textured and does not blend.
|
||||
// The hand falls back to gbuffers_textured and does not blend; its translucent items
|
||||
// fall back to the same program, after deferred and blended.
|
||||
let hand = post.hand.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
(hand.name.as_str(), hand.group, hand.blend),
|
||||
("gbuffers_textured", Group::Gbuffers, None)
|
||||
);
|
||||
let hand_water = post.hand_water.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
(hand_water.name.as_str(), hand_water.group, hand_water.blend),
|
||||
(
|
||||
"gbuffers_textured",
|
||||
Group::Translucent,
|
||||
Some(TRANSLUCENT_BLEND)
|
||||
)
|
||||
);
|
||||
// Weather falls back to gbuffers_textured, after deferred, blended.
|
||||
let weather = post.weather.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
|
||||
Reference in new issue
Block a user