use naga_oil::compose::ShaderDefValue;
use std::collections::HashMap;
const COMMON_WGSL: &str = include_str!("../shaders/common.wgsl");
pub(crate) fn native_render_defs() -> HashMap<String, ShaderDefValue> {
HashMap::from([
("SHADOWS".to_string(), ShaderDefValue::Bool(true)),
("SKELETON_GROUP".to_string(), ShaderDefValue::UInt(3)),
("INSTANCE_GROUP".to_string(), ShaderDefValue::UInt(4)),
])
}
#[allow(dead_code)] pub(crate) fn web_render_defs() -> HashMap<String, ShaderDefValue> {
HashMap::from([
("SKELETON_GROUP".to_string(), ShaderDefValue::UInt(2)),
("INSTANCE_GROUP".to_string(), ShaderDefValue::UInt(3)),
])
}
pub(crate) fn compose_wgsl(
source: &str,
label: &str,
shader_defs: HashMap<String, ShaderDefValue>,
) -> String {
use naga_oil::compose::{
ComposableModuleDescriptor, Composer, NagaModuleDescriptor, ShaderLanguage,
};
let mut composer = Composer::default();
composer
.add_composable_module(ComposableModuleDescriptor {
source: COMMON_WGSL,
file_path: "gizmo/common.wgsl",
language: ShaderLanguage::Wgsl,
..Default::default()
})
.unwrap_or_else(|e| panic!("composing common.wgsl failed: {e}"));
let module = composer
.make_naga_module(NagaModuleDescriptor {
source,
file_path: label,
shader_defs,
..Default::default()
})
.unwrap_or_else(|e| panic!("naga_oil compose of '{label}' failed: {e}"));
let info = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::all(),
)
.validate(&module)
.unwrap_or_else(|e| panic!("validating composed '{label}' failed: {e:?}"));
naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::empty())
.unwrap_or_else(|e| panic!("emitting WGSL for '{label}' failed: {e}"))
}
pub fn load_shader(
device: &wgpu::Device,
file_path: &str,
fallback_src: &str,
label: &str,
) -> wgpu::ShaderModule {
let source = std::fs::read_to_string(file_path).unwrap_or_else(|_| fallback_src.to_string());
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(source.into()),
})
}
pub fn load_shader_composed(
device: &wgpu::Device,
file_path: &str,
fallback_src: &str,
label: &str,
) -> wgpu::ShaderModule {
let source = std::fs::read_to_string(file_path).unwrap_or_else(|_| fallback_src.to_string());
let composed = compose_wgsl(&source, label, native_render_defs());
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(composed.into()),
})
}
#[cfg(target_arch = "wasm32")]
pub fn load_shader_composed_web(
device: &wgpu::Device,
fallback_src: &str,
label: &str,
) -> wgpu::ShaderModule {
let composed = compose_wgsl(fallback_src, label, web_render_defs());
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(composed.into()),
})
}
#[cfg(test)]
mod tests {
use super::*;
const SHADER_SRC: &str = include_str!("../shaders/shader.wgsl");
#[test]
fn web_compose_strips_shadows_and_shifts_groups() {
let web = compose_wgsl(SHADER_SRC, "shader.wgsl", web_render_defs());
assert!(
!web.contains("#import") && !web.contains("#ifdef") && !web.contains("#{"),
"web compose left unresolved naga_oil tokens"
);
assert!(!web.contains("var t_shadow"), "web variant must not declare t_shadow");
assert!(
!web.contains("textureSampleCompare"),
"web variant must not sample shadows"
);
assert!(!web.contains("@group(4)"), "web variant must not use @group(4)");
}
#[test]
fn native_compose_keeps_shadows_and_groups() {
let native = compose_wgsl(SHADER_SRC, "shader.wgsl", native_render_defs());
assert!(
!native.contains("#import") && !native.contains("#ifdef") && !native.contains("#{"),
"native compose left unresolved naga_oil tokens"
);
assert!(native.contains("var t_shadow"), "native variant must keep the shadow bindings");
assert!(
native.contains("textureSampleCompare"),
"native variant must keep shadow sampling"
);
assert!(native.contains("@group(4)"), "native variant keeps instance at group 4");
}
}