Compare commits

...
Author SHA1 Message Date
Horizon agentandClaude Opus 5.5 33c7315b8b Custom uniforms: the renderer drives them through horizon-render-pipeline's CustomUniforms
Val and the CustomUniforms trait move to the engine crate; horizon-shaders'
Custom implements it and keeps its parser and evaluator. PackPost holds the
evaluator as a Box<dyn CustomUniforms>: one call a frame, on a pack's path only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 16:51:59 +01:00
5 changed files with 107 additions and 33 deletions

No files matched your search

@@ -690,7 +690,7 @@ pub struct PackPost {
/// `depthtex0` at the screen's centre, read back for `centerDepthSmooth`.
center_depth: Option<super::pack_center_depth::CenterDepth>,
/// `shaders.properties`' custom uniforms, after the frame's in `uniforms.values`.
custom: horizon_shaders::Custom,
custom: Box<dyn horizon_render_pipeline::CustomUniforms>,
atlas: Option<wgpu::TextureView>,
material_maps: Option<[wgpu::TextureView; 2]>,
/// The shared atlas's normal and specular paged maps (`set_paged_maps`).
@@ -2528,17 +2528,15 @@ impl PackPost {
}
// shadowcolor2 to 7 only for a pack that names `HIGHER_SHADOWCOLOR`.
let shadow_colors = (1u16 << post.shadow_colors) - 1;
let mut custom = post.custom;
let custom_names: Vec<(String, String)> = (custom.uniforms())
.map(|(name, ty)| (name.to_owned(), ty.to_owned()))
.collect();
let mut custom: Box<dyn horizon_render_pipeline::CustomUniforms> = Box::new(post.custom);
let custom_names = custom.names();
// A first run finds the names neither the frame nor the pack defines.
custom.eval(0.0, &pack_uniforms::zero);
// Its zeros are no start for `smooth`: the first frame's values are, as in Iris.
custom.restart_smoothing();
if !custom.notes.is_empty() {
notes.push(format!("custom uniforms: {}", custom.notes.join("; ")));
custom.notes.clear();
let custom_notes = custom.take_notes();
if !custom_notes.is_empty() {
notes.push(format!("custom uniforms: {}", custom_notes.join("; ")));
}
let mask =
|flips: &[(u32, bool)]| flips.iter().filter(|f| f.1).fold(0u16, |m, f| m | 1 << f.0);
@@ -5394,7 +5392,7 @@ impl PackPost {
self.uniforms.update(frame, inputs, size);
// `smooth` starts on the first frame that knows the camera's biome.
self.custom.world_frame(inputs.biome_known);
self.uniforms.eval_custom(&mut self.custom);
self.uniforms.eval_custom(self.custom.as_mut());
if self.targets.is_none() {
return;
}
@@ -28,7 +28,7 @@
//! [-w, w], and the depth buffers packs read hold GL window depth.
use glam::{DMat4, DVec3, Mat4, Vec3, Vec4};
use horizon_render_pipeline::Shadow;
use horizon_render_pipeline::{CustomUniforms, Shadow, Val};
use super::Frame;
@@ -1050,8 +1050,8 @@ impl FrameUniforms {
impl FrameUniforms {
/// Evaluates the pack's custom uniforms with this frame's values and puts them after
/// `NAMES`' values, in `custom.uniforms()` order.
pub fn eval_custom(&mut self, custom: &mut horizon_shaders::Custom) {
/// `NAMES`' values, in `custom.names()` order.
pub fn eval_custom(&mut self, custom: &mut dyn CustomUniforms) {
if let Some(tick) = self.custom_restart {
custom.restart_at(tick);
}
@@ -1061,8 +1061,8 @@ impl FrameUniforms {
let v = values.get(index(name)?)?;
let n = components(name);
Some(match *v {
Value::F(f) => horizon_shaders::Val::vector(&f.map(f64::from)[..n]),
Value::I(i) => horizon_shaders::Val::vector(&i.map(f64::from)[..n.min(3)]),
Value::F(f) => Val::vector(&f.map(f64::from)[..n]),
Value::I(i) => Val::vector(&i.map(f64::from)[..n.min(3)]),
})
};
let dt = match index("frameTime").and_then(|i| values.get(i)) {
@@ -1144,9 +1144,9 @@ fn components(name: &str) -> usize {
/// What `name` gives custom uniforms before the first frame: zeros, at its size, so their
/// shapes check out as on a frame (#9086).
pub fn zero(name: &str) -> Option<horizon_shaders::Val> {
pub fn zero(name: &str) -> Option<Val> {
index(name)?;
Some(horizon_shaders::Val::vector(&[0.0; 16][..components(name)]))
Some(Val::vector(&[0.0; 16][..components(name)]))
}
/// The position of `name` in [`NAMES`].
@@ -0,0 +1,53 @@
//! A pack's custom uniforms: values the pack computes on the CPU each frame from the game's own
//! uniforms, which the renderer then hands its programs after the frame's. The renderer only
//! drives the evaluator ([`CustomUniforms`]); the pack format supplies it (`horizon-shaders`'
//! `Custom` reads `shaders.properties`' expressions).
/// A value: up to 16 components (matrices only as names, for `m.column.row`).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Val {
pub v: [f64; 16],
/// Components: 1 for a scalar.
pub n: usize,
}
impl Val {
pub fn scalar(x: f64) -> Self {
let mut v = [0.0; 16];
v[0] = x;
Self { v, n: 1 }
}
pub fn vector(c: &[f64]) -> Self {
let mut v = [0.0; 16];
v[..c.len()].copy_from_slice(c);
Self { v, n: c.len() }
}
}
/// What the renderer asks of a pack's custom uniforms. Called once per frame of a pack, never on
/// vanilla's path.
pub trait CustomUniforms: Send + Sync {
/// The uniforms' names and declared types, in the order [`CustomUniforms::eval`] returns
/// their values.
fn names(&self) -> Vec<(String, String)>;
/// Evaluates everything for a frame `dt` seconds after the last; `builtin` gives the values
/// of the game's uniforms and constants. Returns the uniforms' values, in `names` order.
fn eval(&mut self, dt: f64, builtin: &dyn Fn(&str) -> Option<Val>) -> Vec<Val>;
/// Forgets what smoothing keeps: each smoothed value starts again from its next value.
fn restart_smoothing(&mut self);
/// Called before each frame's `eval`, with whether the camera's biome is known.
fn world_frame(&mut self, biome_known: bool);
/// Forgets the world's frames, as a pack just loaded has seen none.
fn forget_world(&mut self);
/// Restarts smoothing and randomness for a frame of the world's tick `tick`, so a frame
/// drawn again at that tick evaluates the same.
fn restart_at(&mut self, tick: u64);
/// The problems found so far (expressions that did not parse, unknown names), emptied.
fn take_notes(&mut self) -> Vec<String>;
}
@@ -5,6 +5,7 @@
//! `shaders/` packs). No wgpu and no pack file here.
//!
//! - [`config`]: the colortex buffers and the shadow map: formats, clearing, mipmaps, settings.
//! - [`custom`]: the pack's custom uniforms, which the renderer evaluates each frame.
//! - [`program`]: a translated program (naga modules and their interface) and the vertex
//! inputs the renderer offers it.
//! - [`textures`]: the custom textures the programs read; [`RENDER_STAGES`]: the stages they draw in.
@@ -14,6 +15,7 @@
mod api;
pub mod config;
pub mod custom;
pub mod inputs;
pub mod program;
mod render_stage;
@@ -25,6 +27,7 @@ pub use config::{
BUFFERS, Buffer, Config, Format, HALF_LIVES, Kind, SHADOW_COLORS, Shadow, buffer_index,
shadow_color,
};
pub use custom::{CustomUniforms, Val};
pub use inputs::Frame;
pub use program::{
AlphaTest, ComputeProgram, GbuffersInputs, GeometryInputs, Interface, Program, WATER_MASK,
+37 -17
View File
@@ -24,6 +24,8 @@
use std::collections::{HashMap, HashSet};
use horizon_render_pipeline::custom::CustomUniforms;
use crate::Properties;
/// The seconds one unit of a `smooth` fade time lasts. Fitted to Iris 1.11.7 with a probe pack of
@@ -36,25 +38,17 @@ use crate::Properties;
/// unit (`pack_uniforms.rs` `SECONDS_A_TICK`).
const FADE_UNIT: f64 = 0.1;
/// A value: up to 16 components (matrices only as names, for `m.column.row`).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Val {
pub v: [f64; 16],
/// Components: 1 for a scalar.
pub n: usize,
pub use horizon_render_pipeline::custom::Val;
/// The evaluator's arithmetic on [`Val`].
trait ValOps {
fn x(&self) -> f64;
fn truth(&self) -> bool;
fn zip(self, o: Val, f: impl Fn(f64, f64) -> f64) -> Val;
fn map(self, f: impl Fn(f64) -> f64) -> Val;
}
impl Val {
pub fn scalar(x: f64) -> Self {
let mut v = [0.0; 16];
v[0] = x;
Self { v, n: 1 }
}
pub fn vector(c: &[f64]) -> Self {
let mut v = [0.0; 16];
v[..c.len()].copy_from_slice(c);
Self { v, n: c.len() }
}
impl ValOps for Val {
fn x(&self) -> f64 {
self.v[0]
}
@@ -588,6 +582,32 @@ impl Custom {
}
}
impl CustomUniforms for Custom {
fn names(&self) -> Vec<(String, String)> {
(self.uniforms())
.map(|(name, ty)| (name.to_owned(), ty.to_owned()))
.collect()
}
fn eval(&mut self, dt: f64, builtin: &dyn Fn(&str) -> Option<Val>) -> Vec<Val> {
Custom::eval(self, dt, builtin)
}
fn restart_smoothing(&mut self) {
Custom::restart_smoothing(self);
}
fn world_frame(&mut self, biome_known: bool) {
Custom::world_frame(self, biome_known);
}
fn forget_world(&mut self) {
Custom::forget_world(self);
}
fn restart_at(&mut self, tick: u64) {
Custom::restart_at(self, tick);
}
fn take_notes(&mut self) -> Vec<String> {
std::mem::take(&mut self.notes)
}
}
struct Parser<'a> {
tokens: Vec<String>,
at: usize,