feat(renderer): add selectable debug render modes
This commit is contained in:
parent
a5af42f119
commit
5f4b30a4dc
|
|
@ -53,9 +53,13 @@ pub fn create_logical_device(
|
||||||
let mut dynamic_rendering_features =
|
let mut dynamic_rendering_features =
|
||||||
vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true);
|
vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true);
|
||||||
|
|
||||||
|
// `fillModeNonSolid` unlocks the `POINT` and `LINE` polygon modes used by the debug render modes.
|
||||||
|
let enabled_features = vk::PhysicalDeviceFeatures::default().fill_mode_non_solid(true);
|
||||||
|
|
||||||
let create_info = vk::DeviceCreateInfo::default()
|
let create_info = vk::DeviceCreateInfo::default()
|
||||||
.queue_create_infos(std::slice::from_ref(&queue_info))
|
.queue_create_infos(std::slice::from_ref(&queue_info))
|
||||||
.enabled_extension_names(&device_extensions)
|
.enabled_extension_names(&device_extensions)
|
||||||
|
.enabled_features(&enabled_features)
|
||||||
.push_next(&mut synchronization2_features)
|
.push_next(&mut synchronization2_features)
|
||||||
.push_next(&mut dynamic_rendering_features);
|
.push_next(&mut dynamic_rendering_features);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||||
use std::ffi::c_char;
|
use std::ffi::c_char;
|
||||||
|
|
||||||
pub use error::RendererError;
|
pub use error::RendererError;
|
||||||
pub use renderer::{MeshKey, Renderer};
|
pub use renderer::{MeshKey, RenderMode, Renderer};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|
@ -124,8 +124,17 @@ impl Renderer {
|
||||||
|
|
||||||
// 12. Graphics Pipeline Configuration
|
// 12. Graphics Pipeline Configuration
|
||||||
let pipeline_layout = pipeline::create_pipeline_layout(&device)?;
|
let pipeline_layout = pipeline::create_pipeline_layout(&device)?;
|
||||||
let graphics_pipeline =
|
|
||||||
pipeline::create_graphics_pipeline(&device, pipeline_layout, swapchain_format)?;
|
// One pipeline per render mode, built up front so switching modes is a bind-time choice rather than a pipeline compilation stall. All variants share `pipeline_layout`; only their rasterisation state differs.
|
||||||
|
let mut pipelines = [vk::Pipeline::null(); RenderMode::COUNT];
|
||||||
|
for mode in RenderMode::ALL {
|
||||||
|
pipelines[mode.index()] = pipeline::create_graphics_pipeline(
|
||||||
|
&device,
|
||||||
|
pipeline_layout,
|
||||||
|
swapchain_format,
|
||||||
|
mode.polygon_mode(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let (depth_image, depth_allocation, depth_image_view) =
|
let (depth_image, depth_allocation, depth_image_view) =
|
||||||
create_depth_resources(&device, &mut allocator, swapchain_extent)?;
|
create_depth_resources(&device, &mut allocator, swapchain_extent)?;
|
||||||
|
|
@ -155,7 +164,8 @@ impl Renderer {
|
||||||
depth_allocation: Some(depth_allocation),
|
depth_allocation: Some(depth_allocation),
|
||||||
depth_image_view,
|
depth_image_view,
|
||||||
pipeline_layout,
|
pipeline_layout,
|
||||||
graphics_pipeline,
|
pipelines,
|
||||||
|
render_mode: RenderMode::default(),
|
||||||
sync: Some(sync),
|
sync: Some(sync),
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ pub fn create_graphics_pipeline(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: vk::PipelineLayout,
|
layout: vk::PipelineLayout,
|
||||||
color_format: vk::Format,
|
color_format: vk::Format,
|
||||||
|
polygon_mode: vk::PolygonMode,
|
||||||
) -> Result<vk::Pipeline, RendererError> {
|
) -> Result<vk::Pipeline, RendererError> {
|
||||||
// 1. Load and compile shader modules
|
// 1. Load and compile shader modules
|
||||||
let (vert_module, frag_module) = load_shader_modules(device)?;
|
let (vert_module, frag_module) = load_shader_modules(device)?;
|
||||||
|
|
@ -101,7 +102,7 @@ pub fn create_graphics_pipeline(
|
||||||
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
||||||
.depth_clamp_enable(false)
|
.depth_clamp_enable(false)
|
||||||
.rasterizer_discard_enable(false)
|
.rasterizer_discard_enable(false)
|
||||||
.polygon_mode(vk::PolygonMode::FILL)
|
.polygon_mode(polygon_mode)
|
||||||
.line_width(1.0)
|
.line_width(1.0)
|
||||||
.cull_mode(vk::CullModeFlags::BACK)
|
.cull_mode(vk::CullModeFlags::BACK)
|
||||||
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,41 @@ use std::collections::HashMap;
|
||||||
/// Opaque, renderer-side identifier for one uploaded chunk mesh.
|
/// Opaque, renderer-side identifier for one uploaded chunk mesh.
|
||||||
pub type MeshKey = (i32, i32, i32);
|
pub type MeshKey = (i32, i32, i32);
|
||||||
|
|
||||||
|
/// Selects how chunk meshes are rasterised.
|
||||||
|
///
|
||||||
|
/// Non-[`RenderMode::Filled`] variants are debug modes for inspecting the mesher's output; they are not gameplay state. One pipeline is built per variant at initialisation and held in [`Renderer::pipelines`], indexed by [`RenderMode::index`].
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub enum RenderMode {
|
||||||
|
/// Filled triangles; the normal presentation path.
|
||||||
|
#[default]
|
||||||
|
Filled,
|
||||||
|
/// One point per polygon vertex, exposing the density of the geometry the mesher emitted.
|
||||||
|
Points,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderMode {
|
||||||
|
/// Number of variants, and therefore the number of pipelines built at initialisation.
|
||||||
|
pub const COUNT: usize = 2;
|
||||||
|
|
||||||
|
/// Every variant, in discriminant order. The array length is checked against [`RenderMode::COUNT`] at compile time, so a new variant that is not listed here fails to build.
|
||||||
|
pub const ALL: [Self; Self::COUNT] = [Self::Filled, Self::Points];
|
||||||
|
|
||||||
|
/// Returns the rasterisation polygon mode backing this render mode.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn polygon_mode(self) -> vk::PolygonMode {
|
||||||
|
match self {
|
||||||
|
Self::Filled => vk::PolygonMode::FILL,
|
||||||
|
Self::Points => vk::PolygonMode::POINT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this mode's position in [`RenderMode::ALL`], used to index [`Renderer::pipelines`].
|
||||||
|
#[must_use]
|
||||||
|
pub const fn index(self) -> usize {
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// GPU resources for a single chunk mesh, drawn at a fixed world offset.
|
/// GPU resources for a single chunk mesh, drawn at a fixed world offset.
|
||||||
pub(crate) struct GpuMesh {
|
pub(crate) struct GpuMesh {
|
||||||
/// Buffer holding the chunk's vertex data.
|
/// Buffer holding the chunk's vertex data.
|
||||||
|
|
@ -70,8 +105,10 @@ pub struct Renderer {
|
||||||
pub(crate) command_buffers: Vec<vk::CommandBuffer>,
|
pub(crate) command_buffers: Vec<vk::CommandBuffer>,
|
||||||
/// The layout of the graphics pipeline.
|
/// The layout of the graphics pipeline.
|
||||||
pub(crate) pipeline_layout: vk::PipelineLayout,
|
pub(crate) pipeline_layout: vk::PipelineLayout,
|
||||||
/// The compiled graphics pipeline state.
|
/// One compiled pipeline per [`RenderMode`], indexed by [`RenderMode::index`]. All variants share [`Renderer::pipeline_layout`] and differ only in rasterisation state.
|
||||||
pub(crate) graphics_pipeline: vk::Pipeline,
|
pub(crate) pipelines: [vk::Pipeline; RenderMode::COUNT],
|
||||||
|
/// The rasterisation mode selected for subsequent frames, chosen by [`Renderer::set_render_mode`].
|
||||||
|
pub(crate) render_mode: RenderMode,
|
||||||
/// Memory manager for GPU allocations.
|
/// Memory manager for GPU allocations.
|
||||||
pub(crate) allocator: Option<Allocator>,
|
pub(crate) allocator: Option<Allocator>,
|
||||||
/// Uploaded chunk meshes, keyed by an opaque renderer-side handle and drawn independently.
|
/// Uploaded chunk meshes, keyed by an opaque renderer-side handle and drawn independently.
|
||||||
|
|
@ -385,10 +422,11 @@ impl Renderer {
|
||||||
/// Issues the actual draw calls for the frame.
|
/// Issues the actual draw calls for the frame.
|
||||||
fn issue_draw_calls(&self, cmd: vk::CommandBuffer, camera_view: glam::Mat4) {
|
fn issue_draw_calls(&self, cmd: vk::CommandBuffer, camera_view: glam::Mat4) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// The bound pipeline is the sole difference between render modes; every other command recorded below is mode-independent.
|
||||||
self.device.cmd_bind_pipeline(
|
self.device.cmd_bind_pipeline(
|
||||||
cmd,
|
cmd,
|
||||||
vk::PipelineBindPoint::GRAPHICS,
|
vk::PipelineBindPoint::GRAPHICS,
|
||||||
self.graphics_pipeline,
|
self.pipelines[self.render_mode.index()],
|
||||||
);
|
);
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
|
|
@ -592,6 +630,17 @@ impl Renderer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the rasterisation mode used for subsequent frames.
|
||||||
|
pub const fn set_render_mode(&mut self, mode: RenderMode) {
|
||||||
|
self.render_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the rasterisation mode currently in use.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn render_mode(&self) -> RenderMode {
|
||||||
|
self.render_mode
|
||||||
|
}
|
||||||
|
|
||||||
/// Frees the GPU mesh stored under `key`. Does nothing if no mesh is present.
|
/// Frees the GPU mesh stored under `key`. Does nothing if no mesh is present.
|
||||||
pub fn remove_mesh(&mut self, key: MeshKey) {
|
pub fn remove_mesh(&mut self, key: MeshKey) {
|
||||||
let Some(mesh) = self.chunk_meshes.remove(&key) else {
|
let Some(mesh) = self.chunk_meshes.remove(&key) else {
|
||||||
|
|
@ -617,7 +666,10 @@ impl Drop for Renderer {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = self.device.device_wait_idle();
|
let _ = self.device.device_wait_idle();
|
||||||
|
|
||||||
self.device.destroy_pipeline(self.graphics_pipeline, None);
|
// Every rasterisation variant is a distinct pipeline object and must be destroyed, or the validation layers report the survivors as leaked at teardown.
|
||||||
|
for pipeline in self.pipelines {
|
||||||
|
self.device.destroy_pipeline(pipeline, None);
|
||||||
|
}
|
||||||
self.device
|
self.device
|
||||||
.destroy_pipeline_layout(self.pipeline_layout, None);
|
.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue