mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Restore upstream shadow visibility after gameplay trace comparison
This commit is contained in:
1 parent
71a5f8624f
commit
54b8f989ca
4 files changed
+134
-4
No files matched your search
@@ -22,7 +22,6 @@ fn retail_shader(path: &str) -> ShaderRef {
|
||||
impl Plugin for RetailRenderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
shadow::install(app);
|
||||
shadow_visibility::install(app);
|
||||
exposure::install(app);
|
||||
if std::env::var_os("SKATE_WEATHERING_COMPARE").is_some_and(|v| v == "1") {
|
||||
app.add_systems(Update, compare_weathering);
|
||||
@@ -41,9 +40,10 @@ impl Plugin for RetailRenderPlugin {
|
||||
MaterialPlugin::<RetailSkyMaterial>::default(),
|
||||
));
|
||||
}
|
||||
fn finish(&self, app: &mut App) { shadow_visibility::finish(app); }
|
||||
}
|
||||
|
||||
// Retained for headless experiments; production uses Bevy directional visibility.
|
||||
#[cfg(test)]
|
||||
#[path = "retail_shadow_visibility.rs"]
|
||||
pub(crate) mod shadow_visibility;
|
||||
|
||||
|
||||
@@ -150,7 +150,21 @@ fn indexed_system_matches_upstream_across_changes_and_removal() {
|
||||
}
|
||||
original.run(&mut world);
|
||||
let expected = read(&world);
|
||||
for &entity in &entities {
|
||||
if let Some(mut visibility) = world.get_mut::<ViewVisibility>(entity) {
|
||||
*visibility = ViewVisibility::HIDDEN;
|
||||
}
|
||||
}
|
||||
indexed.run(&mut world);
|
||||
for &entity in &entities {
|
||||
if let Some(visibility) = world.get::<ViewVisibility>(entity) {
|
||||
assert_eq!(
|
||||
visibility.get(),
|
||||
expected.iter().any(|list| list.contains(&entity)),
|
||||
"visibility marking at step {step}"
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(read(&world), expected, "step {step}");
|
||||
}
|
||||
for entity in entities {
|
||||
@@ -178,6 +192,7 @@ pub(super) fn benchmark_bounds(bounds: Vec<Aabb>) -> serde_json::Value {
|
||||
let mut world = World::new();
|
||||
let mut index = Index::default();
|
||||
let count = bounds.len();
|
||||
let system_bounds = bounds.clone();
|
||||
let start = std::time::Instant::now();
|
||||
index.rebuild(
|
||||
bounds
|
||||
@@ -247,5 +262,84 @@ pub(super) fn benchmark_bounds(bounds: Vec<Aabb>) -> serde_json::Value {
|
||||
}
|
||||
}
|
||||
let indexed_ms = start.elapsed().as_secs_f64() * 1000.;
|
||||
serde_json::json!({"static_batches":count,"queries":repetitions*frusta.len(),"flat_ms":flat_ms,"indexed_ms":indexed_ms,"build_ms":build_ms,"visible_sets_equal":true,"note":"real University material bounds; representative shadow frusta, single-thread query kernel only, not whole-frame FPS"})
|
||||
serde_json::json!({"full_system":benchmark_systems(system_bounds),"static_batches":count,"queries":repetitions*frusta.len(),"flat_ms":flat_ms,"indexed_ms":indexed_ms,"build_ms":build_ms,"visible_sets_equal":true,"note":"real University material bounds; representative shadow frusta, single-thread query kernel only, not whole-frame FPS"})
|
||||
}
|
||||
|
||||
fn benchmark_systems(bounds: Vec<Aabb>) -> serde_json::Value {
|
||||
bevy::tasks::ComputeTaskPool::get_or_init(Default::default);
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Index>();
|
||||
let view = world.spawn_empty().id();
|
||||
let center = Vec3::new(330., 133., -710.);
|
||||
let direction = Vec3::new(-0.60385966, 0.73098797, 0.31782088);
|
||||
let frusta: Vec<_> = [5., 10., 30., 100.]
|
||||
.into_iter()
|
||||
.map(|radius| {
|
||||
Frustum::from_clip_from_world(
|
||||
&(Mat4::orthographic_rh(-radius, radius, -radius, radius, 0.1, 400.)
|
||||
* Mat4::look_at_rh(center + direction * 200., center, Vec3::Y)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for layer in [0, 28] {
|
||||
let mut cascades = CascadesFrusta::default();
|
||||
cascades.frusta.insert(view, frusta.clone());
|
||||
let light = world
|
||||
.spawn((
|
||||
DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
cascades,
|
||||
CascadesVisibleEntities::default(),
|
||||
RenderLayers::layer(layer),
|
||||
))
|
||||
.id();
|
||||
world
|
||||
.get_mut::<ViewVisibility>(light)
|
||||
.unwrap()
|
||||
.set_visible();
|
||||
}
|
||||
for aabb in bounds {
|
||||
world.spawn((
|
||||
StaticShadowCaster,
|
||||
Mesh3d::default(),
|
||||
aabb,
|
||||
GlobalTransform::IDENTITY,
|
||||
InheritedVisibility::VISIBLE,
|
||||
));
|
||||
}
|
||||
// A second view has overlapping cascades, deliberately exercising duplicate
|
||||
// visibility publication, which the old query-only benchmark omitted.
|
||||
let second_view = world.spawn_empty().id();
|
||||
for mut cascades in world.query::<&mut CascadesFrusta>().iter_mut(&mut world) {
|
||||
cascades.frusta.insert(second_view, frusta.clone());
|
||||
}
|
||||
let mut old = bevy::ecs::schedule::Schedule::default();
|
||||
old.add_systems((refresh, check_indexed).chain());
|
||||
let mut upstream = bevy::ecs::schedule::Schedule::default();
|
||||
upstream.add_systems(bevy::light::check_dir_light_mesh_visibility);
|
||||
old.run(&mut world);
|
||||
upstream.run(&mut world);
|
||||
let mut old_times = vec![];
|
||||
let mut upstream_times = vec![];
|
||||
for round in 0..5 {
|
||||
let mut run = |schedule: &mut bevy::ecs::schedule::Schedule| {
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..200 {
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
start.elapsed().as_secs_f64() * 1000. / 200.
|
||||
};
|
||||
if round % 2 == 0 {
|
||||
old_times.push(run(&mut old));
|
||||
upstream_times.push(run(&mut upstream));
|
||||
} else {
|
||||
upstream_times.push(run(&mut upstream));
|
||||
old_times.push(run(&mut old));
|
||||
}
|
||||
}
|
||||
old_times.sort_by(f64::total_cmp);
|
||||
upstream_times.sort_by(f64::total_cmp);
|
||||
serde_json::json!({"v9_ms_per_update":old_times[2],"upstream_ms_per_update":upstream_times[2],"note":"complete headless schedules including refresh and deferred writes; real map bounds, fixed representative frusta, two overlapping views, not gameplay FPS"})
|
||||
}
|
||||
@@ -700,7 +700,7 @@ pub(crate) fn spawn(
|
||||
}
|
||||
if let Some(material) = retail {
|
||||
let material = retail_materials.add(material);
|
||||
commands.spawn((crate::retail_render::shadow_visibility::StaticShadowCaster, Name::new(m.name.clone()), Mesh3d(meshes.add(mesh)), MeshMaterial3d(material), Transform::default()));
|
||||
commands.spawn((Name::new(m.name.clone()), Mesh3d(meshes.add(mesh)), MeshMaterial3d(material), Transform::default()));
|
||||
continue;
|
||||
}
|
||||
// Vertex colours above carry retail decal coordinates, never PBR tint.
|
||||
|
||||
@@ -83,3 +83,39 @@ system-only DLL imports were verified without running gameplay. EXE SHA-256:
|
||||
v8 for comparison. The new capture is armed until F9 and records 20 seconds.
|
||||
Existing compiler warnings remain. Runtime appearance and FPS acceptance are
|
||||
pending the user's test; the agent's GPU runs were isolated headless tests only.
|
||||
|
||||
|
||||
## v10: restore the measured shadow baseline
|
||||
|
||||
The completed user captures `university-cpu-bindings-v8-8702-22955.json` and
|
||||
`university-cpu-followup-v9-5644-32244.json` each cover 20 seconds without dropped
|
||||
trace events. Median frame time improved from 4.578 to 3.976 ms, but mean frame
|
||||
time increased from 4.601 to 4.806 ms and p99 from 7.775 to 21.713 ms. These are
|
||||
not matched camera routes; neither the aggregate improvement nor the hitches
|
||||
can be attributed conclusively to a particular change.
|
||||
|
||||
Directional shadow system plus deferred-command time increased from approximately
|
||||
0.525 + 0.036 = 0.561 ms/frame in v8 to 0.573 + 0.243 = 0.816 ms/frame in v9.
|
||||
The indexed implementation repeats visibility publication across cascade lists.
|
||||
A candidate using a reused entity set and a bulk mutable query passed visibility
|
||||
parity tests but did not improve a complete headless schedule benchmark: 0.0610
|
||||
versus 0.0590 ms/update for v9. That candidate was discarded.
|
||||
|
||||
The same fixture measured upstream at 0.1410 ms/update, which disagrees with the
|
||||
relative result in gameplay. It uses actual University bounds but fixed frusta,
|
||||
two overlapping views, no moving casters and an otherwise idle ECS schedule.
|
||||
This makes it useful for detecting local overhead, not choosing the fastest
|
||||
production path or predicting FPS. The inventory now includes full schedule
|
||||
measurements, including deferred writes, alongside the older query-only kernel.
|
||||
|
||||
v10 restores upstream Bevy directional shadow visibility by removing the custom
|
||||
system installation/replacement and the retail entity marker. The index remains
|
||||
compiled only for headless tests and experiments. Material resource reuse,
|
||||
controller metadata caching and the v8 bindless shader fix remain active.
|
||||
Shadow geometry, quality and distance are unchanged. This is a conservative
|
||||
return to the earlier measured path, not a claim that it resolves every hitch.
|
||||
|
||||
Visibility parity tests now also reset and verify per-entity ViewVisibility after
|
||||
each lifecycle change, in addition to comparing cascade membership. The private
|
||||
v10 launcher is `TRACE-UNIVERSITY-SHADOW-FIX.bat`; v8 and v9 remain available.
|
||||
A matched user capture is required to determine the actual frame-time effect.
|
||||
Reference in new issue
Block a user