use super::error::{GpuError, GpuResult};
use super::DEFAULT_WORKGROUP_SIZE;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone)]
pub enum ShaderSource {
Wgsl(String),
SpirV(Vec<u32>),
}
impl ShaderSource {
#[must_use]
pub fn wgsl(source: impl Into<String>) -> Self {
Self::Wgsl(source.into())
}
#[must_use]
pub fn spirv(bytecode: Vec<u32>) -> Self {
Self::SpirV(bytecode)
}
#[must_use]
pub fn is_wgsl(&self) -> bool {
matches!(self, Self::Wgsl(_))
}
#[must_use]
pub fn is_spirv(&self) -> bool {
matches!(self, Self::SpirV(_))
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Wgsl(s) => s.len(),
Self::SpirV(v) => v.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone)]
pub struct ShaderModuleDescriptor {
pub source: ShaderSource,
pub label: Option<String>,
}
impl ShaderModuleDescriptor {
#[must_use]
pub fn wgsl(source: impl Into<String>) -> Self {
Self {
source: ShaderSource::wgsl(source),
label: None,
}
}
#[must_use]
pub fn spirv(bytecode: Vec<u32>) -> Self {
Self {
source: ShaderSource::spirv(bytecode),
label: None,
}
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn validate(&self) -> GpuResult<()> {
if self.source.is_empty() {
return Err(GpuError::shader("Shader source cannot be empty"));
}
Ok(())
}
}
#[derive(Debug)]
pub struct ShaderModule {
id: u64,
source_type: ShaderSourceType,
label: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShaderSourceType {
Wgsl,
SpirV,
}
impl ShaderModule {
#[allow(clippy::items_after_statements)]
pub fn new(descriptor: ShaderModuleDescriptor) -> GpuResult<Self> {
descriptor.validate()?;
static MODULE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
let source_type = if descriptor.source.is_wgsl() {
ShaderSourceType::Wgsl
} else {
ShaderSourceType::SpirV
};
Ok(Self {
id: MODULE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
source_type,
label: descriptor.label,
})
}
#[must_use]
pub fn id(&self) -> u64 {
self.id
}
#[must_use]
pub fn source_type(&self) -> ShaderSourceType {
self.source_type
}
#[must_use]
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BindingType {
StorageBuffer,
ReadOnlyStorageBuffer,
UniformBuffer,
}
impl BindingType {
#[must_use]
pub fn is_storage(&self) -> bool {
matches!(self, Self::StorageBuffer | Self::ReadOnlyStorageBuffer)
}
#[must_use]
pub fn is_read_only(&self) -> bool {
matches!(self, Self::ReadOnlyStorageBuffer | Self::UniformBuffer)
}
}
#[derive(Debug, Clone)]
pub struct BindGroupLayoutEntry {
pub binding: u32,
pub binding_type: BindingType,
pub optional: bool,
}
impl BindGroupLayoutEntry {
#[must_use]
pub fn storage_buffer(binding: u32) -> Self {
Self {
binding,
binding_type: BindingType::StorageBuffer,
optional: false,
}
}
#[must_use]
pub fn read_only_storage_buffer(binding: u32) -> Self {
Self {
binding,
binding_type: BindingType::ReadOnlyStorageBuffer,
optional: false,
}
}
#[must_use]
pub fn uniform_buffer(binding: u32) -> Self {
Self {
binding,
binding_type: BindingType::UniformBuffer,
optional: false,
}
}
#[must_use]
pub fn optional(mut self) -> Self {
self.optional = true;
self
}
}
#[derive(Debug, Clone)]
pub struct BindGroupLayoutDescriptor {
pub entries: Vec<BindGroupLayoutEntry>,
pub label: Option<String>,
}
impl BindGroupLayoutDescriptor {
#[must_use]
pub fn new(entries: Vec<BindGroupLayoutEntry>) -> Self {
Self {
entries,
label: None,
}
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn validate(&self) -> GpuResult<()> {
let mut seen = std::collections::HashSet::new();
for entry in &self.entries {
if !seen.insert(entry.binding) {
return Err(GpuError::pipeline(format!(
"Duplicate binding index: {}",
entry.binding
)));
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct BindGroupLayout {
id: u64,
entry_count: usize,
label: Option<String>,
}
impl BindGroupLayout {
#[allow(clippy::items_after_statements)]
pub fn new(descriptor: BindGroupLayoutDescriptor) -> GpuResult<Self> {
descriptor.validate()?;
static LAYOUT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
Ok(Self {
id: LAYOUT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
entry_count: descriptor.entries.len(),
label: descriptor.label,
})
}
#[must_use]
pub fn id(&self) -> u64 {
self.id
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entry_count
}
#[must_use]
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}
#[derive(Debug)]
pub struct ComputePipelineDescriptor {
pub shader_module_id: u64,
pub entry_point: String,
pub bind_group_layout_ids: Vec<u64>,
pub label: Option<String>,
}
impl ComputePipelineDescriptor {
#[must_use]
pub fn new(shader_module_id: u64, entry_point: impl Into<String>) -> Self {
Self {
shader_module_id,
entry_point: entry_point.into(),
bind_group_layout_ids: Vec::new(),
label: None,
}
}
#[must_use]
pub fn with_bind_group_layout(mut self, layout_id: u64) -> Self {
self.bind_group_layout_ids.push(layout_id);
self
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn validate(&self) -> GpuResult<()> {
if self.entry_point.is_empty() {
return Err(GpuError::pipeline("Entry point cannot be empty"));
}
Ok(())
}
}
#[derive(Debug)]
pub struct ComputePipeline {
id: u64,
shader_module_id: u64,
entry_point: String,
bind_group_count: usize,
label: Option<String>,
}
impl ComputePipeline {
#[allow(clippy::items_after_statements)]
pub fn new(descriptor: ComputePipelineDescriptor) -> GpuResult<Self> {
descriptor.validate()?;
static PIPELINE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
Ok(Self {
id: PIPELINE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
shader_module_id: descriptor.shader_module_id,
entry_point: descriptor.entry_point,
bind_group_count: descriptor.bind_group_layout_ids.len(),
label: descriptor.label,
})
}
#[must_use]
pub fn id(&self) -> u64 {
self.id
}
#[must_use]
pub fn shader_module_id(&self) -> u64 {
self.shader_module_id
}
#[must_use]
pub fn entry_point(&self) -> &str {
&self.entry_point
}
#[must_use]
pub fn bind_group_count(&self) -> usize {
self.bind_group_count
}
#[must_use]
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WorkgroupDimensions {
pub x: u32,
pub y: u32,
pub z: u32,
}
impl Default for WorkgroupDimensions {
fn default() -> Self {
Self { x: 1, y: 1, z: 1 }
}
}
impl WorkgroupDimensions {
#[must_use]
pub fn new_1d(x: u32) -> Self {
Self { x, y: 1, z: 1 }
}
#[must_use]
pub fn new_2d(x: u32, y: u32) -> Self {
Self { x, y, z: 1 }
}
#[must_use]
pub fn new_3d(x: u32, y: u32, z: u32) -> Self {
Self { x, y, z }
}
#[must_use]
pub fn total(&self) -> u64 {
u64::from(self.x) * u64::from(self.y) * u64::from(self.z)
}
#[must_use]
pub fn is_1d(&self) -> bool {
self.y == 1 && self.z == 1
}
#[must_use]
pub fn is_2d(&self) -> bool {
self.z == 1 && !self.is_1d()
}
#[must_use]
pub fn is_3d(&self) -> bool {
!self.is_1d() && !self.is_2d()
}
}
#[derive(Debug, Clone)]
pub struct ComputeDispatch {
pub pipeline_id: u64,
pub workgroups: WorkgroupDimensions,
pub workgroup_size: u32,
}
impl ComputeDispatch {
#[must_use]
pub fn new(pipeline_id: u64, workgroups: WorkgroupDimensions) -> Self {
Self {
pipeline_id,
workgroups,
workgroup_size: DEFAULT_WORKGROUP_SIZE,
}
}
#[must_use]
pub fn for_elements(pipeline_id: u64, elements: u32) -> Self {
let workgroups = elements.div_ceil(DEFAULT_WORKGROUP_SIZE);
Self {
pipeline_id,
workgroups: WorkgroupDimensions::new_1d(workgroups),
workgroup_size: DEFAULT_WORKGROUP_SIZE,
}
}
#[must_use]
pub fn with_workgroup_size(mut self, size: u32) -> Self {
self.workgroup_size = size;
self
}
#[must_use]
pub fn total_threads(&self) -> u64 {
self.workgroups.total() * u64::from(self.workgroup_size)
}
}
#[derive(Debug, Clone)]
pub struct BufferBinding {
pub buffer_id: u64,
pub offset: u64,
pub size: Option<u64>,
}
impl BufferBinding {
#[must_use]
pub fn new(buffer_id: u64) -> Self {
Self {
buffer_id,
offset: 0,
size: None,
}
}
#[must_use]
pub fn with_range(buffer_id: u64, offset: u64, size: u64) -> Self {
Self {
buffer_id,
offset,
size: Some(size),
}
}
#[must_use]
pub fn at_offset(mut self, offset: u64) -> Self {
self.offset = offset;
self
}
#[must_use]
pub fn with_size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
}
#[derive(Debug, Clone)]
pub struct BindGroupEntry {
pub binding: u32,
pub resource: BufferBinding,
}
impl BindGroupEntry {
#[must_use]
pub fn new(binding: u32, buffer_id: u64) -> Self {
Self {
binding,
resource: BufferBinding::new(buffer_id),
}
}
#[must_use]
pub fn with_buffer(binding: u32, resource: BufferBinding) -> Self {
Self { binding, resource }
}
}
#[derive(Debug, Clone)]
pub struct BindGroupDescriptor {
pub layout_id: u64,
pub entries: Vec<BindGroupEntry>,
pub label: Option<String>,
}
impl BindGroupDescriptor {
#[must_use]
pub fn new(layout_id: u64, entries: Vec<BindGroupEntry>) -> Self {
Self {
layout_id,
entries,
label: None,
}
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn validate(&self) -> GpuResult<()> {
let mut seen = std::collections::HashSet::new();
for entry in &self.entries {
if !seen.insert(entry.binding) {
return Err(GpuError::pipeline(format!(
"Duplicate binding in bind group: {}",
entry.binding
)));
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct BindGroup {
id: u64,
layout_id: u64,
entry_count: usize,
label: Option<String>,
}
impl BindGroup {
#[allow(clippy::items_after_statements)]
pub fn new(descriptor: BindGroupDescriptor) -> GpuResult<Self> {
descriptor.validate()?;
static BIND_GROUP_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
Ok(Self {
id: BIND_GROUP_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
layout_id: descriptor.layout_id,
entry_count: descriptor.entries.len(),
label: descriptor.label,
})
}
#[must_use]
pub fn id(&self) -> u64 {
self.id
}
#[must_use]
pub fn layout_id(&self) -> u64 {
self.layout_id
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entry_count
}
#[must_use]
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}