#![allow(
dead_code,
reason = "False positives as this module is used at build time."
)]
use naga::{Arena, GlobalVariable, back::glsl::ReflectionInfo};
use std::collections::BTreeMap;
#[derive(Debug)]
pub(crate) struct ReflectionMap {
texture_mapping: BTreeMap<String, String>,
uniforms: BTreeMap<String, String>,
}
impl ReflectionMap {
pub(crate) fn new(info: ReflectionInfo, global_vars: &Arena<GlobalVariable>) -> Self {
debug_assert_eq!(info.varying.len(), 0, "unimplemented");
debug_assert_eq!(info.immediates_items.len(), 0, "unimplemented");
let mut texture_mapping = BTreeMap::default();
let mut uniforms = BTreeMap::default();
for (glsl_name, texture_handles) in info.texture_mapping {
if let Ok(wgsl_var) = global_vars.try_get(texture_handles.texture)
&& let Some(wgsl_name) = &wgsl_var.name
{
texture_mapping.insert(wgsl_name.clone(), glsl_name);
}
}
for (handle, glsl_name) in info.uniforms {
if let Ok(wgsl_var) = global_vars.try_get(handle)
&& let Some(wgsl_name) = &wgsl_var.name
{
uniforms.insert(wgsl_name.clone(), glsl_name);
}
}
Self {
texture_mapping,
uniforms,
}
}
}
#[derive(Debug)]
pub(crate) struct Stage {
pub(crate) source: String,
pub(crate) reflection_map: ReflectionMap,
}
#[derive(Debug)]
pub(crate) struct CompiledGlsl {
pub(crate) vertex: Stage,
pub(crate) fragment: Stage,
}
impl CompiledGlsl {
pub(crate) fn to_generated_code(&self, shader_name: &str) -> String {
let mut code = format!("/// Compiled glsl for `{shader_name}.wgsl`\n");
code.push_str(&format!("pub mod {shader_name} {{\n"));
code.push_str(r#" #![allow(missing_docs, reason="No metadata to generate precise documentation for generated code.")]"#);
code.push_str("\n\n");
code.push_str(" pub const VERTEX_SOURCE: &str = r###\"");
code.push_str(&self.vertex.source);
code.push_str("\"###;\n\n");
if self.vertex.reflection_map.uniforms.len()
+ self.vertex.reflection_map.texture_mapping.len()
!= 0
{
code.push_str(" pub mod vertex {\n");
for (wgsl_name, glsl_name) in &self.vertex.reflection_map.uniforms {
let const_name = wgsl_name.to_uppercase();
code.push_str(&format!(
" pub const {const_name}: &str = \"{glsl_name}\";\n"
));
}
for (wgsl_name, glsl_name) in &self.vertex.reflection_map.texture_mapping {
let const_name = wgsl_name.to_uppercase();
code.push_str(&format!(
" pub const {const_name}: &str = \"{glsl_name}\";\n"
));
}
code.push_str(" }\n");
}
code.push_str(" pub const FRAGMENT_SOURCE: &str = r###\"");
code.push_str(&self.fragment.source);
code.push_str("\"###;\n");
if self.fragment.reflection_map.uniforms.len()
+ self.fragment.reflection_map.texture_mapping.len()
!= 0
{
code.push_str(" pub mod fragment {\n");
for (wgsl_name, glsl_name) in &self.fragment.reflection_map.uniforms {
let const_name = wgsl_name.to_uppercase();
code.push_str(&format!(
" pub const {const_name}: &str = \"{glsl_name}\";\n"
));
}
for (wgsl_name, glsl_name) in &self.fragment.reflection_map.texture_mapping {
let const_name = wgsl_name.to_uppercase();
code.push_str(&format!(
" pub const {const_name}: &str = \"{glsl_name}\";\n"
));
}
code.push_str(" }\n");
}
code.push('}');
code
}
}
#[cfg(test)]
mod tests {
use naga::{
ShaderStage,
back::glsl::{self, PipelineOptions, Version},
front::wgsl,
proc::BoundsCheckPolicies,
valid::{Capabilities, ValidationFlags, Validator},
};
use super::ReflectionMap;
const EXAMPLE_WGSL_SHADER: &str = r#"
struct Config {
width: u32,
}
struct StripInstance {
@location(0) xy: u32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
};
@group(0) @binding(1)
var<uniform> config: Config;
@vertex
fn vs_main(
@builtin(vertex_index) in_vertex_index: u32,
instance: StripInstance,
) -> VertexOutput {
var out: VertexOutput;
let width = config.width;
return out;
}
@group(0) @binding(0)
var alphas_texture: texture_2d<u32>;
@group(0) @binding(2)
var clip_input_texture: texture_2d<f32>;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let tex1_size = textureDimensions(alphas_texture);
let tex2_size = textureDimensions(clip_input_texture);
let width = config.width;
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}
"#;
#[test]
fn construct_reflection_map_from_vertex() {
let module = wgsl::parse_str(EXAMPLE_WGSL_SHADER).unwrap();
let info = Validator::new(ValidationFlags::all(), Capabilities::default())
.validate(&module)
.unwrap();
let options = glsl::Options {
version: Version::Embedded {
version: 300,
is_webgl: true,
},
..Default::default()
};
let pipeline_options = PipelineOptions {
entry_point: "vs_main".into(),
shader_stage: ShaderStage::Vertex,
multiview: None,
};
let mut glsl_vs = String::new();
let mut w = glsl::Writer::new(
&mut glsl_vs,
&module,
&info,
&options,
&pipeline_options,
BoundsCheckPolicies::default(),
)
.unwrap();
let reflection_info = w.write().unwrap();
let result = ReflectionMap::new(reflection_info, &module.global_variables);
assert_eq!(result.uniforms.len(), 1);
assert_eq!(
result.uniforms.get("config"),
Some(&"Config_block_0Vertex".into())
);
assert_eq!(result.texture_mapping.len(), 0);
}
#[test]
fn construct_reflection_map_from_fragment() {
let module = wgsl::parse_str(EXAMPLE_WGSL_SHADER).unwrap();
let info = Validator::new(ValidationFlags::all(), Capabilities::default())
.validate(&module)
.unwrap();
let options = glsl::Options {
version: Version::Embedded {
version: 300,
is_webgl: true,
},
..Default::default()
};
let pipeline_options = PipelineOptions {
entry_point: "fs_main".into(),
shader_stage: ShaderStage::Fragment,
multiview: None,
};
let mut glsl_vs = String::new();
let mut w = glsl::Writer::new(
&mut glsl_vs,
&module,
&info,
&options,
&pipeline_options,
BoundsCheckPolicies::default(),
)
.unwrap();
let reflection_info = w.write().unwrap();
let result = ReflectionMap::new(reflection_info, &module.global_variables);
assert_eq!(result.uniforms.len(), 1);
assert_eq!(
result.uniforms.get("config"),
Some(&"Config_block_0Fragment".into())
);
assert_eq!(result.texture_mapping.len(), 2);
assert_eq!(
result.texture_mapping.get("alphas_texture"),
Some(&"_group_0_binding_0_fs".into())
);
assert_eq!(
result.texture_mapping.get("clip_input_texture"),
Some(&"_group_0_binding_2_fs".into())
);
}
}