Skip to main content

lgpu_hal/
lib.rs

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