pub(crate) mod glsl;
pub(crate) mod lower;
use crate::error::{Error, Result};
use crate::types::Interpolation;
use super::{GpuShaderDesc, UniformValue};
pub(crate) use lower::TextureSource;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WgslUniformType {
Float,
Int,
Bool,
Float3,
FloatArray(u32),
IntArray(u32),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WgslUniform {
pub name: String,
pub ty: WgslUniformType,
pub offset: u32,
pub size: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WgslTextureFormat {
R32Float,
Rgba32Float,
}
impl WgslTextureFormat {
pub fn bytes_per_texel(&self) -> u32 {
match self {
WgslTextureFormat::R32Float => 4,
WgslTextureFormat::Rgba32Float => 16,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WgslTextureBinding {
pub texture_name: String,
pub sampler_name: String,
pub group: u32,
pub binding: u32,
pub dimensions: u32,
pub width: u32,
pub height: u32,
pub depth: u32,
pub format: WgslTextureFormat,
pub interpolation: Interpolation,
}
impl WgslTextureBinding {
pub fn texel_data(&self, values: &[f32]) -> Result<Vec<u8>> {
let texels = self.width as usize * self.height as usize * self.depth as usize;
let mut out = Vec::with_capacity(texels * self.format.bytes_per_texel() as usize);
match self.format {
WgslTextureFormat::R32Float => {
if values.len() < texels {
return Err(Error::msg(format!(
"The texture '{}' needs {texels} values, {} given.",
self.texture_name,
values.len()
)));
}
for v in &values[..texels] {
out.extend_from_slice(&v.to_le_bytes());
}
}
WgslTextureFormat::Rgba32Float => {
if values.len() < texels * 3 {
return Err(Error::msg(format!(
"The texture '{}' needs {} values, {} given.",
self.texture_name,
texels * 3,
values.len()
)));
}
for rgb in values[..texels * 3].as_chunks::<3>().0 {
for v in rgb.iter().chain(std::iter::once(&1.0f32)) {
out.extend_from_slice(&v.to_le_bytes());
}
}
}
}
Ok(out)
}
pub fn bytes_per_row(&self) -> u32 {
self.width * self.format.bytes_per_texel()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct WgslBindings {
pub group: u32,
pub uniform_binding: Option<u32>,
pub uniform_buffer_size: u32,
pub uniforms: Vec<WgslUniform>,
pub textures: Vec<WgslTextureBinding>,
}
impl WgslBindings {
pub fn uniform(&self, name: &str) -> Option<&WgslUniform> {
self.uniforms.iter().find(|u| u.name == name)
}
pub fn texture(&self, texture_name: &str) -> Option<&WgslTextureBinding> {
self.textures
.iter()
.find(|t| t.texture_name == texture_name)
}
pub fn write_uniform_buffer(&self, desc: &GpuShaderDesc, buffer: &mut [u8]) -> Result<()> {
if buffer.len() < self.uniform_buffer_size as usize {
return Err(Error::msg(format!(
"The uniform buffer is too small: {} < {} bytes.",
buffer.len(),
self.uniform_buffer_size
)));
}
for u in &self.uniforms {
let data = (0..desc.num_uniforms())
.filter_map(|i| desc.uniform(i).ok())
.find(|(name, _)| *name == u.name)
.map(|(_, d)| d)
.ok_or_else(|| Error::msg(format!("Unknown uniform '{}'.", u.name)))?;
let at = u.offset as usize;
let put = |buffer: &mut [u8], off: usize, bytes: [u8; 4]| {
buffer[off..off + 4].copy_from_slice(&bytes);
};
match (&data.value, u.ty) {
(UniformValue::Double(g), WgslUniformType::Float) => {
put(buffer, at, (g() as f32).to_le_bytes());
}
(UniformValue::Bool(g), WgslUniformType::Bool) => {
put(buffer, at, i32::from(g()).to_le_bytes());
}
(UniformValue::Float3(g), WgslUniformType::Float3) => {
for (k, v) in g().iter().enumerate() {
put(buffer, at + 4 * k, v.to_le_bytes());
}
}
(UniformValue::VectorFloat(size, g), WgslUniformType::FloatArray(n)) => {
let values = g();
let used = (size().max(0) as usize).min(values.len()).min(n as usize);
buffer[at..at + u.size as usize].fill(0);
for (k, v) in values[..used].iter().enumerate() {
put(buffer, at + 16 * k, v.to_le_bytes());
}
}
(UniformValue::VectorInt(size, g), WgslUniformType::IntArray(n)) => {
let values = g();
let used = (size().max(0) as usize).min(values.len()).min(n as usize);
buffer[at..at + u.size as usize].fill(0);
for (k, v) in values[..used].iter().enumerate() {
put(buffer, at + 16 * k, v.to_le_bytes());
}
}
(v, t) => {
return Err(Error::msg(format!(
"The uniform '{}' ({v:?}) does not match its WGSL type {t:?}.",
u.name
)))
}
}
}
Ok(())
}
pub fn uniform_buffer(&self, desc: &GpuShaderDesc) -> Result<Vec<u8>> {
let mut buffer = vec![0u8; self.uniform_buffer_size as usize];
self.write_uniform_buffer(desc, &mut buffer)?;
Ok(buffer)
}
}
pub(crate) fn translate(
source: &str,
function_name: &str,
group: u32,
textures: &[TextureSource],
) -> Result<(String, WgslBindings)> {
let input = lower::LowerInput {
source,
function_name,
group,
textures,
};
let (body, bindings) = lower::lower(&input)?;
let mut code = lower::bindings_comment(&bindings);
code.push('\n');
code.push_str(&body);
Ok((code, bindings))
}