use crate::TextureFormat;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VertexFormat {
Float32,
Float32x2,
Float32x3,
Float32x4,
Sint32,
Sint32x2,
Sint32x3,
Sint32x4,
Uint32,
Uint32x2,
Uint32x3,
Uint32x4,
Unorm8x4,
}
impl VertexFormat {
pub fn size(&self) -> u32 {
match self {
Self::Float32 | Self::Sint32 | Self::Uint32 => 4,
Self::Float32x2 | Self::Sint32x2 | Self::Uint32x2 => 8,
Self::Float32x3 | Self::Sint32x3 | Self::Uint32x3 => 12,
Self::Float32x4 | Self::Sint32x4 | Self::Uint32x4 | Self::Unorm8x4 => 16,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VertexAttribute {
pub location: u32,
pub offset: u32,
pub format: VertexFormat,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VertexBufferLayout {
pub stride: u32,
pub step_mode: VertexStepMode,
pub attributes: Vec<VertexAttribute>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum VertexStepMode {
#[default]
Vertex,
Instance,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PrimitiveTopology {
PointList,
LineList,
LineStrip,
#[default]
TriangleList,
TriangleStrip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FrontFace {
#[default]
Ccw,
Cw,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CullMode {
#[default]
None,
Front,
Back,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PolygonMode {
#[default]
Fill,
Line,
Point,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct PrimitiveState {
pub topology: PrimitiveTopology,
pub front_face: FrontFace,
pub cull_mode: CullMode,
pub polygon_mode: PolygonMode,
pub strip_index_format: Option<IndexFormat>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IndexFormat {
Uint16,
Uint32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BlendFactor {
Zero,
#[default]
One,
Src,
OneMinusSrc,
SrcAlpha,
OneMinusSrcAlpha,
Dst,
OneMinusDst,
DstAlpha,
OneMinusDstAlpha,
SrcAlphaSaturated,
Constant,
OneMinusConstant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BlendOperation {
#[default]
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlendComponent {
pub src_factor: BlendFactor,
pub dst_factor: BlendFactor,
pub operation: BlendOperation,
}
impl Default for BlendComponent {
fn default() -> Self {
Self {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::Zero,
operation: BlendOperation::Add,
}
}
}
impl BlendComponent {
pub const ALPHA_BLENDING: Self = Self {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
};
pub const PREMULTIPLIED_ALPHA: Self = Self {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
};
pub const ADDITIVE: Self = Self {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlendState {
pub color: BlendComponent,
pub alpha: BlendComponent,
}
impl Default for BlendState {
fn default() -> Self {
Self {
color: BlendComponent::default(),
alpha: BlendComponent::default(),
}
}
}
impl BlendState {
pub const ALPHA_BLENDING: Self = Self {
color: BlendComponent::ALPHA_BLENDING,
alpha: BlendComponent::ALPHA_BLENDING,
};
pub const PREMULTIPLIED_ALPHA: Self = Self {
color: BlendComponent::PREMULTIPLIED_ALPHA,
alpha: BlendComponent::PREMULTIPLIED_ALPHA,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColorWriteMask(u32);
impl ColorWriteMask {
pub const RED: Self = Self(1);
pub const GREEN: Self = Self(2);
pub const BLUE: Self = Self(4);
pub const ALPHA: Self = Self(8);
pub const ALL: Self = Self(15);
pub const NONE: Self = Self(0);
pub fn contains(&self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
}
impl Default for ColorWriteMask {
fn default() -> Self {
Self::ALL
}
}
impl std::ops::BitOr for ColorWriteMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ColorTargetState {
pub format: TextureFormat,
pub blend: Option<BlendState>,
pub write_mask: ColorWriteMask,
}
impl ColorTargetState {
pub fn new(format: TextureFormat) -> Self {
Self {
format,
blend: None,
write_mask: ColorWriteMask::ALL,
}
}
pub fn with_blend(mut self, blend: BlendState) -> Self {
self.blend = Some(blend);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CompareFunction {
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
#[default]
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum StencilOperation {
#[default]
Keep,
Zero,
Replace,
IncrementClamp,
DecrementClamp,
Invert,
IncrementWrap,
DecrementWrap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct StencilFaceState {
pub compare: CompareFunction,
pub fail_op: StencilOperation,
pub depth_fail_op: StencilOperation,
pub pass_op: StencilOperation,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DepthStencilState {
pub format: TextureFormat,
pub depth_write_enabled: bool,
pub depth_compare: CompareFunction,
pub stencil_front: StencilFaceState,
pub stencil_back: StencilFaceState,
pub stencil_read_mask: u32,
pub stencil_write_mask: u32,
pub depth_bias: i32,
pub depth_bias_slope_scale: f32,
pub depth_bias_clamp: f32,
}
impl Default for DepthStencilState {
fn default() -> Self {
Self {
format: TextureFormat::Depth24Stencil8,
depth_write_enabled: true,
depth_compare: CompareFunction::Less,
stencil_front: StencilFaceState::default(),
stencil_back: StencilFaceState::default(),
stencil_read_mask: 0xFF,
stencil_write_mask: 0xFF,
depth_bias: 0,
depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MultisampleState {
pub count: u32,
pub mask: u64,
pub alpha_to_coverage_enabled: bool,
}
impl Default for MultisampleState {
fn default() -> Self {
Self {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
}
}
}
#[derive(Debug, Clone)]
pub struct RenderPipelineDescriptor {
pub label: Option<String>,
pub vertex_shader: String,
pub fragment_shader: String,
pub vertex_entry: String,
pub fragment_entry: String,
pub vertex_buffers: Vec<VertexBufferLayout>,
pub primitive: PrimitiveState,
pub depth_stencil: Option<DepthStencilState>,
pub multisample: MultisampleState,
pub color_targets: Vec<ColorTargetState>,
}
impl RenderPipelineDescriptor {
pub fn new(vertex_shader: &str, fragment_shader: &str) -> Self {
Self {
label: None,
vertex_shader: vertex_shader.to_string(),
fragment_shader: fragment_shader.to_string(),
vertex_entry: "vs_main".to_string(),
fragment_entry: "fs_main".to_string(),
vertex_buffers: Vec::new(),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
color_targets: Vec::new(),
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_vertex_buffer(mut self, layout: VertexBufferLayout) -> Self {
self.vertex_buffers.push(layout);
self
}
pub fn with_color_target(mut self, target: ColorTargetState) -> Self {
self.color_targets.push(target);
self
}
pub fn with_primitive(mut self, primitive: PrimitiveState) -> Self {
self.primitive = primitive;
self
}
pub fn with_depth_stencil(mut self, state: DepthStencilState) -> Self {
self.depth_stencil = Some(state);
self
}
pub fn with_multisample(mut self, state: MultisampleState) -> Self {
self.multisample = state;
self
}
}
#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor {
pub label: Option<String>,
pub shader: String,
pub entry_point: String,
}
impl ComputePipelineDescriptor {
pub fn new(shader: &str) -> Self {
Self {
label: None,
shader: shader.to_string(),
entry_point: "cs_main".to_string(),
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_entry_point(mut self, entry: impl Into<String>) -> Self {
self.entry_point = entry.into();
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PipelineKey {
pub vertex_shader_hash: u64,
pub fragment_shader_hash: u64,
pub vertex_layout_hash: u64,
pub color_formats: Vec<TextureFormat>,
pub depth_format: Option<TextureFormat>,
pub sample_count: u32,
pub blend_hash: u64,
}
fn hash_str(s: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}
impl PipelineKey {
pub fn from_descriptor(desc: &RenderPipelineDescriptor) -> Self {
use std::hash::{Hash, Hasher};
let vertex_shader_hash = hash_str(&desc.vertex_shader);
let fragment_shader_hash = hash_str(&desc.fragment_shader);
let mut layout_hasher = std::collections::hash_map::DefaultHasher::new();
for buf in &desc.vertex_buffers {
buf.stride.hash(&mut layout_hasher);
for attr in &buf.attributes {
attr.location.hash(&mut layout_hasher);
attr.offset.hash(&mut layout_hasher);
}
}
let vertex_layout_hash = layout_hasher.finish();
let mut blend_hasher = std::collections::hash_map::DefaultHasher::new();
for target in &desc.color_targets {
if let Some(blend) = &target.blend {
(blend.color.src_factor as u32).hash(&mut blend_hasher);
(blend.color.dst_factor as u32).hash(&mut blend_hasher);
}
}
let blend_hash = blend_hasher.finish();
Self {
vertex_shader_hash,
fragment_shader_hash,
vertex_layout_hash,
color_formats: desc.color_targets.iter().map(|t| t.format).collect(),
depth_format: desc.depth_stencil.as_ref().map(|ds| ds.format),
sample_count: desc.multisample.count,
blend_hash,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vertex_format_size() {
assert_eq!(VertexFormat::Float32.size(), 4);
assert_eq!(VertexFormat::Float32x2.size(), 8);
assert_eq!(VertexFormat::Float32x3.size(), 12);
assert_eq!(VertexFormat::Float32x4.size(), 16);
}
#[test]
fn test_blend_state() {
let blend = BlendState::ALPHA_BLENDING;
assert_eq!(blend.color.src_factor, BlendFactor::SrcAlpha);
assert_eq!(blend.color.dst_factor, BlendFactor::OneMinusSrcAlpha);
}
#[test]
fn test_color_write_mask() {
let mask = ColorWriteMask::RED | ColorWriteMask::GREEN;
assert!(mask.contains(ColorWriteMask::RED));
assert!(mask.contains(ColorWriteMask::GREEN));
assert!(!mask.contains(ColorWriteMask::BLUE));
}
#[test]
fn test_pipeline_descriptor() {
let desc = RenderPipelineDescriptor::new("vs", "fs")
.with_label("test")
.with_color_target(ColorTargetState::new(TextureFormat::Rgba8Unorm));
assert_eq!(desc.label, Some("test".to_string()));
assert_eq!(desc.color_targets.len(), 1);
}
#[test]
fn test_pipeline_key() {
let desc1 = RenderPipelineDescriptor::new("vs", "fs")
.with_color_target(ColorTargetState::new(TextureFormat::Rgba8Unorm));
let desc2 = RenderPipelineDescriptor::new("vs", "fs")
.with_color_target(ColorTargetState::new(TextureFormat::Rgba8Unorm));
let desc3 = RenderPipelineDescriptor::new("vs2", "fs")
.with_color_target(ColorTargetState::new(TextureFormat::Rgba8Unorm));
let key1 = PipelineKey::from_descriptor(&desc1);
let key2 = PipelineKey::from_descriptor(&desc2);
let key3 = PipelineKey::from_descriptor(&desc3);
assert_eq!(key1, key2);
assert_ne!(key1, key3);
}
}