Skip to main content

lgpu_hal/
lib.rs

1//! # lgpu-hal
2//!
3//! Vulkan 1.3 hardware abstraction layer for `lgpu`.
4
5#![allow(clippy::all)]
6
7pub mod backend;
8#[cfg(feature = "vulkan")]
9pub mod vulkan;
10pub use backend::*;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Backend {
14    Vulkan,
15    Metal,
16    DirectX12,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum DeviceType {
21    DiscreteGpu,
22    IntegratedGpu,
23    VirtualGpu,
24    Cpu,
25    Other,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct DeviceLimits {
30    pub max_buffer_size: u64,
31    pub max_texture_dimension_1d: u32,
32    pub max_texture_dimension_2d: u32,
33    pub max_texture_dimension_3d: u32,
34    pub max_texture_array_layers: u32,
35    pub min_uniform_buffer_offset_alignment: u64,
36    pub min_storage_buffer_offset_alignment: u64,
37    pub max_compute_workgroup_invocations: u32,
38    pub max_compute_workgroup_size: [u32; 3],
39}
40
41impl Default for DeviceLimits {
42    fn default() -> Self {
43        Self {
44            max_buffer_size: 256 * 1024 * 1024,
45            max_texture_dimension_1d: 8192,
46            max_texture_dimension_2d: 8192,
47            max_texture_dimension_3d: 2048,
48            max_texture_array_layers: 2048,
49            min_uniform_buffer_offset_alignment: 256,
50            min_storage_buffer_offset_alignment: 64,
51            max_compute_workgroup_invocations: 1024,
52            max_compute_workgroup_size: [1024, 1024, 64],
53        }
54    }
55}
56
57#[derive(Debug, Clone)]
58pub struct AdapterInfo {
59    pub name: String,
60    pub backend: Backend,
61    pub device_type: DeviceType,
62    pub vendor_id: u32,
63    pub device_id: u32,
64    pub supports_dynamic_rendering: bool,
65    pub supports_timeline_semaphores: bool,
66    pub supports_descriptor_indexing: bool,
67    pub max_compute_workgroup_invocations: u32,
68    pub max_compute_workgroup_size: [u32; 3],
69    pub limits: DeviceLimits,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum MemoryLocation {
74    /// Optimal for GPU read/write. Not accessible directly by CPU.
75    GpuOnly,
76    /// Host-visible and host-coherent. Optimal for CPU-to-GPU data streaming.
77    CpuToGpu,
78    /// Host-visible and cached/coherent. Optimal for GPU-to-CPU readback.
79    GpuToCpu,
80}
81
82bitflags::bitflags! {
83    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
84    pub struct BufferUsage: u32 {
85        const TRANSFER_SRC = 1 << 0;
86        const TRANSFER_DST = 1 << 1;
87        const UNIFORM      = 1 << 2;
88        const STORAGE      = 1 << 3;
89        const VERTEX       = 1 << 4;
90        const INDEX        = 1 << 5;
91        const INDIRECT     = 1 << 6;
92    }
93}
94
95#[derive(Debug, Clone)]
96pub struct BufferDesc<'a> {
97    pub label: Option<&'a str>,
98    pub size: u64,
99    pub usage: BufferUsage,
100    pub memory_location: MemoryLocation,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum TextureFormat {
105    Rgba8Unorm,
106    Rgba8Srgb,
107    Bgra8Unorm,
108    Bgra8Srgb,
109    R32Float,
110    Rgba16Float,
111    Rgba32Float,
112    Depth32Float,
113}
114
115#[cfg(feature = "vulkan")]
116impl TextureFormat {
117    pub fn to_vk(&self) -> ash::vk::Format {
118        match self {
119            TextureFormat::Rgba8Unorm => ash::vk::Format::R8G8B8A8_UNORM,
120            TextureFormat::Rgba8Srgb => ash::vk::Format::R8G8B8A8_SRGB,
121            TextureFormat::Bgra8Unorm => ash::vk::Format::B8G8R8A8_UNORM,
122            TextureFormat::Bgra8Srgb => ash::vk::Format::B8G8R8A8_SRGB,
123            TextureFormat::R32Float => ash::vk::Format::R32_SFLOAT,
124            TextureFormat::Rgba16Float => ash::vk::Format::R16G16B16A16_SFLOAT,
125            TextureFormat::Rgba32Float => ash::vk::Format::R32G32B32A32_SFLOAT,
126            TextureFormat::Depth32Float => ash::vk::Format::D32_SFLOAT,
127        }
128    }
129
130    pub fn from_vk(format: ash::vk::Format) -> Option<Self> {
131        match format {
132            ash::vk::Format::R8G8B8A8_UNORM => Some(TextureFormat::Rgba8Unorm),
133            ash::vk::Format::R8G8B8A8_SRGB => Some(TextureFormat::Rgba8Srgb),
134            ash::vk::Format::B8G8R8A8_UNORM => Some(TextureFormat::Bgra8Unorm),
135            ash::vk::Format::B8G8R8A8_SRGB => Some(TextureFormat::Bgra8Srgb),
136            ash::vk::Format::R32_SFLOAT => Some(TextureFormat::R32Float),
137            ash::vk::Format::R16G16B16A16_SFLOAT => Some(TextureFormat::Rgba16Float),
138            ash::vk::Format::R32G32B32A32_SFLOAT => Some(TextureFormat::Rgba32Float),
139            ash::vk::Format::D32_SFLOAT => Some(TextureFormat::Depth32Float),
140            _ => None,
141        }
142    }
143}
144
145bitflags::bitflags! {
146    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
147    pub struct TextureUsage: u32 {
148        const COPY_SRC         = 1 << 0;
149        const COPY_DST         = 1 << 1;
150        const TEXTURE_BINDING  = 1 << 2;
151        const STORAGE_BINDING  = 1 << 3;
152        const RENDER_ATTACHMENT = 1 << 4;
153    }
154}
155
156#[derive(Debug, Clone)]
157pub struct TextureDesc<'a> {
158    pub label: Option<&'a str>,
159    pub width: u32,
160    pub height: u32,
161    pub depth: u32,
162    pub mip_levels: u32,
163    pub sample_count: u32,
164    pub format: TextureFormat,
165    pub usage: TextureUsage,
166}
167
168impl<'a> TextureDesc<'a> {
169    pub fn new_2d(width: u32, height: u32, format: TextureFormat, usage: TextureUsage) -> Self {
170        Self {
171            label: None,
172            width,
173            height,
174            depth: 1,
175            mip_levels: 1,
176            sample_count: 1,
177            format,
178            usage,
179        }
180    }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum FilterMode {
185    Nearest,
186    Linear,
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum AddressMode {
191    ClampToEdge,
192    Repeat,
193    MirrorRepeat,
194}
195
196#[derive(Debug, Clone)]
197pub struct SamplerDesc<'a> {
198    pub label: Option<&'a str>,
199    pub min_filter: FilterMode,
200    pub mag_filter: FilterMode,
201    pub mipmap_filter: FilterMode,
202    pub address_mode_u: AddressMode,
203    pub address_mode_v: AddressMode,
204    pub address_mode_w: AddressMode,
205}
206
207impl Default for SamplerDesc<'_> {
208    fn default() -> Self {
209        Self {
210            label: None,
211            min_filter: FilterMode::Linear,
212            mag_filter: FilterMode::Linear,
213            mipmap_filter: FilterMode::Linear,
214            address_mode_u: AddressMode::ClampToEdge,
215            address_mode_v: AddressMode::ClampToEdge,
216            address_mode_w: AddressMode::ClampToEdge,
217        }
218    }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq)]
222pub enum LoadOp<T> {
223    Clear(T),
224    Load,
225    DontCare,
226}
227
228pub type ColorLoadOp = LoadOp<[f32; 4]>;
229pub type DepthLoadOp = LoadOp<f32>;
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum PrimitiveTopology {
233    PointList,
234    LineList,
235    LineStrip,
236    TriangleList,
237    TriangleStrip,
238}
239
240#[cfg(feature = "vulkan")]
241impl PrimitiveTopology {
242    pub fn to_vk(&self) -> ash::vk::PrimitiveTopology {
243        match self {
244            PrimitiveTopology::PointList => ash::vk::PrimitiveTopology::POINT_LIST,
245            PrimitiveTopology::LineList => ash::vk::PrimitiveTopology::LINE_LIST,
246            PrimitiveTopology::LineStrip => ash::vk::PrimitiveTopology::LINE_STRIP,
247            PrimitiveTopology::TriangleList => ash::vk::PrimitiveTopology::TRIANGLE_LIST,
248            PrimitiveTopology::TriangleStrip => ash::vk::PrimitiveTopology::TRIANGLE_STRIP,
249        }
250    }
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub enum CullMode {
255    None,
256    Front,
257    Back,
258}
259
260#[cfg(feature = "vulkan")]
261impl CullMode {
262    pub fn to_vk(&self) -> ash::vk::CullModeFlags {
263        match self {
264            CullMode::None => ash::vk::CullModeFlags::NONE,
265            CullMode::Front => ash::vk::CullModeFlags::FRONT,
266            CullMode::Back => ash::vk::CullModeFlags::BACK,
267        }
268    }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum FrontFace {
273    Ccw,
274    Cw,
275}
276
277#[cfg(feature = "vulkan")]
278impl FrontFace {
279    pub fn to_vk(&self) -> ash::vk::FrontFace {
280        match self {
281            FrontFace::Ccw => ash::vk::FrontFace::COUNTER_CLOCKWISE,
282            FrontFace::Cw => ash::vk::FrontFace::CLOCKWISE,
283        }
284    }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub struct PrimitiveState {
289    pub topology: PrimitiveTopology,
290    pub cull_mode: CullMode,
291    pub front_face: FrontFace,
292}
293
294impl Default for PrimitiveState {
295    fn default() -> Self {
296        Self {
297            topology: PrimitiveTopology::TriangleList,
298            cull_mode: CullMode::None,
299            front_face: FrontFace::Cw,
300        }
301    }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum BlendFactor {
306    Zero,
307    One,
308    SrcColor,
309    OneMinusSrcColor,
310    SrcAlpha,
311    OneMinusSrcAlpha,
312    DstColor,
313    OneMinusDstColor,
314    DstAlpha,
315    OneMinusDstAlpha,
316}
317
318#[cfg(feature = "vulkan")]
319impl BlendFactor {
320    pub fn to_vk(&self) -> ash::vk::BlendFactor {
321        match self {
322            BlendFactor::Zero => ash::vk::BlendFactor::ZERO,
323            BlendFactor::One => ash::vk::BlendFactor::ONE,
324            BlendFactor::SrcColor => ash::vk::BlendFactor::SRC_COLOR,
325            BlendFactor::OneMinusSrcColor => ash::vk::BlendFactor::ONE_MINUS_SRC_COLOR,
326            BlendFactor::SrcAlpha => ash::vk::BlendFactor::SRC_ALPHA,
327            BlendFactor::OneMinusSrcAlpha => ash::vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
328            BlendFactor::DstColor => ash::vk::BlendFactor::DST_COLOR,
329            BlendFactor::OneMinusDstColor => ash::vk::BlendFactor::ONE_MINUS_DST_COLOR,
330            BlendFactor::DstAlpha => ash::vk::BlendFactor::DST_ALPHA,
331            BlendFactor::OneMinusDstAlpha => ash::vk::BlendFactor::ONE_MINUS_DST_ALPHA,
332        }
333    }
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub enum BlendOp {
338    Add,
339    Subtract,
340    ReverseSubtract,
341    Min,
342    Max,
343}
344
345#[cfg(feature = "vulkan")]
346impl BlendOp {
347    pub fn to_vk(&self) -> ash::vk::BlendOp {
348        match self {
349            BlendOp::Add => ash::vk::BlendOp::ADD,
350            BlendOp::Subtract => ash::vk::BlendOp::SUBTRACT,
351            BlendOp::ReverseSubtract => ash::vk::BlendOp::REVERSE_SUBTRACT,
352            BlendOp::Min => ash::vk::BlendOp::MIN,
353            BlendOp::Max => ash::vk::BlendOp::MAX,
354        }
355    }
356}
357
358#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub struct BlendComponent {
360    pub src_factor: BlendFactor,
361    pub dst_factor: BlendFactor,
362    pub operation: BlendOp,
363}
364
365impl BlendComponent {
366    pub const REPLACE: Self = Self {
367        src_factor: BlendFactor::One,
368        dst_factor: BlendFactor::Zero,
369        operation: BlendOp::Add,
370    };
371
372    pub const OVER: Self = Self {
373        src_factor: BlendFactor::One,
374        dst_factor: BlendFactor::OneMinusSrcAlpha,
375        operation: BlendOp::Add,
376    };
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub struct BlendState {
381    pub color: BlendComponent,
382    pub alpha: BlendComponent,
383}
384
385impl BlendState {
386    pub const REPLACE: Self = Self {
387        color: BlendComponent::REPLACE,
388        alpha: BlendComponent::REPLACE,
389    };
390
391    pub const ALPHA_BLENDING: Self = Self {
392        color: BlendComponent {
393            src_factor: BlendFactor::SrcAlpha,
394            dst_factor: BlendFactor::OneMinusSrcAlpha,
395            operation: BlendOp::Add,
396        },
397        alpha: BlendComponent {
398            src_factor: BlendFactor::One,
399            dst_factor: BlendFactor::OneMinusSrcAlpha,
400            operation: BlendOp::Add,
401        },
402    };
403
404    pub const PREMULTIPLIED_ALPHA_BLENDING: Self = Self {
405        color: BlendComponent::OVER,
406        alpha: BlendComponent::OVER,
407    };
408
409    pub const ADDITIVE: Self = Self {
410        color: BlendComponent {
411            src_factor: BlendFactor::One,
412            dst_factor: BlendFactor::One,
413            operation: BlendOp::Add,
414        },
415        alpha: BlendComponent {
416            src_factor: BlendFactor::One,
417            dst_factor: BlendFactor::One,
418            operation: BlendOp::Add,
419        },
420    };
421}
422
423#[derive(Debug, Clone)]
424pub struct GraphicsPipelineDesc<'a> {
425    pub label: Option<&'a str>,
426    pub vs_entry: &'a str,
427    pub fs_entry: &'a str,
428    pub color_format: TextureFormat,
429    pub depth_format: Option<TextureFormat>,
430    pub depth_test: bool,
431    pub depth_write: bool,
432    pub sample_count: u32,
433    pub primitive: PrimitiveState,
434    pub blend: Option<BlendState>,
435}
436
437impl<'a> GraphicsPipelineDesc<'a> {
438    pub fn new(vs_entry: &'a str, fs_entry: &'a str, color_format: TextureFormat) -> Self {
439        Self {
440            label: None,
441            vs_entry,
442            fs_entry,
443            color_format,
444            depth_format: None,
445            depth_test: false,
446            depth_write: false,
447            sample_count: 1,
448            primitive: PrimitiveState::default(),
449            blend: None,
450        }
451    }
452}
453
454#[derive(thiserror::Error, Debug)]
455pub enum HalError {
456    #[error("Dynamic loader failed to load driver library: {0}")]
457    LibraryLoadError(String),
458    #[error("No compatible graphics adapter was found")]
459    NoAdapterFound,
460    #[error("Device creation failed: {0}")]
461    DeviceCreationFailed(String),
462    #[error("Out of memory: {0}")]
463    OutOfMemory(String),
464    #[error("Shader module compilation failed: {0}")]
465    ShaderError(String),
466    #[error("Pipeline creation failed: {0}")]
467    PipelineError(String),
468    #[error("Command recording or submission error: {0}")]
469    CommandError(String),
470    #[error("Map buffer error: {0}")]
471    MapError(String),
472    #[error("Invalid parameter: {0}")]
473    InvalidParameter(String),
474    #[error("Backend specific error: {0}")]
475    BackendError(String),
476}