mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #11238 from NikitaMGrimm/pack-history-reset
🤖 Agent: The shader pack reset (both kinds) lands the in-flight frame before replacing targets, so the next frame draws as a fresh pack's first; dev tooling, no change without the reset command. #11239 follows it.
This commit is contained in:
8 files changed
+501
-17
No files matched your search
@@ -6657,6 +6657,22 @@ impl Renderer {
|
||||
}
|
||||
Ok(report)
|
||||
}
|
||||
/// Makes the running shader pack, of either kind, forget what it kept from earlier frames:
|
||||
/// the next frame draws as a freshly loaded pack's first. Nothing to do without a pack.
|
||||
pub fn reset_shader_pack_history(&mut self) {
|
||||
// A frame still on the submit thread uses the targets and readbacks replaced
|
||||
// here.
|
||||
self.land_frame();
|
||||
if let Some((_, Some(post))) = &mut self.pack_post {
|
||||
post.reset_history(&self.device);
|
||||
}
|
||||
if let Some(native) = &mut self.native {
|
||||
native.frame.reset_history();
|
||||
if let Some(post) = &mut native.post {
|
||||
post.reset_history();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// As `set_shader_pack` with a pack, but its pipelines build on a thread of their own while
|
||||
/// the game keeps drawing without one: the driver compiles a big pack's programs for seconds.
|
||||
/// `poll_shader_pack` runs it once they are built. `label` and `started` come back with it.
|
||||
|
||||
@@ -849,6 +849,15 @@ impl NativeFrame {
|
||||
inputs
|
||||
}
|
||||
|
||||
/// Forgets the frames before, as a freshly loaded pack: the post stage's next `history` is
|
||||
/// black, eased values start at their targets, wetness dry, the frame count at 0.
|
||||
pub(super) fn reset_history(&mut self) {
|
||||
self.last = None;
|
||||
self.frames = 0;
|
||||
self.wetness = (0.0, None);
|
||||
self.start = std::time::Instant::now();
|
||||
}
|
||||
|
||||
/// Takes the post stage's pending history reset. It stays pending if post rendering is
|
||||
/// skipped for a frame, so the first frame it does render still sees black history.
|
||||
pub(super) fn take_history_reset(&mut self) -> bool {
|
||||
@@ -1119,6 +1128,13 @@ impl NativePost {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Forgets the pack's own targets (`[[post]] output`): made again, blank, on the next frame,
|
||||
/// as for a pack just made. Its pipelines stay.
|
||||
pub(super) fn reset_history(&mut self) {
|
||||
self.named = None;
|
||||
self.groups = None;
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
@@ -1737,6 +1753,152 @@ mod tests {
|
||||
assert!(!native.take_history_reset());
|
||||
}
|
||||
|
||||
/// After a reset, the same frames give the same inputs, to the bit, as a pack just made,
|
||||
/// however the frames before moved the eased values, wetness and the frame count.
|
||||
#[test]
|
||||
fn a_reset_frame_gives_a_fresh_pack_s_inputs() {
|
||||
use crate::gfx::pack_uniforms::FrameTiming;
|
||||
let Some((device, _)) = device() else {
|
||||
return;
|
||||
};
|
||||
let hud = crate::hud::HudBatch::new([4, 4]);
|
||||
let mut frame = crate::gfx::pack_uniforms::initial_history_tests::frame(&hud);
|
||||
let new = || {
|
||||
NativeFrame::new(
|
||||
&device,
|
||||
None,
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
None,
|
||||
Api::V0_1,
|
||||
)
|
||||
};
|
||||
let (mut used, mut fresh) = (new(), new());
|
||||
frame.pack.rain = 1.0;
|
||||
frame.pack.eye_light = [12, 3];
|
||||
frame.pack.eye_in = 1;
|
||||
for n in 1..=5 {
|
||||
frame.pack.frame_timing = Some(FrameTiming::nth(n, 700.0));
|
||||
used.next_inputs(&frame, [4, 4]);
|
||||
}
|
||||
used.reset_history();
|
||||
frame.pack.eye_light = [2, 15];
|
||||
frame.pack.eye_in = 0;
|
||||
for n in 1..=3 {
|
||||
frame.pack.frame_timing = Some(FrameTiming::nth(n, 50.0));
|
||||
let inputs = [&mut used, &mut fresh].map(|f| f.next_inputs(&frame, [4, 4]));
|
||||
assert_eq!(
|
||||
bytemuck::bytes_of(&inputs[0]),
|
||||
bytemuck::bytes_of(&inputs[1]),
|
||||
"frame {n}"
|
||||
);
|
||||
assert_eq!(used.take_history_reset(), fresh.take_history_reset());
|
||||
}
|
||||
}
|
||||
|
||||
/// After a reset a pass reads its pack's own targets blank, as in a pack just made: here
|
||||
/// `fb`, which a pass reads before the pass that writes it, so it holds last frame's.
|
||||
#[test]
|
||||
fn a_reset_post_reads_its_own_targets_blank() {
|
||||
let Some((device, queue)) = device() else {
|
||||
return;
|
||||
};
|
||||
let manifest = "schema = 1
|
||||
id = \"t\"
|
||||
name = \"T\"
|
||||
version = \"1\"
|
||||
api = \"0.1\"
|
||||
[[post]]
|
||||
file = \"a.wgsl\"
|
||||
[[post]]
|
||||
file = \"b.wgsl\"
|
||||
output = \"fb\"
|
||||
[[post]]
|
||||
file = \"c.wgsl\"
|
||||
";
|
||||
let a = "fn post(input: PostInput) -> vec4<f32> { return vec4<f32>(textureLoad(fb, vec2<i32>(input.pixel), 0).rgb + 0.25, 1.0); }
|
||||
";
|
||||
let pass = "fn post(input: PostInput) -> vec4<f32> { return input.color; }
|
||||
";
|
||||
let files = [
|
||||
(MANIFEST, manifest),
|
||||
("a.wgsl", a),
|
||||
("b.wgsl", pass),
|
||||
("c.wgsl", pass),
|
||||
]
|
||||
.map(|(n, t)| (n.to_owned(), t.as_bytes().to_vec()))
|
||||
.into();
|
||||
let report = check(&Pack::from_files(files).unwrap()).unwrap();
|
||||
let format = wgpu::TextureFormat::Rgba8Unorm;
|
||||
let frame = NativeFrame::new(
|
||||
&device,
|
||||
None,
|
||||
Vec::new(),
|
||||
Default::default(),
|
||||
None,
|
||||
Api::V0_1,
|
||||
);
|
||||
let mut post = NativePost::new(
|
||||
&device,
|
||||
report.stages,
|
||||
&report.manifest,
|
||||
&[],
|
||||
format,
|
||||
&frame,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING;
|
||||
let color = texture(&device, format, usage).create_view(&Default::default());
|
||||
let depth = texture(&device, wgpu::TextureFormat::Depth32Float, usage)
|
||||
.create_view(&Default::default());
|
||||
let screen = texture(
|
||||
&device,
|
||||
format,
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||
);
|
||||
let view = screen.create_view(&Default::default());
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: u64::from(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT * 4),
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let red = |post: &mut NativePost| {
|
||||
let mut encoder = device.create_command_encoder(&Default::default());
|
||||
let world = (&color, &depth, &frame.surface_view);
|
||||
post.run(&device, &mut encoder, world, &view, [4, 4], false, None);
|
||||
encoder.copy_texture_to_buffer(
|
||||
screen.as_image_copy(),
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT),
|
||||
rows_per_image: Some(4),
|
||||
},
|
||||
},
|
||||
screen.size(),
|
||||
);
|
||||
queue.submit([encoder.finish()]);
|
||||
buffer
|
||||
.slice(..)
|
||||
.map_async(wgpu::MapMode::Read, |r| r.unwrap());
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
let red = buffer.slice(..).get_mapped_range().unwrap()[0];
|
||||
buffer.unmap();
|
||||
red
|
||||
};
|
||||
let near = |got: u8, want: u8| got.abs_diff(want) <= 1;
|
||||
for want in [64, 128, 191] {
|
||||
let got = red(&mut post);
|
||||
assert!(near(got, want), "{got} before the reset, {want} expected");
|
||||
}
|
||||
post.reset_history();
|
||||
let got = red(&mut post);
|
||||
assert!(near(got, 64), "{got} after the reset: fb kept last frame's");
|
||||
}
|
||||
|
||||
fn device() -> Option<(wgpu::Device, wgpu::Queue)> {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let adapter = pollster::block_on(instance.request_adapter(&Default::default())).ok()?;
|
||||
|
||||
@@ -23,6 +23,13 @@ pub(super) struct PackImage {
|
||||
}
|
||||
|
||||
impl PackImage {
|
||||
pub(super) fn reset_history(&mut self) {
|
||||
self.view = None;
|
||||
self.copy = None;
|
||||
self.before = None;
|
||||
self.made_for = [0; 2];
|
||||
}
|
||||
|
||||
pub(super) fn sample_type(&self, features: wgpu::Features) -> wgpu::TextureSampleType {
|
||||
self.format
|
||||
.sample_type(None, Some(features))
|
||||
@@ -316,6 +323,11 @@ impl StandIn {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reset_stand_ins(device: &wgpu::Device, images: &mut Vec<StandIn>) {
|
||||
let wanted = images.iter().map(|i| (i.format, i.dimension)).collect();
|
||||
*images = stand_ins(device, wanted);
|
||||
}
|
||||
|
||||
/// One stand-in for each format and dimension of `wanted`.
|
||||
pub(super) fn stand_ins(
|
||||
device: &wgpu::Device,
|
||||
@@ -392,6 +404,12 @@ pub(super) struct Buffers {
|
||||
}
|
||||
|
||||
impl Buffers {
|
||||
pub(super) fn reset_history(&mut self) {
|
||||
self.group = None;
|
||||
self.buffers.clear();
|
||||
self.made_for = [0; 2];
|
||||
}
|
||||
|
||||
/// The layout of `objects`, bound in `stages`; each buffer is at least its entry in
|
||||
/// `minimums` long.
|
||||
pub(super) fn new(
|
||||
|
||||
@@ -31,6 +31,9 @@ const BUFFERS: usize = horizon_shaders::BUFFERS;
|
||||
|
||||
mod packed;
|
||||
|
||||
#[cfg(test)]
|
||||
mod history_reset_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod world_target_tests;
|
||||
|
||||
@@ -399,6 +402,8 @@ struct Pass {
|
||||
layout: wgpu::BindGroupLayout,
|
||||
ubo: wgpu::Buffer,
|
||||
block: Vec<u8>,
|
||||
/// Shader-declared defaults must survive a reset of the mutable upload block.
|
||||
initial_block: Vec<u8>,
|
||||
/// (index into `pack_uniforms::NAMES`, declared type, offset).
|
||||
uniforms: Vec<(Option<usize>, String, u32)>,
|
||||
samplers: Vec<Binding>,
|
||||
@@ -452,6 +457,16 @@ struct Pass {
|
||||
}
|
||||
|
||||
impl Pass {
|
||||
/// Back to the uniform values the pass was made with, and no bind groups.
|
||||
fn reset_history(&mut self) {
|
||||
self.block.clone_from(&self.initial_block);
|
||||
self.groups.clear();
|
||||
self.portal_groups.clear();
|
||||
for compute in &mut self.compute {
|
||||
compute.reset_history();
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) -> &wgpu::RenderPipeline {
|
||||
match &self.pipeline {
|
||||
Pipeline::Render(p) => p,
|
||||
@@ -1973,6 +1988,7 @@ fn build_pass(
|
||||
mapped_at_creation: false,
|
||||
}),
|
||||
block: interface.initial_values.clone(),
|
||||
initial_block: interface.initial_values.clone(),
|
||||
uniforms: interface
|
||||
.uniforms
|
||||
.iter()
|
||||
@@ -2108,6 +2124,7 @@ fn build_compute(
|
||||
mapped_at_creation: false,
|
||||
}),
|
||||
block: interface.initial_values.clone(),
|
||||
initial_block: interface.initial_values.clone(),
|
||||
uniforms: interface
|
||||
.uniforms
|
||||
.iter()
|
||||
@@ -3124,23 +3141,7 @@ impl PackPost {
|
||||
.create_view(&Default::default()),
|
||||
textures,
|
||||
shadow: (
|
||||
device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Shader pack shadow stand-in"),
|
||||
size: wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
||||
| wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&Default::default()),
|
||||
super::pack_shadow::stand_in(device),
|
||||
device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Shader pack shadow"),
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
@@ -3189,6 +3190,65 @@ impl PackPost {
|
||||
})
|
||||
}
|
||||
|
||||
/// Forgets what the pack kept from earlier frames, so the next frame draws as a freshly
|
||||
/// loaded pack's first: buffers, images, storage buffers and shadow maps start over (made
|
||||
/// again at their sizes when next drawn), the frame uniforms' history, the custom uniforms
|
||||
/// and the centre depth too. Compiled programs and the pack's textures stay.
|
||||
pub fn reset_history(&mut self, device: &wgpu::Device) {
|
||||
self.uniforms.reset_history();
|
||||
// A parsed pack's custom uniforms start at tick 0's seed, before any frame of the world.
|
||||
self.custom.restart_at(0);
|
||||
self.custom.forget_world();
|
||||
self.center_depth = None;
|
||||
self.targets = None;
|
||||
self.main = 0;
|
||||
self.mips_valid = 0;
|
||||
self.rgb_unset = true;
|
||||
self.final_target = None;
|
||||
self.blit_group = None;
|
||||
self.page_groups.clear();
|
||||
for image in &mut self.images {
|
||||
image.reset_history();
|
||||
}
|
||||
self.buffers.reset_history();
|
||||
pack_images::reset_stand_ins(device, &mut self.stand_ins);
|
||||
if let Some(shadow) = &mut self.shadow_targets {
|
||||
shadow.reset_history(device, &self.uniforms.shadow);
|
||||
}
|
||||
self.shadow.0 = super::pack_shadow::stand_in(device);
|
||||
self.shadow_cleared = false;
|
||||
self.shadow_cache = Default::default();
|
||||
self.setup_pending = true;
|
||||
self.portal_texture = None;
|
||||
self.portal_rect = [0.0; 4];
|
||||
let sky = self
|
||||
.sky
|
||||
.iter_mut()
|
||||
.flat_map(|s| s.basic.iter_mut().chain(&mut s.textured));
|
||||
let entities = self
|
||||
.entities
|
||||
.iter_mut()
|
||||
.flat_map(|e| e.passes.iter_mut().chain(&mut e.shadow).flatten());
|
||||
for pass in self
|
||||
.passes
|
||||
.iter_mut()
|
||||
.chain(&mut self.terrain)
|
||||
.chain(&mut self.terrain_cutout)
|
||||
.chain(&mut self.water)
|
||||
.chain(sky)
|
||||
.chain(entities)
|
||||
.chain(&mut self.clouds)
|
||||
.chain(&mut self.lines)
|
||||
.chain(self.weather.iter_mut().flatten())
|
||||
.chain(self.hand.iter_mut().map(|h| &mut h.0))
|
||||
.chain(&mut self.shadow_terrain)
|
||||
.chain(&mut self.shadow_water)
|
||||
.chain(&mut self.shadow_comp)
|
||||
{
|
||||
pass.reset_history();
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes `pass`'s uniforms for a target of `out` pixels; its bind group for the buffers'
|
||||
/// current mapping. The buffers it both samples and stores to are copied first, and it
|
||||
/// samples the copies.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
//! `PackPost::reset_history`: a pack reset after some frames draws as a freshly loaded one.
|
||||
use super::destination_tests::Fixture;
|
||||
use std::io::{Cursor, Write};
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires a host GPU with compute and fragment storage"]
|
||||
fn a_reset_pack_draws_its_buffers_images_storage_and_shadow_from_the_start() {
|
||||
let gpu = Fixture::new().expect("a GPU is required for this reset regression");
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let mut add = |name: &str, source: &str| {
|
||||
zip.start_file(
|
||||
format!("shaders/{name}"),
|
||||
zip::write::SimpleFileOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
};
|
||||
let vertex = "#version 120\nvarying vec2 uv;\nvoid main(){uv=gl_MultiTexCoord0.xy;gl_Position=ftransform();}\n";
|
||||
let state = "layout(std430,binding=0) buffer State { uint setups; uint frames; } state;\n";
|
||||
let compute = "#version 430\nlayout(local_size_x=1) in;\nconst ivec3 workGroups=ivec3(1);\n";
|
||||
add(
|
||||
"setup.csh",
|
||||
&format!("{compute}{state}void main(){{state.setups+=1u;}}\n"),
|
||||
);
|
||||
add(
|
||||
"begin.csh",
|
||||
&format!(
|
||||
"{compute}{state}layout(r32ui) uniform uimage2D imgCounter;\n\
|
||||
void main(){{state.frames+=1u;imageStore(imgCounter,ivec2(0),uvec4(imageLoad(imgCounter,ivec2(0)).r+1u));}}\n"
|
||||
),
|
||||
);
|
||||
for name in ["composite", "shadowcomp"] {
|
||||
add(&format!("{name}.vsh"), vertex);
|
||||
let target = if name == "composite" { 2 } else { 0 };
|
||||
let (declarations, value) = if name == "composite" {
|
||||
("", "vec4(0.125)")
|
||||
} else {
|
||||
(
|
||||
"uniform sampler2D shadowcolor0;varying vec2 uv;",
|
||||
"texture2D(shadowcolor0,uv)+vec4(0.125)",
|
||||
)
|
||||
};
|
||||
add(
|
||||
&format!("{name}.fsh"),
|
||||
&format!(
|
||||
"#version 120\n/* RENDERTARGETS:{target} */\n\
|
||||
{declarations}\nvoid main(){{gl_FragData[0]={value};}}\n"
|
||||
),
|
||||
);
|
||||
}
|
||||
add("final.vsh", vertex);
|
||||
add(
|
||||
"final.fsh",
|
||||
&format!(
|
||||
"#version 430 compatibility\n{state}\
|
||||
uniform sampler2D colortex2;uniform sampler2D shadowcolor0;uniform usampler2D texCounter;\n\
|
||||
const bool colortex2Clear=false;const bool shadowcolor0Clear=false;\n\
|
||||
const int shadowMapResolution=16;uniform float baseline=0.375;varying vec2 uv;\n\
|
||||
void main(){{gl_FragColor=gl_FragCoord.x<8.0?\
|
||||
vec4(texture(colortex2,uv).r,float(texelFetch(texCounter,ivec2(0),0).r)/8.0,float(state.frames)/8.0,1):\
|
||||
vec4(texture(shadowcolor0,uv).r,float(state.setups)/8.0,baseline,1);}}\n"
|
||||
),
|
||||
);
|
||||
add(
|
||||
"shaders.properties",
|
||||
"shadow.enabled=true\nbufferObject.0=8\n\
|
||||
image.imgCounter=texCounter RED_INTEGER R32UI UNSIGNED_INT false false 1 1\n\
|
||||
blend.composite=ONE ONE ONE ONE\n",
|
||||
);
|
||||
let pack =
|
||||
horizon_shaders::Pack::from_zip_bytes("original-reset", zip.finish().unwrap().into_inner())
|
||||
.unwrap();
|
||||
let mut macros = horizon_shaders::Macros::standard();
|
||||
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
|
||||
let load = || horizon_shaders::load_post(&pack, "", &properties, ¯os, &Default::default());
|
||||
let mut post = gpu.try_build(load()).expect("reset fixture compiles");
|
||||
let mut fresh = gpu.try_build(load()).expect("fresh control compiles");
|
||||
let check = |pixels: &[[u8; 4]], frame: u8| {
|
||||
let counter = 32 * frame;
|
||||
for (i, pixel) in pixels.iter().enumerate() {
|
||||
let expected = if i % 16 < 8 {
|
||||
[counter, counter, counter, 255]
|
||||
} else {
|
||||
[counter, 32, 96, 255]
|
||||
};
|
||||
assert!(
|
||||
pixel.iter().zip(expected).all(|(a, b)| a.abs_diff(b) <= 1),
|
||||
"frame {frame}, pixel {i}: {pixel:?} != {expected:?}"
|
||||
);
|
||||
}
|
||||
};
|
||||
for frame in 1..=3 {
|
||||
check(&gpu.render(&mut post, [16, 16]), frame);
|
||||
}
|
||||
// At the same size too: the retained colortex2 and shadowcolor0, the image and
|
||||
// the storage buffer start over, and setup runs once again. The control is a pack never
|
||||
// reset.
|
||||
for _ in 0..2 {
|
||||
post.reset_history(&gpu.device);
|
||||
for frame in 1..=2 {
|
||||
let pixels = gpu.render(&mut post, [16, 16]);
|
||||
check(&pixels, frame);
|
||||
if frame == 1 {
|
||||
assert_eq!(pixels, gpu.render(&mut fresh, [16, 16]));
|
||||
}
|
||||
}
|
||||
fresh.reset_history(&gpu.device);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,50 @@ fn target(
|
||||
(texture, view)
|
||||
}
|
||||
|
||||
/// The depth stand-in is initialized to no shadow on the next pack render.
|
||||
pub(super) fn stand_in(device: &wgpu::Device) -> wgpu::TextureView {
|
||||
device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Shader pack shadow stand-in"),
|
||||
size: wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&Default::default())
|
||||
}
|
||||
|
||||
impl ShadowTargets {
|
||||
pub fn reset_history(&mut self, device: &wgpu::Device, shadow: &horizon_shaders::Shadow) {
|
||||
let mut colors = 0;
|
||||
let mut doubled = 0;
|
||||
let formats = std::array::from_fn(|n| match &self.color[n] {
|
||||
Some([a, b]) => {
|
||||
colors |= 1 << n;
|
||||
if a.texture() != b.texture() {
|
||||
doubled |= 1 << n;
|
||||
}
|
||||
a.texture().format()
|
||||
}
|
||||
None => wgpu::TextureFormat::Rgba8Unorm,
|
||||
});
|
||||
*self = Self::new(
|
||||
device,
|
||||
shadow,
|
||||
formats,
|
||||
colors,
|
||||
doubled,
|
||||
self.depth1.is_some(),
|
||||
);
|
||||
}
|
||||
|
||||
/// The buffers of `shadow` the pack uses: `colors` (bit n: shadowcolor n), `depth1` when
|
||||
/// some program reads `shadowtex1`, `doubled` (bit n: shadowcolor n needs a B texture). The
|
||||
/// resolution is kept within the device's limit.
|
||||
|
||||
@@ -709,6 +709,16 @@ fn smooth(value: f32, target: f32, half_life: f32, dt: f32) -> f32 {
|
||||
}
|
||||
|
||||
impl FrameUniforms {
|
||||
/// Forgets the frames before (the previous camera, smoothed values, the clock and the
|
||||
/// centre depth), keeping the pack's atlas size and shadow settings.
|
||||
pub(super) fn reset_history(&mut self) {
|
||||
*self = Self {
|
||||
atlas: self.atlas,
|
||||
shadow: std::mem::take(&mut self.shadow),
|
||||
..Self::default()
|
||||
};
|
||||
}
|
||||
|
||||
/// This frame's values for a render size of `size` pixels.
|
||||
pub fn update(&mut self, frame: &Frame, inputs: &PackInputs, size: [u32; 2]) {
|
||||
let now = std::time::Instant::now();
|
||||
@@ -2367,6 +2377,61 @@ pub(super) mod initial_history_tests {
|
||||
assert_eq!((sixty.elapsed, sixty.delta), (1.0, 1.0 / 60.0));
|
||||
}
|
||||
|
||||
/// A reset pack draws its next frame as its first: no previous camera, smoothed values
|
||||
/// at their targets, the clock from 0; the atlas size it was given stays.
|
||||
#[test]
|
||||
fn a_reset_forgets_the_uniforms_history_and_keeps_the_pack_s_settings() {
|
||||
let hud = HudBatch::new([16, 16]);
|
||||
let mut f = frame(&hud);
|
||||
let mut uniforms = FrameUniforms {
|
||||
atlas: [256, 128],
|
||||
..Default::default()
|
||||
};
|
||||
let mut inputs = PackInputs {
|
||||
rain: 1.0,
|
||||
eye_light: [15, 15],
|
||||
frame_timing: Some(FrameTiming {
|
||||
counter: 800,
|
||||
elapsed: 99.0,
|
||||
delta: 1.0,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
uniforms.center_depth_raw = Some(0.25);
|
||||
uniforms.update(&f, &inputs, [16, 16]);
|
||||
f.camera = glam::DVec3::new(7.0, 12.0, -30.0);
|
||||
f.yaw = 77.0;
|
||||
inputs.rain = 0.25;
|
||||
inputs.eye_light = [3, 8];
|
||||
inputs.frame_timing = None;
|
||||
uniforms.reset_history();
|
||||
uniforms.update(&f, &inputs, [16, 16]);
|
||||
let float = |name| match uniforms.values[index(name).unwrap()] {
|
||||
Value::F(v) => v,
|
||||
_ => panic!("float uniform expected"),
|
||||
};
|
||||
let int = |name| match uniforms.values[index(name).unwrap()] {
|
||||
Value::I(v) => v,
|
||||
_ => panic!("integer uniform expected"),
|
||||
};
|
||||
assert_eq!(int("frameCounter")[0], 0);
|
||||
assert_eq!(float("frameTime")[0], 0.0);
|
||||
assert_eq!(int("atlasSize")[..2], [256, 128]);
|
||||
assert_eq!(float("wetness")[0], 0.25);
|
||||
assert_eq!(int("eyeBrightnessSmooth")[..2], [48, 128]);
|
||||
assert_eq!(float("centerDepthSmooth")[0], 1.0);
|
||||
for (previous, current) in [
|
||||
("previousCameraPosition", "cameraPosition"),
|
||||
("gbufferPreviousModelView", "gbufferModelView"),
|
||||
("gbufferPreviousProjection", "gbufferProjection"),
|
||||
] {
|
||||
assert_eq!(
|
||||
float(previous).map(f32::to_bits),
|
||||
float(current).map(f32::to_bits)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iris 1.11.7 (probe pack): `ambientLight` is the dimension type's (0.1 in the Nether,
|
||||
/// 0.25 in the End), and `playerMood` and `constantMood` both the cave mood.
|
||||
#[test]
|
||||
|
||||
@@ -260,6 +260,13 @@ impl Custom {
|
||||
}
|
||||
}
|
||||
|
||||
/// Forgets the world's frames, as a pack just loaded has seen none: `smooth` restarts every
|
||||
/// frame again until one knows the camera's biome (`Renderer::reset_shader_pack_history`).
|
||||
pub fn forget_world(&mut self) {
|
||||
self.restart_smoothing();
|
||||
self.world_seen = false;
|
||||
}
|
||||
|
||||
/// Restarts `smooth` and `random` for a frame of the world's tick `tick`, so a frame drawn
|
||||
/// again at that tick evaluates the same (`HORIZON_STILL`).
|
||||
pub fn restart_at(&mut self, tick: u64) {
|
||||
@@ -1299,6 +1306,10 @@ mod tests {
|
||||
let (cat, y) = (0..60).fold((0.0, 0.0), |_, _| frame(&mut c, true, 10.0, -5.38));
|
||||
assert!(cat > 9.9, "the biome's category from the start: {cat}");
|
||||
assert!(y > 290.0, "still fading from the spawn's 300: {y}");
|
||||
// Forgotten, as by a pack history reset: it starts again from the first known frame.
|
||||
c.forget_world();
|
||||
frame(&mut c, false, 0.0, 100.0);
|
||||
assert_eq!(frame(&mut c, true, 10.0, 100.0), (10.0, 300.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user