use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TargetLoad {
Load,
Clear(Color),
Discard,
}
impl TargetLoad {
pub fn overwrites(self) -> bool {
matches!(self, Self::Clear(_) | Self::Discard)
}
}
impl Color {
pub const BLACK: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const RED: Color = Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const GREEN: Color = Color {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
pub const BLUE: Color = Color {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
pub const CORNFLOWER_BLUE: Color = Color {
r: 0.392,
g: 0.584,
b: 0.929,
a: 1.0,
};
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}
}
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: a as f32 / 255.0,
}
}
pub fn to_rgba8(&self) -> [u8; 4] {
[
(self.r * 255.0) as u8,
(self.g * 255.0) as u8,
(self.b * 255.0) as u8,
(self.a * 255.0) as u8,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextureFormat {
R8Unorm,
Rg8Unorm,
Rgba8UnormSrgb,
#[default]
Rgba8Unorm,
Bgra8UnormSrgb,
Bgra8Unorm,
Rgba16Float,
Rgba32Float,
}
impl TextureFormat {
pub fn bytes_per_pixel(&self) -> u32 {
match self {
TextureFormat::R8Unorm => 1,
TextureFormat::Rg8Unorm => 2,
TextureFormat::Rgba8UnormSrgb => 4,
TextureFormat::Rgba8Unorm => 4,
TextureFormat::Bgra8UnormSrgb => 4,
TextureFormat::Bgra8Unorm => 4,
TextureFormat::Rgba16Float => 8,
TextureFormat::Rgba32Float => 16,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Pod, Zeroable)]
pub struct DispatchShape {
pub x: u32,
pub y: u32,
pub z: u32,
}
impl DispatchShape {
pub const fn new(x: u32, y: u32, z: u32) -> Self {
Self { x, y, z }
}
}
impl From<(u32, u32, u32)> for DispatchShape {
fn from((x, y, z): (u32, u32, u32)) -> Self {
Self::new(x, y, z)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ResourceAccess {
#[default]
Read,
Write,
ReadWrite,
}
#[cfg(all(feature = "dx12", target_os = "windows"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum BindlessSlotKind {
StorageUav,
ReadOnlySrv,
UniformCbv,
}
#[cfg(all(feature = "dx12", target_os = "windows"))]
impl BindlessSlotKind {
pub(crate) fn name(self) -> &'static str {
match self {
Self::StorageUav => "storage UAV",
Self::ReadOnlySrv => "read-only SRV",
Self::UniformCbv => "uniform CBV",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BufferKind {
#[default]
Scattered,
Broadcast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BufferResizeCost {
Constant,
PageBind,
#[default]
Copy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum ResourceCategory {
Scattered,
Broadcast,
StorageImage,
Texture,
Sampler,
}
impl ResourceCategory {
pub fn name(self) -> &'static str {
match self {
ResourceCategory::Scattered => "scattered",
ResourceCategory::Broadcast => "broadcast",
ResourceCategory::StorageImage => "storage_image",
ResourceCategory::Texture => "texture",
ResourceCategory::Sampler => "sampler",
}
}
pub fn is_compatible_with(self, expected: ResourceCategory) -> bool {
self == expected
}
}
impl From<BufferKind> for ResourceCategory {
fn from(access: BufferKind) -> Self {
match access {
BufferKind::Scattered => ResourceCategory::Scattered,
BufferKind::Broadcast => ResourceCategory::Broadcast,
}
}
}
impl From<TextureKind> for ResourceCategory {
fn from(access: TextureKind) -> Self {
match access {
TextureKind::Interpolated => ResourceCategory::Texture,
TextureKind::Direct => ResourceCategory::StorageImage,
TextureKind::DirectInterpolated => ResourceCategory::StorageImage,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResourceHandle {
category: ResourceCategory,
index: u32,
}
impl ResourceHandle {
pub(crate) const fn new(category: ResourceCategory, index: u32) -> Self {
Self { category, index }
}
pub(crate) const fn index(self) -> u32 {
self.index
}
pub const fn category(self) -> ResourceCategory {
self.category
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PresentMode {
Fifo,
Mailbox,
Immediate,
#[default]
Auto,
}
#[derive(Debug, Clone, Default)]
pub struct SurfaceConfig {
pub present_mode: PresentMode,
pub depth_format: Option<DepthFormat>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextureKind {
#[default]
Interpolated,
Direct,
DirectInterpolated,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BufferFlags: u32 {
const COPY_SRC = 1 << 0;
const COPY_DST = 1 << 1;
const CPU_READABLE = 1 << 2;
const GPU_ONLY = 1 << 3;
const CPU_WRITABLE = 1 << 4;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VertexFormat {
Float32,
Float32x2,
Float32x3,
Float32x4,
Uint32,
Sint32,
Uint8x4,
Unorm8x4,
}
impl VertexFormat {
pub fn size(&self) -> u32 {
match self {
VertexFormat::Float32 => 4,
VertexFormat::Float32x2 => 8,
VertexFormat::Float32x3 => 12,
VertexFormat::Float32x4 => 16,
VertexFormat::Uint32 => 4,
VertexFormat::Sint32 => 4,
VertexFormat::Uint8x4 => 4,
VertexFormat::Unorm8x4 => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct VertexAttribute {
pub location: u32,
pub format: VertexFormat,
pub offset: u32,
}
#[derive(Debug, Clone)]
pub struct VertexBufferLayout {
pub stride: u32,
pub attributes: Vec<VertexAttribute>,
}
impl VertexBufferLayout {
pub fn empty() -> Self {
Self {
stride: 0,
attributes: Vec::new(),
}
}
pub fn from_formats<T>(formats: &[VertexFormat]) -> Self {
let mut offset = 0u32;
let attributes: Vec<VertexAttribute> = formats
.iter()
.enumerate()
.map(|(i, fmt)| {
let attr = VertexAttribute {
location: i as u32,
offset,
format: *fmt,
};
offset += fmt.size();
attr
})
.collect();
let expected_stride = std::mem::size_of::<T>() as u32;
assert_eq!(
offset,
expected_stride,
"VertexBufferLayout::from_formats: sum of format sizes ({offset}) != \
size_of::<{}>() ({expected_stride}). Check field order and padding.",
std::any::type_name::<T>(),
);
Self {
stride: expected_stride,
attributes,
}
}
}
#[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 IndexFormat {
#[default]
Uint16,
Uint32,
}
impl IndexFormat {
pub fn size(&self) -> u32 {
match self {
IndexFormat::Uint16 => 2,
IndexFormat::Uint32 => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceType {
DiscreteGpu,
IntegratedGpu,
Cpu,
Other,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
pub enum OptimizationLevel {
None,
#[default]
Default,
High,
Maximal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackendType {
Vulkan,
Metal,
Dx12,
WebGpu,
Cuda,
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex2D {
pub position: [f32; 2],
pub color: [f32; 4],
}
impl Vertex2D {
pub const fn new(x: f32, y: f32, color: Color) -> Self {
Self {
position: [x, y],
color: [color.r, color.g, color.b, color.a],
}
}
pub fn layout() -> VertexBufferLayout {
VertexBufferLayout::from_formats::<Self>(&[
VertexFormat::Float32x2, VertexFormat::Float32x4, ])
}
}
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex2DUv {
pub position: [f32; 2],
pub uv: [f32; 2],
}
impl Vertex2DUv {
pub const fn new(x: f32, y: f32, u: f32, v: f32) -> Self {
Self {
position: [x, y],
uv: [u, v],
}
}
pub fn layout() -> VertexBufferLayout {
VertexBufferLayout::from_formats::<Self>(&[
VertexFormat::Float32x2, VertexFormat::Float32x2, ])
}
}
use crate::buffer::StructuredBufferElement;
impl StructuredBufferElement for Vertex2D {}
impl StructuredBufferElement for Vertex2DUv {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DepthFormat {
Depth16Unorm,
Depth24Plus,
Depth24PlusStencil8,
Depth32Float,
Depth32FloatStencil8,
}
impl DepthFormat {
pub fn has_stencil(&self) -> bool {
matches!(
self,
DepthFormat::Depth24PlusStencil8 | DepthFormat::Depth32FloatStencil8
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CompareFunction {
Never,
#[default]
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always,
}
#[derive(Debug, Clone)]
pub struct DepthStencilState {
pub format: DepthFormat,
pub depth_write_enabled: bool,
pub depth_compare: CompareFunction,
}
impl Default for DepthStencilState {
fn default() -> Self {
Self {
format: DepthFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: CompareFunction::Less,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextureFlags: u32 {
const COPY_SRC = 1 << 0;
const COPY_DST = 1 << 1;
const RENDER_TARGET = 1 << 2;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AddressMode {
#[default]
ClampToEdge,
Repeat,
MirrorRepeat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FilterMode {
#[default]
Nearest,
Linear,
}
#[derive(Debug, Clone)]
pub struct SamplerDesc {
pub address_mode_u: AddressMode,
pub address_mode_v: AddressMode,
pub address_mode_w: AddressMode,
pub mag_filter: FilterMode,
pub min_filter: FilterMode,
pub mipmap_filter: FilterMode,
pub max_anisotropy: f32,
pub compare: Option<CompareFunction>,
pub lod_min_clamp: f32,
pub lod_max_clamp: f32,
}
impl Default for SamplerDesc {
fn default() -> Self {
Self {
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
address_mode_w: AddressMode::ClampToEdge,
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
mipmap_filter: FilterMode::Nearest,
max_anisotropy: 1.0,
compare: None,
lod_min_clamp: 0.0,
lod_max_clamp: 32.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_from_rgb() {
let color = Color::from_rgb(255, 128, 0);
assert!((color.r - 1.0).abs() < 0.01);
assert!((color.g - 0.502).abs() < 0.01);
assert!((color.b - 0.0).abs() < 0.01);
assert!((color.a - 1.0).abs() < 0.01);
}
#[test]
fn test_color_to_rgba8() {
let color = Color {
r: 1.0,
g: 0.5,
b: 0.0,
a: 1.0,
};
let rgba = color.to_rgba8();
assert_eq!(rgba[0], 255);
assert_eq!(rgba[1], 127);
assert_eq!(rgba[2], 0);
assert_eq!(rgba[3], 255);
}
#[test]
fn test_color_constants() {
assert_eq!(Color::BLACK.r, 0.0);
assert_eq!(Color::WHITE.r, 1.0);
assert_eq!(Color::RED.r, 1.0);
assert_eq!(Color::RED.g, 0.0);
}
#[test]
fn test_texture_format_bytes_per_pixel() {
assert_eq!(TextureFormat::R8Unorm.bytes_per_pixel(), 1);
assert_eq!(TextureFormat::Rg8Unorm.bytes_per_pixel(), 2);
assert_eq!(TextureFormat::Rgba8Unorm.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::Rgba16Float.bytes_per_pixel(), 8);
assert_eq!(TextureFormat::Rgba32Float.bytes_per_pixel(), 16);
}
#[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_vertex2d_layout() {
let layout = Vertex2D::layout();
assert_eq!(layout.stride, 24); assert_eq!(layout.attributes.len(), 2);
assert_eq!(layout.attributes[0].location, 0);
assert_eq!(layout.attributes[1].location, 1);
}
#[test]
fn test_vertex2duv_layout() {
let layout = Vertex2DUv::layout();
assert_eq!(layout.stride, 16); assert_eq!(layout.attributes.len(), 2);
}
#[test]
fn test_buffer_flags() {
let flags = BufferFlags::COPY_SRC | BufferFlags::COPY_DST;
assert!(flags.contains(BufferFlags::COPY_SRC));
assert!(flags.contains(BufferFlags::COPY_DST));
}
#[test]
fn test_buffer_kind_default() {
assert_eq!(BufferKind::default(), BufferKind::Scattered);
}
#[test]
fn test_texture_kind_default() {
assert_eq!(TextureKind::default(), TextureKind::Interpolated);
}
#[test]
fn test_index_format_size() {
assert_eq!(IndexFormat::Uint16.size(), 2);
assert_eq!(IndexFormat::Uint32.size(), 4);
}
#[test]
fn test_index_format_default() {
assert_eq!(IndexFormat::default(), IndexFormat::Uint16);
}
#[test]
fn test_depth_format_has_stencil() {
assert!(!DepthFormat::Depth16Unorm.has_stencil());
assert!(!DepthFormat::Depth24Plus.has_stencil());
assert!(DepthFormat::Depth24PlusStencil8.has_stencil());
assert!(!DepthFormat::Depth32Float.has_stencil());
assert!(DepthFormat::Depth32FloatStencil8.has_stencil());
}
#[test]
fn test_compare_function_default() {
assert_eq!(CompareFunction::default(), CompareFunction::Less);
}
#[test]
fn test_depth_stencil_state_default() {
let state = DepthStencilState::default();
assert_eq!(state.format, DepthFormat::Depth24Plus);
assert!(state.depth_write_enabled);
assert_eq!(state.depth_compare, CompareFunction::Less);
}
#[test]
fn test_dispatch_shape_size() {
assert_eq!(std::mem::size_of::<DispatchShape>(), 12);
}
#[test]
fn test_texture_flags() {
let flags = TextureFlags::COPY_SRC | TextureFlags::COPY_DST;
assert!(flags.contains(TextureFlags::COPY_SRC));
assert!(flags.contains(TextureFlags::COPY_DST));
assert!(!flags.contains(TextureFlags::RENDER_TARGET));
}
#[test]
fn test_address_mode_default() {
assert_eq!(AddressMode::default(), AddressMode::ClampToEdge);
}
#[test]
fn test_filter_mode_default() {
assert_eq!(FilterMode::default(), FilterMode::Nearest);
}
#[test]
fn test_sampler_desc_default() {
let desc = SamplerDesc::default();
assert_eq!(desc.address_mode_u, AddressMode::ClampToEdge);
assert_eq!(desc.address_mode_v, AddressMode::ClampToEdge);
assert_eq!(desc.address_mode_w, AddressMode::ClampToEdge);
assert_eq!(desc.mag_filter, FilterMode::Nearest);
assert_eq!(desc.min_filter, FilterMode::Nearest);
assert_eq!(desc.mipmap_filter, FilterMode::Nearest);
assert_eq!(desc.max_anisotropy, 1.0);
assert!(desc.compare.is_none());
assert_eq!(desc.lod_min_clamp, 0.0);
assert_eq!(desc.lod_max_clamp, 32.0);
}
}