use crate::backend::{GpuBackend, PipelineHandle, BindGroupLayoutHandle};
use crate::bind_group::BindGroupLayout;
use crate::device::Device;
use crate::shader::ShaderModule;
use crate::types::*;
use anyhow::Result;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
pub struct RenderPipelineDesc<'a> {
pub vertex_layout: VertexBufferLayout,
pub topology: PrimitiveTopology,
pub target_format: TextureFormat,
pub bind_group_layouts: &'a [&'a BindGroupLayout],
pub depth_stencil: Option<DepthStencilState>,
}
impl<'a> std::fmt::Debug for RenderPipelineDesc<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RenderPipelineDesc")
.field("vertex_layout", &self.vertex_layout)
.field("topology", &self.topology)
.field("target_format", &self.target_format)
.field("bind_group_layouts_count", &self.bind_group_layouts.len())
.field("depth_stencil", &self.depth_stencil)
.finish()
}
}
impl Default for VertexBufferLayout {
fn default() -> Self {
Vertex2D::layout()
}
}
impl Default for TextureFormat {
fn default() -> Self {
TextureFormat::Rgba8Unorm
}
}
pub struct RenderPipeline {
backend: Arc<Mutex<Box<dyn GpuBackend>>>,
pub(crate) handle: PipelineHandle,
}
impl RenderPipeline {
pub fn new(
device: &Device,
vertex_shader: &ShaderModule,
fragment_shader: &ShaderModule,
desc: &RenderPipelineDesc,
) -> Result<Self> {
let mut backend = device.backend.lock().unwrap();
let layout_handles: Vec<BindGroupLayoutHandle> = desc
.bind_group_layouts
.iter()
.map(|l| l.handle)
.collect();
let handle = if desc.depth_stencil.is_some() || !desc.bind_group_layouts.is_empty() {
backend.create_pipeline_with_depth(
device.handle,
vertex_shader.handle,
fragment_shader.handle,
&desc.vertex_layout,
desc.topology,
desc.target_format,
&layout_handles,
desc.depth_stencil.as_ref(),
)?
} else {
backend.create_pipeline(
device.handle,
vertex_shader.handle,
fragment_shader.handle,
&desc.vertex_layout,
desc.topology,
desc.target_format,
)?
};
Ok(Self {
backend: Arc::clone(&device.backend),
handle,
})
}
}
impl Drop for RenderPipeline {
fn drop(&mut self) {
let mut backend = self.backend.lock().unwrap();
backend.destroy_pipeline(self.handle);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::mock::MockBackend;
use crate::bind_group::{BindGroupLayout, BindGroupLayoutBinding};
fn create_test_device() -> Device {
Device::from_backend(Box::new(MockBackend::new())).unwrap()
}
fn create_test_shader(device: &Device) -> ShaderModule {
ShaderModule::from_slang(device, "mock shader source").unwrap()
}
#[test]
fn test_render_pipeline_desc_default() {
let desc = RenderPipelineDesc::default();
assert_eq!(desc.target_format, TextureFormat::Rgba8Unorm);
assert!(desc.bind_group_layouts.is_empty());
assert!(desc.depth_stencil.is_none());
}
#[test]
fn test_render_pipeline_desc_debug() {
let desc = RenderPipelineDesc {
target_format: TextureFormat::Rgba8Unorm,
..Default::default()
};
let debug_str = format!("{:?}", desc);
assert!(debug_str.contains("RenderPipelineDesc"));
assert!(debug_str.contains("Rgba8Unorm"));
}
#[test]
fn test_simple_pipeline_creation() {
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc::default(),
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_custom_format() {
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
target_format: TextureFormat::Bgra8UnormSrgb,
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_topology() {
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
topology: PrimitiveTopology::LineList,
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_bind_group_layout() {
let device = create_test_device();
let shader = create_test_shader(&device);
let layout = BindGroupLayout::new(&device, &[
BindGroupLayoutBinding::uniform(0),
]).unwrap();
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
bind_group_layouts: &[&layout],
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_multiple_bind_group_layouts() {
let device = create_test_device();
let shader = create_test_shader(&device);
let layout0 = BindGroupLayout::new(&device, &[
BindGroupLayoutBinding::uniform(0),
]).unwrap();
let layout1 = BindGroupLayout::new(&device, &[
BindGroupLayoutBinding::storage(0, true),
BindGroupLayoutBinding::storage(1, false),
]).unwrap();
let layout2 = BindGroupLayout::new(&device, &[
BindGroupLayoutBinding::texture(0),
BindGroupLayoutBinding::sampler(1),
]).unwrap();
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
bind_group_layouts: &[&layout0, &layout1, &layout2],
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_depth_stencil() {
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
depth_stencil: Some(DepthStencilState {
format: DepthFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: CompareFunction::Less,
}),
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_bind_group_layouts_and_depth() {
let device = create_test_device();
let shader = create_test_shader(&device);
let layout = BindGroupLayout::new(&device, &[
BindGroupLayoutBinding::uniform(0),
]).unwrap();
let pipeline = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
bind_group_layouts: &[&layout],
depth_stencil: Some(DepthStencilState {
format: DepthFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: CompareFunction::LessEqual,
}),
..Default::default()
},
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_pipeline_with_vertex_layout() {
use crate::types::Vertex2D;
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline_2d = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
vertex_layout: Vertex2D::layout(),
..Default::default()
},
).unwrap();
assert!(pipeline_2d.handle > 0);
}
#[test]
fn test_pipeline_different_vertex_fragment_shaders() {
let device = create_test_device();
let vertex_shader = create_test_shader(&device);
let fragment_shader = create_test_shader(&device);
let pipeline = RenderPipeline::new(
&device,
&vertex_shader,
&fragment_shader,
&RenderPipelineDesc::default(),
).unwrap();
assert!(pipeline.handle > 0);
}
#[test]
fn test_multiple_pipelines() {
let device = create_test_device();
let shader = create_test_shader(&device);
let pipeline1 = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
target_format: TextureFormat::Rgba8Unorm,
..Default::default()
},
).unwrap();
let pipeline2 = RenderPipeline::new(
&device,
&shader,
&shader,
&RenderPipelineDesc {
target_format: TextureFormat::Bgra8UnormSrgb,
..Default::default()
},
).unwrap();
assert_ne!(pipeline1.handle, pipeline2.handle);
}
}