use utils::Extent;
use crate::{AccessPolicies, TextureViewTypes};
pub enum Sources<'a> {
SPIRV(&'a [u8]),
DXIL(&'a [u8]),
HLSL { source: &'a str, entry_point: &'a str },
MTLB {
binary: &'a [u8],
entry_point: &'a str,
threadgroup_size: Option<Extent>,
},
MTL { source: &'a str, entry_point: &'a str },
}
#[derive(Clone, Copy)]
pub enum ShaderSource<'a> {
#[cfg(target_os = "linux")]
Glsl(&'a str),
Msl { source: &'a str, entry_point: &'a str },
Hlsl { source: &'a str, entry_point: &'a str },
Platform {
glsl: &'a str,
msl: &'a str,
msl_entry_point: &'a str,
},
PlatformNative {
glsl: &'a str,
msl: &'a str,
msl_entry_point: &'a str,
hlsl: &'a str,
hlsl_entry_point: &'a str,
},
}
pub enum CompiledShaderSource {
SPIRV(Vec<u8>),
HLSL { source: String, entry_point: String },
MTL { source: String, entry_point: String },
}
impl CompiledShaderSource {
pub fn as_source(&self) -> Sources<'_> {
match self {
Self::SPIRV(binary) => Sources::SPIRV(binary.as_slice()),
Self::HLSL { source, entry_point } => Sources::HLSL {
source: source.as_str(),
entry_point: entry_point.as_str(),
},
Self::MTL { source, entry_point } => Sources::MTL {
source: source.as_str(),
entry_point: entry_point.as_str(),
},
}
}
}
pub fn compile(name: &str, source: ShaderSource) -> Result<CompiledShaderSource, String> {
match source {
#[cfg(target_os = "linux")]
ShaderSource::Glsl(source) => compile_glsl(name, source),
ShaderSource::Hlsl { source, entry_point } => Ok(CompiledShaderSource::HLSL {
source: source.to_string(),
entry_point: entry_point.to_string(),
}),
ShaderSource::Msl { source, entry_point } => Ok(CompiledShaderSource::MTL {
source: source.to_string(),
entry_point: entry_point.to_string(),
}),
ShaderSource::Platform {
glsl,
msl,
msl_entry_point,
} => {
if crate::implementation::USES_METAL {
Ok(CompiledShaderSource::MTL {
source: msl.to_string(),
entry_point: msl_entry_point.to_string(),
})
} else if crate::implementation::USES_VULKAN {
compile_glsl(name, glsl)
} else {
Err("Platform shader source does not include a native backend for this OS. The most likely cause is using GLSL/MSL-only source on DX12.".to_string())
}
}
ShaderSource::PlatformNative {
glsl,
msl,
msl_entry_point,
hlsl,
hlsl_entry_point,
} => {
if crate::implementation::USES_DX12 {
Ok(CompiledShaderSource::HLSL {
source: hlsl.to_string(),
entry_point: hlsl_entry_point.to_string(),
})
} else if crate::implementation::USES_METAL {
Ok(CompiledShaderSource::MTL {
source: msl.to_string(),
entry_point: msl_entry_point.to_string(),
})
} else if crate::implementation::USES_VULKAN {
compile_glsl(name, glsl)
} else {
Err("Platform-native shader source does not include a supported backend for this OS. The most likely cause is compiling on an unsupported operating system.".to_string())
}
}
}
}
#[cfg(target_os = "linux")]
fn compile_glsl(name: &str, source: &str) -> Result<CompiledShaderSource, String> {
resource_management::shader::glsl_compile::compile(source, name)
.map(|artifact| CompiledShaderSource::SPIRV(artifact.as_ref().to_vec()))
}
#[cfg(not(target_os = "linux"))]
fn compile_glsl(_name: &str, _source: &str) -> Result<CompiledShaderSource, String> {
Err(
"GLSL shader compilation requires a Vulkan backend (Linux only). Use a BESL shader for cross-platform support."
.to_string(),
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ResourceSlot(u32);
impl ResourceSlot {
pub const fn new(slot: u32) -> Self {
Self(slot)
}
pub const fn index(self) -> u32 {
self.0
}
}
impl From<u32> for ResourceSlot {
fn from(slot: u32) -> Self {
Self::new(slot)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResourceKind {
UniformBuffer,
StorageBuffer,
SampledImage,
CombinedImageSampler,
StorageImage,
InputAttachment,
Sampler,
AccelerationStructure,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ShaderResourceDescriptor {
pub(crate) slot: ResourceSlot,
pub(crate) kind: ResourceKind,
pub(crate) count: u32,
pub(crate) access: AccessPolicies,
pub(crate) texture_view_type: TextureViewTypes,
pub(crate) buffer_stride: u32,
}
impl ShaderResourceDescriptor {
pub const fn new(slot: ResourceSlot, kind: ResourceKind, count: u32, access: AccessPolicies) -> Self {
assert!(
count > 0,
"Invalid shader resource count. The most likely cause is that a shader declared an empty resource array."
);
assert!(
slot.index().checked_add(count).is_some(),
"Invalid shader resource slot range. The most likely cause is that a resource array extends beyond the flat slot namespace."
);
Self {
slot,
kind,
count,
access,
texture_view_type: TextureViewTypes::Texture2D,
buffer_stride: 4,
}
}
pub const fn single(slot: ResourceSlot, kind: ResourceKind, access: AccessPolicies) -> Self {
Self::new(slot, kind, 1, access)
}
pub const fn texture_view_type(mut self, texture_view_type: TextureViewTypes) -> Self {
self.texture_view_type = texture_view_type;
self
}
pub const fn buffer_stride(mut self, buffer_stride: u32) -> Self {
self.buffer_stride = buffer_stride;
self
}
pub const fn slot(self) -> ResourceSlot {
self.slot
}
pub const fn kind(self) -> ResourceKind {
self.kind
}
pub const fn count(self) -> u32 {
self.count
}
pub const fn access(self) -> AccessPolicies {
self.access
}
pub const fn texture_view(self) -> TextureViewTypes {
self.texture_view_type
}
pub const fn buffer_element_stride(self) -> u32 {
self.buffer_stride
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn platform_native_selects_backend_specific_shader_variant() {
let compiled = compile(
"platform-native",
ShaderSource::PlatformNative {
glsl: "#version 450\n#pragma shader_stage(compute)\nlayout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\nvoid main() {}",
msl: "kernel void main0() {}",
msl_entry_point: "main0",
hlsl: "[numthreads(1, 1, 1)] void main() {}",
hlsl_entry_point: "main",
},
)
.expect("Expected platform-native shader selection to compile.");
if crate::implementation::USES_DX12 {
assert!(matches!(
compiled,
CompiledShaderSource::HLSL { entry_point, .. } if entry_point == "main"
));
} else if crate::implementation::USES_METAL {
assert!(matches!(
compiled,
CompiledShaderSource::MTL { entry_point, .. } if entry_point == "main0"
));
} else {
assert!(matches!(compiled, CompiledShaderSource::SPIRV(binary) if !binary.is_empty()));
}
}
}