diff --git a/crates/skate-game/src/retail_depth.wgsl b/crates/skate-game/src/retail_depth.wgsl index 00394267..5d59ee0f 100644 --- a/crates/skate-game/src/retail_depth.wgsl +++ b/crates/skate-game/src/retail_depth.wgsl @@ -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, foliage_debug: vec4, surface: vec4, family: vec4, fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4, decal: vec4, water: array, 4> } +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var p: WorldParams; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d; +@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 diff --git a/crates/skate-game/src/retail_material_bindings.wgsl b/crates/skate-game/src/retail_material_bindings.wgsl deleted file mode 100644 index 02b385a6..00000000 --- a/crates/skate-game/src/retail_material_bindings.wgsl +++ /dev/null @@ -1,54 +0,0 @@ -#define_import_path skate_retail::material_bindings -struct WorldParams { - mode: vec4, foliage_debug: vec4, surface: vec4, family: vec4, - fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4, decal: vec4, water: array, 4>, -} -struct FrameState { shadow: vec4, clock: vec4, pca: array, 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 indices: array; -@group(#{MATERIAL_BIND_GROUP}) @binding(1) var samplers: binding_array; -@group(#{MATERIAL_BIND_GROUP}) @binding(5) var textures: binding_array>; -@group(#{MATERIAL_BIND_GROUP}) @binding(8) var cubes: binding_array>; -@group(#{MATERIAL_BIND_GROUP}) @binding(17) var params: array; -@group(#{MATERIAL_BIND_GROUP}) @binding(18) var frames: binding_array; -#else -@group(#{MATERIAL_BIND_GROUP}) @binding(0) var p: WorldParams; -@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(3) var lightmap: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(4) var lm_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(5) var normal_map: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(6) var normal_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(7) var detail_map: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(8) var detail_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(9) var macro_map: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(10) var macro_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(11) var decal_map: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(12) var decal_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(13) var specular_map: texture_2d; -@group(#{MATERIAL_BIND_GROUP}) @binding(14) var specular_sampler: sampler; -@group(#{MATERIAL_BIND_GROUP}) @binding(15) var environment_map: texture_cube; - -@group(#{MATERIAL_BIND_GROUP}) @binding(16) var frame_state: FrameState; - -#endif diff --git a/crates/skate-game/src/retail_render.rs b/crates/skate-game/src/retail_render.rs index 457b02d6..5fc1a56d 100644 --- a/crates/skate-game/src/retail_render.rs +++ b/crates/skate-game/src/retail_render.rs @@ -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>, #[texture(15, dimension = "cube")] pub environment: Option>, - #[storage(16, read_only, binding_array(18))] + #[storage(16, read_only)] pub shadow_state: Handle, 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 { diff --git a/crates/skate-game/src/retail_bindless_tests.rs b/crates/skate-game/src/retail_shader_tests.rs similarity index 70% rename from crates/skate-game/src/retail_bindless_tests.rs rename to crates/skate-game/src/retail_shader_tests.rs index 485fd5bc..214653c1 100644 --- a/crates/skate-game/src/retail_bindless_tests.rs +++ b/crates/skate-game/src/retail_shader_tests.rs @@ -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,viewport:vec4,world_position:vec3,padding:f32}\nstruct Light {flags:u32}\nstruct Lights {n_directional_lights:u32,directional_lights:array}\n@group(0) @binding(0) var view:View;\n@group(0) @binding(1) var lights:Lights;"), ("shadows", "#define_import_path bevy_pbr::shadows\nfn fetch_directional_shadow(id:u32,p:vec4,n:vec3,z:f32)->f32 {return 1.0;}"), ("motion", "#define_import_path bevy_pbr::pbr_prepass_functions\nfn calculate_motion_vector(p:vec4,q:vec4)->vec2 {return vec2(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"]); diff --git a/crates/skate-game/src/retail_world.wgsl b/crates/skate-game/src/retail_world.wgsl index 076d1e1d..04eada51 100644 --- a/crates/skate-game/src/retail_world.wgsl +++ b/crates/skate-game/src/retail_world.wgsl @@ -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, foliage_debug: vec4, surface: vec4, family: vec4, + fog_ramp: vec4, fog_color: vec4, shadow_color: vec4, sun_direction: vec4, decal: vec4, water: array, 4>, +} +@group(#{MATERIAL_BIND_GROUP}) @binding(0) var p: WorldParams; +@group(#{MATERIAL_BIND_GROUP}) @binding(1) var diffuse: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(2) var diffuse_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(3) var lightmap: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(4) var lm_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(5) var normal_map: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(6) var normal_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(7) var detail_map: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(8) var detail_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(9) var macro_map: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(10) var macro_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(11) var decal_map: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(12) var decal_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(13) var specular_map: texture_2d; +@group(#{MATERIAL_BIND_GROUP}) @binding(14) var specular_sampler: sampler; +@group(#{MATERIAL_BIND_GROUP}) @binding(15) var environment_map: texture_cube; + +struct FrameState { shadow: vec4, clock: vec4, pca: array, 7> } +@group(#{MATERIAL_BIND_GROUP}) @binding(16) var 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, scale: f32) -> vec2 { return vec2(uv.x*scale, 1.0-(1.0-uv.y)*scale); } -fn shade(i: VertexOutput, - p: WorldParams, - frame_state: FrameState, - diffuse: texture_2d, - diffuse_sampler: sampler, - lightmap: texture_2d, - lm_sampler: sampler, - normal_map: texture_2d, - normal_sampler: sampler, - detail_map: texture_2d, - detail_sampler: sampler, - macro_map: texture_2d, - macro_sampler: sampler, - decal_map: texture_2d, - decal_sampler: sampler, - specular_map: texture_2d, - specular_sampler: sampler, - environment_map: texture_cube -) -> vec4 { +@fragment +fn fragment(i: VertexOutput) -> @location(0) vec4 { 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(xe,alpha); } - -@fragment -fn fragment(i: VertexOutput) -> @location(0) vec4 { -#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 -} diff --git a/docs/cpu-material-binding-experiment.md b/docs/cpu-material-binding-experiment.md index 29c8ece6..b5a1f39b 100644 --- a/docs/cpu-material-binding-experiment.md +++ b/docs/cpu-material-binding-experiment.md @@ -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.