Restore original material path after v6 native crash

This commit is contained in:
chazmm committed 2026-09-09 17:50:09 +01:00
1 parent 46ae9834f8
commit 0b89cb8f47
6 files changed
+55 -164

No files matched your search

+5 -13
View File
@@ -5,25 +5,17 @@
#ifdef MOTION_VECTOR_PREPASS
#import bevy_pbr::pbr_prepass_functions::calculate_motion_vector
#endif
#import skate_retail::material_bindings as bindings
#ifdef BINDLESS
#import bevy_pbr::mesh_bindings::mesh
#endif
struct WorldParams { mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>, fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, decal: vec4<f32>, water: array<vec4<f32>, 4> }
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> p: WorldParams;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler;
@fragment
fn fragment(i: VertexOutput)
#ifdef PREPASS_FRAGMENT
-> FragmentOutput
#endif
{
#ifdef BINDLESS
let index = bindings::indices[mesh[i.instance_index].material_and_lightmap_bind_group_slot & 0xffffu];
let alpha = textureSample(bindings::textures[index.diffuse],bindings::samplers[index.diffuse_sampler],i.uv).a;
let cutoff = bindings::params[index.params].mode.z;
#else
let alpha = textureSample(bindings::diffuse,bindings::diffuse_sampler,i.uv).a;
let cutoff = bindings::p.mode.z;
#endif
if alpha < cutoff { discard; }
if textureSample(diffuse,diffuse_sampler,i.uv).a < p.mode.z { discard; }
#ifdef PREPASS_FRAGMENT
var out: FragmentOutput;
#ifdef NORMAL_PREPASS
@@ -1,54 +0,0 @@
#define_import_path skate_retail::material_bindings
struct WorldParams {
mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>,
fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, decal: vec4<f32>, water: array<vec4<f32>, 4>,
}
struct FrameState { shadow: vec4<f32>, clock: vec4<f32>, pca: array<vec4<f32>, 7> }
#ifdef BINDLESS
struct MaterialIndices {
params: u32,
diffuse: u32,
diffuse_sampler: u32,
lightmap: u32,
lm_sampler: u32,
normal_map: u32,
normal_sampler: u32,
detail_map: u32,
detail_sampler: u32,
macro_map: u32,
macro_sampler: u32,
decal_map: u32,
decal_sampler: u32,
specular_map: u32,
specular_sampler: u32,
environment_map: u32,
frame_state: u32,
}
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<storage> indices: array<MaterialIndices>;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var samplers: binding_array<sampler>;
@group(#{MATERIAL_BIND_GROUP}) @binding(5) var textures: binding_array<texture_2d<f32>>;
@group(#{MATERIAL_BIND_GROUP}) @binding(8) var cubes: binding_array<texture_cube<f32>>;
@group(#{MATERIAL_BIND_GROUP}) @binding(17) var<storage> params: array<WorldParams>;
@group(#{MATERIAL_BIND_GROUP}) @binding(18) var<storage> frames: binding_array<FrameState>;
#else
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> p: WorldParams;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(3) var lightmap: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(4) var lm_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(5) var normal_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(6) var normal_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(7) var detail_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(8) var detail_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(9) var macro_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(10) var macro_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(11) var decal_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(12) var decal_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(13) var specular_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(14) var specular_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(15) var environment_map: texture_cube<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(16) var<storage, read> frame_state: FrameState;
#endif
+4 -9
View File
@@ -12,8 +12,8 @@ use std::collections::BTreeMap;
pub(crate) struct RetailRenderPlugin;
#[cfg(test)]
#[path = "retail_bindless_tests.rs"]
mod bindless_tests;
#[path = "retail_shader_tests.rs"]
mod shader_tests;
// Use the same path derivation as embedded_asset!: alternate binary targets
// have a different crate namespace even though they share these source files.
fn retail_shader(path: &str) -> ShaderRef {
@@ -31,7 +31,6 @@ impl Plugin for RetailRenderPlugin {
eprintln!("SKATE_FOLIAGE_DEBUG: solid cyan tree-wall cards, magenta other foliage; alpha rejection disabled for foliage only");
}
embedded_asset!(app, "retail_world.wgsl");
bevy::shader::load_shader_library!(app, "retail_material_bindings.wgsl");
embedded_asset!(app, "retail_tone.wgsl");
embedded_asset!(app, "retail_depth.wgsl");
embedded_asset!(app, "retail_sky.wgsl");
@@ -114,9 +113,8 @@ pub(crate) struct WorldParams {
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
#[bind_group_data(RetailKey)]
#[data(0, WorldParams, binding_array(17))]
#[bindless(limit(64))]
pub(crate) struct RetailWorldMaterial {
#[uniform(0)]
pub params: WorldParams,
#[texture(1)]
#[sampler(2)]
@@ -141,14 +139,11 @@ pub(crate) struct RetailWorldMaterial {
pub specular: Option<Handle<Image>>,
#[texture(15, dimension = "cube")]
pub environment: Option<Handle<Image>>,
#[storage(16, read_only, binding_array(18))]
#[storage(16, read_only)]
pub shadow_state: Handle<bevy::render::storage::ShaderStorageBuffer>,
pub alpha: AlphaMode,
pub two_sided: bool,
}
impl From<&RetailWorldMaterial> for WorldParams {
fn from(material: &RetailWorldMaterial) -> Self { material.params.clone() }
}
/// Identity of the actual GPU inputs, independent of unused source metadata.
#[derive(PartialEq, Eq, Hash)]
pub(crate) struct WorldMaterialKey {
@@ -1,26 +1,14 @@
//! GPU-free validation of the actual material shaders and binding-array access.
//! GPU-free validation of the original retail material shaders.
//! Bevy vertex interfaces are real; unrelated lighting functions/resources use
//! interface fixtures. Runtime pipeline/device validation remains a user check.
use naga_oil::compose::{Composer, ComposableModuleDescriptor, NagaModuleDescriptor, ShaderDefValue};
use std::collections::HashMap;
#[test]
fn material_index_table_matches_shader_resources() {
use bevy::render::render_resource::{AsBindGroup, BindlessResourceType as R};
let descriptor = super::RetailWorldMaterial::bindless_descriptor().unwrap();
assert_eq!(descriptor.resources.as_ref(), &[
R::DataBuffer, R::Texture2d, R::SamplerFiltering,
R::Texture2d, R::SamplerFiltering, R::Texture2d, R::SamplerFiltering,
R::Texture2d, R::SamplerFiltering, R::Texture2d, R::SamplerFiltering,
R::Texture2d, R::SamplerFiltering, R::Texture2d, R::SamplerFiltering,
R::TextureCube, R::Buffer,
]);
assert_eq!(descriptor.index_tables.len(), 1);
let table = &descriptor.index_tables[0];
assert_eq!((table.binding_number.0, table.indices.start.0, table.indices.end.0), (0, 0, 17));
let mut buffers: Vec<_> = descriptor.buffers.iter().map(|b| (b.bindless_index.0, b.binding_number.0)).collect();
buffers.sort_unstable();
assert_eq!(buffers, [(0, 17), (16, 18)]);
fn retail_material_uses_original_bindings() {
use bevy::render::render_resource::AsBindGroup;
assert!(super::RetailWorldMaterial::bindless_descriptor().is_none());
assert!(super::RetailWorldMaterial::bindless_slot_count().is_none());
}
fn validate(bindless: bool, prepass: bool, extras: &[&str]) {
@@ -37,7 +25,6 @@ fn validate(bindless: bool, prepass: bool, extras: &[&str]) {
("frame", "#define_import_path bevy_pbr::mesh_view_bindings\nstruct View {view_from_world:mat4x4<f32>,viewport:vec4<f32>,world_position:vec3<f32>,padding:f32}\nstruct Light {flags:u32}\nstruct Lights {n_directional_lights:u32,directional_lights:array<Light,10>}\n@group(0) @binding(0) var<uniform> view:View;\n@group(0) @binding(1) var<storage> lights:Lights;"),
("shadows", "#define_import_path bevy_pbr::shadows\nfn fetch_directional_shadow(id:u32,p:vec4<f32>,n:vec3<f32>,z:f32)->f32 {return 1.0;}"),
("motion", "#define_import_path bevy_pbr::pbr_prepass_functions\nfn calculate_motion_vector(p:vec4<f32>,q:vec4<f32>)->vec2<f32> {return vec2<f32>(0.0);}"),
("bindings",include_str!("retail_material_bindings.wgsl")),
];
for (path,source) in fixtures {
if let Err(error) = composer.add_composable_module(ComposableModuleDescriptor {source,file_path:path,shader_defs:defs.clone(),..Default::default()}) {
@@ -51,8 +38,8 @@ fn validate(bindless: bool, prepass: bool, extras: &[&str]) {
}
#[test]
fn material_shaders_validate_with_bindless_and_fallback_bindings() {
for bindless in [false,true] {
fn original_material_shaders_validate() {
for bindless in [false] {
validate(bindless,false,&[]);
validate(bindless,true,&[]);
validate(bindless,true,&["PREPASS_FRAGMENT","NORMAL_PREPASS","NORMAL_PREPASS_OR_DEFERRED_PREPASS","MOTION_VECTOR_PREPASS","UNCLIPPED_DEPTH_ORTHO_EMULATION"]);
+25 -68
View File
@@ -1,11 +1,29 @@
#import bevy_pbr::{forward_io::VertexOutput, mesh_view_bindings as frame}
#import bevy_pbr::shadows::fetch_directional_shadow
#import skate_retail::material_bindings::{WorldParams, FrameState}
#import skate_retail::material_bindings as bindings
#ifdef BINDLESS
#import bevy_pbr::mesh_bindings::mesh
#endif
struct WorldParams {
mode: vec4<f32>, foliage_debug: vec4<f32>, surface: vec4<f32>, family: vec4<f32>,
fog_ramp: vec4<f32>, fog_color: vec4<f32>, shadow_color: vec4<f32>, sun_direction: vec4<f32>, decal: vec4<f32>, water: array<vec4<f32>, 4>,
}
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> p: WorldParams;
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(3) var lightmap: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(4) var lm_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(5) var normal_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(6) var normal_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(7) var detail_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(8) var detail_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(9) var macro_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(10) var macro_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(11) var decal_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(12) var decal_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(13) var specular_map: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(14) var specular_sampler: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(15) var environment_map: texture_cube<f32>;
struct FrameState { shadow: vec4<f32>, clock: vec4<f32>, pca: array<vec4<f32>, 7> }
@group(#{MATERIAL_BIND_GROUP}) @binding(16) var<storage, read> frame_state: FrameState;
// Textures and base UVs both have V flipped by the exporter. Scale in the
// authored coordinate system, then return to the flipped texture rows.
@@ -13,25 +31,8 @@ fn scaled_uv(uv: vec2<f32>, scale: f32) -> vec2<f32> {
return vec2<f32>(uv.x*scale, 1.0-(1.0-uv.y)*scale);
}
fn shade(i: VertexOutput,
p: WorldParams,
frame_state: FrameState,
diffuse: texture_2d<f32>,
diffuse_sampler: sampler,
lightmap: texture_2d<f32>,
lm_sampler: sampler,
normal_map: texture_2d<f32>,
normal_sampler: sampler,
detail_map: texture_2d<f32>,
detail_sampler: sampler,
macro_map: texture_2d<f32>,
macro_sampler: sampler,
decal_map: texture_2d<f32>,
decal_sampler: sampler,
specular_map: texture_2d<f32>,
specular_sampler: sampler,
environment_map: texture_cube<f32>
) -> vec4<f32> {
@fragment
fn fragment(i: VertexOutput) -> @location(0) vec4<f32> {
let fam = u32(p.mode.x);
let flags = u32(p.mode.y);
var diffuse_uv=i.uv;
@@ -268,47 +269,3 @@ fn shade(i: VertexOutput,
if a.a < p.mode.z { discard; }
return vec4<f32>(xe,alpha);
}
@fragment
fn fragment(i: VertexOutput) -> @location(0) vec4<f32> {
#ifdef BINDLESS
let index = bindings::indices[mesh[i.instance_index].material_and_lightmap_bind_group_slot & 0xffffu];
return shade(i,
bindings::params[index.params],
bindings::frames[index.frame_state],
bindings::textures[index.diffuse],
bindings::samplers[index.diffuse_sampler],
bindings::textures[index.lightmap],
bindings::samplers[index.lm_sampler],
bindings::textures[index.normal_map],
bindings::samplers[index.normal_sampler],
bindings::textures[index.detail_map],
bindings::samplers[index.detail_sampler],
bindings::textures[index.macro_map],
bindings::samplers[index.macro_sampler],
bindings::textures[index.decal_map],
bindings::samplers[index.decal_sampler],
bindings::textures[index.specular_map],
bindings::samplers[index.specular_sampler],
bindings::cubes[index.environment_map]);
#else
return shade(i,
bindings::p,
bindings::frame_state,
bindings::diffuse,
bindings::diffuse_sampler,
bindings::lightmap,
bindings::lm_sampler,
bindings::normal_map,
bindings::normal_sampler,
bindings::detail_map,
bindings::detail_sampler,
bindings::macro_map,
bindings::macro_sampler,
bindings::decal_map,
bindings::decal_sampler,
bindings::specular_map,
bindings::specular_sampler,
bindings::environment_map);
#endif
}
+14
View File
@@ -1,5 +1,19 @@
# CPU material binding experiment
**Withdrawn after the user's runtime test.** Build `bea625f` exited with native
access violation `0xC0000005` about 11.4 seconds after launch, shortly after map
initialization, on an RTX 5090 using Vulkan. The report contains no native stack
or module map, so it cannot establish the faulting subsystem or blame the driver.
The binding-array change is treated as a suspected regression, not a confirmed
root cause. Passing Naga tests did not establish runtime safety.
The recovery revision restores the exact pre-v6 main/depth shader source and
original uniform, texture and storage bindings. It removes the array shader
library and adds a regression assertion that RetailWorldMaterial has no bindless
descriptor or slot count. The v5 shadow proxies remain disabled. Recovery does
not claim an FPS improvement or a confirmed crash fix until the user runs it.
The remaining sections describe the withdrawn experiment for reference.
The user reports poor FPS while looking into University and good FPS looking out
of the map, still CPU bound. The map being resident does not establish that every
triangle is drawn. The captured CPU render work is the actionable bottleneck.