mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 10:39:46 -10:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27740e2e8f | ||
|
|
a7d211a1fc | ||
|
|
7ae28b9d3e | ||
|
|
0b7d3f8105 | ||
|
|
950c368a3c | ||
|
|
a6093def90 | ||
|
|
80aa1a5b06 | ||
|
|
8da02c530e | ||
|
|
a23f327048 |
No files matched your search
Generated
+5
-1
@@ -1856,6 +1856,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "horizon-render-pipeline"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"naga",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-server"
|
||||
@@ -1905,7 +1909,7 @@ dependencies = [
|
||||
name = "horizon-shader-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"horizon-render-pipeline",
|
||||
"naga",
|
||||
"png",
|
||||
"serde",
|
||||
|
||||
@@ -7661,7 +7661,7 @@ impl Renderer {
|
||||
) -> std::result::Result<Vec<String>, String> {
|
||||
let linear = pack
|
||||
.as_ref()
|
||||
.is_some_and(|p| p.0.manifest.api == horizon_shader_api::Api::V0_2);
|
||||
.is_some_and(|p| p.0.manifest.api == horizon_render_pipeline::Api::V0_2);
|
||||
if (linear || self.output.frame.format == LINEAR_FRAME)
|
||||
&& linear != (self.output.frame.format == LINEAR_FRAME)
|
||||
&& !std::mem::replace(&mut self.frame_kept_logged, true)
|
||||
@@ -7714,7 +7714,7 @@ impl Renderer {
|
||||
inputs.declarations.push_str(
|
||||
"layout(set = 5, binding = 0) readonly buffer HzIndices { uint hz_indices[]; };\n",
|
||||
);
|
||||
horizon_shaders::GeometryInputs {
|
||||
horizon_render_pipeline::GeometryInputs {
|
||||
inputs,
|
||||
pull: pack_atlas::PULL.to_owned(),
|
||||
index: "hz_indices[@]".to_owned(),
|
||||
@@ -7737,7 +7737,7 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_terrain_inputs(&mut self) -> Option<horizon_shaders::GbuffersInputs> {
|
||||
fn pack_terrain_inputs(&mut self) -> Option<horizon_render_pipeline::GbuffersInputs> {
|
||||
// Flat draws whenever the device has them (`render` keeps them on with a pack).
|
||||
let flat = self.flat.is_some() && self.flat_on;
|
||||
// Flat draws read each sprite's place from a table: the atlas is stitched as the game's.
|
||||
@@ -10410,7 +10410,7 @@ impl Renderer {
|
||||
self.own_timing.as_mut(),
|
||||
);
|
||||
pass.set_bind_group(0, &self.group, &[]);
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, &shadow.group, &[]);
|
||||
pass.set_bind_group(horizon_render_pipeline::stages::GROUP, &shadow.group, &[]);
|
||||
if flat_frame
|
||||
&& let Some(flat) = &self.flat
|
||||
&& let Some(pipeline) = &shadow.flat
|
||||
@@ -10443,7 +10443,7 @@ impl Renderer {
|
||||
self.own_timing.as_mut(),
|
||||
);
|
||||
pass.set_bind_group(0, &self.group, &[]);
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, &shadow.group, &[]);
|
||||
pass.set_bind_group(horizon_render_pipeline::stages::GROUP, &shadow.group, &[]);
|
||||
if flat_frame
|
||||
&& let Some(flat) = &self.flat
|
||||
&& let Some(pipeline) = &shadow.flat_translucent
|
||||
@@ -10475,7 +10475,7 @@ impl Renderer {
|
||||
.begin_entities(&mut encoder, "Native shader pack entity shadow");
|
||||
pass.set_pipeline(&shadow.entity);
|
||||
pass.set_bind_group(0, &self.entity_group, &[]);
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, &shadow.group, &[]);
|
||||
pass.set_bind_group(horizon_render_pipeline::stages::GROUP, &shadow.group, &[]);
|
||||
pass.set_vertex_buffer(0, self.entity_buffer.slice(..));
|
||||
pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..entity_quads * 6, 0, 0..1);
|
||||
@@ -10696,7 +10696,7 @@ impl Renderer {
|
||||
let gpu_occluding = gpu_frame && self.gpu_culling.as_ref().is_some_and(|g| g.occluding());
|
||||
let pack_frame = |pass: &mut wgpu::RenderPass<'_>| {
|
||||
if let Some((_, group)) = material {
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, group, &[]);
|
||||
pass.set_bind_group(horizon_render_pipeline::stages::GROUP, group, &[]);
|
||||
}
|
||||
};
|
||||
// A pack's solid terrain that calls `set_surface` also writes the surface target, in
|
||||
@@ -12084,7 +12084,7 @@ impl Renderer {
|
||||
&& self
|
||||
.native
|
||||
.as_ref()
|
||||
.is_none_or(|n| n.frame.api == horizon_shader_api::Api::V0_1);
|
||||
.is_none_or(|n| n.frame.api == horizon_render_pipeline::Api::V0_1);
|
||||
match (&self.hdr, &surface_frame) {
|
||||
(Some(hdr), Some(f)) => hdr.present(
|
||||
&self.queue,
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
//! colour. They read the terrain's group 0, the flat draws' group 1 where those run, and the
|
||||
//! pack's frame at group 3.
|
||||
|
||||
use horizon_shader_api::{check::CheckedStage, stages::GROUP};
|
||||
use horizon_render_pipeline::stages::GROUP;
|
||||
use horizon_shader_api::check::CheckedStage;
|
||||
|
||||
use super::native_post::{NativeFrame, present_constants};
|
||||
use super::wgsl_prefix::{TERRAIN, prefixed, section};
|
||||
@@ -369,7 +370,7 @@ fn shader_source(
|
||||
tags: &[(u32, u32)],
|
||||
materials: &[(u32, u32)],
|
||||
(vertex, displaced): (bool, bool),
|
||||
api: horizon_shader_api::Api,
|
||||
api: horizon_render_pipeline::Api,
|
||||
) -> String {
|
||||
format!(
|
||||
"{source}\n// Horizon's terrain (terrain.wgsl)\n{}{}{}{}{}{ENTRIES}{}{}",
|
||||
@@ -398,7 +399,7 @@ fn module(
|
||||
materials: &[(u32, u32)],
|
||||
vertex: bool,
|
||||
displaced: bool,
|
||||
api: horizon_shader_api::Api,
|
||||
api: horizon_render_pipeline::Api,
|
||||
) -> Result<naga::Module, String> {
|
||||
let text = shader_source(&stage.source, tags, materials, (vertex, displaced), api);
|
||||
naga::front::wgsl::parse_str(&text)
|
||||
@@ -417,7 +418,7 @@ impl NativeMaterial {
|
||||
vertex: bool,
|
||||
displaced: Option<&DisplacedShape<'_>>,
|
||||
) -> Result<Self, String> {
|
||||
let writes_surface = horizon_shader_api::stages::calls_set_surface(&stage.source);
|
||||
let writes_surface = horizon_render_pipeline::stages::calls_set_surface(&stage.source);
|
||||
let module = module(
|
||||
stage,
|
||||
shape.sprite_tags,
|
||||
@@ -586,7 +587,7 @@ impl NativeMaterial {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn coverage_test_shader(api: horizon_shader_api::Api) -> String {
|
||||
pub(super) fn coverage_test_shader(api: horizon_render_pipeline::Api) -> String {
|
||||
use horizon_shader_api::{MANIFEST, Pack, StageKind, check};
|
||||
let manifest = format!(
|
||||
"schema = 1\nid = \"coverage\"\nname = \"Coverage\"\nversion = \"1\"\napi = \"{api}\"\n[material]\nfile = \"material.wgsl\"\n[displacement]\n"
|
||||
@@ -829,7 +830,7 @@ mod tests {
|
||||
.into();
|
||||
let report = check(&Pack::from_files(files).unwrap()).unwrap();
|
||||
let source = report.stages[0].source.clone()
|
||||
+ horizon_shader_api::stages::PBR_STUB
|
||||
+ horizon_render_pipeline::stages::PBR_STUB
|
||||
+ super::super::native_post::line_helpers(api)
|
||||
+ entry;
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! One of Horizon's own shader packs while it runs (docs/shaders.md): the frame its stages
|
||||
//! read, its sky, material and post stages, and the timer of their passes.
|
||||
|
||||
use horizon_shader_api::{Report, StageKind};
|
||||
use horizon_render_pipeline::StageKind;
|
||||
use horizon_shader_api::Report;
|
||||
|
||||
use super::displace::Displacement;
|
||||
use super::native_material::{DisplacedShape, NativeMaterial, TerrainShape};
|
||||
@@ -253,7 +254,7 @@ impl NativePack {
|
||||
.transpose()?;
|
||||
// A 0.2 pack always has one: its decode and tonemap run without a chain too.
|
||||
let tonemap = others.iter().find(|s| s.kind == StageKind::Tonemap);
|
||||
let post = (!post.is_empty() || api == horizon_shader_api::Api::V0_2)
|
||||
let post = (!post.is_empty() || api == horizon_render_pipeline::Api::V0_2)
|
||||
.then(|| {
|
||||
let manifest = &report.manifest;
|
||||
NativePost::new(device, post, manifest, constants, format, &frame, tonemap)
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
//! (`NativeFrame`).
|
||||
|
||||
use glam::{Mat4, Vec3};
|
||||
use horizon_shader_api::{
|
||||
Api, Frame as FrameInputs, StageKind, check::CheckedStage, stages::GROUP,
|
||||
};
|
||||
use horizon_render_pipeline::stages::{GROUP, POST_TARGET_BINDING};
|
||||
use horizon_render_pipeline::{Api, Frame as FrameInputs, StageKind};
|
||||
use horizon_shader_api::check::CheckedStage;
|
||||
|
||||
use super::Frame;
|
||||
|
||||
@@ -1014,10 +1014,9 @@ impl NativePost {
|
||||
texture_entry(11, wgpu::TextureSampleType::Uint),
|
||||
];
|
||||
entries.extend(pack_texture_entries(frame.textures.len()));
|
||||
entries
|
||||
.extend((0..targets.len() as u32).map(|j| {
|
||||
texture_entry(horizon_shader_api::stages::POST_TARGET_BINDING + j, float)
|
||||
}));
|
||||
entries.extend(
|
||||
(0..targets.len() as u32).map(|j| texture_entry(POST_TARGET_BINDING + j, float)),
|
||||
);
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Native shader pack post"),
|
||||
entries: &entries,
|
||||
@@ -1346,7 +1345,7 @@ impl NativePost {
|
||||
view
|
||||
};
|
||||
wgpu::BindGroupEntry {
|
||||
binding: horizon_shader_api::stages::POST_TARGET_BINDING + j as u32,
|
||||
binding: POST_TARGET_BINDING + j as u32,
|
||||
resource: wgpu::BindingResource::TextureView(view),
|
||||
}
|
||||
}));
|
||||
@@ -1655,7 +1654,7 @@ pub fn pack_textures(
|
||||
|
||||
/// Layout entries of `count` pack textures and their samplers.
|
||||
pub(super) fn pack_texture_entries(count: usize) -> Vec<wgpu::BindGroupLayoutEntry> {
|
||||
use horizon_shader_api::stages::{TEXTURE_BINDING, TEXTURE_SAMPLER_BINDING};
|
||||
use horizon_render_pipeline::stages::{TEXTURE_BINDING, TEXTURE_SAMPLER_BINDING};
|
||||
(0..count as u32)
|
||||
.flat_map(|i| {
|
||||
[
|
||||
@@ -1684,7 +1683,7 @@ pub(super) fn pack_texture_entries(count: usize) -> Vec<wgpu::BindGroupLayoutEnt
|
||||
pub(super) fn pack_texture_group_entries(
|
||||
textures: &[(wgpu::TextureView, wgpu::Sampler)],
|
||||
) -> Vec<wgpu::BindGroupEntry<'_>> {
|
||||
use horizon_shader_api::stages::{TEXTURE_BINDING, TEXTURE_SAMPLER_BINDING};
|
||||
use horizon_render_pipeline::stages::{TEXTURE_BINDING, TEXTURE_SAMPLER_BINDING};
|
||||
textures
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
//! prelude's `shadow_position` undoes.
|
||||
|
||||
use horizon_render_pipeline::Shadow;
|
||||
use horizon_shader_api::{Api, Frame as FrameInputs, manifest::Shadow as Settings, stages::GROUP};
|
||||
use horizon_render_pipeline::{Api, Frame as FrameInputs, stages::GROUP};
|
||||
use horizon_shader_api::manifest::Shadow as Settings;
|
||||
|
||||
use super::native_material::TerrainShape;
|
||||
use super::wgsl_prefix::{TERRAIN, prefixed, section};
|
||||
@@ -387,7 +388,7 @@ fn entity_source() -> String {
|
||||
format!(
|
||||
"{}@group({GROUP}) @binding(0) var<uniform> frame: Frame;\n{}{ENTITY_ENTRIES}",
|
||||
FrameInputs::WGSL.wgsl(),
|
||||
horizon_shader_api::stages::SHADOW_HELPERS,
|
||||
horizon_render_pipeline::stages::SHADOW_HELPERS,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -486,7 +487,7 @@ fn shader_source(
|
||||
(Some(material), _) => (
|
||||
format!(
|
||||
"{material}{}{table}{}{}{GRID_ENTRIES}",
|
||||
horizon_shader_api::stages::PBR_STUB,
|
||||
horizon_render_pipeline::stages::PBR_STUB,
|
||||
super::displace::RELIEF,
|
||||
super::displace::GRID,
|
||||
),
|
||||
@@ -504,7 +505,7 @@ fn shader_source(
|
||||
format!(
|
||||
"{}@group({GROUP}) @binding(0) var<uniform> frame: Frame;\n{}{UNTAKEN}",
|
||||
FrameInputs::WGSL.wgsl(),
|
||||
horizon_shader_api::stages::SHADOW_HELPERS,
|
||||
horizon_render_pipeline::stages::SHADOW_HELPERS,
|
||||
),
|
||||
super::native_material::DISPLACE_VANILLA,
|
||||
),
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
//! names). It reads the renderer's globals (group 0) and the pack's frame (group 3), and
|
||||
//! draws where vanilla's sky draws; the sun, moon, stars and the dark disc are drawn over it.
|
||||
|
||||
use horizon_shader_api::{check::CheckedStage, stages::GROUP};
|
||||
use horizon_render_pipeline::stages::GROUP;
|
||||
use horizon_shader_api::check::CheckedStage;
|
||||
|
||||
use super::native_post::{NativeFrame, present_constants};
|
||||
use super::wgsl_prefix::{TERRAIN, prefixed, section};
|
||||
@@ -40,7 +41,7 @@ fn vanilla_sky() -> String {
|
||||
}
|
||||
|
||||
/// The sky shader for the pack's sky stage `source`.
|
||||
fn shader_source(source: &str, api: horizon_shader_api::Api) -> String {
|
||||
fn shader_source(source: &str, api: horizon_render_pipeline::Api) -> String {
|
||||
format!(
|
||||
"{source}\n// Horizon's sky (terrain.wgsl)\n{}{}{PACK_ENTRY}",
|
||||
vanilla_sky(),
|
||||
@@ -198,7 +199,7 @@ mod tests {
|
||||
// A pack may use the renderer's names for its own things.
|
||||
let own = "override gain: f32;\nconst PI: f32 = 3.0;\nfn view_ray() -> f32 { return PI; }\n\
|
||||
fn sky(input: SkyInput) -> vec4<f32> { return input.color * gain * view_ray(); }\n";
|
||||
let text = shader_source(&stage(own).source, horizon_shader_api::Api::V0_1);
|
||||
let text = shader_source(&stage(own).source, horizon_render_pipeline::Api::V0_1);
|
||||
let module = naga::front::wgsl::parse_str(&text).unwrap_or_else(|e| {
|
||||
panic!("{}", e.emit_to_string(&text));
|
||||
});
|
||||
@@ -261,7 +262,7 @@ mod tests {
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
None,
|
||||
horizon_shader_api::Api::V0_1,
|
||||
horizon_render_pipeline::Api::V0_1,
|
||||
);
|
||||
queue.write_buffer(
|
||||
&frame.buffer,
|
||||
|
||||
@@ -597,9 +597,9 @@ const FACE: &str = " uint hz_q = (uint(gl_VertexIndex) & ~3u) * 6u;\n \
|
||||
/// quad lands whole in one part. The colour's alpha can't decide it, as with `separateAo` it
|
||||
/// holds each vertex's own ambient occlusion.
|
||||
pub fn terrain_parts(
|
||||
terrain: &horizon_shaders::GbuffersInputs,
|
||||
terrain: &horizon_render_pipeline::GbuffersInputs,
|
||||
lava: &[u16],
|
||||
) -> [horizon_shaders::GbuffersInputs; 2] {
|
||||
) -> [horizon_render_pipeline::GbuffersInputs; 2] {
|
||||
let opaque = crate::mesher::OPAQUE_LAYER;
|
||||
let lava: String = lava
|
||||
.iter()
|
||||
@@ -619,7 +619,7 @@ pub fn terrain_parts(
|
||||
impl PackAtlas {
|
||||
/// The vertex inputs of a gbuffers program drawing terrain (`TerrainVertex`, terrain.wgsl's
|
||||
/// `vs_terrain` or, for `flat` draws, `vs_terrain_flat`), in atlas coordinates.
|
||||
pub fn terrain_inputs(&self, flat: bool) -> horizon_shaders::GbuffersInputs {
|
||||
pub fn terrain_inputs(&self, flat: bool) -> horizon_render_pipeline::GbuffersInputs {
|
||||
let mut declarations = String::from(
|
||||
"layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in uvec2 hz_v_uv;\n\
|
||||
layout(location = 2) in uint hz_v_layer;\nlayout(location = 3) in vec4 hz_v_color;\n\
|
||||
@@ -753,7 +753,7 @@ impl PackAtlas {
|
||||
("vaColor", "hz_Color"),
|
||||
("vaNormal", "hz_Normal"),
|
||||
];
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: attributes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! translator's `DRAW_UNIFORMS`): one slot per render stage of a uniform buffer, picked with a
|
||||
//! dynamic offset at each draw.
|
||||
|
||||
use horizon_shaders::{RENDER_STAGES, render_stage};
|
||||
use horizon_render_pipeline::{RENDER_STAGES, render_stage};
|
||||
|
||||
/// Where a run of `Frame::entities` begins, and what it is (slice 7): the run lasts until the
|
||||
/// next span. Recorded only while a shader pack draws entities, so Horizon's own frames carry
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use horizon_shaders::render_stage;
|
||||
use horizon_render_pipeline::render_stage;
|
||||
|
||||
use super::Vertex;
|
||||
use super::pack_draw::EntitySpan;
|
||||
@@ -31,7 +31,7 @@ pub fn buffers() -> [Option<wgpu::VertexBufferLayout<'static>>; 1] {
|
||||
/// position, the shared atlas's UV, colour, light (block, sky in 16ths) and the world-oriented
|
||||
/// normal. The quad's corners, read from the same vertices at set 1, give the face normal where
|
||||
/// the vertex has none (custom geometry) and `at_tangent` along the texture's u, as for terrain.
|
||||
pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let words = std::mem::size_of::<Vertex>() / 4;
|
||||
let mut declarations = "layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in vec2 hz_v_uv;\n\
|
||||
layout(location = 2) in uint hz_v_overlay;\nlayout(location = 3) in vec4 hz_v_color;\n\
|
||||
@@ -112,7 +112,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
// bright.
|
||||
("horizonTier", "float((hz_v_overlay >> 16u) & 255u)"),
|
||||
];
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: attributes
|
||||
@@ -121,7 +121,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
.collect(),
|
||||
after: String::new(),
|
||||
// Cutout, as vanilla's entity types mostly are (OQ-23): the loader's test at 0.1.
|
||||
alpha_test: Some(horizon_shaders::AlphaTest {
|
||||
alpha_test: Some(horizon_render_pipeline::AlphaTest {
|
||||
op: ">",
|
||||
reference: 0.1,
|
||||
}),
|
||||
@@ -138,7 +138,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
/// inputs of [`inputs`], each vertex read from set 1 (`hz_entity_vertices`), as `Vertex` lays
|
||||
/// it out. The draw's corners walk its quads two triangles each, as `quad_indices` does: corner
|
||||
/// `c` of a draw from vertex `first` (a whole quad) is drawn as vertex `first / 4 * 6 + c`.
|
||||
pub fn geometry_inputs() -> horizon_shaders::GeometryInputs {
|
||||
pub fn geometry_inputs() -> horizon_render_pipeline::GeometryInputs {
|
||||
let words = std::mem::size_of::<Vertex>() / 4;
|
||||
let pull = format!(
|
||||
" uint hz_pw = hz_vertex_index * {words}u;\n \
|
||||
@@ -150,7 +150,7 @@ pub fn geometry_inputs() -> horizon_shaders::GeometryInputs {
|
||||
hz_v_light = uvec4(hz_pl & 255u, (hz_pl >> 8u) & 255u, (hz_pl >> 16u) & 255u, hz_pl >> 24u);\n \
|
||||
hz_v_normal = unpackSnorm4x8(hz_entity_vertices[hz_pw + 8u]);\n"
|
||||
);
|
||||
horizon_shaders::GeometryInputs {
|
||||
horizon_render_pipeline::GeometryInputs {
|
||||
inputs: inputs(),
|
||||
pull,
|
||||
// Quad `c / 6`, corner 0, 1, 2, 0, 2, 3 of it.
|
||||
@@ -163,7 +163,7 @@ pub fn geometry_inputs() -> horizon_shaders::GeometryInputs {
|
||||
/// What `gbuffers_clouds` reads from the clouds' mesh (entity vertices, `vs_cloud`): the
|
||||
/// position moved by the clouds' offset, the colour of the face's shade from their palette,
|
||||
/// full light, the face's normal. Set 1 holds the clouds' uniform (`clouds::Uniform`).
|
||||
pub fn cloud_inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn cloud_inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let declarations = "layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in vec2 hz_v_uv;\n\
|
||||
layout(location = 2) in uint hz_v_overlay;\nlayout(location = 3) in vec4 hz_v_color;\n\
|
||||
layout(location = 4) in uvec4 hz_v_light;\nlayout(location = 5) in vec4 hz_v_normal;\n\
|
||||
@@ -183,7 +183,7 @@ pub fn cloud_inputs() -> horizon_shaders::GbuffersInputs {
|
||||
("vaColor", "hz_Color"),
|
||||
("vaNormal", "hz_Normal"),
|
||||
];
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: attributes
|
||||
|
||||
@@ -17,7 +17,7 @@ const UNIFORM: u64 = 2 * 64;
|
||||
const SLOT: u64 = 32;
|
||||
|
||||
/// What `gbuffers_hand` reads: the entity inputs, and the hand's projection from set 1.
|
||||
pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let mut inputs = super::pack_entities::inputs();
|
||||
// No last frame's hand to measure from: the hand's `at_velocity` reads zero (and its set 1
|
||||
// has no previous vertices).
|
||||
@@ -270,7 +270,7 @@ fn translucent_quad(quad: &[Vertex]) -> bool {
|
||||
/// 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(if translucent {
|
||||
let stage = horizon_render_pipeline::render_stage(if translucent {
|
||||
"HAND_TRANSLUCENT"
|
||||
} else {
|
||||
"HAND_SOLID"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/// width in pixels. The strip's corners 0 and 1 are at the start, 2 and 3 at the end (six
|
||||
/// vertices per edge: pack pipelines draw triangle lists); the
|
||||
/// width is applied in screen space after the pack's transform.
|
||||
pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let declarations =
|
||||
"layout(location = 0) in vec3 hz_l_start;\nlayout(location = 1) in vec3 hz_l_end;\n\
|
||||
layout(location = 2) in vec4 hz_l_argb;\nlayout(location = 3) in float hz_l_width;\n\
|
||||
@@ -41,7 +41,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
if ((hz_l_c & 1) != 0) hz_l_o = -hz_l_o;\n \
|
||||
gl_Position.xy += hz_l_o * gl_Position.w;\n }\n"
|
||||
.to_owned();
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: attributes
|
||||
|
||||
@@ -125,7 +125,7 @@ enum Source {
|
||||
ShadowDepth(usize, bool),
|
||||
/// `shadowcolor0/1` read outside the shadow programs (in them they are `Color`).
|
||||
ShadowColor(usize),
|
||||
/// The boats' water patches' depth (`horizon_shaders::WATER_MASK`), for translucent terrain.
|
||||
/// The boats' water patches' depth (`horizon_render_pipeline::WATER_MASK`), for translucent terrain.
|
||||
WaterMask,
|
||||
}
|
||||
|
||||
@@ -883,7 +883,7 @@ fn fits(ty: &str, sample_type: wgpu::TextureSampleType) -> bool {
|
||||
/// `hz_depth_linear` for a program: 1 for each of shadowtex0, shadowtex1, `shadow` and
|
||||
/// `watershadow` whose depth filters (`linear`, bit n: shadowtex n). `shadow` is shadowtex1 in a
|
||||
/// program that also declares `watershadow` (6.3).
|
||||
fn depth_linear(interface: &horizon_shaders::Interface, linear: u8) -> [f32; 4] {
|
||||
fn depth_linear(interface: &horizon_render_pipeline::Interface, linear: u8) -> [f32; 4] {
|
||||
let watershadow = interface.samplers.iter().any(|s| s.name == "watershadow");
|
||||
[0, 1, usize::from(watershadow), 0].map(|n| f32::from(linear >> n & 1))
|
||||
}
|
||||
@@ -921,7 +921,7 @@ fn source(name: &str, shadow_space: bool, shadow_pass: bool, watershadow: bool)
|
||||
"lightmap" => Source::Lightmap,
|
||||
"normals" => Source::Normals,
|
||||
"specular" => Source::Specular,
|
||||
horizon_shaders::WATER_MASK => Source::WaterMask,
|
||||
horizon_render_pipeline::WATER_MASK => Source::WaterMask,
|
||||
n => horizon_render_pipeline::buffer_index(n).map_or(Source::Dummy, Source::Color),
|
||||
}
|
||||
}
|
||||
@@ -1503,7 +1503,7 @@ fn owned_buffers(
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn bindings(
|
||||
features: wgpu::Features,
|
||||
interface: &horizon_shaders::Interface,
|
||||
interface: &horizon_render_pipeline::Interface,
|
||||
name: &str,
|
||||
group: Group,
|
||||
buffers: &[u32],
|
||||
@@ -1574,7 +1574,7 @@ fn bindings(
|
||||
// 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
|
||||
&& t.stage != horizon_render_pipeline::textures::EVERY_STAGE
|
||||
&& source(&s.name, false, true, false) == Source::Dummy;
|
||||
!unbound
|
||||
&& (t.stage != pack_textures::stage(group)
|
||||
@@ -2283,7 +2283,7 @@ fn build_compute(
|
||||
device: &wgpu::Device,
|
||||
name: String,
|
||||
group: Group,
|
||||
c: horizon_shaders::ComputeProgram,
|
||||
c: horizon_render_pipeline::ComputeProgram,
|
||||
formats: &[wgpu::TextureFormat; BUFFERS],
|
||||
shadow: Option<&[wgpu::TextureFormat; SHADOW_COLORS]>,
|
||||
notes: &mut Vec<String>,
|
||||
@@ -6065,7 +6065,7 @@ mod tests {
|
||||
texture("composite", "tr", false),
|
||||
texture("composite", "gaux2", true),
|
||||
texture("composite", "noisetex", true),
|
||||
texture(horizon_shaders::EVERY_STAGE, "cp", true),
|
||||
texture(horizon_render_pipeline::textures::EVERY_STAGE, "cp", true),
|
||||
];
|
||||
let names = ["tp", "tr", "gaux2", "noisetex", "cp"];
|
||||
let interface = horizon_shaders::Interface {
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(super) const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32];
|
||||
|
||||
/// What `gbuffers_skybasic` reads from these vertices.
|
||||
pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let declarations =
|
||||
"layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in uint hz_v_kind;\n\
|
||||
layout(set = 1, binding = 0) uniform HzSky { mat4 hz_sky_stars; vec4 hz_sky_color; \
|
||||
@@ -90,7 +90,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
hz_MultiTexCoord2 = hz_MultiTexCoord1;\n \
|
||||
hz_Normal = vec3(0.0, 0.0, 1.0);\n"
|
||||
);
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: vec![
|
||||
@@ -111,8 +111,8 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
}
|
||||
|
||||
/// What `gbuffers_skytextured` reads from the sun and moon's vertices.
|
||||
pub fn celestial_inputs() -> horizon_shaders::GbuffersInputs {
|
||||
horizon_shaders::GbuffersInputs {
|
||||
pub fn celestial_inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations:
|
||||
"layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in vec2 hz_v_uv;\n\
|
||||
layout(location = 2) in vec4 hz_v_color;\n"
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
//! which of them a program's sampler reads.
|
||||
|
||||
use horizon_render_pipeline::Kind;
|
||||
use horizon_shaders::{CustomTexture, Group, Texels, Texture, TextureDim, TextureSource};
|
||||
use horizon_render_pipeline::textures::{
|
||||
CustomTexture, Dim as TextureDim, EVERY_STAGE, Texels, Texture, TextureSource,
|
||||
};
|
||||
use horizon_shaders::Group;
|
||||
|
||||
/// What a custom texture is once uploaded: an image of ours, or one the game already has.
|
||||
pub(super) enum Image {
|
||||
@@ -127,16 +130,16 @@ pub(super) fn find(textures: &[PackTexture], group: Group, ty: &str, name: &str)
|
||||
|| buffer.is_some() && horizon_render_pipeline::buffer_index(&t.name) == buffer)
|
||||
});
|
||||
staged.or_else(|| {
|
||||
textures.iter().position(|t| {
|
||||
t.stage == horizon_shaders::EVERY_STAGE && t.dimension == dimension && t.name == name
|
||||
})
|
||||
textures
|
||||
.iter()
|
||||
.position(|t| t.stage == EVERY_STAGE && t.dimension == dimension && t.name == name)
|
||||
})
|
||||
}
|
||||
|
||||
/// The `shaders.properties` key a texture came from, for notes.
|
||||
fn key(stage: &str, name: &str) -> String {
|
||||
match stage {
|
||||
horizon_shaders::EVERY_STAGE => format!("customTexture.{name}"),
|
||||
EVERY_STAGE => format!("customTexture.{name}"),
|
||||
_ => format!("texture.{stage}.{name}"),
|
||||
}
|
||||
}
|
||||
@@ -351,7 +354,7 @@ mod tests {
|
||||
#[test]
|
||||
fn custom_textures_reach_every_program() {
|
||||
use wgpu::TextureViewDimension as D;
|
||||
let every = horizon_shaders::EVERY_STAGE;
|
||||
let every = EVERY_STAGE;
|
||||
let textures = [
|
||||
lightmap(every, "texClouds", D::D3),
|
||||
lightmap("composite", "texClouds", D::D3),
|
||||
|
||||
@@ -2717,7 +2717,7 @@ pub(super) mod initial_history_tests {
|
||||
/// `horizonWaterPatch<k>`'s `k`: patch `k / 2`, its corner and y range (even) or edges (odd).
|
||||
fn water_patch_index(name: &str) -> Option<usize> {
|
||||
let k: usize = name.strip_prefix("horizonWaterPatch")?.parse().ok()?;
|
||||
(k < 2 * horizon_shaders::WATER_PATCHES).then_some(k)
|
||||
(k < 2 * horizon_render_pipeline::WATER_PATCHES).then_some(k)
|
||||
}
|
||||
|
||||
/// The boats' water patches among the frame's entity quads (`WATER_MASK_OVERLAY`), the first
|
||||
@@ -2729,7 +2729,7 @@ pub(super) fn water_patches(entities: &[super::Vertex]) -> Vec<[[f32; 4]; 2]> {
|
||||
let mut points: Vec<[f32; 3]> = Vec::new();
|
||||
let flush = |points: &mut Vec<[f32; 3]>, out: &mut Vec<[[f32; 4]; 2]>| {
|
||||
if let Some(b) = patch_box(points)
|
||||
&& out.len() < horizon_shaders::WATER_PATCHES
|
||||
&& out.len() < horizon_render_pipeline::WATER_PATCHES
|
||||
{
|
||||
out.push(b);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::pack_post::Weather;
|
||||
/// What `gbuffers_weather` reads from a weather vertex: camera-relative position, the UV in
|
||||
/// repeats of the sprite, colour (with vanilla's fade) and light. Weather has no normal; the
|
||||
/// pack sees straight up.
|
||||
pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
pub fn inputs() -> horizon_render_pipeline::GbuffersInputs {
|
||||
let declarations =
|
||||
"layout(location = 0) in vec3 hz_v_pos;\nlayout(location = 1) in vec2 hz_v_uv;\n\
|
||||
layout(location = 2) in uint hz_v_overlay;\nlayout(location = 3) in vec4 hz_v_color;\n\
|
||||
@@ -31,7 +31,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
("vaColor", "hz_Color"),
|
||||
("vaNormal", "hz_Normal"),
|
||||
];
|
||||
horizon_shaders::GbuffersInputs {
|
||||
horizon_render_pipeline::GbuffersInputs {
|
||||
declarations,
|
||||
body,
|
||||
attributes: attributes
|
||||
@@ -40,7 +40,7 @@ pub fn inputs() -> horizon_shaders::GbuffersInputs {
|
||||
.collect(),
|
||||
after: String::new(),
|
||||
// Vanilla's weather drops texels below 0.1, as Horizon's own weather shader does.
|
||||
alpha_test: Some(horizon_shaders::AlphaTest {
|
||||
alpha_test: Some(horizon_render_pipeline::AlphaTest {
|
||||
op: ">",
|
||||
reference: 0.1,
|
||||
}),
|
||||
|
||||
@@ -158,7 +158,7 @@ impl Weather {
|
||||
match native {
|
||||
Some((pipeline, frame)) => {
|
||||
pass.set_pipeline(pipeline);
|
||||
pass.set_bind_group(horizon_shader_api::stages::GROUP, frame, &[]);
|
||||
pass.set_bind_group(horizon_render_pipeline::stages::GROUP, frame, &[]);
|
||||
}
|
||||
None => pass.set_pipeline(&self.pipeline),
|
||||
}
|
||||
@@ -225,7 +225,7 @@ struct HorizonWeatherOut {
|
||||
";
|
||||
|
||||
/// The shader of a native pack's weather stage `source`.
|
||||
fn native_source(source: &str, api: horizon_shader_api::Api) -> String {
|
||||
fn native_source(source: &str, api: horizon_render_pipeline::Api) -> String {
|
||||
format!(
|
||||
"{source}\n// Horizon's weather (entity.wgsl)\n{}{}{NATIVE_ENTRIES}",
|
||||
super::wgsl_prefix::prefixed(&super::entity_shader(), "horizon_entity_"),
|
||||
@@ -245,7 +245,7 @@ pub(super) fn native_pipeline(
|
||||
stage: &horizon_shader_api::check::CheckedStage,
|
||||
constants: &[(String, f64)],
|
||||
) -> Result<wgpu::RenderPipeline, String> {
|
||||
use horizon_shader_api::stages::GROUP;
|
||||
use horizon_render_pipeline::stages::GROUP;
|
||||
let text = native_source(&stage.source, frame.api);
|
||||
let module = naga::front::wgsl::parse_str(&text)
|
||||
.map_err(|e| format!("{}: {}", stage.file, e.emit_to_string(&text)))?;
|
||||
@@ -322,7 +322,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_native_weather_stage_validates() {
|
||||
use horizon_shader_api::{Api, stages::StageKind};
|
||||
use horizon_render_pipeline::{Api, StageKind};
|
||||
for api in [Api::V0_1, Api::V0_2] {
|
||||
let source = StageKind::Weather.prelude(api)
|
||||
+ "fn weather(input: WeatherInput) -> vec4<f32> {\n \
|
||||
|
||||
Generated
+9
-1
@@ -493,11 +493,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-shader-api"
|
||||
name = "horizon-render-pipeline"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"naga",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-shader-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"horizon-render-pipeline",
|
||||
"naga",
|
||||
"png",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -7,6 +7,10 @@ rust-version.workspace = true
|
||||
description = "What an external shader pipeline gives the renderer: its buffers, shadow map and programs. Engine layer; the pack formats that fill it are platform crates"
|
||||
|
||||
[dependencies]
|
||||
# The frame uniform is uploaded as bytes (inputs.rs).
|
||||
bytemuck = { version = "1.24", features = ["derive"] }
|
||||
# A translated program's stages are naga modules (program.rs).
|
||||
naga = "30.0.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,33 @@
|
||||
//! The lines of the WGSL stage interface ([`crate::stages`], [`crate::inputs`]).
|
||||
|
||||
/// A shader API line: the stages, their signatures and the inputs a pack's WGSL may read.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Api {
|
||||
/// Vanilla's gamma values.
|
||||
V0_1,
|
||||
/// Linear light (docs/design/hdr-linear-light.md). Experimental: checked, not yet drawn.
|
||||
V0_2,
|
||||
}
|
||||
|
||||
impl Api {
|
||||
/// Every line this build reads and checks, oldest first.
|
||||
pub const SERVED: [Api; 2] = [Api::V0_1, Api::V0_2];
|
||||
|
||||
/// The manifest's `api` value.
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Api::V0_1 => "0.1",
|
||||
Api::V0_2 => "0.2",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(line: &str) -> Option<Api> {
|
||||
Self::SERVED.into_iter().find(|a| a.as_str() == line)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Api {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
//! What a pack's WGSL reads: the frame uniform every stage sees, which the
|
||||
//! renderer fills and uploads as the Rust struct below, and the stage input
|
||||
//! and output structs. The WGSL of each is generated from these definitions
|
||||
//! (`Struct::wgsl`), and so is the reference (docs/api/shaders-reference.md).
|
||||
|
||||
/// One field of a WGSL struct, for the prelude and the reference.
|
||||
#[derive(Debug)]
|
||||
pub struct Field {
|
||||
pub name: &'static str,
|
||||
pub ty: &'static str,
|
||||
/// Its meaning, unit and range.
|
||||
pub doc: &'static str,
|
||||
}
|
||||
|
||||
/// A struct a pack's WGSL can name.
|
||||
#[derive(Debug)]
|
||||
pub struct Struct {
|
||||
pub name: &'static str,
|
||||
pub doc: &'static str,
|
||||
pub fields: &'static [Field],
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
pub fn wgsl(&self) -> String {
|
||||
let mut out = format!("struct {} {{\n", self.name);
|
||||
for f in self.fields {
|
||||
out += &format!(" {}: {},\n", f.name, f.ty);
|
||||
}
|
||||
out + "}\n"
|
||||
}
|
||||
}
|
||||
|
||||
/// The WGSL type of a Rust field the renderer uploads.
|
||||
pub trait Wgsl {
|
||||
const TYPE: &'static str;
|
||||
}
|
||||
|
||||
macro_rules! wgsl_types {
|
||||
($($rust:ty => $wgsl:literal),* $(,)?) => {
|
||||
$(impl Wgsl for $rust {
|
||||
const TYPE: &'static str = $wgsl;
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
wgsl_types! {
|
||||
f32 => "f32",
|
||||
u32 => "u32",
|
||||
i32 => "i32",
|
||||
[f32; 2] => "vec2<f32>",
|
||||
[f32; 3] => "vec3<f32>",
|
||||
[f32; 4] => "vec4<f32>",
|
||||
[[f32; 4]; 4] => "mat4x4<f32>",
|
||||
[[f32; 4]; 8] => "array<vec4<f32>, 8>",
|
||||
}
|
||||
|
||||
/// A uniform struct: the Rust type the renderer uploads, and its WGSL. `pad` names a trailing
|
||||
/// field in Rust only, which fills the struct out to WGSL's size (a multiple of its 16-byte
|
||||
/// alignment), since `Pod` allows no implicit padding.
|
||||
macro_rules! uniform {
|
||||
($(#[doc = $doc:literal])* $name:ident {
|
||||
$($(#[doc = $fdoc:literal])* $field:ident: $ty:ty,)*
|
||||
} $(pad $pad:ident: $pad_ty:ty)?) => {
|
||||
$(#[doc = $doc])*
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct $name {
|
||||
$($(#[doc = $fdoc])* pub $field: $ty,)*
|
||||
$(/// Not in the WGSL: zeros up to its size.
|
||||
pub $pad: $pad_ty,)?
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const WGSL: Struct = Struct {
|
||||
name: stringify!($name),
|
||||
doc: concat!($($doc, "\n"),*),
|
||||
fields: &[$(Field {
|
||||
name: stringify!($field),
|
||||
ty: <$ty as Wgsl>::TYPE,
|
||||
doc: concat!($($fdoc),*),
|
||||
}),*],
|
||||
};
|
||||
/// Each field's byte offset, in `WGSL.fields`' order.
|
||||
pub const OFFSETS: &'static [usize] = &[$(std::mem::offset_of!($name, $field)),*];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
uniform! {
|
||||
/// The frame, as `frame` in every stage. Positions and directions are
|
||||
/// in world axes relative to the camera (x east, y up, z south), so they
|
||||
/// stay precise far from the world's origin; depth is reversed (1 near,
|
||||
/// 0 at the far plane and in the sky).
|
||||
Frame {
|
||||
/// Camera-relative world to view space: the camera's rotation.
|
||||
view: [[f32; 4]; 4],
|
||||
/// View to clip space (reversed depth, wgpu's clip space: z 0..1).
|
||||
projection: [[f32; 4]; 4],
|
||||
/// View to camera-relative world space.
|
||||
view_inverse: [[f32; 4]; 4],
|
||||
/// Clip to view space.
|
||||
projection_inverse: [[f32; 4]; 4],
|
||||
/// The camera's world position, in blocks (about a millimetre of precision 10 000 blocks out).
|
||||
camera_position: [f32; 3],
|
||||
/// Seconds since the pack was loaded, wrapping to 0 every hour.
|
||||
time: f32,
|
||||
/// Unit vector toward the sun (below the horizon at night).
|
||||
sun_direction: [f32; 3],
|
||||
/// The time of day, 0..1: 0 sunrise, 0.25 noon, 0.5 sunset, 0.75 midnight.
|
||||
day_time: f32,
|
||||
/// Unit vector toward the moon.
|
||||
moon_direction: [f32; 3],
|
||||
/// Vanilla's GameTime: (game time % 24000 + partial tick) / 24000, 0..1.
|
||||
game_time: f32,
|
||||
/// Vanilla's sky colour this frame, 0..1.
|
||||
sky_color: [f32; 3],
|
||||
/// Rain strength, 0..1.
|
||||
rain: f32,
|
||||
/// Vanilla's fog colour this frame (also the clear colour), 0..1.
|
||||
fog_color: [f32; 3],
|
||||
/// Thunder strength, 0..1.
|
||||
thunder: f32,
|
||||
/// The size of the world's picture, in pixels (the resolution scale applied).
|
||||
resolution: [f32; 2],
|
||||
/// Block and sky light at the camera, 0..1 (level / 15).
|
||||
eye_light: [f32; 2],
|
||||
/// Where vanilla's fog starts, in blocks from the camera.
|
||||
fog_start: f32,
|
||||
/// Where vanilla's fog is full, in blocks.
|
||||
fog_end: f32,
|
||||
/// The render distance, in blocks.
|
||||
render_distance: f32,
|
||||
/// The far plane, in blocks.
|
||||
far: f32,
|
||||
/// Night vision strength, 0..1.
|
||||
night_vision: f32,
|
||||
/// Blindness strength, 0..1.
|
||||
blindness: f32,
|
||||
/// The darkness effect's strength, 0..1.
|
||||
darkness: f32,
|
||||
/// The Brightness option, 0..1 (Moody to Bright).
|
||||
brightness: f32,
|
||||
/// What the camera is in: 0 air, 1 water, 2 lava, 3 powder snow.
|
||||
eye_in: u32,
|
||||
/// 0 the Overworld, -1 the Nether, 1 the End, 2 any other dimension.
|
||||
dimension: i32,
|
||||
/// Frames drawn since the pack was loaded (wraps).
|
||||
frame_index: u32,
|
||||
/// 1 while F1 hides the HUD, else 0.
|
||||
hide_gui: u32,
|
||||
/// What falls in the biome at the camera while it rains: 0 nothing, 1 rain, 2 snow.
|
||||
precipitation: u32,
|
||||
/// The biome at the camera's temperature (vanilla's, -0.7..2.0; it snows below 0.15).
|
||||
temperature: f32,
|
||||
/// The biome at the camera's downfall, 0..1.
|
||||
downfall: f32,
|
||||
/// `rain`, smoothed: rises over about 30 seconds and dries over about 10, 0..1.
|
||||
wetness: f32,
|
||||
/// Last frame's camera-relative world to clip space (its `projection * view`); a point
|
||||
/// at camera-relative `p` now was at `p + camera_motion` relative to last frame's camera.
|
||||
/// This frame's on the first frame and after a gap of over half a second (a pause, a
|
||||
/// world load), when last frame's camera says nothing about this one.
|
||||
previous_view_projection: [[f32; 4]; 4],
|
||||
/// How far the camera moved since last frame, in blocks (0 after such a gap).
|
||||
camera_motion: [f32; 3],
|
||||
/// Seconds since last frame (0 on the first and after such a gap).
|
||||
delta_time: f32,
|
||||
/// `eye_light` eased with a half-life of 2 seconds: it follows the light levels' steps
|
||||
/// without stepping itself (for exposure and indoor fog).
|
||||
eye_light_smooth: [f32; 2],
|
||||
/// 1 while the camera is in water (`eye_in` 1), eased with a half-life of a quarter
|
||||
/// second in and out.
|
||||
in_water: f32,
|
||||
/// The same for lava (`eye_in` 2).
|
||||
in_lava: f32,
|
||||
/// The y of the horizontal water plane `reflection` mirrors the world about, in world
|
||||
/// blocks, while `has_reflection` is 1.
|
||||
water_plane: f32,
|
||||
/// 1 while the material stage's `reflection` holds this frame's water mirror, else 0.
|
||||
has_reflection: u32,
|
||||
/// The size of `reflection`, in pixels: half the world's picture.
|
||||
reflection_resolution: [f32; 2],
|
||||
/// Eight values a native mod hands its own pack each frame (`native_shader_params`,
|
||||
/// docs/native-mods.md): what they mean is that mod's, such as where and when
|
||||
/// something happened in the world. All 0 when no mod sets them.
|
||||
mods: [[f32; 4]; 8],
|
||||
/// Unit vector toward the body casting this frame's shadows: the sun by day, the moon
|
||||
/// by night.
|
||||
light_direction: [f32; 3],
|
||||
/// 1 while `shadow_map` holds this frame's shadow map, else 0.
|
||||
has_shadow: u32,
|
||||
/// Camera-relative world to the shadow camera's clip space, before the distortion
|
||||
/// (orthographic, looking along `-light_direction`; z 0 at the light, 1 away from it).
|
||||
shadow_view_proj: [[f32; 4]; 4],
|
||||
/// Half the side of the square the shadow map covers, in blocks (the manifest's
|
||||
/// `[shadow] distance`); 0 without a shadow map.
|
||||
shadow_distance: f32,
|
||||
/// How strongly the shadow map is distorted toward its centre, 0..1: a point at
|
||||
/// shadow clip `xy` is stored at `xy / (length(xy) * k + 1 - k)`, so texels near the
|
||||
/// camera cover less ground (`shadow_position` applies it).
|
||||
shadow_distortion: f32,
|
||||
/// The shadow map's side, in texels; 0 without a shadow map.
|
||||
shadow_resolution: f32,
|
||||
/// The ground one shadow texel covers at the map's centre, in blocks (for a bias that
|
||||
/// scales with it); 0 without a shadow map.
|
||||
shadow_center_texel: f32,
|
||||
/// 1 while the output is HDR, else 0.
|
||||
hdr: u32,
|
||||
/// The peak over paper white: the brightest value the display shows (Apple's EDR
|
||||
/// headroom, Android's HDR/SDR ratio). 1 in SDR. It changes while the game runs (the
|
||||
/// system's SDR white, a calibration, another display), so read it every frame.
|
||||
headroom: f32,
|
||||
/// Paper white in nits; a nominal 80 in SDR.
|
||||
paper_white: f32,
|
||||
/// The peak brightness in nits; a nominal 80 in SDR, as `paper_white`.
|
||||
peak: f32,
|
||||
/// The display's black in nits (HDR's Black Level); 0 in SDR.
|
||||
black_level: f32,
|
||||
} pad padding: [u32; 3]
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// The fields 0.1's prelude declares: the struct up to `shadow_center_texel`, a prefix of
|
||||
/// the buffer the renderer uploads.
|
||||
pub const FIELDS_0_1: usize = 49;
|
||||
|
||||
/// `Frame` as the line's prelude declares it.
|
||||
pub fn line(api: crate::Api) -> Struct {
|
||||
let fields = match api {
|
||||
crate::Api::V0_1 => &Self::WGSL.fields[..Self::FIELDS_0_1],
|
||||
crate::Api::V0_2 => Self::WGSL.fields,
|
||||
};
|
||||
Struct {
|
||||
fields,
|
||||
..Self::WGSL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! fields {
|
||||
($($name:ident: $ty:literal = $doc:literal,)*) => {
|
||||
&[$(Field { name: stringify!($name), ty: $ty, doc: $doc }),*]
|
||||
};
|
||||
}
|
||||
|
||||
/// The post stage's input: one pixel of the picture.
|
||||
pub const POST_INPUT: Struct = Struct {
|
||||
name: "PostInput",
|
||||
doc: "One pixel of the picture, for `fn post(input: PostInput) -> vec4<f32>`.",
|
||||
fields: fields! {
|
||||
uv: "vec2<f32>" = "Where the pixel is, 0..1 from the top left.",
|
||||
pixel: "vec2<f32>" = "The pixel's centre, in pixels from the top left of what the pass draws (the world's size; the window's for the last pass).",
|
||||
color: "vec4<f32>" = "The previous pass's colour here (the world's picture for the first pass).",
|
||||
depth: "f32" = "The world's depth here, reversed: 0 is the sky.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The material stage's input: one point of a block's surface.
|
||||
pub const MATERIAL_INPUT: Struct = Struct {
|
||||
name: "MaterialInput",
|
||||
doc: "One point of a block's surface, for `fn material(input: MaterialInput) -> Surface`.",
|
||||
fields: fields! {
|
||||
albedo: "vec4<f32>" = "The block texture's colour here (alpha 0..1).",
|
||||
tint: "vec4<f32>" = "The vertex colour: biome tint, face shade and ambient occlusion.",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1, smoothed over the face.",
|
||||
lightmap: "vec3<f32>" = "Vanilla's light colour for that light this frame.",
|
||||
position: "vec3<f32>" = "The point, camera-relative, in blocks.",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block atlas.",
|
||||
translucent: "bool" = "Drawn in the translucent pass (water, stained glass, ice).",
|
||||
tags: "u32" = "The pack's sprite tags this surface's sprite has: bit i for the manifest's i-th `[[tags]]`.",
|
||||
material: "u32" = "The `[[materials]]` id of this surface's sprite (0 when no entry names it).",
|
||||
screen: "vec2<f32>" = "Where the point is on the screen, 0..1 from the top left (`opaque_depth` and `opaque_color` at `screen * frame.resolution`).",
|
||||
face: "u32" = "For a fluid, which face of the block the point is on, `Direction` order: 0 down, 1 up, 2 north, 3 south, 4 west, 5 east. 0 elsewhere.",
|
||||
fluid: "u32" = "0 not a fluid, 1 water, 2 lava.",
|
||||
emission: "f32" = "How strongly the block itself shines, 0..1, by its kind of light: a redstone torch or a candle 7/15, a torch or lava 14/15, glowstone 1; 0 for most blocks.",
|
||||
normal: "vec3<f32>" = "The surface's geometric normal, a unit vector in world axes, facing the camera. A normal within about 3.6 degrees of an axis is that axis exactly, so distant block faces do not shimmer; flowing water's slopes are steeper and keep theirs.",
|
||||
tangent: "vec4<f32>" = "The surface's tangent: xyz the unit direction in world axes along which `uv.x` grows, w the sign (1 or -1) that makes `cross(normal, tangent.xyz) * tangent.w` the direction `uv.y` grows along (down the texture). With `normal` it turns a normal map's normals into world axes.",
|
||||
colored_light: "vec3<f32>" = "The frame's coloured lights at this point (`[[particle_lights]]`, held and dropped items in their `[[lights]]` colour while dynamic lights are on, and mods' visual lights), 0..1, to light the surface with as the pack likes; 0 where none reaches.",
|
||||
displaced: "f32" = "How much of the relief `[displacement]` gave this point as geometry, 0..1: 1 near the camera, where the surface already lies where `displacement` put it (`position` is that point; a parallax effect would count its depth twice), easing to 0 at `[displacement] distance`; always 0 without `[displacement]`.",
|
||||
},
|
||||
};
|
||||
|
||||
/// What the material stage returns.
|
||||
pub const SURFACE: Struct = Struct {
|
||||
name: "Surface",
|
||||
doc: "What `material` returns. Vanilla's own is `albedo * tint * vec4(lightmap, 1.0)`; \
|
||||
Horizon then applies the alpha test (cutout and translucent) and fog.",
|
||||
fields: fields! {
|
||||
color: "vec4<f32>" = "The lit colour, 0..1, with its alpha.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The sky stage's input: one direction of the sky.
|
||||
pub const SKY_INPUT: Struct = Struct {
|
||||
name: "SkyInput",
|
||||
doc: "One direction of the sky, for `fn sky(input: SkyInput) -> vec4<f32>`. The sun, moon, \
|
||||
stars and clouds are drawn over the result.",
|
||||
fields: fields! {
|
||||
direction: "vec3<f32>" = "Unit view ray, camera-relative world axes.",
|
||||
color: "vec4<f32>" = "Vanilla's sky colour along the ray (sky, fog and sunrise).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The input of the material stage's `displacement`, under `[displacement]`.
|
||||
pub const DISPLACEMENT_INPUT: Struct = Struct {
|
||||
name: "DisplacementInput",
|
||||
doc: "One corner of the grid a block face near the camera is cut into, for `fn displacement(input: DisplacementInput) -> f32` (`[displacement]`), which returns how deep below the face, in blocks, the surface lies at `height`.",
|
||||
fields: fields! {
|
||||
position: "vec3<f32>" = "The corner on the flat face, camera-relative, in blocks.",
|
||||
normal: "vec3<f32>" = "The face's normal, a unit vector in world axes.",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block's sprite (as `MaterialInput.uv`).",
|
||||
material: "u32" = "The `[[materials]]` id of the block texture (0 when none matches).",
|
||||
height: "f32" = "A LabPBR height, 0..1 (1 the top of the relief): the normal map's alpha here, or the texture's average height (what the face's edges are pinned to).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The vertex stage's input: one corner of a block's surface.
|
||||
pub const VERTEX_INPUT: Struct = Struct {
|
||||
name: "VertexInput",
|
||||
doc: "One corner of a block's surface, for `fn vertex(input: VertexInput) -> vec3<f32>`, which returns where it is drawn (camera-relative, in blocks; `input.position` where it stays).",
|
||||
fields: fields! {
|
||||
position: "vec3<f32>" = "The corner, camera-relative, in blocks (`+ frame.camera_position` for the world's).",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block's sprite.",
|
||||
material: "u32" = "The `[[materials]]` id of the block's sprite (0 when none matches).",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1.",
|
||||
tint: "vec4<f32>" = "The vertex colour (biome tint, face shade, ambient occlusion); alpha 1.",
|
||||
face: "u32" = "For a fluid, the face of the block the corner is on (as `MaterialInput.face`), else 0.",
|
||||
fluid: "u32" = "1 on a fluid's surface (water or lava), else 0.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The weather stage's input: one point of a rain or snow streak.
|
||||
pub const WEATHER_INPUT: Struct = Struct {
|
||||
name: "WeatherInput",
|
||||
doc: "One point of a rain or snow streak, for `fn weather(input: WeatherInput) -> vec4<f32>`, \
|
||||
which returns the colour blended over the world (alpha its opacity).",
|
||||
fields: fields! {
|
||||
color: "vec4<f32>" = "The rain or snow texture's colour here times the streak's vertex colour (alpha 0.1..1).",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1.",
|
||||
position: "vec3<f32>" = "The point, camera-relative, in blocks.",
|
||||
snow: "bool" = "A snow streak (rain otherwise).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The tonemap stage's input (0.2): one pixel of the window.
|
||||
pub const TONEMAP_INPUT: Struct = Struct {
|
||||
name: "TonemapInput",
|
||||
doc: "One pixel of an output (the window), for `fn tonemap(input: TonemapInput) -> vec3<f32>`, which returns its colour for the output `frame` describes: linear light, 1.0 at paper white, from 0 to `frame.headroom` (Horizon clamps it there).",
|
||||
fields: fields! {
|
||||
uv: "vec2<f32>" = "Where the pixel is, 0..1 from the top left.",
|
||||
pixel: "vec2<f32>" = "The pixel's centre, in pixels from the top left of the output (the window).",
|
||||
color: "vec3<f32>" = "The post chain's picture here (the world's without one), linear light, never negative or NaN.",
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn frame_fields_carry_their_docs() {
|
||||
let wgsl = Frame::WGSL;
|
||||
assert_eq!(wgsl.fields.len(), 54);
|
||||
assert!(wgsl.fields.iter().all(|f| !f.doc.trim().is_empty()));
|
||||
// 0.1's 49 fields, then 0.2's five and the padding to a multiple of 16.
|
||||
assert_eq!(
|
||||
std::mem::size_of::<Frame>(),
|
||||
6 * 64 + 15 * 16 + 8 * 16 + 2 * 16
|
||||
);
|
||||
assert_eq!(Frame::OFFSETS[Frame::FIELDS_0_1], 6 * 64 + 15 * 16 + 8 * 16);
|
||||
assert_eq!(
|
||||
Frame::line(crate::Api::V0_1).fields.len(),
|
||||
Frame::FIELDS_0_1
|
||||
);
|
||||
assert!(
|
||||
wgsl.wgsl()
|
||||
.contains(" camera_position: vec3<f32>,\n time: f32,\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,30 @@
|
||||
//! `shaders/` packs). No wgpu and no pack file here.
|
||||
//!
|
||||
//! - [`config`]: the colortex buffers and the shadow map: formats, clearing, mipmaps, settings.
|
||||
//! - [`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.
|
||||
//! - [`stages`], [`inputs`], [`Api`]: the WGSL stage interface Horizon's own packs are written
|
||||
//! against (`horizon-shader-api` reads and checks those packs): the stages' functions, preludes,
|
||||
//! bindings and entry points, and the frame uniform the renderer fills.
|
||||
|
||||
mod api;
|
||||
pub mod config;
|
||||
pub mod inputs;
|
||||
pub mod program;
|
||||
mod render_stage;
|
||||
pub mod stages;
|
||||
pub mod textures;
|
||||
|
||||
pub use api::Api;
|
||||
pub use config::{
|
||||
BUFFERS, Buffer, Config, Format, HALF_LIVES, Kind, SHADOW_COLORS, Shadow, buffer_index,
|
||||
shadow_color,
|
||||
};
|
||||
pub use inputs::Frame;
|
||||
pub use program::{
|
||||
AlphaTest, ComputeProgram, GbuffersInputs, GeometryInputs, Interface, Program, WATER_MASK,
|
||||
WATER_PATCHES,
|
||||
};
|
||||
pub use render_stage::{RENDER_STAGES, render_stage};
|
||||
pub use stages::StageKind;
|
||||
@@ -0,0 +1,199 @@
|
||||
//! A translated program as the renderer builds its pipelines from it: its naga modules and the
|
||||
//! interface they expect (uniforms, samplers, images, outputs), and what the renderer offers a
|
||||
//! program's vertex stage (`GbuffersInputs`, `GeometryInputs`). `horizon-shaders` translates a
|
||||
//! `shaders/` pack's GLSL into these.
|
||||
|
||||
/// What a program's stages need from the game.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Interface {
|
||||
/// The uniform block at binding 0: its size and each value's name, GLSL type and offset.
|
||||
/// Ours come first: `hz_target` (the target's width, height, 1/width, 1/height), and the
|
||||
/// fixed-function fog the compatibility `gl_Fog` reads (OQ-28): `hz_fog_color` (vanilla's fog
|
||||
/// colour) and `hz_fog` (density, start, end, 1 / (end - start)), then `hz_depth_linear`:
|
||||
/// 1 where `shadowtex0`, `shadowtex1`, `shadow` and `watershadow` (x to w) read through a
|
||||
/// plain sampler filter linearly, 0 where they take the nearest texel.
|
||||
pub uniform_size: u32,
|
||||
pub uniforms: Vec<Uniform>,
|
||||
/// std140 initial contents; runtime values overwrite their members each frame.
|
||||
pub initial_values: Vec<u8>,
|
||||
/// Each sampler: texture at `binding`, sampler object at `binding + 1`.
|
||||
pub samplers: Vec<Sampler>,
|
||||
/// The fragment stage's output locations.
|
||||
pub outputs: Vec<u32>,
|
||||
/// A gbuffers program reads `DRAW_UNIFORMS`: the block at set 2, binding 0, set per draw.
|
||||
pub draw: bool,
|
||||
/// Storage images (`colorimg4`...), after the samplers.
|
||||
pub images: Vec<Image>,
|
||||
/// The SSBOs' bindings in set 4 (`bufferObject.<n>`).
|
||||
pub buffers: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Image {
|
||||
pub name: String,
|
||||
/// `image2D`, `uimage3D`...
|
||||
pub ty: String,
|
||||
/// The format qualifier (`rgba16f`).
|
||||
pub format: String,
|
||||
/// `readonly`, `writeonly`, empty for both, or `atomic` for an image an atomic function
|
||||
/// uses.
|
||||
pub access: String,
|
||||
pub binding: u32,
|
||||
/// A compute program's copy of image `before` as it was before the dispatch, which its
|
||||
/// loads read (see `ordering`).
|
||||
pub before: Option<String>,
|
||||
}
|
||||
|
||||
/// A compute program (`deferred4_a.csh`): its module, what it reads and writes, and how many
|
||||
/// work groups it runs: `workGroups`, or `workGroupsRender` times the target's size over the
|
||||
/// local size (1, 1 when neither is set).
|
||||
#[derive(Clone)]
|
||||
pub struct ComputeProgram {
|
||||
pub module: naga::Module,
|
||||
pub interface: Interface,
|
||||
/// Non-fatal translation diagnostics, located in the authored source.
|
||||
pub notes: Vec<Error>,
|
||||
pub work_groups: Option<[u32; 3]>,
|
||||
pub work_groups_render: [f32; 2],
|
||||
}
|
||||
|
||||
/// The uniforms a gbuffers program reads per draw (5.3's "D"), in a block of their own
|
||||
/// (`HzDraw`, std140: four ints, a vec4; 32 bytes) the game sets for each draw.
|
||||
pub const DRAW_UNIFORMS: [(&str, &str); 5] = [
|
||||
("renderStage", "int"),
|
||||
("entityId", "int"),
|
||||
("blockEntityId", "int"),
|
||||
("currentRenderedItemId", "int"),
|
||||
("entityColor", "vec4"),
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Uniform {
|
||||
pub name: String,
|
||||
/// The type the pack declared (`float`, `ivec2`, `mat4`; `bool` is stored as an `int`).
|
||||
pub ty: String,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Sampler {
|
||||
pub name: String,
|
||||
/// The type the pack declared: `sampler2D`, `sampler2DShadow`, `usampler2D`, `sampler3D`...
|
||||
pub ty: String,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
/// A translation diagnostic at a pack file and line.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Error {
|
||||
pub file: String,
|
||||
pub line: u32,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Program {
|
||||
pub vertex: naga::Module,
|
||||
pub fragment: naga::Module,
|
||||
pub interface: Interface,
|
||||
/// Non-fatal translation diagnostics, located in the authored source.
|
||||
pub notes: Vec<Error>,
|
||||
}
|
||||
|
||||
/// Where the program draws, which decides the conventions of 5.5.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Target {
|
||||
/// Pack buffers: rows stored as GL stores them.
|
||||
Buffers,
|
||||
/// The window (`final`).
|
||||
Window,
|
||||
}
|
||||
|
||||
/// What a gbuffers-style program's vertex stage reads. The game writes it: it knows its vertex
|
||||
/// formats, the translator only places it.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GbuffersInputs {
|
||||
/// GLSL declarations: the vertex inputs and any bindings of set 1.
|
||||
pub declarations: String,
|
||||
/// GLSL statements that set `hz_Vertex`, `hz_Color`, `hz_MultiTexCoord0`,
|
||||
/// `hz_MultiTexCoord1` and `hz_Normal` (the compatibility inputs), run first in `main`.
|
||||
pub body: String,
|
||||
/// The value of each attribute a pack may declare (`mc_Entity`, `vaPosition`...), as a GLSL
|
||||
/// expression of the declared type's size; an attribute not listed reads zero. Terrain
|
||||
/// also supplies `mc_chunkFade`, an implicit float input rather than a declared attribute.
|
||||
pub attributes: Vec<(String, String)>,
|
||||
/// GLSL statements run last in `main`, with `gl_Position` in wgpu's clip space.
|
||||
pub after: String,
|
||||
/// The alpha test applied to output 0 (`gl_FragData[0]`, `gl_FragColor`), the loader's
|
||||
/// (3.2 `alphaTest.<program>`): `None` keeps every fragment.
|
||||
pub alpha_test: Option<AlphaTest>,
|
||||
/// The program may read `currentRenderedItemId` without declaring it, as Iris 1.11.7
|
||||
/// lets the hand's (observed with probe packs; terrain's may not).
|
||||
pub item_id_declared: bool,
|
||||
/// The depth test sees the far plane (window depth 1) unless the program writes
|
||||
/// `gl_FragDepth`, while `gl_FragCoord.z` stays the geometry's own: the sky shows only where
|
||||
/// nothing else drew, and reads its own depth as in Iris 1.11.7.
|
||||
pub far_depth: bool,
|
||||
/// `normals` and `specular` are bound as paged maps of the atlas `texture` reads
|
||||
/// (`horizon-client-render`'s `paged_maps.rs`), not as images of its layout: the program's
|
||||
/// texture calls on them read through `hz_pagedTexture`.
|
||||
pub paged_maps: bool,
|
||||
/// Translucent terrain: a fragment behind the boats' water patches (`horizonWaterMask`, the
|
||||
/// patches' depth, 1 where there are none) is discarded, so no water shows inside a hull
|
||||
/// (`RenderTypes.waterMask`), while the frame's depth stays the pack's own.
|
||||
pub water_mask: bool,
|
||||
/// The shadow pass's translucent terrain: a fragment inside a boat's water patch
|
||||
/// (`horizonWaterPatch*`, camera-relative boxes) is discarded, so no water casts its light
|
||||
/// into a hull. Not with a geometry stage.
|
||||
pub water_patches: bool,
|
||||
}
|
||||
|
||||
/// The sampler of the boats' water patches' depth (`GbuffersInputs::water_mask`).
|
||||
pub const WATER_MASK: &str = "horizonWaterMask";
|
||||
|
||||
/// How many boats' water patches the shadow pass keeps water out of (`GbuffersInputs::water_patches`).
|
||||
pub const WATER_PATCHES: usize = 16;
|
||||
|
||||
/// A fragment stays when its output 0's alpha compares to `reference` as `op` says (a GLSL
|
||||
/// comparison operator, `true` for GL's `ALWAYS`, `false` for `NEVER`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AlphaTest {
|
||||
pub op: &'static str,
|
||||
pub reference: f32,
|
||||
}
|
||||
|
||||
impl AlphaTest {
|
||||
/// `alphaTest.<program>`'s value: `off`, or a GL comparison and a reference.
|
||||
pub fn parse(value: &str) -> Option<Option<Self>> {
|
||||
let mut words = value.split_whitespace();
|
||||
let func = words.next()?;
|
||||
if func == "off" {
|
||||
return Some(None);
|
||||
}
|
||||
let op = match func.trim_start_matches("GL_") {
|
||||
"NEVER" => "false",
|
||||
"LESS" => "<",
|
||||
"EQUAL" => "==",
|
||||
"LEQUAL" => "<=",
|
||||
"GREATER" => ">",
|
||||
"NOTEQUAL" => "!=",
|
||||
"GEQUAL" => ">=",
|
||||
"ALWAYS" => return Some(None),
|
||||
_ => return None,
|
||||
};
|
||||
let reference = words.next()?.parse().ok()?;
|
||||
Some(Some(Self { op, reference }))
|
||||
}
|
||||
}
|
||||
|
||||
/// How a geometry program's vertex invocations reach their triangle's vertices.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GeometryInputs {
|
||||
/// The game's vertex inputs, as `GbuffersInputs`; their `layout(location = n) in`
|
||||
/// declarations become plain globals the pull sets.
|
||||
pub inputs: GbuffersInputs,
|
||||
/// GLSL statements that set the vertex inputs from vertex `hz_vertex_index` (a `uint`).
|
||||
pub pull: String,
|
||||
/// The vertex of the `@`th corner of the draw (a `uint` expression, `hz_indices[@]`).
|
||||
pub index: String,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//! The render stages a pipeline's programs are told they draw in (`renderStage`, and the
|
||||
//! `MC_RENDER_STAGE_*` macros `horizon-shaders` defines from them).
|
||||
|
||||
/// `MC_RENDER_STAGE_*` in the order the stages draw (spec 5.4); each is its index (OQ-7).
|
||||
pub const RENDER_STAGES: [&str; 24] = [
|
||||
"NONE",
|
||||
"SKY",
|
||||
"SUNSET",
|
||||
"CUSTOM_SKY",
|
||||
"SUN",
|
||||
"MOON",
|
||||
"STARS",
|
||||
"VOID",
|
||||
"TERRAIN_SOLID",
|
||||
"TERRAIN_CUTOUT_MIPPED",
|
||||
"TERRAIN_CUTOUT",
|
||||
"ENTITIES",
|
||||
"BLOCK_ENTITIES",
|
||||
"DESTROY",
|
||||
"OUTLINE",
|
||||
"DEBUG",
|
||||
"HAND_SOLID",
|
||||
"TERRAIN_TRANSLUCENT",
|
||||
"TRIPWIRE",
|
||||
"PARTICLES",
|
||||
"CLOUDS",
|
||||
"RAIN_SNOW",
|
||||
"WORLD_BORDER",
|
||||
"HAND_TRANSLUCENT",
|
||||
];
|
||||
|
||||
/// The value of `MC_RENDER_STAGE_<name>` (OQ-7: its index in `RENDER_STAGES`).
|
||||
pub fn render_stage(name: &str) -> u32 {
|
||||
RENDER_STAGES
|
||||
.iter()
|
||||
.position(|s| *s == name)
|
||||
.unwrap_or_else(|| panic!("no render stage {name}")) as u32
|
||||
}
|
||||
@@ -0,0 +1,803 @@
|
||||
//! The stages a WGSL pack can fill, each a WGSL function with a fixed signature
|
||||
//! that the renderer calls inside its own passes, and the WGSL put around a
|
||||
//! pack's files to build a stage: the prelude before (the inputs and the
|
||||
//! resources), the entry points after. `horizon-shader-api` composes a pack's
|
||||
//! files between them.
|
||||
use crate::Api;
|
||||
use crate::inputs::{
|
||||
DISPLACEMENT_INPUT, Frame, MATERIAL_INPUT, POST_INPUT, SKY_INPUT, SURFACE, Struct,
|
||||
TONEMAP_INPUT, VERTEX_INPUT, WEATHER_INPUT,
|
||||
};
|
||||
|
||||
/// The bind group of a pack's resources; groups 0 to 2 are the renderer's.
|
||||
pub const GROUP: u32 = 3;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum StageKind {
|
||||
/// The sky's colour along each view ray, before the sun, moon and stars.
|
||||
Sky,
|
||||
/// The colour of block surfaces (terrain), before the alpha test and fog.
|
||||
Material,
|
||||
/// Where each corner of a block's surface is drawn (waving plants, rolling water), for
|
||||
/// the world and the shadow map.
|
||||
Vertex,
|
||||
/// The colour of rain and snow streaks.
|
||||
Weather,
|
||||
/// A full-screen pass over the world's picture, under the hand and HUD.
|
||||
Post,
|
||||
/// 0.2: the picture for an output (the display), once per pixel of it after the post chain,
|
||||
/// before the camera overlays and the HUD.
|
||||
Tonemap,
|
||||
}
|
||||
|
||||
/// A resource a stage reads, declared by its prelude.
|
||||
#[derive(Debug)]
|
||||
pub struct Resource {
|
||||
pub binding: u32,
|
||||
pub name: &'static str,
|
||||
pub ty: &'static str,
|
||||
pub doc: &'static str,
|
||||
}
|
||||
|
||||
const FRAME: Resource = Resource {
|
||||
binding: 0,
|
||||
name: "frame",
|
||||
ty: "var<uniform> frame: Frame",
|
||||
doc: "The frame (`Frame`).",
|
||||
};
|
||||
|
||||
/// The shadow map at `binding`.
|
||||
const fn shadow_map(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_map",
|
||||
ty: "var shadow_map: texture_depth_2d",
|
||||
doc: "This frame's shadow map: the depth of the solid terrain and entities the light sees, 0 at the light, 1 away from it (and where nothing is), stored at `shadow_position`'s mapping. Valid while `frame.has_shadow` is 1 (the pack asks for it with `[shadow]`), else a 1×1 map that lights everything.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map's comparison sampler at `binding`.
|
||||
const fn shadow_sampler(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_sampler",
|
||||
ty: "var shadow_sampler: sampler_comparison",
|
||||
doc: "Depth comparison, bilinear (2×2 percentage-closer filtering): `textureSampleCompareLevel(shadow_map, shadow_sampler, p.xy, p.z)` is 1 where the point is lit, 0 in shadow.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map with translucent terrain at `binding`.
|
||||
const fn shadow_map_translucent(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_map_translucent",
|
||||
ty: "var shadow_map_translucent: texture_depth_2d",
|
||||
doc: "`shadow_map` with the translucent terrain (water, stained glass, ice) the light passes through drawn in too, read with `shadow_sampler` the same way: a point lit in `shadow_map` but not here lies under such a surface, which `shadow_color` gives the colour of.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour of the translucent terrain in the shadow map at `binding`.
|
||||
const fn shadow_color(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_color",
|
||||
ty: "var shadow_color: texture_2d<f32>",
|
||||
doc: "The texture colour times the vertex colour (rgb: the tint, the face's directional shade and ambient occlusion) of the nearest translucent surface the light passes through at each texel of `shadow_map_translucent`, white where there is none; alpha is 1 on a fluid (water) and the texture's alpha, at most 0.99, on anything else. Read it with `textureLoad` at `shadow_position`'s xy times `textureDimensions(shadow_color)`.",
|
||||
}
|
||||
}
|
||||
|
||||
/// A function the material and post preludes give packs.
|
||||
pub const SHADOW_HELPERS: &str = "\
|
||||
// The shadow map's distortion toward its centre, of a point at shadow clip `xy`.
|
||||
fn shadow_distort(xy: vec2<f32>) -> vec2<f32> {
|
||||
let k = frame.shadow_distortion;
|
||||
return xy / (length(xy) * k + 1.0 - k);
|
||||
}
|
||||
// Where the camera-relative point `position` is stored in the shadow map: xy its 0..1 texture
|
||||
// coordinate (from the top left), z its depth from the light, for `textureSampleCompareLevel`.
|
||||
fn shadow_position(position: vec3<f32>) -> vec3<f32> {
|
||||
let clip = frame.shadow_view_proj * vec4<f32>(position, 1.0);
|
||||
let xy = shadow_distort(clip.xy);
|
||||
// At most 1, the depth where nothing is: past the map's far end a point stays lit.
|
||||
return vec3<f32>(xy.x * 0.5 + 0.5, 0.5 - xy.y * 0.5, min(clip.z, 1.0));
|
||||
}
|
||||
";
|
||||
|
||||
const LINEAR_SAMPLER: Resource = Resource {
|
||||
binding: 4,
|
||||
name: "linear_sampler",
|
||||
ty: "var linear_sampler: sampler",
|
||||
doc: "Bilinear filtering, clamped to the edges.",
|
||||
};
|
||||
|
||||
const NEAREST_SAMPLER: Resource = Resource {
|
||||
binding: 5,
|
||||
name: "nearest_sampler",
|
||||
ty: "var nearest_sampler: sampler",
|
||||
doc: "Nearest texel, clamped to the edges.",
|
||||
};
|
||||
|
||||
/// The tonemap stage's resources, at the post stage's bindings for the same names.
|
||||
const TONEMAP_RESOURCES: [Resource; 4] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 3,
|
||||
name: "previous",
|
||||
ty: "var previous: texture_2d<f32>",
|
||||
doc: "The post chain's picture at the window's size (without a post chain, the world's at the world's size), linear light: what `input.color` samples, for a curve that reads around the pixel.",
|
||||
},
|
||||
LINEAR_SAMPLER,
|
||||
NEAREST_SAMPLER,
|
||||
];
|
||||
|
||||
/// 0.2's colour boundary, in every 0.2 prelude: vanilla's 2.2 power both ways.
|
||||
const COLOR_HELPERS: &str = "\
|
||||
// Negatives and NaN made 0 (a comparison with NaN is false), so no `pow` sees them.
|
||||
fn horizon_sanitize(c: vec3<f32>) -> vec3<f32> {
|
||||
return select(vec3<f32>(0.0), c, c > vec3<f32>(0.0));
|
||||
}
|
||||
fn horizon_decode(c: vec3<f32>) -> vec3<f32> {
|
||||
return pow(horizon_sanitize(c), vec3<f32>(2.2));
|
||||
}
|
||||
fn horizon_encode(c: vec3<f32>) -> vec3<f32> {
|
||||
return pow(horizon_sanitize(c), vec3<f32>(1.0 / 2.2));
|
||||
}
|
||||
// Alpha is coverage: it stays as it is.
|
||||
fn horizon_decode_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(horizon_decode(c.rgb), c.a);
|
||||
}
|
||||
fn horizon_encode_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(horizon_encode(c.rgb), c.a);
|
||||
}
|
||||
";
|
||||
|
||||
const POST_RESOURCES: [Resource; 12] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
name: "scene_color",
|
||||
ty: "var scene_color: texture_2d<f32>",
|
||||
doc: "The world's picture before any pass.",
|
||||
},
|
||||
Resource {
|
||||
binding: 2,
|
||||
name: "scene_depth",
|
||||
ty: "var scene_depth: texture_depth_2d",
|
||||
doc: "The world's depth, reversed (1 near, 0 far and sky).",
|
||||
},
|
||||
Resource {
|
||||
binding: 3,
|
||||
name: "previous",
|
||||
ty: "var previous: texture_2d<f32>",
|
||||
doc: "The previous pass's output (`scene_color` for the first pass).",
|
||||
},
|
||||
LINEAR_SAMPLER,
|
||||
NEAREST_SAMPLER,
|
||||
shadow_map(6),
|
||||
shadow_sampler(7),
|
||||
Resource {
|
||||
binding: 8,
|
||||
name: "history",
|
||||
ty: "var history: texture_2d<f32>",
|
||||
doc: "Last frame's input to the last pass (what the pass before it wrote), for temporal passes (TAA, denoising): put such a pass second to last and it reads its own previous output; reproject with `frame.previous_view_projection`. Black on the first frame, after a gap of over half a second, and with a single pass.",
|
||||
},
|
||||
shadow_map_translucent(9),
|
||||
shadow_color(10),
|
||||
Resource {
|
||||
binding: 11,
|
||||
name: "scene_surface",
|
||||
ty: "var scene_surface: texture_2d<u32>",
|
||||
doc: "What the material stage's `set_surface` stored for the solid terrain at each pixel, unfiltered (`textureLoad`; its texels are the bits of floats, so `bitcast<vec4<f32>>` them): read it through the prelude's `surface_at`, which knows its layout and where it no longer shows. Blank without a material stage that calls `set_surface`.",
|
||||
},
|
||||
];
|
||||
|
||||
/// The material stage's resources: the frame, and the world as it was before translucent
|
||||
/// terrain drew (for water and glass to see what lies behind them).
|
||||
const MATERIAL_RESOURCES: [Resource; 11] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
name: "reflection",
|
||||
ty: "var reflection: texture_2d<f32>",
|
||||
doc: "The water mirror: the world as a camera mirrored about the horizontal water plane around the camera (`frame.water_plane`) sees it, at half resolution, mapped like the world's picture, so water samples it at `input.screen`. Solid terrain, cut away below the plane, over the vanilla sky gradient: not the pack's `sky` stage, nor the sun, moon and stars. Drawn only for a pack whose material reads it, while `frame.has_reflection` is 1; else a 1×1 blank.",
|
||||
},
|
||||
Resource {
|
||||
binding: 2,
|
||||
name: "reflection_sampler",
|
||||
ty: "var reflection_sampler: sampler",
|
||||
doc: "Bilinear filtering, clamped to the edges.",
|
||||
},
|
||||
shadow_map(3),
|
||||
shadow_sampler(4),
|
||||
shadow_map_translucent(7),
|
||||
shadow_color(8),
|
||||
Resource {
|
||||
binding: 6,
|
||||
name: "opaque_depth",
|
||||
ty: "var opaque_depth: texture_depth_2d",
|
||||
doc: "The world's depth before translucent terrain (reversed, like `scene_depth`), for the translucent pass: `textureLoad(opaque_depth, vec2<i32>(input.screen * frame.resolution), 0)` is what lies behind water or glass. Stale while solid terrain draws.",
|
||||
},
|
||||
Resource {
|
||||
binding: 9,
|
||||
name: "block_light_colors",
|
||||
ty: "var block_light_colors: texture_3d<f32>",
|
||||
doc: "The colour of the block light around the camera, from the `[[lights]]` of the light sources nearby: rgb their colours averaged by how near each is, over 4; a how strongly they reach the cell. Read it through `block_light_color`.",
|
||||
},
|
||||
Resource {
|
||||
binding: 10,
|
||||
name: "block_light_grid",
|
||||
ty: "var<uniform> block_light_grid: vec4<f32>",
|
||||
doc: "Where `block_light_colors` lies: xyz its corner, camera-relative, w the side of one of its cells in blocks.",
|
||||
},
|
||||
Resource {
|
||||
binding: 11,
|
||||
name: "opaque_color",
|
||||
ty: "var opaque_color: texture_2d<f32>",
|
||||
doc: "The world's picture before translucent terrain (sky, solid terrain, entities), the size of `opaque_depth`, for the translucent pass: what a surface refracts. Read it with `textureLoad`. Stale while solid terrain draws; black when the world draws straight to the window (a pack without `[[post]]` passes, at full resolution).",
|
||||
},
|
||||
];
|
||||
|
||||
/// Functions the post prelude gives packs.
|
||||
const POST_HELPERS: &str = "\
|
||||
// The world's depth at `uv` (reversed: 0 is the sky).
|
||||
fn depth_at(uv: vec2<f32>) -> f32 {
|
||||
let size = vec2<f32>(textureDimensions(scene_depth));
|
||||
let texel = vec2<i32>(clamp(uv * size, vec2<f32>(0.0), size - 1.0));
|
||||
return textureLoad(scene_depth, texel, 0);
|
||||
}
|
||||
// The camera-relative world position, in blocks, of the point at `uv` and `depth`.
|
||||
fn view_position(uv: vec2<f32>, depth: f32) -> vec3<f32> {
|
||||
let clip = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);
|
||||
let view = frame.projection_inverse * clip;
|
||||
return (frame.view_inverse * vec4<f32>(view.xyz / view.w, 0.0)).xyz;
|
||||
}
|
||||
";
|
||||
|
||||
/// What the material entries (the checker's below and the renderer's) share: the
|
||||
/// surface's normal and tangent from the derivatives of its position and uv. The arguments are
|
||||
/// the line's return statements of `block_light_color`, `block_texture` and
|
||||
/// `block_texture_level`, the helpers that read colour.
|
||||
macro_rules! material_helpers {
|
||||
($light:literal, $texture:literal, $texture_level:literal) => {
|
||||
concat!(
|
||||
"\
|
||||
// The colour of the block light at the camera-relative `position` (rgb, from the `[[lights]]`
|
||||
// of the sources nearby) and how strongly they reach it (a, 0 where none does). Block light
|
||||
// keeps its own brightness (`input.light.x`): this is its colour only.
|
||||
fn block_light_color(position: vec3<f32>) -> vec4<f32> {
|
||||
let size = vec3<f32>(textureDimensions(block_light_colors));
|
||||
let uvw = (position - block_light_grid.xyz) / (block_light_grid.w * size);
|
||||
if any(uvw < vec3<f32>(0.0)) || any(uvw > vec3<f32>(1.0)) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let c = textureSampleLevel(block_light_colors, reflection_sampler, uvw, 0.0);
|
||||
",
|
||||
$light,
|
||||
"}
|
||||
// What the post stage's `surface_at` reads for this point of solid terrain: its `normal` (a
|
||||
// unit vector in world axes), `smoothness` (0..1, say for reflections) and `data`, a value of
|
||||
// the pack's own passed through. Unset, the point keeps the face's own normal and 0 for both;
|
||||
// translucent terrain stores none.
|
||||
var<private> horizon_surface: vec4<f32>;
|
||||
fn set_surface(normal: vec3<f32>, smoothness: f32, data: f32) {
|
||||
// Octahedral, y up: the upper half folds onto xz, the lower half unfolds around it.
|
||||
let n = normal / (abs(normal.x) + abs(normal.y) + abs(normal.z));
|
||||
var e = n.xz;
|
||||
if n.y < 0.0 {
|
||||
e = (1.0 - abs(n.zx)) * select(vec2<f32>(-1.0), vec2<f32>(1.0), n.xz >= vec2<f32>(0.0));
|
||||
}
|
||||
horizon_surface = vec4<f32>(bitcast<f32>(pack2x16snorm(e)), smoothness, data, 1.0);
|
||||
}
|
||||
// The point being shaded: its sprite's layer and mip level (the entries set them).
|
||||
var<private> horizon_block_layer: u32;
|
||||
var<private> horizon_block_lod: f32;
|
||||
// The surface's block texture at `uv` (the sprite's space, as `input.uv`), without the tint:
|
||||
// what `input.albedo` holds at the point itself, readable anywhere on the sprite, for normals
|
||||
// or detail generated from the texture.
|
||||
fn block_texture(uv: vec2<f32>) -> vec4<f32> {
|
||||
",
|
||||
$texture,
|
||||
"}
|
||||
// `block_texture` at mip level `lod` (0 the full-size texture).
|
||||
fn block_texture_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
",
|
||||
$texture_level,
|
||||
"}
|
||||
// The block texture's size, in texels (16 x 16 for vanilla's).
|
||||
fn block_texture_size() -> vec2<f32> {
|
||||
return horizon_block_size();
|
||||
}
|
||||
// The mip level `block_texture` reads at this point (0 up close, more far away).
|
||||
fn block_texture_lod() -> f32 {
|
||||
return horizon_block_lod;
|
||||
}
|
||||
// LabPBR: whether the resource packs ship normal (`_n`) or specular (`_s`) maps beside their
|
||||
// block textures. Without them `block_normal_map` and `block_specular_map` give the defaults
|
||||
// below.
|
||||
fn block_pbr_maps() -> bool {
|
||||
return horizon_block_pbr_present();
|
||||
}
|
||||
// The surface's LabPBR normal map at `uv` (the sprite's space, as `input.uv`): rg the normal's
|
||||
// x and y in the texture's space (x along `input.tangent`, y down the texture; 0..1 for
|
||||
// -1..1), b ambient occlusion (1 none), a height (1 the top). (0.5, 0.5, 1, 1) where a texture
|
||||
// has none.
|
||||
fn block_normal_map(uv: vec2<f32>) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, horizon_block_lod, 0u);
|
||||
}
|
||||
// `block_normal_map` at the block texture's mip level `lod` (the same footprint on the surface,
|
||||
// where the map is smaller than the texture).
|
||||
fn block_normal_map_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, lod, 0u);
|
||||
}
|
||||
// The surface's LabPBR specular map at `uv`: r perceptual smoothness, g F0 up to 229/255 and
|
||||
// a metal's id above, b porosity or subsurface scattering, a emission (up to 254/255; 1
|
||||
// none). (0, 0, 0, 0) where a texture has none.
|
||||
fn block_specular_map(uv: vec2<f32>) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, horizon_block_lod, 1u);
|
||||
}
|
||||
// `block_specular_map` at the block texture's mip level `lod`.
|
||||
fn block_specular_map_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, lod, 1u);
|
||||
}
|
||||
// The LabPBR maps' size, in texels (they may be smaller than the block's texture).
|
||||
fn block_pbr_size() -> vec2<f32> {
|
||||
return horizon_block_pbr_size();
|
||||
}
|
||||
// Block faces are flat: the derivatives of the position give the face's normal, turned
|
||||
// toward the camera. Far away they lose precision, so a normal within about 3.6 degrees of
|
||||
// an axis (every full block face) is that axis exactly: no shimmer on distant faces. The
|
||||
// gentlest slope of flowing water, one level over a block, tilts about 6 degrees.
|
||||
fn horizon_material_normal(position: vec3<f32>) -> vec3<f32> {
|
||||
let n = normalize(cross(dpdx(position), dpdy(position)));
|
||||
let a = abs(n);
|
||||
let m = max(a.x, max(a.y, a.z));
|
||||
let axis = select(select(vec3(0.0, 0.0, sign(n.z)), vec3(0.0, sign(n.y), 0.0), a.y == m),
|
||||
vec3(sign(n.x), 0.0, 0.0), a.x == m);
|
||||
let flat_n = select(n, axis, m > 0.998);
|
||||
return select(flat_n, -flat_n, dot(flat_n, position) > 0.0);
|
||||
}
|
||||
// The direction `uv.x` grows along on the surface, and the sign of the one `uv.y` grows
|
||||
// along against `cross(normal, tangent)`.
|
||||
fn horizon_material_tangent(position: vec3<f32>, uv: vec2<f32>, normal: vec3<f32>) -> vec4<f32> {
|
||||
let dp1 = dpdx(position);
|
||||
let dp2 = dpdy(position);
|
||||
let duv1 = dpdx(uv);
|
||||
let duv2 = dpdy(uv);
|
||||
let dp2perp = cross(dp2, normal);
|
||||
let dp1perp = cross(normal, dp1);
|
||||
let t = dp2perp * duv1.x + dp1perp * duv2.x;
|
||||
let b = dp2perp * duv1.y + dp1perp * duv2.y;
|
||||
if length(t) < 1e-12 {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
let tangent = normalize(t - normal * dot(normal, t));
|
||||
return vec4<f32>(tangent, select(-1.0, 1.0, dot(cross(normal, tangent), b) >= 0.0));
|
||||
}
|
||||
"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const MATERIAL_HELPERS: &str = material_helpers!(
|
||||
" return vec4<f32>(c.rgb * 4.0, c.a);\n",
|
||||
" return horizon_block_sample(uv, horizon_block_layer, horizon_block_lod);\n",
|
||||
" return horizon_block_sample(uv, horizon_block_layer, lod);\n"
|
||||
);
|
||||
|
||||
/// 0.2's: the `[[lights]]` colours and the block texture decoded (the LabPBR maps are data).
|
||||
const MATERIAL_HELPERS_0_2: &str = material_helpers!(
|
||||
" return vec4<f32>(horizon_decode(c.rgb * 4.0), c.a);\n",
|
||||
" return horizon_decode_rgba(horizon_block_sample(uv, horizon_block_layer, horizon_block_lod));\n",
|
||||
" return horizon_decode_rgba(horizon_block_sample(uv, horizon_block_layer, lod));\n"
|
||||
);
|
||||
|
||||
/// The post prelude's `surface_at`, for the post helpers' end.
|
||||
const POST_SURFACE: &str = "\
|
||||
// The solid terrain's surface at a pixel, as the material stage's `set_surface` left it.
|
||||
struct SurfaceData {
|
||||
// False where the sky, an entity, water, glass or a particle shows instead.
|
||||
present: bool,
|
||||
// A unit vector in world axes.
|
||||
normal: vec3<f32>,
|
||||
smoothness: f32,
|
||||
// The pack's own value, passed through.
|
||||
data: f32,
|
||||
}
|
||||
fn surface_at(texel: vec2<i32>) -> SurfaceData {
|
||||
let size = vec2<i32>(textureDimensions(scene_surface));
|
||||
let t = clamp(texel, vec2<i32>(0), size - 1);
|
||||
let s = bitcast<vec4<f32>>(textureLoad(scene_surface, t, 0));
|
||||
let depth_size = vec2<i32>(textureDimensions(scene_depth));
|
||||
let depth = textureLoad(scene_depth, clamp(texel, vec2<i32>(0), depth_size - 1), 0);
|
||||
// The terrain's own depth is stored with it: anything drawn nearer since hides it.
|
||||
let present = s.w > 0.0 && abs(s.w - depth) <= s.w * 1e-5;
|
||||
let e = unpack2x16snorm(bitcast<u32>(s.x));
|
||||
var n = vec3<f32>(e.x, 1.0 - abs(e.x) - abs(e.y), e.y);
|
||||
if n.y < 0.0 {
|
||||
n = vec3<f32>((1.0 - abs(n.z)) * sign(n.x), n.y, (1.0 - abs(n.x)) * sign(n.z));
|
||||
}
|
||||
return SurfaceData(present, select(vec3<f32>(0.0), normalize(n), present),
|
||||
select(0.0, s.y, present), select(0.0, s.z, present));
|
||||
}
|
||||
";
|
||||
|
||||
/// The post stage's entry points, as the renderer builds them: a full-screen
|
||||
/// triangle, and a fragment that calls the pack's `post` for each pixel.
|
||||
const POST_ENTRIES: &str = "\
|
||||
struct HorizonPostOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_post(@builtin(vertex_index) i: u32) -> HorizonPostOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonPostOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_post(in: HorizonPostOut) -> @location(0) vec4<f32> {
|
||||
let color = textureSampleLevel(previous, linear_sampler, in.uv, 0.0);
|
||||
return post(PostInput(in.uv, in.position.xy, color, depth_at(in.uv)));
|
||||
}
|
||||
";
|
||||
|
||||
/// What the renderer gives the material prelude's LabPBR functions (`native_material.rs`); the
|
||||
/// check and the entries below put a stand-in in its place.
|
||||
macro_rules! pbr_stub {
|
||||
() => {
|
||||
"fn horizon_block_sample(uv: vec2<f32>, layer: u32, lod: f32) -> vec4<f32> {
|
||||
return vec4<f32>(1.0);
|
||||
}
|
||||
fn horizon_block_size() -> vec2<f32> {
|
||||
return vec2<f32>(16.0);
|
||||
}
|
||||
fn horizon_block_pbr(uv: vec2<f32>, layer: u32, lod: f32, map: u32) -> vec4<f32> {
|
||||
return select(vec4<f32>(0.5, 0.5, 1.0, 1.0), vec4<f32>(0.0), map == 1u);
|
||||
}
|
||||
fn horizon_block_pbr_present() -> bool {
|
||||
return false;
|
||||
}
|
||||
fn horizon_block_pbr_size() -> vec2<f32> {
|
||||
return vec2<f32>(16.0);
|
||||
}
|
||||
"
|
||||
};
|
||||
}
|
||||
pub const PBR_STUB: &str = pbr_stub!();
|
||||
|
||||
/// Entry points with the material stage's inputs: the renderer calls
|
||||
/// `material` from its own terrain shader in the same way. The arguments are the line's
|
||||
/// `MaterialInput` and return statements.
|
||||
macro_rules! material_entries {
|
||||
($input:literal, $result:literal) => {
|
||||
concat!(
|
||||
pbr_stub!(),
|
||||
"\
|
||||
struct HorizonMaterialIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) albedo: vec4<f32>,
|
||||
@location(1) tint: vec4<f32>,
|
||||
@location(2) light: vec2<f32>,
|
||||
@location(3) lightmap: vec3<f32>,
|
||||
@location(4) world: vec3<f32>,
|
||||
@location(5) uv: vec2<f32>,
|
||||
@location(6) @interpolate(flat) translucent: u32,
|
||||
@location(7) @interpolate(flat) tags: u32,
|
||||
@location(8) @interpolate(flat) face: u32,
|
||||
@location(9) @interpolate(flat) fluid_quad: u32,
|
||||
@location(10) @interpolate(flat) emission: f32,
|
||||
@location(11) @interpolate(flat) material: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_material(in: HorizonMaterialIn) -> @location(0) vec4<f32> {
|
||||
let fluid = select(0u, select(2u, 1u, in.translucent != 0u), in.fluid_quad != 0u);
|
||||
let normal = horizon_material_normal(in.world);
|
||||
let tangent = horizon_material_tangent(in.world, in.uv, normal);
|
||||
",
|
||||
$input,
|
||||
$result,
|
||||
"}
|
||||
"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const MATERIAL_ENTRIES: &str = material_entries!(
|
||||
" let input = MaterialInput(in.albedo, in.tint, in.light, in.lightmap, in.world, in.uv, in.translucent != 0u, in.tags, in.material,
|
||||
in.position.xy / frame.resolution, in.face, fluid, in.emission, normal, tangent, vec3<f32>(0.0), 0.0);
|
||||
",
|
||||
" return material(input).color;\n"
|
||||
);
|
||||
|
||||
/// 0.2's: the colours decoded on the way in, and the result encoded for the frame
|
||||
/// (`[frame] blend = "gamma"`; under `"linear"` the renderer stores it as it is).
|
||||
const MATERIAL_ENTRIES_0_2: &str = material_entries!(
|
||||
" let input = MaterialInput(horizon_decode_rgba(in.albedo), horizon_decode_rgba(in.tint), in.light,
|
||||
horizon_decode(in.lightmap), in.world, in.uv, in.translucent != 0u, in.tags, in.material,
|
||||
in.position.xy / frame.resolution, in.face, fluid, in.emission, normal, tangent, vec3<f32>(0.0), 0.0);
|
||||
",
|
||||
" return horizon_encode_rgba(material(input).color);\n"
|
||||
);
|
||||
|
||||
/// Entry points with the sky stage's inputs: the renderer calls `sky` from
|
||||
/// its own sky shader in the same way.
|
||||
const SKY_ENTRIES: &str = "\
|
||||
struct HorizonSkyIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) direction: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
@fragment fn horizon_fs_sky(in: HorizonSkyIn) -> @location(0) vec4<f32> {
|
||||
return sky(SkyInput(normalize(in.direction), in.color));
|
||||
}
|
||||
";
|
||||
|
||||
const SKY_ENTRIES_0_2: &str = "\
|
||||
struct HorizonSkyIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) direction: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
@fragment fn horizon_fs_sky(in: HorizonSkyIn) -> @location(0) vec4<f32> {
|
||||
return horizon_encode_rgba(sky(SkyInput(normalize(in.direction), horizon_decode_rgba(in.color))));
|
||||
}
|
||||
";
|
||||
|
||||
/// An entry point with the weather stage's inputs: the renderer calls `weather` from its own
|
||||
/// weather shader in the same way.
|
||||
const VERTEX_ENTRIES: &str = "\
|
||||
@vertex fn horizon_vs_vertex(
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) light: vec2<f32>,
|
||||
@location(3) tint: vec4<f32>, @location(4) material: u32, @location(5) flags: u32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let input = VertexInput(position, uv, material, light, tint, flags & 7u, flags >> 31u);
|
||||
return frame.projection * frame.view * vec4<f32>(vertex(input), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// 0.2's: the tint is a colour, so it is decoded too.
|
||||
const VERTEX_ENTRIES_0_2: &str = "\
|
||||
@vertex fn horizon_vs_vertex(
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) light: vec2<f32>,
|
||||
@location(3) tint: vec4<f32>, @location(4) material: u32, @location(5) flags: u32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let input = VertexInput(position, uv, material, light, horizon_decode_rgba(tint), flags & 7u, flags >> 31u);
|
||||
return frame.projection * frame.view * vec4<f32>(vertex(input), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// `fn displacement(input: DisplacementInput) -> f32`, the material stage's function
|
||||
/// under `[displacement]`.
|
||||
pub const DISPLACEMENT_SIGNATURE: &str = "fn displacement(input: DisplacementInput) -> f32";
|
||||
|
||||
/// An entry point calling the material stage's `displacement`, after its entries under
|
||||
/// `[displacement]`: the renderer calls it from its own terrain vertex shader in the same way.
|
||||
pub const DISPLACEMENT_ENTRIES: &str = "@vertex fn horizon_vs_displacement(
|
||||
@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>,
|
||||
@location(3) material: u32, @location(4) height: f32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let depth = displacement(DisplacementInput(position, normal, uv, material, height));
|
||||
return frame.projection * frame.view * vec4<f32>(position - normal * depth, 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
const WEATHER_ENTRIES: &str = "\
|
||||
struct HorizonWeatherIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) light: vec2<f32>,
|
||||
@location(2) world: vec3<f32>,
|
||||
@location(3) @interpolate(flat) snow: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_weather(in: HorizonWeatherIn) -> @location(0) vec4<f32> {
|
||||
return weather(WeatherInput(in.color, in.light, in.world, in.snow != 0u));
|
||||
}
|
||||
";
|
||||
|
||||
const WEATHER_ENTRIES_0_2: &str = "\
|
||||
struct HorizonWeatherIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) light: vec2<f32>,
|
||||
@location(2) world: vec3<f32>,
|
||||
@location(3) @interpolate(flat) snow: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_weather(in: HorizonWeatherIn) -> @location(0) vec4<f32> {
|
||||
let input = WeatherInput(horizon_decode_rgba(in.color), in.light, in.world, in.snow != 0u);
|
||||
return horizon_encode_rgba(weather(input));
|
||||
}
|
||||
";
|
||||
|
||||
/// The post stage's entry points under 0.2: the chain's targets hold linear light, so nothing
|
||||
/// converts, and the pass's alpha is ignored.
|
||||
const POST_ENTRIES_0_2: &str = "\
|
||||
struct HorizonPostOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_post(@builtin(vertex_index) i: u32) -> HorizonPostOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonPostOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_post(in: HorizonPostOut) -> @location(0) vec4<f32> {
|
||||
let color = textureSampleLevel(previous, linear_sampler, in.uv, 0.0);
|
||||
return vec4<f32>(post(PostInput(in.uv, in.position.xy, color, depth_at(in.uv))).rgb, 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// The tonemap stage's entry points: a full-screen triangle at the window's size, and a fragment
|
||||
/// that holds the result to what the display pass takes.
|
||||
const TONEMAP_ENTRIES: &str = "\
|
||||
struct HorizonTonemapOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_tonemap(@builtin(vertex_index) i: u32) -> HorizonTonemapOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonTonemapOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_tonemap(in: HorizonTonemapOut) -> @location(0) vec4<f32> {
|
||||
let color = horizon_sanitize(textureSampleLevel(previous, linear_sampler, in.uv, 0.0).rgb);
|
||||
let out = tonemap(TonemapInput(in.uv, in.position.xy, color));
|
||||
return vec4<f32>(min(horizon_sanitize(out), vec3<f32>(frame.headroom)), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
impl Api {
|
||||
/// The stages the line has, in `StageKind::ALL`'s order.
|
||||
pub fn stages(self) -> &'static [StageKind] {
|
||||
match self {
|
||||
Api::V0_1 => &StageKind::ALL[..5],
|
||||
Api::V0_2 => &StageKind::ALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StageKind {
|
||||
pub const ALL: [StageKind; 6] = [
|
||||
StageKind::Sky,
|
||||
StageKind::Vertex,
|
||||
StageKind::Material,
|
||||
StageKind::Weather,
|
||||
StageKind::Post,
|
||||
StageKind::Tonemap,
|
||||
];
|
||||
|
||||
/// The manifest's name for the stage (`[sky]`, `[vertex]`, `[material]`, `[weather]`,
|
||||
/// `[[post]]`, `[tonemap]`).
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
StageKind::Sky => "sky",
|
||||
StageKind::Material => "material",
|
||||
StageKind::Vertex => "vertex",
|
||||
StageKind::Weather => "weather",
|
||||
StageKind::Post => "post",
|
||||
StageKind::Tonemap => "tonemap",
|
||||
}
|
||||
}
|
||||
|
||||
/// The function a pack defines: it has the stage's name.
|
||||
pub fn function(self) -> &'static str {
|
||||
self.name()
|
||||
}
|
||||
|
||||
pub fn input(self) -> &'static Struct {
|
||||
match self {
|
||||
StageKind::Sky => &SKY_INPUT,
|
||||
StageKind::Material => &MATERIAL_INPUT,
|
||||
StageKind::Vertex => &VERTEX_INPUT,
|
||||
StageKind::Weather => &WEATHER_INPUT,
|
||||
StageKind::Post => &POST_INPUT,
|
||||
StageKind::Tonemap => &TONEMAP_INPUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// The type the function returns.
|
||||
pub fn output(self) -> &'static str {
|
||||
match self {
|
||||
StageKind::Material => SURFACE.name,
|
||||
StageKind::Vertex | StageKind::Tonemap => "vec3<f32>",
|
||||
StageKind::Sky | StageKind::Weather | StageKind::Post => "vec4<f32>",
|
||||
}
|
||||
}
|
||||
|
||||
/// `fn <name>(input: <Input>) -> <Output>`.
|
||||
pub fn signature(self) -> String {
|
||||
format!(
|
||||
"fn {}(input: {}) -> {}",
|
||||
self.function(),
|
||||
self.input().name,
|
||||
self.output()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resources(self) -> &'static [Resource] {
|
||||
match self {
|
||||
StageKind::Post => &POST_RESOURCES,
|
||||
StageKind::Sky | StageKind::Vertex | StageKind::Weather => std::slice::from_ref(&FRAME),
|
||||
StageKind::Material => &MATERIAL_RESOURCES,
|
||||
StageKind::Tonemap => &TONEMAP_RESOURCES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything the pack's files may use under the line `api`, declared before them.
|
||||
pub fn prelude(self, api: Api) -> String {
|
||||
let mut out = match api {
|
||||
Api::V0_1 => {
|
||||
String::from("// Horizon shader API prelude (docs/api/shaders-reference.md)\n")
|
||||
}
|
||||
Api::V0_2 => String::from(
|
||||
"// Horizon shader API 0.2 prelude (docs/api/shaders-reference-0.2.md)\n",
|
||||
),
|
||||
};
|
||||
out += &Frame::line(api).wgsl();
|
||||
out += &self.input().wgsl();
|
||||
if self == StageKind::Material {
|
||||
out += &SURFACE.wgsl();
|
||||
// The vertex stage's file joins the material's module (`compose`).
|
||||
out += &VERTEX_INPUT.wgsl();
|
||||
out += &DISPLACEMENT_INPUT.wgsl();
|
||||
}
|
||||
for r in self.resources() {
|
||||
out += &format!("@group({GROUP}) @binding({}) {};\n", r.binding, r.ty);
|
||||
}
|
||||
if api == Api::V0_2 {
|
||||
out += COLOR_HELPERS;
|
||||
}
|
||||
match self {
|
||||
StageKind::Post => {
|
||||
out += POST_HELPERS;
|
||||
out += POST_SURFACE;
|
||||
out += SHADOW_HELPERS;
|
||||
}
|
||||
StageKind::Material => {
|
||||
out += match api {
|
||||
Api::V0_1 => MATERIAL_HELPERS,
|
||||
Api::V0_2 => MATERIAL_HELPERS_0_2,
|
||||
};
|
||||
out += SHADOW_HELPERS;
|
||||
}
|
||||
StageKind::Vertex => out += SHADOW_HELPERS,
|
||||
StageKind::Sky | StageKind::Weather | StageKind::Tonemap => {}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The entry points put after the pack's files under the line `api`.
|
||||
pub fn entries(self, api: Api) -> &'static str {
|
||||
match (api, self) {
|
||||
(Api::V0_1, StageKind::Sky) => SKY_ENTRIES,
|
||||
(Api::V0_1, StageKind::Material) => MATERIAL_ENTRIES,
|
||||
(Api::V0_1, StageKind::Vertex) => VERTEX_ENTRIES,
|
||||
(Api::V0_1, StageKind::Weather) => WEATHER_ENTRIES,
|
||||
(Api::V0_1, StageKind::Post) => POST_ENTRIES,
|
||||
(Api::V0_2, StageKind::Sky) => SKY_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Material) => MATERIAL_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Vertex) => VERTEX_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Weather) => WEATHER_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Post) => POST_ENTRIES_0_2,
|
||||
// 0.1 has no tonemap stage: a manifest with one is refused before it is built.
|
||||
(_, StageKind::Tonemap) => TONEMAP_ENTRIES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The first binding of a pack's `[[textures]]` in group 3; their samplers follow at
|
||||
/// `TEXTURE_SAMPLER_BINDING`.
|
||||
pub const TEXTURE_BINDING: u32 = 16;
|
||||
pub const TEXTURE_SAMPLER_BINDING: u32 = 24;
|
||||
|
||||
/// The binding of a pack's first post target (`[[post]] output`) in group 3, the others after.
|
||||
pub const POST_TARGET_BINDING: u32 = 32;
|
||||
|
||||
/// Whether a material stage's `source` (with its prelude) calls `set_surface` itself, and so
|
||||
/// needs the surface target, which a pack without it doesn't pay for.
|
||||
pub fn calls_set_surface(source: &str) -> bool {
|
||||
let call = "set_surface(";
|
||||
source.matches(call).count() > MATERIAL_HELPERS.matches(call).count()
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//! The custom textures a pipeline's programs read: an image of the pack (a PNG or a raw dump,
|
||||
//! decoded) or a resource location the game resolves, bound to a sampler name in the programs of
|
||||
//! a stage (`horizon-shaders` reads them from `shaders.properties`, spec 3.4).
|
||||
|
||||
use crate::Format;
|
||||
|
||||
/// The stage of a `customTexture.<name>`: every program's.
|
||||
pub const EVERY_STAGE: &str = "*";
|
||||
|
||||
/// A texture bound to sampler `name` in the programs of `stage` (`gbuffers`, `deferred`,
|
||||
/// `composite`...; [`EVERY_STAGE`] for a `customTexture`), or the noise texture (stage and
|
||||
/// name `noise`).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomTexture {
|
||||
pub stage: String,
|
||||
pub name: String,
|
||||
pub source: TextureSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextureSource {
|
||||
Image(Texture),
|
||||
/// A resource location for the game to resolve.
|
||||
Resource(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Dim {
|
||||
D1,
|
||||
D2,
|
||||
D3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Texture {
|
||||
pub dim: Dim,
|
||||
/// Width, height and depth (1 for the dimensions the texture lacks).
|
||||
pub size: [u32; 3],
|
||||
/// The internal format the pack asked for.
|
||||
pub format: Format,
|
||||
pub texels: Texels,
|
||||
/// 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).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Texels {
|
||||
Float(Vec<f32>),
|
||||
Int(Vec<i32>),
|
||||
Uint(Vec<u32>),
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn components(&self) -> usize {
|
||||
components(self.format.channels)
|
||||
}
|
||||
}
|
||||
|
||||
/// The components per texel a texture of `channels` channels stores: three widen to four.
|
||||
pub fn components(channels: u8) -> usize {
|
||||
match channels {
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ rust-version.workspace = true
|
||||
description = "Horizon's own shader packs (WGSL, docs/shaders.md): the format, the stages, the check, and the horizon-shader tool"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.24", features = ["derive"] }
|
||||
# The stage interface packs are written against (the renderer's, engine).
|
||||
horizon-render-pipeline = { path = "../horizon-render-pipeline" }
|
||||
# Parses and validates a pack's WGSL, and translates it as wgpu does for Vulkan, Metal and DirectX 12.
|
||||
png = "0.18"
|
||||
naga = { version = "30.0.1", features = ["wgsl-in", "spv-out", "msl-out", "hlsl-out"] }
|
||||
|
||||
@@ -1,380 +1,3 @@
|
||||
//! What a pack's WGSL reads: the frame uniform every stage sees, which the
|
||||
//! renderer fills and uploads as the Rust struct below, and the stage input
|
||||
//! and output structs. The WGSL of each is generated from these definitions
|
||||
//! (`Struct::wgsl`), and so is the reference (docs/api/shaders-reference.md).
|
||||
|
||||
/// One field of a WGSL struct, for the prelude and the reference.
|
||||
#[derive(Debug)]
|
||||
pub struct Field {
|
||||
pub name: &'static str,
|
||||
pub ty: &'static str,
|
||||
/// Its meaning, unit and range.
|
||||
pub doc: &'static str,
|
||||
}
|
||||
|
||||
/// A struct a pack's WGSL can name.
|
||||
#[derive(Debug)]
|
||||
pub struct Struct {
|
||||
pub name: &'static str,
|
||||
pub doc: &'static str,
|
||||
pub fields: &'static [Field],
|
||||
}
|
||||
|
||||
impl Struct {
|
||||
pub fn wgsl(&self) -> String {
|
||||
let mut out = format!("struct {} {{\n", self.name);
|
||||
for f in self.fields {
|
||||
out += &format!(" {}: {},\n", f.name, f.ty);
|
||||
}
|
||||
out + "}\n"
|
||||
}
|
||||
}
|
||||
|
||||
/// The WGSL type of a Rust field the renderer uploads.
|
||||
pub trait Wgsl {
|
||||
const TYPE: &'static str;
|
||||
}
|
||||
|
||||
macro_rules! wgsl_types {
|
||||
($($rust:ty => $wgsl:literal),* $(,)?) => {
|
||||
$(impl Wgsl for $rust {
|
||||
const TYPE: &'static str = $wgsl;
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
wgsl_types! {
|
||||
f32 => "f32",
|
||||
u32 => "u32",
|
||||
i32 => "i32",
|
||||
[f32; 2] => "vec2<f32>",
|
||||
[f32; 3] => "vec3<f32>",
|
||||
[f32; 4] => "vec4<f32>",
|
||||
[[f32; 4]; 4] => "mat4x4<f32>",
|
||||
[[f32; 4]; 8] => "array<vec4<f32>, 8>",
|
||||
}
|
||||
|
||||
/// A uniform struct: the Rust type the renderer uploads, and its WGSL. `pad` names a trailing
|
||||
/// field in Rust only, which fills the struct out to WGSL's size (a multiple of its 16-byte
|
||||
/// alignment), since `Pod` allows no implicit padding.
|
||||
macro_rules! uniform {
|
||||
($(#[doc = $doc:literal])* $name:ident {
|
||||
$($(#[doc = $fdoc:literal])* $field:ident: $ty:ty,)*
|
||||
} $(pad $pad:ident: $pad_ty:ty)?) => {
|
||||
$(#[doc = $doc])*
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct $name {
|
||||
$($(#[doc = $fdoc])* pub $field: $ty,)*
|
||||
$(/// Not in the WGSL: zeros up to its size.
|
||||
pub $pad: $pad_ty,)?
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const WGSL: Struct = Struct {
|
||||
name: stringify!($name),
|
||||
doc: concat!($($doc, "\n"),*),
|
||||
fields: &[$(Field {
|
||||
name: stringify!($field),
|
||||
ty: <$ty as Wgsl>::TYPE,
|
||||
doc: concat!($($fdoc),*),
|
||||
}),*],
|
||||
};
|
||||
/// Each field's byte offset, in `WGSL.fields`' order.
|
||||
pub const OFFSETS: &'static [usize] = &[$(std::mem::offset_of!($name, $field)),*];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
uniform! {
|
||||
/// The frame, as `frame` in every stage. Positions and directions are
|
||||
/// in world axes relative to the camera (x east, y up, z south), so they
|
||||
/// stay precise far from the world's origin; depth is reversed (1 near,
|
||||
/// 0 at the far plane and in the sky).
|
||||
Frame {
|
||||
/// Camera-relative world to view space: the camera's rotation.
|
||||
view: [[f32; 4]; 4],
|
||||
/// View to clip space (reversed depth, wgpu's clip space: z 0..1).
|
||||
projection: [[f32; 4]; 4],
|
||||
/// View to camera-relative world space.
|
||||
view_inverse: [[f32; 4]; 4],
|
||||
/// Clip to view space.
|
||||
projection_inverse: [[f32; 4]; 4],
|
||||
/// The camera's world position, in blocks (about a millimetre of precision 10 000 blocks out).
|
||||
camera_position: [f32; 3],
|
||||
/// Seconds since the pack was loaded, wrapping to 0 every hour.
|
||||
time: f32,
|
||||
/// Unit vector toward the sun (below the horizon at night).
|
||||
sun_direction: [f32; 3],
|
||||
/// The time of day, 0..1: 0 sunrise, 0.25 noon, 0.5 sunset, 0.75 midnight.
|
||||
day_time: f32,
|
||||
/// Unit vector toward the moon.
|
||||
moon_direction: [f32; 3],
|
||||
/// Vanilla's GameTime: (game time % 24000 + partial tick) / 24000, 0..1.
|
||||
game_time: f32,
|
||||
/// Vanilla's sky colour this frame, 0..1.
|
||||
sky_color: [f32; 3],
|
||||
/// Rain strength, 0..1.
|
||||
rain: f32,
|
||||
/// Vanilla's fog colour this frame (also the clear colour), 0..1.
|
||||
fog_color: [f32; 3],
|
||||
/// Thunder strength, 0..1.
|
||||
thunder: f32,
|
||||
/// The size of the world's picture, in pixels (the resolution scale applied).
|
||||
resolution: [f32; 2],
|
||||
/// Block and sky light at the camera, 0..1 (level / 15).
|
||||
eye_light: [f32; 2],
|
||||
/// Where vanilla's fog starts, in blocks from the camera.
|
||||
fog_start: f32,
|
||||
/// Where vanilla's fog is full, in blocks.
|
||||
fog_end: f32,
|
||||
/// The render distance, in blocks.
|
||||
render_distance: f32,
|
||||
/// The far plane, in blocks.
|
||||
far: f32,
|
||||
/// Night vision strength, 0..1.
|
||||
night_vision: f32,
|
||||
/// Blindness strength, 0..1.
|
||||
blindness: f32,
|
||||
/// The darkness effect's strength, 0..1.
|
||||
darkness: f32,
|
||||
/// The Brightness option, 0..1 (Moody to Bright).
|
||||
brightness: f32,
|
||||
/// What the camera is in: 0 air, 1 water, 2 lava, 3 powder snow.
|
||||
eye_in: u32,
|
||||
/// 0 the Overworld, -1 the Nether, 1 the End, 2 any other dimension.
|
||||
dimension: i32,
|
||||
/// Frames drawn since the pack was loaded (wraps).
|
||||
frame_index: u32,
|
||||
/// 1 while F1 hides the HUD, else 0.
|
||||
hide_gui: u32,
|
||||
/// What falls in the biome at the camera while it rains: 0 nothing, 1 rain, 2 snow.
|
||||
precipitation: u32,
|
||||
/// The biome at the camera's temperature (vanilla's, -0.7..2.0; it snows below 0.15).
|
||||
temperature: f32,
|
||||
/// The biome at the camera's downfall, 0..1.
|
||||
downfall: f32,
|
||||
/// `rain`, smoothed: rises over about 30 seconds and dries over about 10, 0..1.
|
||||
wetness: f32,
|
||||
/// Last frame's camera-relative world to clip space (its `projection * view`); a point
|
||||
/// at camera-relative `p` now was at `p + camera_motion` relative to last frame's camera.
|
||||
/// This frame's on the first frame and after a gap of over half a second (a pause, a
|
||||
/// world load), when last frame's camera says nothing about this one.
|
||||
previous_view_projection: [[f32; 4]; 4],
|
||||
/// How far the camera moved since last frame, in blocks (0 after such a gap).
|
||||
camera_motion: [f32; 3],
|
||||
/// Seconds since last frame (0 on the first and after such a gap).
|
||||
delta_time: f32,
|
||||
/// `eye_light` eased with a half-life of 2 seconds: it follows the light levels' steps
|
||||
/// without stepping itself (for exposure and indoor fog).
|
||||
eye_light_smooth: [f32; 2],
|
||||
/// 1 while the camera is in water (`eye_in` 1), eased with a half-life of a quarter
|
||||
/// second in and out.
|
||||
in_water: f32,
|
||||
/// The same for lava (`eye_in` 2).
|
||||
in_lava: f32,
|
||||
/// The y of the horizontal water plane `reflection` mirrors the world about, in world
|
||||
/// blocks, while `has_reflection` is 1.
|
||||
water_plane: f32,
|
||||
/// 1 while the material stage's `reflection` holds this frame's water mirror, else 0.
|
||||
has_reflection: u32,
|
||||
/// The size of `reflection`, in pixels: half the world's picture.
|
||||
reflection_resolution: [f32; 2],
|
||||
/// Eight values a native mod hands its own pack each frame (`native_shader_params`,
|
||||
/// docs/native-mods.md): what they mean is that mod's, such as where and when
|
||||
/// something happened in the world. All 0 when no mod sets them.
|
||||
mods: [[f32; 4]; 8],
|
||||
/// Unit vector toward the body casting this frame's shadows: the sun by day, the moon
|
||||
/// by night.
|
||||
light_direction: [f32; 3],
|
||||
/// 1 while `shadow_map` holds this frame's shadow map, else 0.
|
||||
has_shadow: u32,
|
||||
/// Camera-relative world to the shadow camera's clip space, before the distortion
|
||||
/// (orthographic, looking along `-light_direction`; z 0 at the light, 1 away from it).
|
||||
shadow_view_proj: [[f32; 4]; 4],
|
||||
/// Half the side of the square the shadow map covers, in blocks (the manifest's
|
||||
/// `[shadow] distance`); 0 without a shadow map.
|
||||
shadow_distance: f32,
|
||||
/// How strongly the shadow map is distorted toward its centre, 0..1: a point at
|
||||
/// shadow clip `xy` is stored at `xy / (length(xy) * k + 1 - k)`, so texels near the
|
||||
/// camera cover less ground (`shadow_position` applies it).
|
||||
shadow_distortion: f32,
|
||||
/// The shadow map's side, in texels; 0 without a shadow map.
|
||||
shadow_resolution: f32,
|
||||
/// The ground one shadow texel covers at the map's centre, in blocks (for a bias that
|
||||
/// scales with it); 0 without a shadow map.
|
||||
shadow_center_texel: f32,
|
||||
/// 1 while the output is HDR, else 0.
|
||||
hdr: u32,
|
||||
/// The peak over paper white: the brightest value the display shows (Apple's EDR
|
||||
/// headroom, Android's HDR/SDR ratio). 1 in SDR. It changes while the game runs (the
|
||||
/// system's SDR white, a calibration, another display), so read it every frame.
|
||||
headroom: f32,
|
||||
/// Paper white in nits; a nominal 80 in SDR.
|
||||
paper_white: f32,
|
||||
/// The peak brightness in nits; a nominal 80 in SDR, as `paper_white`.
|
||||
peak: f32,
|
||||
/// The display's black in nits (HDR's Black Level); 0 in SDR.
|
||||
black_level: f32,
|
||||
} pad padding: [u32; 3]
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// The fields 0.1's prelude declares: the struct up to `shadow_center_texel`, a prefix of
|
||||
/// the buffer the renderer uploads.
|
||||
pub const FIELDS_0_1: usize = 49;
|
||||
|
||||
/// `Frame` as the line's prelude declares it.
|
||||
pub fn line(api: crate::Api) -> Struct {
|
||||
let fields = match api {
|
||||
crate::Api::V0_1 => &Self::WGSL.fields[..Self::FIELDS_0_1],
|
||||
crate::Api::V0_2 => Self::WGSL.fields,
|
||||
};
|
||||
Struct {
|
||||
fields,
|
||||
..Self::WGSL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! fields {
|
||||
($($name:ident: $ty:literal = $doc:literal,)*) => {
|
||||
&[$(Field { name: stringify!($name), ty: $ty, doc: $doc }),*]
|
||||
};
|
||||
}
|
||||
|
||||
/// The post stage's input: one pixel of the picture.
|
||||
pub const POST_INPUT: Struct = Struct {
|
||||
name: "PostInput",
|
||||
doc: "One pixel of the picture, for `fn post(input: PostInput) -> vec4<f32>`.",
|
||||
fields: fields! {
|
||||
uv: "vec2<f32>" = "Where the pixel is, 0..1 from the top left.",
|
||||
pixel: "vec2<f32>" = "The pixel's centre, in pixels from the top left of what the pass draws (the world's size; the window's for the last pass).",
|
||||
color: "vec4<f32>" = "The previous pass's colour here (the world's picture for the first pass).",
|
||||
depth: "f32" = "The world's depth here, reversed: 0 is the sky.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The material stage's input: one point of a block's surface.
|
||||
pub const MATERIAL_INPUT: Struct = Struct {
|
||||
name: "MaterialInput",
|
||||
doc: "One point of a block's surface, for `fn material(input: MaterialInput) -> Surface`.",
|
||||
fields: fields! {
|
||||
albedo: "vec4<f32>" = "The block texture's colour here (alpha 0..1).",
|
||||
tint: "vec4<f32>" = "The vertex colour: biome tint, face shade and ambient occlusion.",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1, smoothed over the face.",
|
||||
lightmap: "vec3<f32>" = "Vanilla's light colour for that light this frame.",
|
||||
position: "vec3<f32>" = "The point, camera-relative, in blocks.",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block atlas.",
|
||||
translucent: "bool" = "Drawn in the translucent pass (water, stained glass, ice).",
|
||||
tags: "u32" = "The pack's sprite tags this surface's sprite has: bit i for the manifest's i-th `[[tags]]`.",
|
||||
material: "u32" = "The `[[materials]]` id of this surface's sprite (0 when no entry names it).",
|
||||
screen: "vec2<f32>" = "Where the point is on the screen, 0..1 from the top left (`opaque_depth` and `opaque_color` at `screen * frame.resolution`).",
|
||||
face: "u32" = "For a fluid, which face of the block the point is on, `Direction` order: 0 down, 1 up, 2 north, 3 south, 4 west, 5 east. 0 elsewhere.",
|
||||
fluid: "u32" = "0 not a fluid, 1 water, 2 lava.",
|
||||
emission: "f32" = "How strongly the block itself shines, 0..1, by its kind of light: a redstone torch or a candle 7/15, a torch or lava 14/15, glowstone 1; 0 for most blocks.",
|
||||
normal: "vec3<f32>" = "The surface's geometric normal, a unit vector in world axes, facing the camera. A normal within about 3.6 degrees of an axis is that axis exactly, so distant block faces do not shimmer; flowing water's slopes are steeper and keep theirs.",
|
||||
tangent: "vec4<f32>" = "The surface's tangent: xyz the unit direction in world axes along which `uv.x` grows, w the sign (1 or -1) that makes `cross(normal, tangent.xyz) * tangent.w` the direction `uv.y` grows along (down the texture). With `normal` it turns a normal map's normals into world axes.",
|
||||
colored_light: "vec3<f32>" = "The frame's coloured lights at this point (`[[particle_lights]]`, held and dropped items in their `[[lights]]` colour while dynamic lights are on, and mods' visual lights), 0..1, to light the surface with as the pack likes; 0 where none reaches.",
|
||||
displaced: "f32" = "How much of the relief `[displacement]` gave this point as geometry, 0..1: 1 near the camera, where the surface already lies where `displacement` put it (`position` is that point; a parallax effect would count its depth twice), easing to 0 at `[displacement] distance`; always 0 without `[displacement]`.",
|
||||
},
|
||||
};
|
||||
|
||||
/// What the material stage returns.
|
||||
pub const SURFACE: Struct = Struct {
|
||||
name: "Surface",
|
||||
doc: "What `material` returns. Vanilla's own is `albedo * tint * vec4(lightmap, 1.0)`; \
|
||||
Horizon then applies the alpha test (cutout and translucent) and fog.",
|
||||
fields: fields! {
|
||||
color: "vec4<f32>" = "The lit colour, 0..1, with its alpha.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The sky stage's input: one direction of the sky.
|
||||
pub const SKY_INPUT: Struct = Struct {
|
||||
name: "SkyInput",
|
||||
doc: "One direction of the sky, for `fn sky(input: SkyInput) -> vec4<f32>`. The sun, moon, \
|
||||
stars and clouds are drawn over the result.",
|
||||
fields: fields! {
|
||||
direction: "vec3<f32>" = "Unit view ray, camera-relative world axes.",
|
||||
color: "vec4<f32>" = "Vanilla's sky colour along the ray (sky, fog and sunrise).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The input of the material stage's `displacement`, under `[displacement]`.
|
||||
pub const DISPLACEMENT_INPUT: Struct = Struct {
|
||||
name: "DisplacementInput",
|
||||
doc: "One corner of the grid a block face near the camera is cut into, for `fn displacement(input: DisplacementInput) -> f32` (`[displacement]`), which returns how deep below the face, in blocks, the surface lies at `height`.",
|
||||
fields: fields! {
|
||||
position: "vec3<f32>" = "The corner on the flat face, camera-relative, in blocks.",
|
||||
normal: "vec3<f32>" = "The face's normal, a unit vector in world axes.",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block's sprite (as `MaterialInput.uv`).",
|
||||
material: "u32" = "The `[[materials]]` id of the block texture (0 when none matches).",
|
||||
height: "f32" = "A LabPBR height, 0..1 (1 the top of the relief): the normal map's alpha here, or the texture's average height (what the face's edges are pinned to).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The vertex stage's input: one corner of a block's surface.
|
||||
pub const VERTEX_INPUT: Struct = Struct {
|
||||
name: "VertexInput",
|
||||
doc: "One corner of a block's surface, for `fn vertex(input: VertexInput) -> vec3<f32>`, which returns where it is drawn (camera-relative, in blocks; `input.position` where it stays).",
|
||||
fields: fields! {
|
||||
position: "vec3<f32>" = "The corner, camera-relative, in blocks (`+ frame.camera_position` for the world's).",
|
||||
uv: "vec2<f32>" = "The texture coordinate inside the block's sprite.",
|
||||
material: "u32" = "The `[[materials]]` id of the block's sprite (0 when none matches).",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1.",
|
||||
tint: "vec4<f32>" = "The vertex colour (biome tint, face shade, ambient occlusion); alpha 1.",
|
||||
face: "u32" = "For a fluid, the face of the block the corner is on (as `MaterialInput.face`), else 0.",
|
||||
fluid: "u32" = "1 on a fluid's surface (water or lava), else 0.",
|
||||
},
|
||||
};
|
||||
|
||||
/// The weather stage's input: one point of a rain or snow streak.
|
||||
pub const WEATHER_INPUT: Struct = Struct {
|
||||
name: "WeatherInput",
|
||||
doc: "One point of a rain or snow streak, for `fn weather(input: WeatherInput) -> vec4<f32>`, \
|
||||
which returns the colour blended over the world (alpha its opacity).",
|
||||
fields: fields! {
|
||||
color: "vec4<f32>" = "The rain or snow texture's colour here times the streak's vertex colour (alpha 0.1..1).",
|
||||
light: "vec2<f32>" = "Block and sky light, 0..1.",
|
||||
position: "vec3<f32>" = "The point, camera-relative, in blocks.",
|
||||
snow: "bool" = "A snow streak (rain otherwise).",
|
||||
},
|
||||
};
|
||||
|
||||
/// The tonemap stage's input (0.2): one pixel of the window.
|
||||
pub const TONEMAP_INPUT: Struct = Struct {
|
||||
name: "TonemapInput",
|
||||
doc: "One pixel of an output (the window), for `fn tonemap(input: TonemapInput) -> vec3<f32>`, which returns its colour for the output `frame` describes: linear light, 1.0 at paper white, from 0 to `frame.headroom` (Horizon clamps it there).",
|
||||
fields: fields! {
|
||||
uv: "vec2<f32>" = "Where the pixel is, 0..1 from the top left.",
|
||||
pixel: "vec2<f32>" = "The pixel's centre, in pixels from the top left of the output (the window).",
|
||||
color: "vec3<f32>" = "The post chain's picture here (the world's without one), linear light, never negative or NaN.",
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn frame_fields_carry_their_docs() {
|
||||
let wgsl = Frame::WGSL;
|
||||
assert_eq!(wgsl.fields.len(), 54);
|
||||
assert!(wgsl.fields.iter().all(|f| !f.doc.trim().is_empty()));
|
||||
// 0.1's 49 fields, then 0.2's five and the padding to a multiple of 16.
|
||||
assert_eq!(
|
||||
std::mem::size_of::<Frame>(),
|
||||
6 * 64 + 15 * 16 + 8 * 16 + 2 * 16
|
||||
);
|
||||
assert_eq!(Frame::OFFSETS[Frame::FIELDS_0_1], 6 * 64 + 15 * 16 + 8 * 16);
|
||||
assert_eq!(
|
||||
Frame::line(crate::Api::V0_1).fields.len(),
|
||||
Frame::FIELDS_0_1
|
||||
);
|
||||
assert!(
|
||||
wgsl.wgsl()
|
||||
.contains(" camera_position: vec3<f32>,\n time: f32,\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
//! What a pack's WGSL reads: the renderer's frame uniform and the stages' input and output
|
||||
//! structs (`horizon_render_pipeline::inputs`).
|
||||
pub use horizon_render_pipeline::inputs::*;
|
||||
@@ -9,37 +9,7 @@ pub const SCHEMA: u32 = 1;
|
||||
/// templates write, and what the game and the registry take.
|
||||
pub const API: &str = Api::V0_1.as_str();
|
||||
|
||||
/// A shader API line: the stages, their signatures and the inputs a pack's WGSL may read.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Api {
|
||||
/// Vanilla's gamma values.
|
||||
V0_1,
|
||||
/// Linear light (docs/design/hdr-linear-light.md). Experimental: checked, not yet drawn.
|
||||
V0_2,
|
||||
}
|
||||
|
||||
impl Api {
|
||||
/// Every line this build reads and checks, oldest first.
|
||||
pub const SERVED: [Api; 2] = [Api::V0_1, Api::V0_2];
|
||||
|
||||
/// The manifest's `api` value.
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Api::V0_1 => "0.1",
|
||||
Api::V0_2 => "0.2",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(line: &str) -> Option<Api> {
|
||||
Self::SERVED.into_iter().find(|a| a.as_str() == line)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Api {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
pub use horizon_render_pipeline::Api;
|
||||
|
||||
/// `[frame] blend` (0.2): what the frame stores, and so how see-through draws blend.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
|
||||
@@ -1,797 +1,10 @@
|
||||
//! The stages a pack can fill, each a WGSL function with a fixed signature
|
||||
//! that Horizon calls inside its own passes, and the WGSL put around a
|
||||
//! pack's files to build a stage: the prelude before (the inputs and the
|
||||
//! resources), the entry points after.
|
||||
use crate::inputs::{
|
||||
DISPLACEMENT_INPUT, Frame, MATERIAL_INPUT, POST_INPUT, SKY_INPUT, SURFACE, Struct,
|
||||
TONEMAP_INPUT, VERTEX_INPUT, WEATHER_INPUT,
|
||||
};
|
||||
use crate::manifest::Api;
|
||||
//! The stages a pack can fill and the WGSL around its files: the stage interface is the
|
||||
//! renderer's (`horizon_render_pipeline::stages`, re-exported here); this module puts a pack's
|
||||
//! files and declarations between its prelude and its entry points.
|
||||
pub use horizon_render_pipeline::stages::*;
|
||||
|
||||
use crate::package::Pack;
|
||||
|
||||
/// The bind group of a pack's resources; groups 0 to 2 are the renderer's.
|
||||
pub const GROUP: u32 = 3;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum StageKind {
|
||||
/// The sky's colour along each view ray, before the sun, moon and stars.
|
||||
Sky,
|
||||
/// The colour of block surfaces (terrain), before the alpha test and fog.
|
||||
Material,
|
||||
/// Where each corner of a block's surface is drawn (waving plants, rolling water), for
|
||||
/// the world and the shadow map.
|
||||
Vertex,
|
||||
/// The colour of rain and snow streaks.
|
||||
Weather,
|
||||
/// A full-screen pass over the world's picture, under the hand and HUD.
|
||||
Post,
|
||||
/// 0.2: the picture for an output (the display), once per pixel of it after the post chain,
|
||||
/// before the camera overlays and the HUD.
|
||||
Tonemap,
|
||||
}
|
||||
|
||||
/// A resource a stage reads, declared by its prelude.
|
||||
#[derive(Debug)]
|
||||
pub struct Resource {
|
||||
pub binding: u32,
|
||||
pub name: &'static str,
|
||||
pub ty: &'static str,
|
||||
pub doc: &'static str,
|
||||
}
|
||||
|
||||
const FRAME: Resource = Resource {
|
||||
binding: 0,
|
||||
name: "frame",
|
||||
ty: "var<uniform> frame: Frame",
|
||||
doc: "The frame (`Frame`).",
|
||||
};
|
||||
|
||||
/// The shadow map at `binding`.
|
||||
const fn shadow_map(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_map",
|
||||
ty: "var shadow_map: texture_depth_2d",
|
||||
doc: "This frame's shadow map: the depth of the solid terrain and entities the light sees, 0 at the light, 1 away from it (and where nothing is), stored at `shadow_position`'s mapping. Valid while `frame.has_shadow` is 1 (the pack asks for it with `[shadow]`), else a 1×1 map that lights everything.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map's comparison sampler at `binding`.
|
||||
const fn shadow_sampler(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_sampler",
|
||||
ty: "var shadow_sampler: sampler_comparison",
|
||||
doc: "Depth comparison, bilinear (2×2 percentage-closer filtering): `textureSampleCompareLevel(shadow_map, shadow_sampler, p.xy, p.z)` is 1 where the point is lit, 0 in shadow.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The shadow map with translucent terrain at `binding`.
|
||||
const fn shadow_map_translucent(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_map_translucent",
|
||||
ty: "var shadow_map_translucent: texture_depth_2d",
|
||||
doc: "`shadow_map` with the translucent terrain (water, stained glass, ice) the light passes through drawn in too, read with `shadow_sampler` the same way: a point lit in `shadow_map` but not here lies under such a surface, which `shadow_color` gives the colour of.",
|
||||
}
|
||||
}
|
||||
|
||||
/// The colour of the translucent terrain in the shadow map at `binding`.
|
||||
const fn shadow_color(binding: u32) -> Resource {
|
||||
Resource {
|
||||
binding,
|
||||
name: "shadow_color",
|
||||
ty: "var shadow_color: texture_2d<f32>",
|
||||
doc: "The texture colour times the vertex colour (rgb: the tint, the face's directional shade and ambient occlusion) of the nearest translucent surface the light passes through at each texel of `shadow_map_translucent`, white where there is none; alpha is 1 on a fluid (water) and the texture's alpha, at most 0.99, on anything else. Read it with `textureLoad` at `shadow_position`'s xy times `textureDimensions(shadow_color)`.",
|
||||
}
|
||||
}
|
||||
|
||||
/// A function the material and post preludes give packs.
|
||||
pub const SHADOW_HELPERS: &str = "\
|
||||
// The shadow map's distortion toward its centre, of a point at shadow clip `xy`.
|
||||
fn shadow_distort(xy: vec2<f32>) -> vec2<f32> {
|
||||
let k = frame.shadow_distortion;
|
||||
return xy / (length(xy) * k + 1.0 - k);
|
||||
}
|
||||
// Where the camera-relative point `position` is stored in the shadow map: xy its 0..1 texture
|
||||
// coordinate (from the top left), z its depth from the light, for `textureSampleCompareLevel`.
|
||||
fn shadow_position(position: vec3<f32>) -> vec3<f32> {
|
||||
let clip = frame.shadow_view_proj * vec4<f32>(position, 1.0);
|
||||
let xy = shadow_distort(clip.xy);
|
||||
// At most 1, the depth where nothing is: past the map's far end a point stays lit.
|
||||
return vec3<f32>(xy.x * 0.5 + 0.5, 0.5 - xy.y * 0.5, min(clip.z, 1.0));
|
||||
}
|
||||
";
|
||||
|
||||
const LINEAR_SAMPLER: Resource = Resource {
|
||||
binding: 4,
|
||||
name: "linear_sampler",
|
||||
ty: "var linear_sampler: sampler",
|
||||
doc: "Bilinear filtering, clamped to the edges.",
|
||||
};
|
||||
|
||||
const NEAREST_SAMPLER: Resource = Resource {
|
||||
binding: 5,
|
||||
name: "nearest_sampler",
|
||||
ty: "var nearest_sampler: sampler",
|
||||
doc: "Nearest texel, clamped to the edges.",
|
||||
};
|
||||
|
||||
/// The tonemap stage's resources, at the post stage's bindings for the same names.
|
||||
const TONEMAP_RESOURCES: [Resource; 4] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 3,
|
||||
name: "previous",
|
||||
ty: "var previous: texture_2d<f32>",
|
||||
doc: "The post chain's picture at the window's size (without a post chain, the world's at the world's size), linear light: what `input.color` samples, for a curve that reads around the pixel.",
|
||||
},
|
||||
LINEAR_SAMPLER,
|
||||
NEAREST_SAMPLER,
|
||||
];
|
||||
|
||||
/// 0.2's colour boundary, in every 0.2 prelude: vanilla's 2.2 power both ways.
|
||||
const COLOR_HELPERS: &str = "\
|
||||
// Negatives and NaN made 0 (a comparison with NaN is false), so no `pow` sees them.
|
||||
fn horizon_sanitize(c: vec3<f32>) -> vec3<f32> {
|
||||
return select(vec3<f32>(0.0), c, c > vec3<f32>(0.0));
|
||||
}
|
||||
fn horizon_decode(c: vec3<f32>) -> vec3<f32> {
|
||||
return pow(horizon_sanitize(c), vec3<f32>(2.2));
|
||||
}
|
||||
fn horizon_encode(c: vec3<f32>) -> vec3<f32> {
|
||||
return pow(horizon_sanitize(c), vec3<f32>(1.0 / 2.2));
|
||||
}
|
||||
// Alpha is coverage: it stays as it is.
|
||||
fn horizon_decode_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(horizon_decode(c.rgb), c.a);
|
||||
}
|
||||
fn horizon_encode_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(horizon_encode(c.rgb), c.a);
|
||||
}
|
||||
";
|
||||
|
||||
const POST_RESOURCES: [Resource; 12] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
name: "scene_color",
|
||||
ty: "var scene_color: texture_2d<f32>",
|
||||
doc: "The world's picture before any pass.",
|
||||
},
|
||||
Resource {
|
||||
binding: 2,
|
||||
name: "scene_depth",
|
||||
ty: "var scene_depth: texture_depth_2d",
|
||||
doc: "The world's depth, reversed (1 near, 0 far and sky).",
|
||||
},
|
||||
Resource {
|
||||
binding: 3,
|
||||
name: "previous",
|
||||
ty: "var previous: texture_2d<f32>",
|
||||
doc: "The previous pass's output (`scene_color` for the first pass).",
|
||||
},
|
||||
LINEAR_SAMPLER,
|
||||
NEAREST_SAMPLER,
|
||||
shadow_map(6),
|
||||
shadow_sampler(7),
|
||||
Resource {
|
||||
binding: 8,
|
||||
name: "history",
|
||||
ty: "var history: texture_2d<f32>",
|
||||
doc: "Last frame's input to the last pass (what the pass before it wrote), for temporal passes (TAA, denoising): put such a pass second to last and it reads its own previous output; reproject with `frame.previous_view_projection`. Black on the first frame, after a gap of over half a second, and with a single pass.",
|
||||
},
|
||||
shadow_map_translucent(9),
|
||||
shadow_color(10),
|
||||
Resource {
|
||||
binding: 11,
|
||||
name: "scene_surface",
|
||||
ty: "var scene_surface: texture_2d<u32>",
|
||||
doc: "What the material stage's `set_surface` stored for the solid terrain at each pixel, unfiltered (`textureLoad`; its texels are the bits of floats, so `bitcast<vec4<f32>>` them): read it through the prelude's `surface_at`, which knows its layout and where it no longer shows. Blank without a material stage that calls `set_surface`.",
|
||||
},
|
||||
];
|
||||
|
||||
/// The material stage's resources: the frame, and the world as it was before translucent
|
||||
/// terrain drew (for water and glass to see what lies behind them).
|
||||
const MATERIAL_RESOURCES: [Resource; 11] = [
|
||||
FRAME,
|
||||
Resource {
|
||||
binding: 1,
|
||||
name: "reflection",
|
||||
ty: "var reflection: texture_2d<f32>",
|
||||
doc: "The water mirror: the world as a camera mirrored about the horizontal water plane around the camera (`frame.water_plane`) sees it, at half resolution, mapped like the world's picture, so water samples it at `input.screen`. Solid terrain, cut away below the plane, over the vanilla sky gradient: not the pack's `sky` stage, nor the sun, moon and stars. Drawn only for a pack whose material reads it, while `frame.has_reflection` is 1; else a 1×1 blank.",
|
||||
},
|
||||
Resource {
|
||||
binding: 2,
|
||||
name: "reflection_sampler",
|
||||
ty: "var reflection_sampler: sampler",
|
||||
doc: "Bilinear filtering, clamped to the edges.",
|
||||
},
|
||||
shadow_map(3),
|
||||
shadow_sampler(4),
|
||||
shadow_map_translucent(7),
|
||||
shadow_color(8),
|
||||
Resource {
|
||||
binding: 6,
|
||||
name: "opaque_depth",
|
||||
ty: "var opaque_depth: texture_depth_2d",
|
||||
doc: "The world's depth before translucent terrain (reversed, like `scene_depth`), for the translucent pass: `textureLoad(opaque_depth, vec2<i32>(input.screen * frame.resolution), 0)` is what lies behind water or glass. Stale while solid terrain draws.",
|
||||
},
|
||||
Resource {
|
||||
binding: 9,
|
||||
name: "block_light_colors",
|
||||
ty: "var block_light_colors: texture_3d<f32>",
|
||||
doc: "The colour of the block light around the camera, from the `[[lights]]` of the light sources nearby: rgb their colours averaged by how near each is, over 4; a how strongly they reach the cell. Read it through `block_light_color`.",
|
||||
},
|
||||
Resource {
|
||||
binding: 10,
|
||||
name: "block_light_grid",
|
||||
ty: "var<uniform> block_light_grid: vec4<f32>",
|
||||
doc: "Where `block_light_colors` lies: xyz its corner, camera-relative, w the side of one of its cells in blocks.",
|
||||
},
|
||||
Resource {
|
||||
binding: 11,
|
||||
name: "opaque_color",
|
||||
ty: "var opaque_color: texture_2d<f32>",
|
||||
doc: "The world's picture before translucent terrain (sky, solid terrain, entities), the size of `opaque_depth`, for the translucent pass: what a surface refracts. Read it with `textureLoad`. Stale while solid terrain draws; black when the world draws straight to the window (a pack without `[[post]]` passes, at full resolution).",
|
||||
},
|
||||
];
|
||||
|
||||
/// Functions the post prelude gives packs.
|
||||
const POST_HELPERS: &str = "\
|
||||
// The world's depth at `uv` (reversed: 0 is the sky).
|
||||
fn depth_at(uv: vec2<f32>) -> f32 {
|
||||
let size = vec2<f32>(textureDimensions(scene_depth));
|
||||
let texel = vec2<i32>(clamp(uv * size, vec2<f32>(0.0), size - 1.0));
|
||||
return textureLoad(scene_depth, texel, 0);
|
||||
}
|
||||
// The camera-relative world position, in blocks, of the point at `uv` and `depth`.
|
||||
fn view_position(uv: vec2<f32>, depth: f32) -> vec3<f32> {
|
||||
let clip = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);
|
||||
let view = frame.projection_inverse * clip;
|
||||
return (frame.view_inverse * vec4<f32>(view.xyz / view.w, 0.0)).xyz;
|
||||
}
|
||||
";
|
||||
|
||||
/// What the material entries (the checker's below and the renderer's) share: the
|
||||
/// surface's normal and tangent from the derivatives of its position and uv. The arguments are
|
||||
/// the line's return statements of `block_light_color`, `block_texture` and
|
||||
/// `block_texture_level`, the helpers that read colour.
|
||||
macro_rules! material_helpers {
|
||||
($light:literal, $texture:literal, $texture_level:literal) => {
|
||||
concat!(
|
||||
"\
|
||||
// The colour of the block light at the camera-relative `position` (rgb, from the `[[lights]]`
|
||||
// of the sources nearby) and how strongly they reach it (a, 0 where none does). Block light
|
||||
// keeps its own brightness (`input.light.x`): this is its colour only.
|
||||
fn block_light_color(position: vec3<f32>) -> vec4<f32> {
|
||||
let size = vec3<f32>(textureDimensions(block_light_colors));
|
||||
let uvw = (position - block_light_grid.xyz) / (block_light_grid.w * size);
|
||||
if any(uvw < vec3<f32>(0.0)) || any(uvw > vec3<f32>(1.0)) {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
let c = textureSampleLevel(block_light_colors, reflection_sampler, uvw, 0.0);
|
||||
",
|
||||
$light,
|
||||
"}
|
||||
// What the post stage's `surface_at` reads for this point of solid terrain: its `normal` (a
|
||||
// unit vector in world axes), `smoothness` (0..1, say for reflections) and `data`, a value of
|
||||
// the pack's own passed through. Unset, the point keeps the face's own normal and 0 for both;
|
||||
// translucent terrain stores none.
|
||||
var<private> horizon_surface: vec4<f32>;
|
||||
fn set_surface(normal: vec3<f32>, smoothness: f32, data: f32) {
|
||||
// Octahedral, y up: the upper half folds onto xz, the lower half unfolds around it.
|
||||
let n = normal / (abs(normal.x) + abs(normal.y) + abs(normal.z));
|
||||
var e = n.xz;
|
||||
if n.y < 0.0 {
|
||||
e = (1.0 - abs(n.zx)) * select(vec2<f32>(-1.0), vec2<f32>(1.0), n.xz >= vec2<f32>(0.0));
|
||||
}
|
||||
horizon_surface = vec4<f32>(bitcast<f32>(pack2x16snorm(e)), smoothness, data, 1.0);
|
||||
}
|
||||
// The point being shaded: its sprite's layer and mip level (the entries set them).
|
||||
var<private> horizon_block_layer: u32;
|
||||
var<private> horizon_block_lod: f32;
|
||||
// The surface's block texture at `uv` (the sprite's space, as `input.uv`), without the tint:
|
||||
// what `input.albedo` holds at the point itself, readable anywhere on the sprite, for normals
|
||||
// or detail generated from the texture.
|
||||
fn block_texture(uv: vec2<f32>) -> vec4<f32> {
|
||||
",
|
||||
$texture,
|
||||
"}
|
||||
// `block_texture` at mip level `lod` (0 the full-size texture).
|
||||
fn block_texture_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
",
|
||||
$texture_level,
|
||||
"}
|
||||
// The block texture's size, in texels (16 x 16 for vanilla's).
|
||||
fn block_texture_size() -> vec2<f32> {
|
||||
return horizon_block_size();
|
||||
}
|
||||
// The mip level `block_texture` reads at this point (0 up close, more far away).
|
||||
fn block_texture_lod() -> f32 {
|
||||
return horizon_block_lod;
|
||||
}
|
||||
// LabPBR: whether the resource packs ship normal (`_n`) or specular (`_s`) maps beside their
|
||||
// block textures. Without them `block_normal_map` and `block_specular_map` give the defaults
|
||||
// below.
|
||||
fn block_pbr_maps() -> bool {
|
||||
return horizon_block_pbr_present();
|
||||
}
|
||||
// The surface's LabPBR normal map at `uv` (the sprite's space, as `input.uv`): rg the normal's
|
||||
// x and y in the texture's space (x along `input.tangent`, y down the texture; 0..1 for
|
||||
// -1..1), b ambient occlusion (1 none), a height (1 the top). (0.5, 0.5, 1, 1) where a texture
|
||||
// has none.
|
||||
fn block_normal_map(uv: vec2<f32>) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, horizon_block_lod, 0u);
|
||||
}
|
||||
// `block_normal_map` at the block texture's mip level `lod` (the same footprint on the surface,
|
||||
// where the map is smaller than the texture).
|
||||
fn block_normal_map_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, lod, 0u);
|
||||
}
|
||||
// The surface's LabPBR specular map at `uv`: r perceptual smoothness, g F0 up to 229/255 and
|
||||
// a metal's id above, b porosity or subsurface scattering, a emission (up to 254/255; 1
|
||||
// none). (0, 0, 0, 0) where a texture has none.
|
||||
fn block_specular_map(uv: vec2<f32>) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, horizon_block_lod, 1u);
|
||||
}
|
||||
// `block_specular_map` at the block texture's mip level `lod`.
|
||||
fn block_specular_map_level(uv: vec2<f32>, lod: f32) -> vec4<f32> {
|
||||
return horizon_block_pbr(uv, horizon_block_layer, lod, 1u);
|
||||
}
|
||||
// The LabPBR maps' size, in texels (they may be smaller than the block's texture).
|
||||
fn block_pbr_size() -> vec2<f32> {
|
||||
return horizon_block_pbr_size();
|
||||
}
|
||||
// Block faces are flat: the derivatives of the position give the face's normal, turned
|
||||
// toward the camera. Far away they lose precision, so a normal within about 3.6 degrees of
|
||||
// an axis (every full block face) is that axis exactly: no shimmer on distant faces. The
|
||||
// gentlest slope of flowing water, one level over a block, tilts about 6 degrees.
|
||||
fn horizon_material_normal(position: vec3<f32>) -> vec3<f32> {
|
||||
let n = normalize(cross(dpdx(position), dpdy(position)));
|
||||
let a = abs(n);
|
||||
let m = max(a.x, max(a.y, a.z));
|
||||
let axis = select(select(vec3(0.0, 0.0, sign(n.z)), vec3(0.0, sign(n.y), 0.0), a.y == m),
|
||||
vec3(sign(n.x), 0.0, 0.0), a.x == m);
|
||||
let flat_n = select(n, axis, m > 0.998);
|
||||
return select(flat_n, -flat_n, dot(flat_n, position) > 0.0);
|
||||
}
|
||||
// The direction `uv.x` grows along on the surface, and the sign of the one `uv.y` grows
|
||||
// along against `cross(normal, tangent)`.
|
||||
fn horizon_material_tangent(position: vec3<f32>, uv: vec2<f32>, normal: vec3<f32>) -> vec4<f32> {
|
||||
let dp1 = dpdx(position);
|
||||
let dp2 = dpdy(position);
|
||||
let duv1 = dpdx(uv);
|
||||
let duv2 = dpdy(uv);
|
||||
let dp2perp = cross(dp2, normal);
|
||||
let dp1perp = cross(normal, dp1);
|
||||
let t = dp2perp * duv1.x + dp1perp * duv2.x;
|
||||
let b = dp2perp * duv1.y + dp1perp * duv2.y;
|
||||
if length(t) < 1e-12 {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
let tangent = normalize(t - normal * dot(normal, t));
|
||||
return vec4<f32>(tangent, select(-1.0, 1.0, dot(cross(normal, tangent), b) >= 0.0));
|
||||
}
|
||||
"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const MATERIAL_HELPERS: &str = material_helpers!(
|
||||
" return vec4<f32>(c.rgb * 4.0, c.a);\n",
|
||||
" return horizon_block_sample(uv, horizon_block_layer, horizon_block_lod);\n",
|
||||
" return horizon_block_sample(uv, horizon_block_layer, lod);\n"
|
||||
);
|
||||
|
||||
/// 0.2's: the `[[lights]]` colours and the block texture decoded (the LabPBR maps are data).
|
||||
const MATERIAL_HELPERS_0_2: &str = material_helpers!(
|
||||
" return vec4<f32>(horizon_decode(c.rgb * 4.0), c.a);\n",
|
||||
" return horizon_decode_rgba(horizon_block_sample(uv, horizon_block_layer, horizon_block_lod));\n",
|
||||
" return horizon_decode_rgba(horizon_block_sample(uv, horizon_block_layer, lod));\n"
|
||||
);
|
||||
|
||||
/// The post prelude's `surface_at`, for the post helpers' end.
|
||||
const POST_SURFACE: &str = "\
|
||||
// The solid terrain's surface at a pixel, as the material stage's `set_surface` left it.
|
||||
struct SurfaceData {
|
||||
// False where the sky, an entity, water, glass or a particle shows instead.
|
||||
present: bool,
|
||||
// A unit vector in world axes.
|
||||
normal: vec3<f32>,
|
||||
smoothness: f32,
|
||||
// The pack's own value, passed through.
|
||||
data: f32,
|
||||
}
|
||||
fn surface_at(texel: vec2<i32>) -> SurfaceData {
|
||||
let size = vec2<i32>(textureDimensions(scene_surface));
|
||||
let t = clamp(texel, vec2<i32>(0), size - 1);
|
||||
let s = bitcast<vec4<f32>>(textureLoad(scene_surface, t, 0));
|
||||
let depth_size = vec2<i32>(textureDimensions(scene_depth));
|
||||
let depth = textureLoad(scene_depth, clamp(texel, vec2<i32>(0), depth_size - 1), 0);
|
||||
// The terrain's own depth is stored with it: anything drawn nearer since hides it.
|
||||
let present = s.w > 0.0 && abs(s.w - depth) <= s.w * 1e-5;
|
||||
let e = unpack2x16snorm(bitcast<u32>(s.x));
|
||||
var n = vec3<f32>(e.x, 1.0 - abs(e.x) - abs(e.y), e.y);
|
||||
if n.y < 0.0 {
|
||||
n = vec3<f32>((1.0 - abs(n.z)) * sign(n.x), n.y, (1.0 - abs(n.x)) * sign(n.z));
|
||||
}
|
||||
return SurfaceData(present, select(vec3<f32>(0.0), normalize(n), present),
|
||||
select(0.0, s.y, present), select(0.0, s.z, present));
|
||||
}
|
||||
";
|
||||
|
||||
/// The post stage's entry points, as the renderer builds them: a full-screen
|
||||
/// triangle, and a fragment that calls the pack's `post` for each pixel.
|
||||
const POST_ENTRIES: &str = "\
|
||||
struct HorizonPostOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_post(@builtin(vertex_index) i: u32) -> HorizonPostOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonPostOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_post(in: HorizonPostOut) -> @location(0) vec4<f32> {
|
||||
let color = textureSampleLevel(previous, linear_sampler, in.uv, 0.0);
|
||||
return post(PostInput(in.uv, in.position.xy, color, depth_at(in.uv)));
|
||||
}
|
||||
";
|
||||
|
||||
/// What the renderer gives the material prelude's LabPBR functions (`native_material.rs`); the
|
||||
/// check and the entries below put a stand-in in its place.
|
||||
macro_rules! pbr_stub {
|
||||
() => {
|
||||
"fn horizon_block_sample(uv: vec2<f32>, layer: u32, lod: f32) -> vec4<f32> {
|
||||
return vec4<f32>(1.0);
|
||||
}
|
||||
fn horizon_block_size() -> vec2<f32> {
|
||||
return vec2<f32>(16.0);
|
||||
}
|
||||
fn horizon_block_pbr(uv: vec2<f32>, layer: u32, lod: f32, map: u32) -> vec4<f32> {
|
||||
return select(vec4<f32>(0.5, 0.5, 1.0, 1.0), vec4<f32>(0.0), map == 1u);
|
||||
}
|
||||
fn horizon_block_pbr_present() -> bool {
|
||||
return false;
|
||||
}
|
||||
fn horizon_block_pbr_size() -> vec2<f32> {
|
||||
return vec2<f32>(16.0);
|
||||
}
|
||||
"
|
||||
};
|
||||
}
|
||||
pub const PBR_STUB: &str = pbr_stub!();
|
||||
|
||||
/// Entry points with the material stage's inputs: the renderer calls
|
||||
/// `material` from its own terrain shader in the same way. The arguments are the line's
|
||||
/// `MaterialInput` and return statements.
|
||||
macro_rules! material_entries {
|
||||
($input:literal, $result:literal) => {
|
||||
concat!(
|
||||
pbr_stub!(),
|
||||
"\
|
||||
struct HorizonMaterialIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) albedo: vec4<f32>,
|
||||
@location(1) tint: vec4<f32>,
|
||||
@location(2) light: vec2<f32>,
|
||||
@location(3) lightmap: vec3<f32>,
|
||||
@location(4) world: vec3<f32>,
|
||||
@location(5) uv: vec2<f32>,
|
||||
@location(6) @interpolate(flat) translucent: u32,
|
||||
@location(7) @interpolate(flat) tags: u32,
|
||||
@location(8) @interpolate(flat) face: u32,
|
||||
@location(9) @interpolate(flat) fluid_quad: u32,
|
||||
@location(10) @interpolate(flat) emission: f32,
|
||||
@location(11) @interpolate(flat) material: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_material(in: HorizonMaterialIn) -> @location(0) vec4<f32> {
|
||||
let fluid = select(0u, select(2u, 1u, in.translucent != 0u), in.fluid_quad != 0u);
|
||||
let normal = horizon_material_normal(in.world);
|
||||
let tangent = horizon_material_tangent(in.world, in.uv, normal);
|
||||
",
|
||||
$input,
|
||||
$result,
|
||||
"}
|
||||
"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const MATERIAL_ENTRIES: &str = material_entries!(
|
||||
" let input = MaterialInput(in.albedo, in.tint, in.light, in.lightmap, in.world, in.uv, in.translucent != 0u, in.tags, in.material,
|
||||
in.position.xy / frame.resolution, in.face, fluid, in.emission, normal, tangent, vec3<f32>(0.0), 0.0);
|
||||
",
|
||||
" return material(input).color;\n"
|
||||
);
|
||||
|
||||
/// 0.2's: the colours decoded on the way in, and the result encoded for the frame
|
||||
/// (`[frame] blend = "gamma"`; under `"linear"` the renderer stores it as it is).
|
||||
const MATERIAL_ENTRIES_0_2: &str = material_entries!(
|
||||
" let input = MaterialInput(horizon_decode_rgba(in.albedo), horizon_decode_rgba(in.tint), in.light,
|
||||
horizon_decode(in.lightmap), in.world, in.uv, in.translucent != 0u, in.tags, in.material,
|
||||
in.position.xy / frame.resolution, in.face, fluid, in.emission, normal, tangent, vec3<f32>(0.0), 0.0);
|
||||
",
|
||||
" return horizon_encode_rgba(material(input).color);\n"
|
||||
);
|
||||
|
||||
/// Entry points with the sky stage's inputs: the renderer calls `sky` from
|
||||
/// its own sky shader in the same way.
|
||||
const SKY_ENTRIES: &str = "\
|
||||
struct HorizonSkyIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) direction: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
@fragment fn horizon_fs_sky(in: HorizonSkyIn) -> @location(0) vec4<f32> {
|
||||
return sky(SkyInput(normalize(in.direction), in.color));
|
||||
}
|
||||
";
|
||||
|
||||
const SKY_ENTRIES_0_2: &str = "\
|
||||
struct HorizonSkyIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) direction: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
@fragment fn horizon_fs_sky(in: HorizonSkyIn) -> @location(0) vec4<f32> {
|
||||
return horizon_encode_rgba(sky(SkyInput(normalize(in.direction), horizon_decode_rgba(in.color))));
|
||||
}
|
||||
";
|
||||
|
||||
/// An entry point with the weather stage's inputs: the renderer calls `weather` from its own
|
||||
/// weather shader in the same way.
|
||||
const VERTEX_ENTRIES: &str = "\
|
||||
@vertex fn horizon_vs_vertex(
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) light: vec2<f32>,
|
||||
@location(3) tint: vec4<f32>, @location(4) material: u32, @location(5) flags: u32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let input = VertexInput(position, uv, material, light, tint, flags & 7u, flags >> 31u);
|
||||
return frame.projection * frame.view * vec4<f32>(vertex(input), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// 0.2's: the tint is a colour, so it is decoded too.
|
||||
const VERTEX_ENTRIES_0_2: &str = "\
|
||||
@vertex fn horizon_vs_vertex(
|
||||
@location(0) position: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) light: vec2<f32>,
|
||||
@location(3) tint: vec4<f32>, @location(4) material: u32, @location(5) flags: u32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let input = VertexInput(position, uv, material, light, horizon_decode_rgba(tint), flags & 7u, flags >> 31u);
|
||||
return frame.projection * frame.view * vec4<f32>(vertex(input), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// `fn displacement(input: DisplacementInput) -> f32`, the material stage's function
|
||||
/// under `[displacement]`.
|
||||
pub const DISPLACEMENT_SIGNATURE: &str = "fn displacement(input: DisplacementInput) -> f32";
|
||||
|
||||
/// An entry point calling the material stage's `displacement`, after its entries under
|
||||
/// `[displacement]`: the renderer calls it from its own terrain vertex shader in the same way.
|
||||
const DISPLACEMENT_ENTRIES: &str = "@vertex fn horizon_vs_displacement(
|
||||
@location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>,
|
||||
@location(3) material: u32, @location(4) height: f32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let depth = displacement(DisplacementInput(position, normal, uv, material, height));
|
||||
return frame.projection * frame.view * vec4<f32>(position - normal * depth, 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
const WEATHER_ENTRIES: &str = "\
|
||||
struct HorizonWeatherIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) light: vec2<f32>,
|
||||
@location(2) world: vec3<f32>,
|
||||
@location(3) @interpolate(flat) snow: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_weather(in: HorizonWeatherIn) -> @location(0) vec4<f32> {
|
||||
return weather(WeatherInput(in.color, in.light, in.world, in.snow != 0u));
|
||||
}
|
||||
";
|
||||
|
||||
const WEATHER_ENTRIES_0_2: &str = "\
|
||||
struct HorizonWeatherIn {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) light: vec2<f32>,
|
||||
@location(2) world: vec3<f32>,
|
||||
@location(3) @interpolate(flat) snow: u32,
|
||||
}
|
||||
@fragment fn horizon_fs_weather(in: HorizonWeatherIn) -> @location(0) vec4<f32> {
|
||||
let input = WeatherInput(horizon_decode_rgba(in.color), in.light, in.world, in.snow != 0u);
|
||||
return horizon_encode_rgba(weather(input));
|
||||
}
|
||||
";
|
||||
|
||||
/// The post stage's entry points under 0.2: the chain's targets hold linear light, so nothing
|
||||
/// converts, and the pass's alpha is ignored.
|
||||
const POST_ENTRIES_0_2: &str = "\
|
||||
struct HorizonPostOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_post(@builtin(vertex_index) i: u32) -> HorizonPostOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonPostOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_post(in: HorizonPostOut) -> @location(0) vec4<f32> {
|
||||
let color = textureSampleLevel(previous, linear_sampler, in.uv, 0.0);
|
||||
return vec4<f32>(post(PostInput(in.uv, in.position.xy, color, depth_at(in.uv))).rgb, 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
/// The tonemap stage's entry points: a full-screen triangle at the window's size, and a fragment
|
||||
/// that holds the result to what the display pass takes.
|
||||
const TONEMAP_ENTRIES: &str = "\
|
||||
struct HorizonTonemapOut {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
@vertex fn horizon_vs_tonemap(@builtin(vertex_index) i: u32) -> HorizonTonemapOut {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
var out: HorizonTonemapOut;
|
||||
out.position = vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(p.x, 1.0 - p.y);
|
||||
return out;
|
||||
}
|
||||
@fragment fn horizon_fs_tonemap(in: HorizonTonemapOut) -> @location(0) vec4<f32> {
|
||||
let color = horizon_sanitize(textureSampleLevel(previous, linear_sampler, in.uv, 0.0).rgb);
|
||||
let out = tonemap(TonemapInput(in.uv, in.position.xy, color));
|
||||
return vec4<f32>(min(horizon_sanitize(out), vec3<f32>(frame.headroom)), 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
impl Api {
|
||||
/// The stages the line has, in `StageKind::ALL`'s order.
|
||||
pub fn stages(self) -> &'static [StageKind] {
|
||||
match self {
|
||||
Api::V0_1 => &StageKind::ALL[..5],
|
||||
Api::V0_2 => &StageKind::ALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StageKind {
|
||||
pub const ALL: [StageKind; 6] = [
|
||||
StageKind::Sky,
|
||||
StageKind::Vertex,
|
||||
StageKind::Material,
|
||||
StageKind::Weather,
|
||||
StageKind::Post,
|
||||
StageKind::Tonemap,
|
||||
];
|
||||
|
||||
/// The manifest's name for the stage (`[sky]`, `[vertex]`, `[material]`, `[weather]`,
|
||||
/// `[[post]]`, `[tonemap]`).
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
StageKind::Sky => "sky",
|
||||
StageKind::Material => "material",
|
||||
StageKind::Vertex => "vertex",
|
||||
StageKind::Weather => "weather",
|
||||
StageKind::Post => "post",
|
||||
StageKind::Tonemap => "tonemap",
|
||||
}
|
||||
}
|
||||
|
||||
/// The function a pack defines: it has the stage's name.
|
||||
pub fn function(self) -> &'static str {
|
||||
self.name()
|
||||
}
|
||||
|
||||
pub fn input(self) -> &'static Struct {
|
||||
match self {
|
||||
StageKind::Sky => &SKY_INPUT,
|
||||
StageKind::Material => &MATERIAL_INPUT,
|
||||
StageKind::Vertex => &VERTEX_INPUT,
|
||||
StageKind::Weather => &WEATHER_INPUT,
|
||||
StageKind::Post => &POST_INPUT,
|
||||
StageKind::Tonemap => &TONEMAP_INPUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// The type the function returns.
|
||||
pub fn output(self) -> &'static str {
|
||||
match self {
|
||||
StageKind::Material => SURFACE.name,
|
||||
StageKind::Vertex | StageKind::Tonemap => "vec3<f32>",
|
||||
StageKind::Sky | StageKind::Weather | StageKind::Post => "vec4<f32>",
|
||||
}
|
||||
}
|
||||
|
||||
/// `fn <name>(input: <Input>) -> <Output>`.
|
||||
pub fn signature(self) -> String {
|
||||
format!(
|
||||
"fn {}(input: {}) -> {}",
|
||||
self.function(),
|
||||
self.input().name,
|
||||
self.output()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resources(self) -> &'static [Resource] {
|
||||
match self {
|
||||
StageKind::Post => &POST_RESOURCES,
|
||||
StageKind::Sky | StageKind::Vertex | StageKind::Weather => std::slice::from_ref(&FRAME),
|
||||
StageKind::Material => &MATERIAL_RESOURCES,
|
||||
StageKind::Tonemap => &TONEMAP_RESOURCES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything the pack's files may use under the line `api`, declared before them.
|
||||
pub fn prelude(self, api: Api) -> String {
|
||||
let mut out = match api {
|
||||
Api::V0_1 => {
|
||||
String::from("// Horizon shader API prelude (docs/api/shaders-reference.md)\n")
|
||||
}
|
||||
Api::V0_2 => String::from(
|
||||
"// Horizon shader API 0.2 prelude (docs/api/shaders-reference-0.2.md)\n",
|
||||
),
|
||||
};
|
||||
out += &Frame::line(api).wgsl();
|
||||
out += &self.input().wgsl();
|
||||
if self == StageKind::Material {
|
||||
out += &SURFACE.wgsl();
|
||||
// The vertex stage's file joins the material's module (`compose`).
|
||||
out += &VERTEX_INPUT.wgsl();
|
||||
out += &DISPLACEMENT_INPUT.wgsl();
|
||||
}
|
||||
for r in self.resources() {
|
||||
out += &format!("@group({GROUP}) @binding({}) {};\n", r.binding, r.ty);
|
||||
}
|
||||
if api == Api::V0_2 {
|
||||
out += COLOR_HELPERS;
|
||||
}
|
||||
match self {
|
||||
StageKind::Post => {
|
||||
out += POST_HELPERS;
|
||||
out += POST_SURFACE;
|
||||
out += SHADOW_HELPERS;
|
||||
}
|
||||
StageKind::Material => {
|
||||
out += match api {
|
||||
Api::V0_1 => MATERIAL_HELPERS,
|
||||
Api::V0_2 => MATERIAL_HELPERS_0_2,
|
||||
};
|
||||
out += SHADOW_HELPERS;
|
||||
}
|
||||
StageKind::Vertex => out += SHADOW_HELPERS,
|
||||
StageKind::Sky | StageKind::Weather | StageKind::Tonemap => {}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The entry points put after the pack's files under the line `api`.
|
||||
pub fn entries(self, api: Api) -> &'static str {
|
||||
match (api, self) {
|
||||
(Api::V0_1, StageKind::Sky) => SKY_ENTRIES,
|
||||
(Api::V0_1, StageKind::Material) => MATERIAL_ENTRIES,
|
||||
(Api::V0_1, StageKind::Vertex) => VERTEX_ENTRIES,
|
||||
(Api::V0_1, StageKind::Weather) => WEATHER_ENTRIES,
|
||||
(Api::V0_1, StageKind::Post) => POST_ENTRIES,
|
||||
(Api::V0_2, StageKind::Sky) => SKY_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Material) => MATERIAL_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Vertex) => VERTEX_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Weather) => WEATHER_ENTRIES_0_2,
|
||||
(Api::V0_2, StageKind::Post) => POST_ENTRIES_0_2,
|
||||
// 0.1 has no tonemap stage: a manifest with one is refused before it is built.
|
||||
(_, StageKind::Tonemap) => TONEMAP_ENTRIES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The first binding of a pack's `[[textures]]` in group 3; their samplers follow at
|
||||
/// `TEXTURE_SAMPLER_BINDING`.
|
||||
pub const TEXTURE_BINDING: u32 = 16;
|
||||
pub const TEXTURE_SAMPLER_BINDING: u32 = 24;
|
||||
|
||||
/// The declarations of a pack's `[[textures]]`, part of every stage's prelude.
|
||||
pub fn texture_declarations(textures: &[crate::manifest::PackTexture]) -> String {
|
||||
let mut out = String::new();
|
||||
@@ -832,9 +45,6 @@ impl Composed {
|
||||
}
|
||||
}
|
||||
|
||||
/// The binding of a pack's first post target (`[[post]] output`) in group 3, the others after.
|
||||
pub const POST_TARGET_BINDING: u32 = 32;
|
||||
|
||||
/// The declarations of a pack's post targets, part of the post stage's prelude.
|
||||
pub fn post_target_declarations(manifest: &crate::manifest::Manifest) -> String {
|
||||
manifest
|
||||
@@ -913,10 +123,3 @@ pub fn compose(pack: &Pack, kind: StageKind, file: &str) -> Composed {
|
||||
}
|
||||
composed
|
||||
}
|
||||
|
||||
/// Whether a material stage's `source` (with its prelude) calls `set_surface` itself, and so
|
||||
/// needs the surface target, which a pack without it doesn't pay for.
|
||||
pub fn calls_set_surface(source: &str) -> bool {
|
||||
let call = "set_surface(";
|
||||
source.matches(call).count() > MATERIAL_HELPERS.matches(call).count()
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use horizon_render_pipeline::{RENDER_STAGES, render_stage};
|
||||
|
||||
/// The feature flags of `iris.features.*` (spec 3.2) Horizon has. A pack that requires one of
|
||||
/// them loads; one it lacks is noted. Horizon defines no `IRIS_FEATURE_<name>` macro for them
|
||||
/// (spec OQ-2, docs/cleanroom/README.md): that macro family names another loader, so packs take
|
||||
@@ -59,34 +61,6 @@ struct Macro {
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Macros(BTreeMap<String, Macro>);
|
||||
|
||||
/// `MC_RENDER_STAGE_*` in the order the stages draw (spec 5.4); each is its index (OQ-7).
|
||||
pub const RENDER_STAGES: [&str; 24] = [
|
||||
"NONE",
|
||||
"SKY",
|
||||
"SUNSET",
|
||||
"CUSTOM_SKY",
|
||||
"SUN",
|
||||
"MOON",
|
||||
"STARS",
|
||||
"VOID",
|
||||
"TERRAIN_SOLID",
|
||||
"TERRAIN_CUTOUT_MIPPED",
|
||||
"TERRAIN_CUTOUT",
|
||||
"ENTITIES",
|
||||
"BLOCK_ENTITIES",
|
||||
"DESTROY",
|
||||
"OUTLINE",
|
||||
"DEBUG",
|
||||
"HAND_SOLID",
|
||||
"TERRAIN_TRANSLUCENT",
|
||||
"TRIPWIRE",
|
||||
"PARTICLES",
|
||||
"CLOUDS",
|
||||
"RAIN_SNOW",
|
||||
"WORLD_BORDER",
|
||||
"HAND_TRANSLUCENT",
|
||||
];
|
||||
|
||||
/// `DH_BLOCK_*`, each its index: Distant Horizons' block materials.
|
||||
pub const DH_BLOCK_MATERIALS: [&str; 16] = [
|
||||
"UNKNOWN",
|
||||
@@ -107,14 +81,6 @@ pub const DH_BLOCK_MATERIALS: [&str; 16] = [
|
||||
"ILLUMINATED",
|
||||
];
|
||||
|
||||
/// The value of `MC_RENDER_STAGE_<name>` (OQ-7: its index in `RENDER_STAGES`).
|
||||
pub fn render_stage(name: &str) -> u32 {
|
||||
RENDER_STAGES
|
||||
.iter()
|
||||
.position(|s| *s == name)
|
||||
.unwrap_or_else(|| panic!("no render stage {name}")) as u32
|
||||
}
|
||||
|
||||
impl Macros {
|
||||
/// The standard macros that need no renderer (spec 1.5.1): `MC_VERSION`, this system's
|
||||
/// `MC_OS_*`, the GL and GLSL versions Iris reports, the render stages and the host
|
||||
|
||||
@@ -4,71 +4,9 @@
|
||||
//! location the game resolves (`minecraft:textures/...`, `minecraft:dynamic/lightmap_1`).
|
||||
|
||||
use crate::{Format, Kind, Pack, Properties};
|
||||
|
||||
/// The stage of a `customTexture.<name>`: every program's.
|
||||
pub const EVERY_STAGE: &str = "*";
|
||||
|
||||
/// A texture bound to sampler `name` in the programs of `stage` (`gbuffers`, `deferred`,
|
||||
/// `composite`...; [`EVERY_STAGE`] for a `customTexture`), or the noise texture (stage and
|
||||
/// name `noise`).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomTexture {
|
||||
pub stage: String,
|
||||
pub name: String,
|
||||
pub source: TextureSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextureSource {
|
||||
Image(Texture),
|
||||
/// A resource location for the game to resolve.
|
||||
Resource(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Dim {
|
||||
D1,
|
||||
D2,
|
||||
D3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Texture {
|
||||
pub dim: Dim,
|
||||
/// Width, height and depth (1 for the dimensions the texture lacks).
|
||||
pub size: [u32; 3],
|
||||
/// The internal format the pack asked for.
|
||||
pub format: Format,
|
||||
pub texels: Texels,
|
||||
/// 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).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Texels {
|
||||
Float(Vec<f32>),
|
||||
Int(Vec<i32>),
|
||||
Uint(Vec<u32>),
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn components(&self) -> usize {
|
||||
components(self.format.channels)
|
||||
}
|
||||
}
|
||||
|
||||
fn components(channels: u8) -> usize {
|
||||
match channels {
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
pub use horizon_render_pipeline::textures::{
|
||||
CustomTexture, Dim, EVERY_STAGE, Texels, Texture, TextureSource, components,
|
||||
};
|
||||
|
||||
/// Every `texture.*` and `customTexture.*` entry, read and decoded, and a note for each that
|
||||
/// failed.
|
||||
|
||||
@@ -13,21 +13,11 @@ use std::collections::BTreeSet;
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::scan::{QUALIFIERS, STORAGE, significant};
|
||||
use super::{Error, GbuffersInputs, Program, Target};
|
||||
use super::{Error, Program, Target};
|
||||
use crate::Preprocessed;
|
||||
use crate::macros::lex;
|
||||
|
||||
/// How a geometry program's vertex invocations reach their triangle's vertices.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GeometryInputs {
|
||||
/// The game's vertex inputs, as `GbuffersInputs`; their `layout(location = n) in`
|
||||
/// declarations become plain globals the pull sets.
|
||||
pub inputs: GbuffersInputs,
|
||||
/// GLSL statements that set the vertex inputs from vertex `hz_vertex_index` (a `uint`).
|
||||
pub pull: String,
|
||||
/// The vertex of the `@`th corner of the draw (a `uint` expression, `hz_indices[@]`).
|
||||
pub index: String,
|
||||
}
|
||||
pub use horizon_render_pipeline::program::GeometryInputs;
|
||||
|
||||
/// Translates `vertex`, `geometry` and `fragment` (a shadow program): the program, and how many
|
||||
/// triangles the geometry stage can emit (the instances to draw).
|
||||
@@ -601,6 +591,7 @@ fn strip_storage(text: &mut [String], tokens: &[&str], range: std::ops::Range<us
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::GbuffersInputs;
|
||||
use crate::pack::tests::zip;
|
||||
use crate::{Macros, Pack, preprocess};
|
||||
|
||||
|
||||
@@ -44,188 +44,10 @@ pub use geometry::{GeometryInputs, translate_geometry};
|
||||
|
||||
use scan::Stage;
|
||||
|
||||
/// What a program's stages need from the game.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Interface {
|
||||
/// The uniform block at binding 0: its size and each value's name, GLSL type and offset.
|
||||
/// Ours come first: `hz_target` (the target's width, height, 1/width, 1/height), and the
|
||||
/// fixed-function fog the compatibility `gl_Fog` reads (OQ-28): `hz_fog_color` (vanilla's fog
|
||||
/// colour) and `hz_fog` (density, start, end, 1 / (end - start)), then `hz_depth_linear`:
|
||||
/// 1 where `shadowtex0`, `shadowtex1`, `shadow` and `watershadow` (x to w) read through a
|
||||
/// plain sampler filter linearly, 0 where they take the nearest texel.
|
||||
pub uniform_size: u32,
|
||||
pub uniforms: Vec<Uniform>,
|
||||
/// std140 initial contents; runtime values overwrite their members each frame.
|
||||
pub initial_values: Vec<u8>,
|
||||
/// Each sampler: texture at `binding`, sampler object at `binding + 1`.
|
||||
pub samplers: Vec<Sampler>,
|
||||
/// The fragment stage's output locations.
|
||||
pub outputs: Vec<u32>,
|
||||
/// A gbuffers program reads `DRAW_UNIFORMS`: the block at set 2, binding 0, set per draw.
|
||||
pub draw: bool,
|
||||
/// Storage images (`colorimg4`...), after the samplers.
|
||||
pub images: Vec<Image>,
|
||||
/// The SSBOs' bindings in set 4 (`bufferObject.<n>`).
|
||||
pub buffers: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Image {
|
||||
pub name: String,
|
||||
/// `image2D`, `uimage3D`...
|
||||
pub ty: String,
|
||||
/// The format qualifier (`rgba16f`).
|
||||
pub format: String,
|
||||
/// `readonly`, `writeonly`, empty for both, or `atomic` for an image an atomic function
|
||||
/// uses.
|
||||
pub access: String,
|
||||
pub binding: u32,
|
||||
/// A compute program's copy of image `before` as it was before the dispatch, which its
|
||||
/// loads read (see `ordering`).
|
||||
pub before: Option<String>,
|
||||
}
|
||||
|
||||
/// A compute program (`deferred4_a.csh`): its module, what it reads and writes, and how many
|
||||
/// work groups it runs: `workGroups`, or `workGroupsRender` times the target's size over the
|
||||
/// local size (1, 1 when neither is set).
|
||||
#[derive(Clone)]
|
||||
pub struct ComputeProgram {
|
||||
pub module: naga::Module,
|
||||
pub interface: Interface,
|
||||
/// Non-fatal translation diagnostics, located in the authored source.
|
||||
pub notes: Vec<Error>,
|
||||
pub work_groups: Option<[u32; 3]>,
|
||||
pub work_groups_render: [f32; 2],
|
||||
}
|
||||
|
||||
/// The uniforms a gbuffers program reads per draw (5.3's "D"), in a block of their own
|
||||
/// (`HzDraw`, std140: four ints, a vec4; 32 bytes) the game sets for each draw.
|
||||
pub const DRAW_UNIFORMS: [(&str, &str); 5] = [
|
||||
("renderStage", "int"),
|
||||
("entityId", "int"),
|
||||
("blockEntityId", "int"),
|
||||
("currentRenderedItemId", "int"),
|
||||
("entityColor", "vec4"),
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Uniform {
|
||||
pub name: String,
|
||||
/// The type the pack declared (`float`, `ivec2`, `mat4`; `bool` is stored as an `int`).
|
||||
pub ty: String,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Sampler {
|
||||
pub name: String,
|
||||
/// The type the pack declared: `sampler2D`, `sampler2DShadow`, `usampler2D`, `sampler3D`...
|
||||
pub ty: String,
|
||||
pub binding: u32,
|
||||
}
|
||||
|
||||
/// A translation diagnostic at a pack file and line.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Error {
|
||||
pub file: String,
|
||||
pub line: u32,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Program {
|
||||
pub vertex: naga::Module,
|
||||
pub fragment: naga::Module,
|
||||
pub interface: Interface,
|
||||
/// Non-fatal translation diagnostics, located in the authored source.
|
||||
pub notes: Vec<Error>,
|
||||
}
|
||||
|
||||
/// Where the program draws, which decides the conventions of 5.5.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Target {
|
||||
/// Pack buffers: rows stored as GL stores them.
|
||||
Buffers,
|
||||
/// The window (`final`).
|
||||
Window,
|
||||
}
|
||||
|
||||
/// What a gbuffers-style program's vertex stage reads. The game writes it: it knows its vertex
|
||||
/// formats, the translator only places it.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GbuffersInputs {
|
||||
/// GLSL declarations: the vertex inputs and any bindings of set 1.
|
||||
pub declarations: String,
|
||||
/// GLSL statements that set `hz_Vertex`, `hz_Color`, `hz_MultiTexCoord0`,
|
||||
/// `hz_MultiTexCoord1` and `hz_Normal` (the compatibility inputs), run first in `main`.
|
||||
pub body: String,
|
||||
/// The value of each attribute a pack may declare (`mc_Entity`, `vaPosition`...), as a GLSL
|
||||
/// expression of the declared type's size; an attribute not listed reads zero. Terrain
|
||||
/// also supplies `mc_chunkFade`, an implicit float input rather than a declared attribute.
|
||||
pub attributes: Vec<(String, String)>,
|
||||
/// GLSL statements run last in `main`, with `gl_Position` in wgpu's clip space.
|
||||
pub after: String,
|
||||
/// The alpha test applied to output 0 (`gl_FragData[0]`, `gl_FragColor`), the loader's
|
||||
/// (3.2 `alphaTest.<program>`): `None` keeps every fragment.
|
||||
pub alpha_test: Option<AlphaTest>,
|
||||
/// The program may read `currentRenderedItemId` without declaring it, as Iris 1.11.7
|
||||
/// lets the hand's (observed with probe packs; terrain's may not).
|
||||
pub item_id_declared: bool,
|
||||
/// The depth test sees the far plane (window depth 1) unless the program writes
|
||||
/// `gl_FragDepth`, while `gl_FragCoord.z` stays the geometry's own: the sky shows only where
|
||||
/// nothing else drew, and reads its own depth as in Iris 1.11.7.
|
||||
pub far_depth: bool,
|
||||
/// `normals` and `specular` are bound as paged maps of the atlas `texture` reads
|
||||
/// (`horizon-client-render`'s `paged_maps.rs`), not as images of its layout: the program's
|
||||
/// texture calls on them read through `hz_pagedTexture`.
|
||||
pub paged_maps: bool,
|
||||
/// Translucent terrain: a fragment behind the boats' water patches (`horizonWaterMask`, the
|
||||
/// patches' depth, 1 where there are none) is discarded, so no water shows inside a hull
|
||||
/// (`RenderTypes.waterMask`), while the frame's depth stays the pack's own.
|
||||
pub water_mask: bool,
|
||||
/// The shadow pass's translucent terrain: a fragment inside a boat's water patch
|
||||
/// (`horizonWaterPatch*`, camera-relative boxes) is discarded, so no water casts its light
|
||||
/// into a hull. Not with a geometry stage.
|
||||
pub water_patches: bool,
|
||||
}
|
||||
|
||||
/// The sampler of the boats' water patches' depth (`GbuffersInputs::water_mask`).
|
||||
pub const WATER_MASK: &str = "horizonWaterMask";
|
||||
|
||||
/// How many boats' water patches the shadow pass keeps water out of (`GbuffersInputs::water_patches`).
|
||||
pub const WATER_PATCHES: usize = 16;
|
||||
|
||||
/// A fragment stays when its output 0's alpha compares to `reference` as `op` says (a GLSL
|
||||
/// comparison operator, `true` for GL's `ALWAYS`, `false` for `NEVER`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AlphaTest {
|
||||
pub op: &'static str,
|
||||
pub reference: f32,
|
||||
}
|
||||
|
||||
impl AlphaTest {
|
||||
/// `alphaTest.<program>`'s value: `off`, or a GL comparison and a reference.
|
||||
pub fn parse(value: &str) -> Option<Option<Self>> {
|
||||
let mut words = value.split_whitespace();
|
||||
let func = words.next()?;
|
||||
if func == "off" {
|
||||
return Some(None);
|
||||
}
|
||||
let op = match func.trim_start_matches("GL_") {
|
||||
"NEVER" => "false",
|
||||
"LESS" => "<",
|
||||
"EQUAL" => "==",
|
||||
"LEQUAL" => "<=",
|
||||
"GREATER" => ">",
|
||||
"NOTEQUAL" => "!=",
|
||||
"GEQUAL" => ">=",
|
||||
"ALWAYS" => return Some(None),
|
||||
_ => return None,
|
||||
};
|
||||
let reference = words.next()?.parse().ok()?;
|
||||
Some(Some(Self { op, reference }))
|
||||
}
|
||||
}
|
||||
pub use horizon_render_pipeline::program::{
|
||||
AlphaTest, ComputeProgram, DRAW_UNIFORMS, Error, GbuffersInputs, Image, Interface, Program,
|
||||
Sampler, Target, Uniform, WATER_MASK, WATER_PATCHES,
|
||||
};
|
||||
|
||||
/// Translates a composite-style program (`composite`, `deferred`, `final`...).
|
||||
pub fn translate_composite(
|
||||
|
||||
Generated
+9
-1
@@ -2841,11 +2841,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-shader-api"
|
||||
name = "horizon-render-pipeline"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"naga",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "horizon-shader-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"horizon-render-pipeline",
|
||||
"naga",
|
||||
"png 0.18.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
Reference in new issue
Block a user