Files
benilla-octo/crates/benilla-world/src/lib.rs
T
gromandClaude Opus 5 c315fb4f35 Anisotropic filtering and trilinear apply in the session
The Options row shipped last commit said the setting needed a relaunch.
It does not have to. The reference's "enabled upon restart" is about its
TEXTURES, not its CVars — both register with `flags = 1`, live — because
its sampler state was baked inside each `TextureCreate` (`0x449ae0`) and
it kept no list of live textures. Ours can be re-stamped in place.

Two things make it possible, and neither is new work:

- A governed sampler now wears `POLICY_SAMPLER_LABEL`, written by the one
  constructor `TexFilterSetting::descriptor`. That is the `applyGlobal`
  split the reference draws, made legible at runtime: terrain, WMO/M2 and
  the liquid sheets take the policy; weather sprites, the minimap, glue,
  Lua widgets, the sky and decals keep their own mode. All four governed
  lanes went through that constructor, so a lane cannot forget the label
  and silently opt itself out of every later change.
- Extracting a `RENDER_WORLD`-only `Image` takes its *pixels* and leaves
  the asset behind (`take_gpu_data` is an `Option::take` on `image.data`).
  So every resident texture's own descriptor is still readable in the main
  world, for nothing — there is no ledger to keep and none to go stale.

The policy itself stops being a `OnceLock` and becomes one atomic word
plus a revision, so it can be published again on every write and both
halves of the refresh can key off the same number in the same frame:
`refresh_policy_samplers` (render world, `ExtractSchedule`, before every
Prepare) swaps `GpuImage::sampler`, and `invalidate_materials::<M>` raises
`AssetEvent::Modified` so bind groups that captured the OLD sampler are
built again. `static_gx` carries two samplers of its own and rebuilds its
cached cell bind groups in place rather than dropping them — clearing the
cache would be one line, but a region with no GPU state does not draw, so
every building in view would blink out and trickle back under the bake
budget.

Measured, not asserted. `WOW_CVAR_LATE=<name>=<value>` is new here: it
writes CVars *after* the scene has settled, then re-settles the image
around them. A latched lever and a live one give the same boot picture, so
no boot-time A/B can tell them apart. On the Elwynn vista:

    anisotropic 16 at boot  vs  16 written late:
        0 of 1,440,000 pixels differ

and the control that proves the harness is not doing the work (boot 16,
written 16 again late, so no refresh runs but the re-settle is still paid)
is byte-identical to a plain boot. The cost of the change is 873 samplers,
75 material bind groups and 264 static_gx cell bind groups, once; nothing
is re-uploaded, since the pixels never move.

The first run of that A/B was NOT identical — 6.7% of the frame, a dapple
of stale tree shadow on a road that was otherwise correct. That is
`CutoutShadowCasterMaterial`, which deliberately reuses the forward pass's
own leaf handle and so holds a governed sampler, and which lives in
`benilla-app` rather than the engine. Hence the shape the invalidation
took: a public `invalidate_materials::<M>` each crate registers beside its
own `MaterialPlugin`, published through the world-API wall with the record
that says why (the engine naming every material type in the program is the
inversion that wall exists to stop). Forgetting one is silent in exactly
that way — a bind group built after the change is right and one already
cached is wrong — so the capture A/B is written into `docs/GRAPHICS.md` as
the check for this feature, not a unit test pretending to be one.

The Options row keeps its deferral to Apply, now for the real reason: the
commit is cheap once and silly sixteen times, which is what a drag across
the groove would ask for.

Verified: ALL GATES GREEN (520 s); `benilla-app --lib` against the install
at the exact 269-failure baseline, set-identical by `comm`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-26 15:44:27 -06:00

176 lines
5.1 KiB
Rust

//! benilla-world: the world renderer, with no game attached. It puts things into a world and
//! answers questions about it, and knows nothing of a session, the wire, an avatar or a UI. The
//! dependency edge holds that line: this crate does not depend on `benilla-app`, and
//! `benilla-worldview` links only this crate. Above it are `benilla-app` and the two binaries;
//! below it, `benilla-assets` and `benilla-formats`.
/// Re-exported so a shim binary needs no `bevy` dependency of its own.
pub use bevy::app::AppExit;
/// Test fixtures shared with dependent crates: ordinary public code, since `#[cfg(test)]` code is
/// not compiled for a downstream crate.
pub mod testing {
use benilla_formats::{
CellRamp, EmitParams, EmitTiming, OverLife, ParamsNow, ParticleBlend, ParticleEmitterDef,
ParticleShape,
};
/// The sampled parameters of [`particle_def`], constant so a sim test is deterministic.
pub fn particle_params_now() -> ParamsNow {
ParamsNow {
emission_speed: 1.0,
speed_variation: 0.0,
vertical_range: 0.5,
horizontal_range: std::f32::consts::PI,
gravity: 0.0,
lifespan: 1.0,
area_length: 2.0,
area_width: 4.0,
z_source: 0.0,
}
}
/// A minimal emitter def for kernel tests; only the fields the emit kernel reads matter.
pub fn particle_def(shape: ParticleShape) -> ParticleEmitterDef {
ParticleEmitterDef {
flags: 0,
position: [1.0, 2.0, 3.0],
bone: 0,
shape,
blend: ParticleBlend::Add,
lit: false,
texture: None,
tile_rows: 1,
tile_cols: 1,
head_tail: 0,
timing: EmitTiming::constant(10.0),
params: EmitParams::constant(particle_params_now()),
drag: 0.0,
tail_time: 0.0,
spline: None,
geometry_model: None,
recursion_model: None,
angular_velocity_min: [0.0; 3],
angular_velocity_max: [0.0; 3],
inherit_scale: 0.0,
follow_speed1: 0.0,
follow_scale1: 0.0,
follow_speed2: 0.0,
follow_scale2: 0.0,
twinkle_speed: 0.0,
twinkle_percent: 1.0,
twinkle_min: 0.0,
twinkle_max: 0.0,
spin: 0.0,
over_life: OverLife {
mid: 0.5,
color: [[1.0; 4]; 3],
scale: [1.0; 3],
head_cells: [CellRamp::new(0, 0); 2],
tail_cells: [CellRamp::new(0, 0); 2],
repeat: [1.0; 2],
},
}
}
/// The plain planar def most emitter tests want.
pub fn plain_particle_def() -> ParticleEmitterDef {
particle_def(ParticleShape::Plane)
}
/// A bind-pose [`crate::rig_anim::RigPose`] with one root-parented joint per entry, for tests
/// that resolve anchors (they spawn on demand through `RigPose::anchor_for`).
pub fn test_rig_pose(
root: bevy::ecs::entity::Entity,
joints: &[bevy::math::Vec3],
) -> crate::rig_anim::RigPose {
let skeleton = benilla_assets::ModelSkeleton {
joints: joints
.iter()
.map(|&t| benilla_assets::ModelJoint {
parent: -1,
local_translation: t,
billboard: None,
parent_arm: None,
})
.collect(),
spine_bone: None,
head_bone: None,
};
crate::rig_anim::RigPose::new(root, &skeleton)
}
}
pub mod art_scope;
pub mod assets;
pub mod bgwin;
pub mod billboard;
pub mod boot;
pub mod build_id;
pub mod clouds;
pub mod clutter;
pub mod collision;
pub mod decal;
pub mod dev_state;
pub mod doodad_anim;
pub mod entity_shade;
pub mod exterior_cull;
pub mod ffx_glow;
pub mod final_pass;
pub mod frame_pace;
pub mod ground_fx;
pub mod instance_tint;
pub mod interact;
pub mod interior;
pub mod lighting;
pub mod liquid;
pub mod log_ring;
/// macOS `Cmd+Q`, re-pointed at the window close so the shutdown systems run.
pub mod mac_quit;
pub mod map_proj;
pub mod mat_anim_table;
pub mod mesh_tag;
pub mod model_fade;
pub mod model_forms;
pub mod model_render;
pub mod modkeys;
pub mod particles;
pub mod ribbons;
pub mod ride_frame;
pub mod rig_anim;
pub mod rig_palette;
pub mod rig_rider;
pub mod schedule;
mod shaders;
pub mod sky;
// MONKEY (sky): the sky quality tier and the Enhanced/High inputs.
pub mod sky_fx;
pub mod sky_order;
pub mod skybox;
// MONKEY (skybox): the skybox rig and material loops.
mod skybox_anim;
pub mod static_gx;
pub mod static_merge;
pub mod straddle;
pub mod sun;
pub mod surface;
pub mod sys_timing;
pub mod terrain_stream;
pub mod tex_filter_live;
pub mod thread_qos;
pub mod view;
pub mod vis_chain;
pub mod water_fx;
pub mod wdl;
pub mod weather;
pub mod wind; // MONKEY (wind): shared stateless gust/veer field and foliage quality gate
pub mod wmo_portal;
pub mod world_census;
pub mod world_map;
pub mod world_plugins;
pub mod world_point;
pub mod world_unit;
pub mod worldview;
pub mod zfill;