use std::{
any::TypeId,
collections::HashMap,
ops::{Deref, DerefMut},
sync::{Arc, LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
use wgsl_rs_ir as ir;
pub use std::marker::PhantomData;
pub use wgsl_rs_macros::{
Wgsl, builtin, compute, fragment, ptr, sampler, storage, texture, uniform, vertex, wgsl_allow,
wgsl_ignore, workgroup, workgroup_size,
};
pub use crate::{discard, get, get_mut, load, slab_copy};
mod atomic;
mod bitcast;
mod derivative;
mod matrix;
mod numeric;
mod packing;
#[cfg(feature = "dispatch-runtime")]
pub mod runtime;
mod synchronization;
mod texture;
mod vector;
pub use atomic::*;
pub use bitcast::*;
pub use derivative::*;
pub use matrix::*;
pub use numeric::*;
pub use packing::*;
#[cfg(feature = "dispatch-runtime")]
pub use runtime::*;
pub use synchronization::*;
pub use texture::*;
pub use vector::*;
#[rustfmt::skip] #[allow(unused_imports)]
#[crate::wgsl(crate_path = crate)]
pub mod builtin_constants {
use crate::std::builtin_matrix_constants::*;
use crate::std::builtin_vector_constants::*;
}
pub enum Error {}
pub trait AnySendSync: std::any::Any + Send + Sync {}
impl<T: std::any::Any + Send + Sync> AnySendSync for T {}
pub trait Wgsl: AnySendSync {
fn to_ir() -> wgsl_rs_ir::Type;
}
pub trait WgslScalar: Wgsl {
fn to_scalar_ir() -> wgsl_rs_ir::ScalarType;
}
pub trait WgslTextureScalar: WgslScalar {}
macro_rules! impl_scalar_wgsl {
($t:ty, $i:ident) => {
impl WgslScalar for $t {
fn to_scalar_ir() -> ir::ScalarType {
ir::ScalarType::$i
}
}
impl Wgsl for $t {
fn to_ir() -> ir::Type {
ir::Type::Scalar(<$t>::to_scalar_ir())
}
}
};
}
macro_rules! impl_texture_scalar_wgsl {
($t:ty, $i:ident) => {
impl_scalar_wgsl!($t, $i);
impl WgslTextureScalar for $t {}
};
}
impl_texture_scalar_wgsl!(f32, F32);
impl_texture_scalar_wgsl!(u32, U32);
impl_texture_scalar_wgsl!(i32, I32);
impl_scalar_wgsl!(bool, Bool);
impl<T: Wgsl, const N: usize> Wgsl for [T; N] {
fn to_ir() -> ir::Type {
ir::Type::Array {
elem: Box::new(T::to_ir()),
len: ir::Expr::Lit(ir::Lit::Int {
digits: N.to_string(),
suffix: String::new(),
}),
}
}
}
pub struct ModuleVarReadGuard<'a, T> {
inner: RwLockReadGuard<'a, TypeMap>,
_phantom: PhantomData<T>,
}
impl<T: AnySendSync> Deref for ModuleVarReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
let box_any = self
.inner
.get(&TypeId::of::<T>())
.unwrap_or_else(|| panic!("Accessed an uninitialized module variable"));
box_any
.downcast_ref()
.unwrap_or_else(|| panic!("Value was not of type {}", std::any::type_name::<T>()))
}
}
pub struct ModuleVarWriteGuard<'a, T> {
inner: RwLockWriteGuard<'a, TypeMap>,
_phantom: PhantomData<T>,
}
impl<T: AnySendSync> Deref for ModuleVarWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
.get(&TypeId::of::<T>())
.unwrap_or_else(|| panic!("Accessed an uninitialized module variable"))
.downcast_ref()
.unwrap_or_else(|| panic!("Value was not of type {}", std::any::type_name::<T>()))
}
}
impl<T: AnySendSync> DerefMut for ModuleVarWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
.get_mut(&TypeId::of::<T>())
.unwrap_or_else(|| panic!("Mutably accessed an uninitialized module variable"))
.downcast_mut()
.unwrap_or_else(|| panic!("Value was not of type {}", std::any::type_name::<T>()))
}
}
type TypeMap = HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>;
pub struct WgslTypeVariable;
impl Wgsl for WgslTypeVariable {
fn to_ir() -> wgsl_rs_ir::Type {
ir::Type::TypeParam {
name: "T".to_string(),
}
}
}
struct ModuleVar<T = WgslTypeVariable> {
inner: LazyLock<Arc<RwLock<TypeMap>>>,
_phantom: PhantomData<T>,
}
impl ModuleVar {
pub fn get<T: AnySendSync>(&self) -> ModuleVarReadGuard<'_, T> {
let lock = self
.inner
.read()
.unwrap_or_else(|_| panic!("could not acquire a read lock on a module variable"));
ModuleVarReadGuard {
inner: lock,
_phantom: PhantomData,
}
}
pub fn get_mut<T: Wgsl>(&self) -> ModuleVarWriteGuard<'_, T> {
let lock = self
.inner
.write()
.unwrap_or_else(|_| panic!("could not acquire a write lock on a module variable"));
ModuleVarWriteGuard {
inner: lock,
_phantom: PhantomData,
}
}
pub fn set_typed<T: Wgsl>(&self, data: T) {
let mut map = self
.inner
.write()
.unwrap_or_else(|_| panic!("could not acquire a write lock on a module variable"));
map.insert(TypeId::of::<T>(), Box::new(data));
}
}
impl<T: AnySendSync> ModuleVar<T> {
pub const fn new() -> Self {
Self {
inner: LazyLock::new(Default::default),
_phantom: PhantomData,
}
}
fn guard_on_generic_use(&self) {
if TypeId::of::<T>() == TypeId::of::<WgslTypeVariable>() {
panic!(
"Requested a generic module variable. All runtime use of generic variables must \
be monomorphized."
);
}
}
pub fn read(&self) -> ModuleVarReadGuard<'_, T> {
self.guard_on_generic_use();
let lock = self
.inner
.read()
.unwrap_or_else(|_| panic!("could not acquire a read lock on a module variable"));
ModuleVarReadGuard {
inner: lock,
_phantom: PhantomData,
}
}
pub fn write(&self) -> ModuleVarWriteGuard<'_, T> {
self.guard_on_generic_use();
let lock = self
.inner
.write()
.unwrap_or_else(|_| panic!("could not acquire a write lock on a module variable"));
ModuleVarWriteGuard {
inner: lock,
_phantom: PhantomData,
}
}
pub fn set(&self, data: T) {
self.guard_on_generic_use();
let mut map = self
.inner
.write()
.unwrap_or_else(|_| panic!("could not acquire a write lock on a module variable"));
map.insert(TypeId::of::<T>(), Box::new(data));
}
}
pub struct Workgroup<T = WgslTypeVariable> {
data: ModuleVar<T>,
}
impl<T: Wgsl> Workgroup<T> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
data: ModuleVar::new(),
}
}
pub fn get(&self) -> ModuleVarReadGuard<'_, T> {
self.data.read()
}
pub fn get_mut(&self) -> ModuleVarWriteGuard<'_, T> {
self.data.write()
}
pub fn set(&self, data: T) {
self.data.set(data);
}
}
impl Workgroup<WgslTypeVariable> {
pub fn get_typed<T: Wgsl>(&self) -> ModuleVarReadGuard<'_, T> {
self.data.get::<T>()
}
pub fn get_mut_typed<T: Wgsl>(&self) -> ModuleVarWriteGuard<'_, T> {
self.data.get_mut::<T>()
}
pub fn set_typed<T: Wgsl>(&self, data: T) {
self.data.set_typed::<T>(data);
}
}
pub struct Uniform<T = WgslTypeVariable> {
pub group: u32,
pub binding: u32,
data: ModuleVar<T>,
}
impl<T: Wgsl> Uniform<T> {
pub const fn new(group: u32, binding: u32) -> Self {
Self {
group,
binding,
data: ModuleVar::new(),
}
}
pub fn get(&self) -> ModuleVarReadGuard<'_, T> {
self.data.read()
}
pub fn set(&self, data: T) {
self.data.set(data);
}
}
impl Uniform<WgslTypeVariable> {
pub fn get_typed<T: Wgsl>(&self) -> ModuleVarReadGuard<'_, T> {
self.data.get::<T>()
}
pub fn set_typed<T: Wgsl>(&self, data: T) {
self.data.set_typed::<T>(data);
}
}
pub struct Read;
pub struct Write;
pub struct ReadWrite;
pub struct Storage<T = WgslTypeVariable, AM = Read> {
group: u32,
binding: u32,
access_mode: PhantomData<AM>,
data: ModuleVar<T>,
}
pub trait AccessMode {}
impl AccessMode for Read {}
impl AccessMode for ReadWrite {}
impl<T: Wgsl, AM: AccessMode> Storage<T, AM> {
pub const fn new(group: u32, binding: u32) -> Self {
Storage {
data: ModuleVar::new(),
group,
binding,
access_mode: PhantomData,
}
}
pub fn get(&self) -> ModuleVarReadGuard<'_, T> {
self.data.read()
}
pub fn get_mut(&self) -> ModuleVarWriteGuard<'_, T> {
self.data.write()
}
pub fn set(&self, data: T) {
self.data.set(data);
}
pub fn group(&self) -> u32 {
self.group
}
pub fn binding(&self) -> u32 {
self.binding
}
}
impl<AM: AccessMode> Storage<WgslTypeVariable, AM> {
pub fn get_typed<T: Wgsl>(&self) -> ModuleVarReadGuard<'_, T> {
self.data.get::<T>()
}
pub fn get_mut_typed<T: Wgsl>(&self) -> ModuleVarWriteGuard<'_, T> {
self.data.get_mut::<T>()
}
pub fn set_typed<T: Wgsl>(&self, data: T) {
self.data.set_typed::<T>(data);
}
}
#[macro_export]
macro_rules! get {
($var:ident) => {
$var.get()
};
($var:ident, $ty:ty) => {
$var.get_typed::<$ty>()
};
}
#[macro_export]
macro_rules! get_mut {
($var:ident) => {
$var.get_mut()
};
($var:ident, $ty:ty) => {
$var.get_mut_typed::<$ty>()
};
}
#[macro_export]
macro_rules! load {
($var:ident) => {{
let v = *$var.get();
v
}};
($var:ident, $ty:ty) => {{
let v = *$var.get_typed::<$ty>();
v
}};
}
#[macro_export]
macro_rules! slab_copy {
($src:expr, $src_offset:expr, $dest:expr, $dest_offset:expr, $size:expr) => {{
let src_offset = $src_offset as usize;
let dest_offset = $dest_offset as usize;
for i in 0..$size as usize {
$dest[dest_offset + i] = $src[src_offset + i];
}
}};
}
#[macro_export]
macro_rules! discard {
() => {
$crate::std::mark_discarded()
};
}
#[inline]
pub fn mark_discarded() {
#[cfg(feature = "dispatch-runtime")]
{
runtime::mark_discarded();
}
}
pub trait Convert<T> {
fn convert(self) -> T;
}
impl<A: Clone + Convert<B>, B> Convert<B> for &A {
fn convert(self) -> B {
let a = self.clone();
a.convert()
}
}
impl Convert<f32> for f32 {
fn convert(self) -> f32 {
self
}
}
impl Convert<u32> for u32 {
fn convert(self) -> u32 {
self
}
}
impl Convert<i32> for i32 {
fn convert(self) -> i32 {
self
}
}
impl<A: Clone + Convert<B> + Wgsl, B> Convert<B> for ModuleVarReadGuard<'_, A> {
fn convert(self) -> B {
(*self).clone().convert()
}
}
impl<A: Clone + Convert<B> + Wgsl, B> Convert<B> for ModuleVarWriteGuard<'_, A> {
fn convert(self) -> B {
(*self).clone().convert()
}
}
macro_rules! impl_convert_as {
($from:ty, $to:ty) => {
impl Convert<$to> for $from {
fn convert(self) -> $to {
self as $to
}
}
};
}
impl_convert_as!(f32, u32);
impl_convert_as!(f32, i32);
impl_convert_as!(i32, f32);
impl_convert_as!(i32, u32);
impl_convert_as!(u32, f32);
impl_convert_as!(u32, i32);
pub fn f32(t: impl Convert<f32>) -> f32 {
t.convert()
}
pub fn u32(t: impl Convert<u32>) -> u32 {
t.convert()
}
pub fn i32(t: impl Convert<i32>) -> i32 {
t.convert()
}
#[derive(Debug, Clone, Default)]
pub struct RuntimeArray<T> {
pub data: std::vec::Vec<T>,
}
impl<T: Wgsl> Wgsl for RuntimeArray<T> {
fn to_ir() -> ir::Type {
ir::Type::RuntimeArray {
elem: Box::new(T::to_ir()),
}
}
}
impl<T> RuntimeArray<T> {
pub fn new() -> Self {
Self {
data: std::vec::Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: std::vec::Vec::with_capacity(capacity),
}
}
pub fn len(&self) -> u32 {
self.data.len() as u32
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn push(&mut self, value: T) {
self.data.push(value);
}
pub fn clear(&mut self) {
self.data.clear();
}
}
impl<T> std::ops::Index<usize> for RuntimeArray<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.data[index]
}
}
impl<T> std::ops::IndexMut<usize> for RuntimeArray<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.data[index]
}
}
impl<T> std::ops::Index<u32> for RuntimeArray<T> {
type Output = T;
fn index(&self, index: u32) -> &Self::Output {
&self.data[index as usize]
}
}
impl<T> std::ops::IndexMut<u32> for RuntimeArray<T> {
fn index_mut(&mut self, index: u32) -> &mut Self::Output {
&mut self.data[index as usize]
}
}
pub trait ArrayLength {
fn array_length(self) -> u32;
}
impl<T> ArrayLength for &RuntimeArray<T> {
fn array_length(self) -> u32 {
self.data.len() as u32
}
}
pub fn array_length(array: impl ArrayLength) -> u32 {
array.array_length()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_copies_concrete_module_var_values() {
static U: Uniform<f32> = Uniform::new(0, 0);
static S: Storage<f32, Read> = Storage::new(0, 1);
static W: Workgroup<f32> = Workgroup::new();
U.set(1.5_f32);
S.set(2.5_f32);
W.set(3.5_f32);
assert_eq!(load!(U), 1.5_f32);
assert_eq!(load!(S), 2.5_f32);
assert_eq!(load!(W), 3.5_f32);
}
#[test]
fn load_typed_copies_generic_module_var_values() {
static G: Uniform = Uniform::new(0, 0);
G.set_typed(4.5_f32);
assert_eq!(load!(G, f32), 4.5_f32);
}
}