khora_core/renderer/api/
common.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Provides common, backend-agnostic enums and data structures for the rendering API.
16
17use crate::{
18    math::{Mat4, Vec3},
19    renderer::{BufferId, RenderPipelineId},
20};
21
22/// Specifies the data type of indices in an index buffer.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum IndexFormat {
25    /// Indices are 16-bit unsigned integers.
26    Uint16,
27    /// Indices are 32-bit unsigned integers.
28    Uint32,
29}
30
31/// A backend-agnostic representation of a graphics API.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
33pub enum GraphicsBackendType {
34    /// Vulkan API.
35    Vulkan,
36    /// Apple's Metal API.
37    Metal,
38    /// Microsoft's DirectX 12 API.
39    Dx12,
40    /// Microsoft's DirectX 11 API.
41    Dx11,
42    /// OpenGL API.
43    OpenGL,
44    /// WebGPU API (for web builds).
45    WebGpu,
46    /// An unknown or unsupported backend.
47    #[default]
48    Unknown,
49}
50
51/// The physical type of a graphics device (GPU).
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub enum RendererDeviceType {
54    /// A GPU integrated into the CPU.
55    IntegratedGpu,
56    /// A discrete, dedicated GPU.
57    DiscreteGpu,
58    /// A virtualized or software-based GPU.
59    VirtualGpu,
60    /// A software renderer running on the CPU.
61    Cpu,
62    /// An unknown or unsupported device type.
63    #[default]
64    Unknown,
65}
66
67/// Defines a high-level rendering strategy.
68/// This will be used by the `RenderAgent` to select the appropriate `RenderLane`.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum RenderStrategy {
71    /// A forward rendering pipeline.
72    Forward,
73    /// A deferred shading pipeline.
74    Deferred,
75    /// A custom, user-defined strategy identified by a number.
76    Custom(u32),
77}
78
79/// The number of samples per pixel for Multisample Anti-Aliasing (MSAA).
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
81pub enum SampleCount {
82    /// 1 sample per pixel (MSAA disabled).
83    #[default]
84    X1,
85    /// 2 samples per pixel.
86    X2,
87    /// 4 samples per pixel.
88    X4,
89    /// 8 samples per pixel.
90    X8,
91    /// 16 samples per pixel.
92    X16,
93    /// 32 samples per pixel.
94    X32,
95    /// 64 samples per pixel.
96    X64,
97}
98
99/// Defines the programmable stage in the graphics pipeline a shader module is for.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub enum ShaderStage {
102    /// The vertex shader stage.
103    Vertex,
104    /// The fragment (or pixel) shader stage.
105    Fragment,
106    /// The compute shader stage.
107    Compute,
108}
109
110/// Flags representing which shader stages can access a resource binding.
111///
112/// This is used in bind group layouts to specify visibility of resources.
113/// Multiple stages can be combined using bitwise operations.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub struct ShaderStageFlags {
116    bits: u32,
117}
118
119impl ShaderStageFlags {
120    /// No shader stages.
121    pub const NONE: Self = Self { bits: 0 };
122    /// Vertex shader stage.
123    pub const VERTEX: Self = Self { bits: 1 << 0 };
124    /// Fragment shader stage.
125    pub const FRAGMENT: Self = Self { bits: 1 << 1 };
126    /// Compute shader stage.
127    pub const COMPUTE: Self = Self { bits: 1 << 2 };
128    /// All graphics stages (vertex + fragment).
129    pub const VERTEX_FRAGMENT: Self = Self {
130        bits: Self::VERTEX.bits | Self::FRAGMENT.bits,
131    };
132    /// All stages.
133    pub const ALL: Self = Self {
134        bits: Self::VERTEX.bits | Self::FRAGMENT.bits | Self::COMPUTE.bits,
135    };
136
137    /// Creates a new set of shader stage flags from raw bits.
138    pub const fn from_bits(bits: u32) -> Self {
139        Self { bits }
140    }
141
142    /// Creates flags from a single shader stage.
143    pub const fn from_stage(stage: ShaderStage) -> Self {
144        match stage {
145            ShaderStage::Vertex => Self::VERTEX,
146            ShaderStage::Fragment => Self::FRAGMENT,
147            ShaderStage::Compute => Self::COMPUTE,
148        }
149    }
150
151    /// Returns the raw bits.
152    pub const fn bits(&self) -> u32 {
153        self.bits
154    }
155
156    /// Combines two sets of flags.
157    pub const fn union(self, other: Self) -> Self {
158        Self {
159            bits: self.bits | other.bits,
160        }
161    }
162
163    /// Checks if these flags contain a specific stage.
164    pub const fn contains(&self, stage: ShaderStage) -> bool {
165        let stage_bits = Self::from_stage(stage).bits;
166        (self.bits & stage_bits) == stage_bits
167    }
168
169    /// Checks if these flags are empty (no stages).
170    pub const fn is_empty(&self) -> bool {
171        self.bits == 0
172    }
173}
174
175impl std::ops::BitOr for ShaderStageFlags {
176    type Output = Self;
177
178    fn bitor(self, rhs: Self) -> Self::Output {
179        self.union(rhs)
180    }
181}
182
183impl std::ops::BitOrAssign for ShaderStageFlags {
184    fn bitor_assign(&mut self, rhs: Self) {
185        *self = self.union(rhs);
186    }
187}
188
189/// Defines the memory format of pixels in a texture.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
191pub enum TextureFormat {
192    // 8-bit formats
193    /// One 8-bit unsigned normalized component.
194    R8Unorm,
195    /// Two 8-bit unsigned normalized components.
196    Rg8Unorm,
197    /// Four 8-bit unsigned normalized components (RGBA).
198    Rgba8Unorm,
199    /// Four 8-bit unsigned normalized components (RGBA) in the sRGB color space.
200    Rgba8UnormSrgb,
201    /// Four 8-bit unsigned normalized components (BGRA) in the sRGB color space. This is a common swapchain format.
202    Bgra8UnormSrgb,
203    // 16-bit float formats
204    /// One 16-bit float component.
205    R16Float,
206    /// Two 16-bit float components.
207    Rg16Float,
208    /// Four 16-bit float components.
209    Rgba16Float,
210    // 32-bit float formats
211    /// One 32-bit float component.
212    R32Float,
213    /// Two 32-bit float components.
214    Rg32Float,
215    /// Four 32-bit float components.
216    Rgba32Float,
217    // Depth/stencil formats
218    /// A 16-bit unsigned normalized depth format.
219    Depth16Unorm,
220    /// A 24-bit unsigned normalized depth format.
221    Depth24Plus,
222    /// A 24-bit unsigned normalized depth format with an 8-bit stencil component.
223    Depth24PlusStencil8,
224    /// A 32-bit float depth format.
225    Depth32Float,
226    /// A 32-bit float depth format with an 8-bit stencil component.
227    Depth32FloatStencil8,
228}
229
230impl TextureFormat {
231    /// Returns the size in bytes of a single pixel for this format.
232    /// Note: This can be an approximation for packed or complex formats.
233    pub fn bytes_per_pixel(&self) -> u32 {
234        match self {
235            TextureFormat::R8Unorm => 1,
236            TextureFormat::Rg8Unorm => 2,
237            TextureFormat::Rgba8Unorm => 4,
238            TextureFormat::Rgba8UnormSrgb => 4,
239            TextureFormat::Bgra8UnormSrgb => 4,
240            TextureFormat::R16Float => 2,
241            TextureFormat::Rg16Float => 4,
242            TextureFormat::Rgba16Float => 8,
243            TextureFormat::R32Float => 4,
244            TextureFormat::Rg32Float => 8,
245            TextureFormat::Rgba32Float => 16,
246            TextureFormat::Depth16Unorm => 2,
247            TextureFormat::Depth24Plus => 4,
248            TextureFormat::Depth24PlusStencil8 => 4,
249            TextureFormat::Depth32Float => 4,
250            TextureFormat::Depth32FloatStencil8 => 5,
251        }
252    }
253}
254
255// --- Structs ---
256
257/// Provides standardized, backend-agnostic information about the graphics adapter.
258#[derive(Debug, Clone, Default)]
259pub struct RendererAdapterInfo {
260    /// The name of the adapter (e.g., "NVIDIA GeForce RTX 4090").
261    pub name: String,
262    /// The graphics API backend this adapter is associated with.
263    pub backend_type: GraphicsBackendType,
264    /// The physical type of the adapter.
265    pub device_type: RendererDeviceType,
266}
267
268/// Provides standardized, backend-agnostic information about a graphics adapter.
269#[derive(Debug, Clone, Default)]
270pub struct GraphicsAdapterInfo {
271    /// The name of the adapter (e.g., "NVIDIA GeForce RTX 4090").
272    pub name: String,
273    /// The graphics API backend this adapter is associated with.
274    pub backend_type: GraphicsBackendType,
275    /// The physical type of the adapter.
276    pub device_type: RendererDeviceType,
277}
278
279/// A simple representation of a single object to be rendered in a pass.
280/// TODO: evolve this structure to a more high level abstraction for real 3D objects.
281#[derive(Debug, Clone)]
282pub struct RenderObject {
283    /// The [`RenderPipelineId`] to bind for this object.
284    pub pipeline: RenderPipelineId,
285    /// The vertex buffer to bind.
286    pub vertex_buffer: BufferId,
287    /// The index buffer to bind.
288    pub index_buffer: BufferId,
289    /// The number of indices to draw from the index buffer.
290    pub index_count: u32,
291}
292
293/// A collection of global settings that can affect the rendering process.
294#[derive(Debug, Clone)]
295pub struct RenderSettings {
296    /// The desired high-level rendering strategy.
297    pub strategy: RenderStrategy,
298    /// A generic quality level (e.g., 1=Low, 2=Medium, 3=High).
299    pub quality_level: u32,
300    /// If `true`, objects should be rendered in wireframe mode.
301    pub show_wireframe: bool,
302    /// The quiet period in milliseconds after a resize event before the surface is reconfigured.
303    pub resize_debounce_ms: u64,
304    /// A fallback number of frames after which a pending resize is forced, even if events are still incoming.
305    pub resize_max_pending_frames: u32,
306    /// A runtime toggle to enable/disable GPU timestamp instrumentation for profiling.
307    pub enable_gpu_timestamps: bool,
308}
309
310impl Default for RenderSettings {
311    fn default() -> Self {
312        Self {
313            strategy: RenderStrategy::Forward,
314            quality_level: 1,
315            show_wireframe: false,
316            resize_debounce_ms: 120,
317            resize_max_pending_frames: 10,
318            enable_gpu_timestamps: true,
319        }
320    }
321}
322
323/// A collection of performance statistics for a single rendered frame.
324#[derive(Debug, Clone)]
325pub struct RenderStats {
326    /// A sequential counter for rendered frames.
327    pub frame_number: u64,
328    /// The CPU time spent in pre-render preparation (resource updates, culling, etc.).
329    pub cpu_preparation_time_ms: f32,
330    /// The CPU time spent submitting encoded command buffers to the GPU.
331    pub cpu_render_submission_time_ms: f32,
332    /// The GPU execution time of the main render pass, as measured by timestamp queries.
333    pub gpu_main_pass_time_ms: f32,
334    /// The total GPU execution time for the entire frame, as measured by timestamp queries.
335    pub gpu_frame_total_time_ms: f32,
336    /// The number of draw calls encoded for the frame.
337    pub draw_calls: u32,
338    /// The total number of triangles submitted for the frame.
339    pub triangles_rendered: u32,
340    /// An estimate of the VRAM usage in megabytes.
341    pub vram_usage_estimate_mb: f32,
342}
343
344/// Represents a specific point in a frame's GPU execution for timestamping.
345///
346/// These are used by a [`GpuProfiler`] to record timestamps and measure performance.
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
348pub enum GpuHook {
349    /// Marks the absolute beginning of GPU work for a frame.
350    FrameStart,
351    /// Marks the beginning of the main render pass.
352    MainPassBegin,
353    /// Marks the end of the main render pass.
354    MainPassEnd,
355    /// Marks the absolute end of GPU work for a frame.
356    FrameEnd,
357}
358
359impl GpuHook {
360    /// An array containing all `GpuHook` variants.
361    pub const ALL: [GpuHook; 4] = [
362        GpuHook::FrameStart,
363        GpuHook::MainPassBegin,
364        GpuHook::MainPassEnd,
365        GpuHook::FrameEnd,
366    ];
367}
368
369impl Default for RenderStats {
370    fn default() -> Self {
371        Self {
372            frame_number: 0,
373            cpu_preparation_time_ms: 0.0,
374            cpu_render_submission_time_ms: 0.0,
375            gpu_main_pass_time_ms: 0.0,
376            gpu_frame_total_time_ms: 0.0,
377            draw_calls: 0,
378            triangles_rendered: 0,
379            vram_usage_estimate_mb: 0.0,
380        }
381    }
382}
383
384/// Contains camera and projection information needed to render a specific view.
385/// This data is typically uploaded to a uniform buffer for shader access.
386#[derive(Debug, Clone)]
387pub struct ViewInfo {
388    /// The camera's view matrix (world to view space).
389    pub view_matrix: Mat4,
390    /// The camera's projection matrix (view to clip space).
391    pub projection_matrix: Mat4,
392    /// The camera's position in world space.
393    pub camera_position: Vec3,
394}
395
396impl ViewInfo {
397    /// Creates a new `ViewInfo` from individual components.
398    pub fn new(view_matrix: Mat4, projection_matrix: Mat4, camera_position: Vec3) -> Self {
399        Self {
400            view_matrix,
401            projection_matrix,
402            camera_position,
403        }
404    }
405
406    /// Calculates the combined view-projection matrix.
407    ///
408    /// This is the product of the projection matrix and the view matrix,
409    /// which transforms from world space directly to clip space.
410    pub fn view_projection_matrix(&self) -> Mat4 {
411        self.projection_matrix * self.view_matrix
412    }
413}
414
415impl Default for ViewInfo {
416    fn default() -> Self {
417        Self {
418            view_matrix: Mat4::IDENTITY,
419            projection_matrix: Mat4::IDENTITY,
420            camera_position: Vec3::ZERO,
421        }
422    }
423}
424
425/// The GPU-side representation of camera uniform data.
426///
427/// This structure is designed to be directly uploaded to a uniform buffer.
428/// The layout must match the uniform block declaration in the shader.
429///
430/// **Important:** WGSL has specific alignment requirements. Mat4 is aligned to 16 bytes,
431/// and Vec3 needs padding to be treated as Vec4 in uniform buffers.
432#[repr(C, align(16))]
433#[derive(Debug, Clone, Copy)]
434pub struct CameraUniformData {
435    /// The combined view-projection matrix (projection * view).
436    pub view_projection: Mat4,
437    /// The camera's position in world space.
438    /// Note: The fourth component is padding for alignment.
439    pub camera_position: [f32; 4],
440}
441
442impl CameraUniformData {
443    /// Creates camera uniform data from a `ViewInfo`.
444    pub fn from_view_info(view_info: &ViewInfo) -> Self {
445        Self {
446            view_projection: view_info.view_projection_matrix(),
447            camera_position: [
448                view_info.camera_position.x,
449                view_info.camera_position.y,
450                view_info.camera_position.z,
451                0.0, // Padding for alignment
452            ],
453        }
454    }
455
456    /// Returns the data as a byte slice suitable for uploading to a GPU buffer.
457    pub fn as_bytes(&self) -> &[u8] {
458        unsafe {
459            std::slice::from_raw_parts(
460                self as *const Self as *const u8,
461                std::mem::size_of::<Self>(),
462            )
463        }
464    }
465}
466
467// Ensure the struct can be safely cast to bytes for GPU upload
468unsafe impl bytemuck::Pod for CameraUniformData {}
469unsafe impl bytemuck::Zeroable for CameraUniformData {}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474
475    #[test]
476    fn test_camera_uniform_data_size() {
477        // Mat4 = 64 bytes, Vec4 (padding included) = 16 bytes
478        // Total should be 80 bytes
479        assert_eq!(std::mem::size_of::<CameraUniformData>(), 80);
480    }
481
482    #[test]
483    fn test_camera_uniform_data_alignment() {
484        // CameraUniformData should be aligned to 16 bytes for GPU compatibility
485        assert_eq!(std::mem::align_of::<CameraUniformData>(), 16);
486    }
487
488    #[test]
489    fn test_camera_uniform_data_from_view_info() {
490        let view_matrix = Mat4::IDENTITY;
491        let projection_matrix = Mat4::IDENTITY;
492        let camera_position = Vec3::new(1.0, 2.0, 3.0);
493
494        let view_info = ViewInfo::new(view_matrix, projection_matrix, camera_position);
495        let uniform_data = CameraUniformData::from_view_info(&view_info);
496
497        // Check that camera position is correctly set
498        assert_eq!(uniform_data.camera_position[0], 1.0);
499        assert_eq!(uniform_data.camera_position[1], 2.0);
500        assert_eq!(uniform_data.camera_position[2], 3.0);
501        assert_eq!(uniform_data.camera_position[3], 0.0); // Padding
502
503        // Check that view_projection is set
504        let expected_vp = projection_matrix * view_matrix;
505        assert_eq!(uniform_data.view_projection, expected_vp);
506    }
507
508    #[test]
509    fn test_camera_uniform_data_as_bytes() {
510        let view_info = ViewInfo::default();
511        let uniform_data = CameraUniformData::from_view_info(&view_info);
512
513        let bytes = uniform_data.as_bytes();
514        assert_eq!(bytes.len(), std::mem::size_of::<CameraUniformData>());
515    }
516
517    #[test]
518    fn test_camera_uniform_data_bytemuck() {
519        // Test that we can use bytemuck functions
520        let uniform_data = CameraUniformData {
521            view_projection: Mat4::IDENTITY,
522            camera_position: [0.0, 0.0, 0.0, 0.0],
523        };
524
525        let data_array = [uniform_data];
526        let bytes: &[u8] = bytemuck::cast_slice(&data_array);
527        assert_eq!(bytes.len(), std::mem::size_of::<CameraUniformData>());
528    }
529
530    #[test]
531    fn test_view_info_new() {
532        let view = Mat4::from_translation(Vec3::new(0.0, 0.0, -5.0));
533        let proj = Mat4::IDENTITY;
534        let pos = Vec3::new(0.0, 1.0, 5.0);
535
536        let view_info = ViewInfo::new(view, proj, pos);
537
538        assert_eq!(view_info.view_matrix, view);
539        assert_eq!(view_info.projection_matrix, proj);
540        assert_eq!(view_info.camera_position, pos);
541    }
542
543    #[test]
544    fn test_view_info_view_projection_matrix() {
545        let view = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));
546        let proj = Mat4::from_scale(Vec3::new(2.0, 2.0, 1.0));
547        let pos = Vec3::ZERO;
548
549        let view_info = ViewInfo::new(view, proj, pos);
550        let vp = view_info.view_projection_matrix();
551
552        // view_projection should be projection * view
553        assert_eq!(vp, proj * view);
554    }
555
556    #[test]
557    fn test_view_info_default() {
558        let view_info = ViewInfo::default();
559
560        assert_eq!(view_info.view_matrix, Mat4::IDENTITY);
561        assert_eq!(view_info.projection_matrix, Mat4::IDENTITY);
562        assert_eq!(view_info.camera_position, Vec3::ZERO);
563    }
564
565    #[test]
566    fn test_shader_stage_flags_bitwise() {
567        let vertex_fragment = ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT;
568
569        assert!(vertex_fragment.contains(ShaderStage::Vertex));
570        assert!(vertex_fragment.contains(ShaderStage::Fragment));
571        assert!(!vertex_fragment.contains(ShaderStage::Compute));
572    }
573
574    #[test]
575    fn test_shader_stage_flags_all() {
576        let all = ShaderStageFlags::ALL;
577
578        assert!(all.contains(ShaderStage::Vertex));
579        assert!(all.contains(ShaderStage::Fragment));
580        assert!(all.contains(ShaderStage::Compute));
581    }
582
583    #[test]
584    fn test_shader_stage_flags_union() {
585        let vertex = ShaderStageFlags::VERTEX;
586        let fragment = ShaderStageFlags::FRAGMENT;
587        let combined = vertex.union(fragment);
588
589        assert!(combined.contains(ShaderStage::Vertex));
590        assert!(combined.contains(ShaderStage::Fragment));
591        assert!(!combined.contains(ShaderStage::Compute));
592    }
593
594    #[test]
595    fn test_shader_stage_flags_from_stage() {
596        let vertex_flags = ShaderStageFlags::from_stage(ShaderStage::Vertex);
597        assert_eq!(vertex_flags, ShaderStageFlags::VERTEX);
598
599        let fragment_flags = ShaderStageFlags::from_stage(ShaderStage::Fragment);
600        assert_eq!(fragment_flags, ShaderStageFlags::FRAGMENT);
601
602        let compute_flags = ShaderStageFlags::from_stage(ShaderStage::Compute);
603        assert_eq!(compute_flags, ShaderStageFlags::COMPUTE);
604    }
605
606    #[test]
607    fn test_shader_stage_flags_is_empty() {
608        let none = ShaderStageFlags::NONE;
609        assert!(none.is_empty());
610
611        let vertex = ShaderStageFlags::VERTEX;
612        assert!(!vertex.is_empty());
613    }
614
615    #[test]
616    fn test_shader_stage_flags_bitor_assign() {
617        let mut flags = ShaderStageFlags::VERTEX;
618        flags |= ShaderStageFlags::FRAGMENT;
619
620        assert!(flags.contains(ShaderStage::Vertex));
621        assert!(flags.contains(ShaderStage::Fragment));
622    }
623}