use crate::{
assets::upload::Asset,
wgpu::{backend::WGPUBackend, binding::BindingEntry},
};
pub struct ComputeDescriptor<'a> {
pub label: Option<&'a str>,
pub shader_source: &'a str,
pub entry_point: Option<&'a str>,
pub entries: Vec<BindingEntry>,
pub own_group: Option<u32>,
pub extra_layouts: Vec<super::layout::OwnedGroupLayout>,
}
impl<'a> Default for ComputeDescriptor<'a> {
fn default() -> Self {
Self {
label: None,
shader_source: "",
entry_point: Some("cs_main"),
entries: Vec::new(),
own_group: Some(0),
extra_layouts: Vec::new(),
}
}
}
pub fn build_compute(
device: &wgpu::Device,
desc: &ComputeDescriptor,
) -> (wgpu::ComputePipeline, wgpu::BindGroupLayout) {
for entry in &desc.entries {
if entry.kind.visibility() != wgpu::ShaderStages::COMPUTE {
panic!(
"compute pass{}: entry '{}' has visibility {:?} — compute bind group entries \
must be visible to exactly the compute stage",
desc.label.map(|l| format!(" '{l}'")).unwrap_or_default(),
entry.name,
entry.kind.visibility()
);
}
}
let layout = super::binding::build_bind_group_layout(device, desc.label, &desc.entries);
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.label,
source: wgpu::ShaderSource::Wgsl(desc.shader_source.into()),
});
let mut slots: Vec<super::layout::GroupLayout> = desc
.extra_layouts
.iter()
.map(|g| super::layout::GroupLayout { group: g.group, layout: &g.layout })
.collect();
if let Some(own_group) = desc.own_group {
slots.push(super::layout::GroupLayout { group: own_group, layout: &layout });
}
let bind_group_layouts = super::layout::assemble_bind_group_layouts(desc.label, slots);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: desc.label,
bind_group_layouts: &bind_group_layouts,
immediate_size: 0,
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: desc.label,
layout: Some(&pipeline_layout),
module: &module,
entry_point: desc.entry_point,
compilation_options: Default::default(),
cache: None,
});
(pipeline, layout)
}
pub struct GPUCompute {
pub pipeline: wgpu::ComputePipeline,
pub layout: wgpu::BindGroupLayout,
pub entries: Vec<BindingEntry>,
}
impl super::binding::BindGroupTarget for GPUCompute {
fn bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.layout
}
fn binding_entries(&self) -> &[BindingEntry] {
&self.entries
}
}
impl Asset<WGPUBackend> for GPUCompute {
type Source = ComputeDescriptor<'static>;
type Deps<'a> = ();
fn upload<'a>(source: &ComputeDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
let (pipeline, layout) = build_compute(&backend.device, source);
Some(Self {
pipeline,
layout,
entries: source.entries.to_vec(),
})
}
}
crate::wgpu::plugin_macros::asset_plugin! {
ComputePlugin, GPUCompute
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wgpu::binding::{BindingEntry, BindingKind};
use crate::wgpu::test_util::with_device;
const MINIMAL_COMPUTE_SHADER: &str = r#"
@compute @workgroup_size(1)
fn cs_main() {}
"#;
#[test]
fn a_fragment_visible_entry_panics_before_touching_the_device() {
with_device!(device, _queue, {
let desc = ComputeDescriptor {
shader_source: MINIMAL_COMPUTE_SHADER,
entries: vec![BindingEntry {
name: "bad",
binding: 0,
kind: BindingKind::sampler(wgpu::ShaderStages::FRAGMENT),
}],
..Default::default()
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
build_compute(&device, &desc);
}));
assert!(result.is_err(), "expected a panic for a non-COMPUTE-visible compute entry");
});
}
#[test]
fn a_vertex_fragment_visible_entry_also_panics() {
with_device!(device, _queue, {
let desc = ComputeDescriptor {
shader_source: MINIMAL_COMPUTE_SHADER,
entries: vec![BindingEntry {
name: "bad",
binding: 0,
kind: BindingKind::storage_buffer_read_write(
wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
),
}],
..Default::default()
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
build_compute(&device, &desc);
}));
assert!(result.is_err(), "expected a panic for a COMPUTE | FRAGMENT compute entry");
});
}
#[test]
fn a_compute_only_entry_builds_without_panicking() {
with_device!(device, _queue, {
let desc = ComputeDescriptor {
shader_source: MINIMAL_COMPUTE_SHADER,
entries: vec![],
own_group: None,
..Default::default()
};
build_compute(&device, &desc);
});
}
}