Toast Engine
Loading...
Searching...
No Matches
vulkan_renderer.hpp
1
4
5#pragma once
6
8#include "debug_draw.hpp"
10#include "gpu_sync.hpp"
11#include "gpu_timer.hpp"
12#include "output_target_base.hpp"
15#include "render_pass_base.hpp"
16#include "shader_layout.hpp"
17#include "shadow_constants.hpp"
18#include "shadow_slots.hpp"
19#include "voxel_gpu_storage.hpp"
21#include "vulkan_core.hpp"
22#include "vulkan_mesh.hpp"
23#include "vulkan_pipeline.hpp"
24#include "vulkan_texture.hpp"
25
26#include <algorithm>
27#include <array>
28#include <chrono>
29#include <cmath>
30#include <condition_variable>
31#include <deque>
32#include <functional>
33#include <glm/gtc/constants.hpp>
34#include <glm/gtc/quaternion.hpp>
35#include <memory>
36#include <mutex>
37#include <optional>
38#include <queue>
39#include <semaphore>
40#include <span>
41#include <string>
42#include <string_view>
43#include <thread>
45#include <toast/assets/mesh.hpp>
47#include <toast/events/event.inl>
48#include <toast/events/listener.hpp>
50#include <tracy/TracyVulkan.hpp>
51#include <type_traits>
52#include <unordered_map>
53#include <unordered_set>
54#include <utility>
55#include <vector>
56
57namespace assets {
58class Material;
59}
60
61namespace toast {
62class Camera;
63class MeshNode;
64class VoxelNode;
65class Light;
66class ReflectionProbe;
69class INodeOwner;
70class Node;
71class Node3D;
72}
73
74namespace renderer {
75
76class MaterialPass;
78class ShadowPass;
79class EnvironmentPass;
81class DepthPrepass;
82class SkinningPass;
83class VoxelPass;
84class DebrisPass;
85class RayTracingScene;
86class SkinnedBlasPool;
87
88class VulkanRenderer {
89public:
90 [[nodiscard]]
91 static auto selectDepthFormat(const VulkanCore& core) -> vk::Format;
92
93 static constexpr uint32_t k_frames_in_flight = 3;
94
95 static constexpr uint8_t k_render_frames = 3;
96
97 struct FrameContext {
98 vk::raii::CommandBuffer command_buffer = nullptr;
99 vk::raii::Semaphore image_available = nullptr;
100 vk::raii::Fence in_flight = nullptr;
101 uint32_t last_image_index = 0;
102 bool has_submitted = false;
103 bool was_probe_capture = false;
104
105 std::vector<vma::raii::Buffer> blas_scratch;
106
108 std::vector<std::shared_ptr<void>> retired;
109 };
110
111 static constexpr uint32_t k_max_directional_lights = 4;
112
113 static constexpr uint32_t k_max_joint_matrices = 4096;
114
115 static constexpr uint32_t k_max_instances = 16384;
116
117 static constexpr uint32_t k_instance_no_motion = 1u << 0;
118
121 glm::mat4 model {1.0f};
122 glm::mat4 previous_model {1.0f};
123 uint32_t joint_offset = 0;
124 uint32_t flags = 0;
125 std::array<uint32_t, 2> pad0 {0, 0};
126 };
127
128 static_assert(sizeof(InstanceData) == 144, "InstanceData stride must match mesh.slang's std430 layout");
129
131 glm::vec4 direction;
132 glm::vec4 color_intensity;
133 };
134
135 static constexpr uint32_t k_max_reflection_probes = 8;
136
138 glm::vec4 position_radius;
139 // xyz box half extents w intensity. Zero extents use sphere projection
140 glm::vec4 box_extents_intensity;
141
142 // x cubemap index in registration order
143 glm::vec4 params {0.0f};
144 };
145
146 static constexpr uint32_t k_max_irradiance_volumes = 4;
147
148 static constexpr uint32_t k_max_irradiance_probes = 4096;
149
150 static constexpr uint32_t k_irradiance_capture_size = 64;
151
154 glm::vec4 min_corner_spacing {0.0f};
156 glm::uvec4 counts_base {0};
158 glm::vec4 extents_intensity {0.0f};
159
161 glm::uvec4 baked_pad {0};
162 };
163
165
166 struct FrameUBO {
167 glm::mat4 view {1.0f};
168 glm::mat4 projection {1.0f};
169 glm::mat4 view_projection {1.0f};
170
171 glm::vec3 camera_position {};
172
173 float time = 0.0f;
174
176 glm::mat4 jittered_view_projection {1.0f};
177
179 glm::mat4 previous_view_projection {1.0f};
180
181 glm::vec4 ambient_color_intensity {0.0f};
182
183 // uvec4 so std140 and cbuffer packing agree
184 glm::uvec4 directional_light_count_pad {0};
185
186 std::array<DirectionalLightData, k_max_directional_lights> directional_lights {};
187
188 glm::uvec4 render_mode_pad {0};
189
190 std::array<glm::mat4, shadows::k_cascade_count> cascade_view_projection {};
191
192 glm::vec4 cascade_splits {0.0f};
193
194 glm::vec4 cascade_texel_world_size {0.0f};
195
196 glm::vec4 cascade_depth_bias {0.0f};
197
198 // x caster index + 1 (0 none) y cascades fitted zw texel size in UV
199 glm::vec4 shadow_params {0.0f};
200
201 // x 1 when the cubemaps hold data y prefiltered roughness levels
202 glm::vec4 environment_params {0.0f};
203
204 glm::uvec4 reflection_probe_count_pad {0};
205
206 std::array<ReflectionProbeData, k_max_reflection_probes> reflection_probes {};
207
208 glm::uvec4 irradiance_volume_count_pad {0};
209
210 std::array<IrradianceVolumeData, k_max_irradiance_volumes> irradiance_volumes {};
211
212 // x 1 traces the TLAS instead of sampling shadow maps
213 glm::vec4 traced_shadow_params {0.0f};
214 };
215
216 static_assert(
217 offsetof(FrameUBO, jittered_view_projection) == 208,
218 "FrameUBO::jittered_view_projection must match lighting.slang and depth_prepass.slang"
219 );
220 static_assert(
221 offsetof(FrameUBO, irradiance_volumes) == 1264, "FrameUBO::irradiance_volumes must match lighting.slang's std140 layout"
222 );
223 static_assert(
224 offsetof(FrameUBO, traced_shadow_params) == 1520, "FrameUBO::traced_shadow_params must match lighting.slang's std140 layout"
225 );
226
228 struct GpuLight {
229 glm::vec4 world_pos_range;
230 glm::vec4 view_pos_type; // w type 0 point 1 spot
231 glm::vec4 view_direction; // xyz view space spot axis
232 glm::vec4 color_intensity;
233 glm::vec4 direction_pad; // w shadow layer or -1
234 glm::vec4 cone_angles; // x cos outer y cos inner z shadow near w shadow far
235
236 // x fraction of the layer rendered y point light face scale inside the guard band
237 glm::vec4 shadow_atlas {1.0f, 1.0f, 0.0f, 0.0f};
238
239 glm::mat4 shadow_view_projection {1.0f};
240 };
241
242 static_assert(sizeof(GpuLight) == 176, "GpuLight must match the stride in lighting.slang and cluster_lighting.slang");
243
244 struct ShadowView {
245 glm::mat4 view_projection {1.0f};
246
247 glm::vec4 cull_sphere {0.0f};
248
249 uint32_t layer = 0;
250 bool directional = false;
251
252 uint32_t resolution = 0;
253 };
254
255 struct ShadowFrame {
256 std::vector<ShadowView> views;
257
259 std::vector<glm::mat4> matrices;
260 };
261
263 VulkanMesh* mesh = nullptr;
264 assets::Material* material = nullptr;
265 assets::Material* root_material = nullptr;
266 glm::mat4 model = glm::mat4(1.0f);
267
268 // joint_count > 0 selects the skinned pipeline
269 uint32_t joint_offset = 0;
270 uint32_t joint_count = 0;
271
272 glm::vec3 bounds_center {0.0f};
273 float bounds_radius = 0.0f;
274
275 bool visible = true;
276
277 uint32_t instance_index = 0;
278
279 static constexpr uint32_t k_no_posed_vertices = ~0u;
280
281 uint32_t posed_vertex_offset = k_no_posed_vertices;
282
283 uint64_t node_rid = 0;
284 };
285
287 uint32_t record_index = 0;
288
289 glm::uvec3 brick_dims {0};
290
291 glm::mat4 model {1.0f};
292 glm::mat4 inverse_model {1.0f};
293
294 glm::mat4 previous_model {1.0f};
295
297 bool has_previous = false;
298
299 glm::vec3 bounds_center {0.0f};
300 float bounds_radius = 0.0f;
301
302 float view_distance = 0.0f;
303
304 bool camera_inside = false;
305
306 bool mirrored = false;
307
308 bool visible = true;
309
310 uint64_t node_rid = 0;
311 uint32_t highlight_record = 0;
312 };
313
315 vk::ImageView view = nullptr;
316 glm::mat4 model = glm::mat4(1.0f);
317 bool always_on_top = false;
318 };
319
321 glm::vec3 world_position {0.0f};
322 toast::GizmoHandle handle = toast::GizmoHandle::none;
323 };
324
325 static constexpr size_t k_max_size_handles = 6;
326
328 bool visible = false;
329 toast::GizmoTool tool = toast::GizmoTool::select;
330 glm::mat4 model {1.0f};
331 toast::GizmoHandle hover = toast::GizmoHandle::none;
332 toast::GizmoHandle active = toast::GizmoHandle::none;
333 float drag_scale_factor = 1.0f;
334
335 std::array<SizeHandleDraw, k_max_size_handles> size_handles {};
336 uint32_t size_handle_count = 0;
337 float size_handle_scale = 1.0f;
338 };
339
340 struct GizmoState {
341 bool visible = false;
342 toast::GizmoTool tool = toast::GizmoTool::select;
343 glm::vec3 origin {0.0f};
344 glm::quat orientation {1.0f, 0.0f, 0.0f, 0.0f};
345 toast::GizmoHandle hover = toast::GizmoHandle::none;
346 toast::GizmoHandle active = toast::GizmoHandle::none;
347 float drag_scale_factor = 1.0f;
348
349 std::array<SizeHandleDraw, k_max_size_handles> size_handles {};
350 uint32_t size_handle_count = 0;
351 float size_handle_scale = 1.0f;
352 };
353
355 int32_t key = 0;
356 bool down = false;
357 };
358
360 glm::vec2 mouse_pos {-1.0f, -1.0f}; // -1 -1 when outside the window
361 std::array<bool, 3> mouse_down {}; // 0 left 1 right 2 middle
362 float mouse_wheel_x = 0.0f;
363 float mouse_wheel_y = 0.0f;
364 std::vector<ImGuiKeyEvent> key_events;
365 std::vector<uint32_t> char_events;
366 };
367
368 struct LightStats {
369 uint32_t submitted = 0;
370 uint32_t offscreen = 0;
371 uint32_t truncated = 0;
372 uint32_t spot_shadows = 0;
373 uint32_t point_shadows = 0;
374 uint64_t shadow_displaced = 0;
375 };
376
377 struct RenderFrame {
378 FrameUBO frame_data;
379
380 std::vector<MeshInstanceProxy> mesh_instances;
381
383 std::vector<VoxelVolumeProxy> voxel_instances;
384
385 std::shared_ptr<const VoxelGpuStorage> voxel_storage;
386
388 float near_plane = 0.0f;
389
390 std::vector<assets::HandleBase> asset_refs;
391
392 std::vector<GpuLight> lights;
393
394 ShadowFrame shadows;
395
396 std::vector<glm::mat4> joint_matrices;
397
398 std::vector<InstanceData> instance_data;
399
400 std::vector<InstanceData> shadow_instance_data;
401
403 assets::Material* root_material = nullptr;
404 uint32_t begin = 0;
405 uint32_t end = 0;
406 };
407
408 std::vector<MaterialRange> material_ranges;
409
410 glm::vec2 viewport_extent {0.0f};
411 float camera_near = 0.01f;
412 float camera_far = 5000.0f;
413
414 float cluster_near = 0.1f;
415 float cluster_far = 5000.0f;
416
417 LightStats light_stats;
418
419 ImGuiInputSnapshot imgui_input;
420
421 uint32_t render_mode = 0;
422
423 uint64_t sequence = 0;
424
426 bool taa_active = false;
427
429 bool taa_reset = false;
430
431 PostProcessSettings post_process;
432
433 int32_t probe_capture_index = -1;
434 uint32_t probe_capture_face = 0;
435
436 int32_t irradiance_capture_index = -1;
437 uint32_t irradiance_capture_face = 0;
438
439 uint32_t capture_extent = 0;
440
441 std::vector<debug::Vertex> debug_line_vertices; // pairs form line segments
442 std::vector<debug::Vertex> debug_triangle_vertices;
443 std::vector<glm::mat4> debug_gizmo_instances;
444 std::vector<debug::Billboard> debug_billboards;
445 std::vector<debug::Mesh> debug_meshes;
446
447 TransformGizmoDraw transform_gizmo;
448
449 std::vector<vk::CommandBuffer> ui_command_buffers;
450 std::vector<vk::ImageView> ui_output_views;
451 std::vector<UIWorldPanelProxy> ui_world_panels;
452 std::shared_ptr<const void> ui_slot_guard;
453 };
454
455 using UIFrameBuilder = std::function<void(RenderFrame&)>;
456
457 void setUIFrameBuilder(UIFrameBuilder builder) { m_ui_frame_builder = std::move(builder); }
458
459 VulkanRenderer(const VulkanCore& core, std::unique_ptr<IOutputTarget> output_target) noexcept;
460
462
463 VulkanRenderer(const VulkanRenderer&) = delete;
464 auto operator=(const VulkanRenderer&) -> VulkanRenderer& = delete;
465 VulkanRenderer(VulkanRenderer&&) = delete;
466 auto operator=(VulkanRenderer&&) -> VulkanRenderer& = delete;
467
468 void start() noexcept;
469
470 [[nodiscard]]
471 auto getCurrentFrameBuild() noexcept -> RenderFrame& {
472 return m_render_frames[m_write_index];
473 }
474
475 void submitFrame() noexcept;
476
478 void queueDebugLine(glm::vec3 a, glm::vec3 b, glm::vec4 color);
479
480 void tick(float time) noexcept;
481
482 void registerMeshNodeProxy(toast::MeshNode* node);
483 void registerDebugDraw(toast::Node3D* node, void (*draw)(toast::Node3D&));
484 void unregisterDebugDraw(toast::Node3D* node);
485
486 void unregisterMeshNodeProxy(toast::MeshNode* node);
487
488 void registerVoxelNodeProxy(toast::VoxelNode* node);
489
490 void unregisterVoxelNodeProxy(toast::VoxelNode* node);
491
492 void registerLightNodeProxy(toast::Light* node);
493
494 void unregisterLightNodeProxy(toast::Light* node);
495
496 void registerCameraNodeProxy(toast::Camera* node);
497
498 void unregisterCameraNodeProxy(toast::Camera* node);
499
500 void registerReflectionProbeProxy(toast::ReflectionProbe* node);
501
502 void unregisterReflectionProbeProxy(toast::ReflectionProbe* node);
503
504 void registerIrradianceVolumeProxy(toast::IrradianceVolume* node);
505
506 void registerPostProcessVolumeProxy(toast::PostProcessVolume* node);
507
508 void unregisterPostProcessVolumeProxy(toast::PostProcessVolume* node);
509
510 void unregisterIrradianceVolumeProxy(toast::IrradianceVolume* node);
511
512private:
513 void cancelIrradianceBake();
514
515public:
517 void setFrameRateLimit(double max_fps) noexcept { m_frame_rate_limit_hz.store(max_fps, std::memory_order_relaxed); }
518
519 [[nodiscard]]
520 auto frameRateLimit() const noexcept -> double {
521 return m_frame_rate_limit_hz.load(std::memory_order_relaxed);
522 }
523
524 static constexpr double k_background_frame_rate_limit = 30.0;
525
527 void setClampToSimulation(bool clamp) noexcept { m_clamp_to_simulation.store(clamp, std::memory_order_relaxed); }
528
529 [[nodiscard]]
530 auto clampToSimulation() const noexcept -> bool {
531 return m_clamp_to_simulation.load(std::memory_order_relaxed);
532 }
533
534 void setDebugWindowsVisible(bool visible) noexcept { m_debug_windows_visible.store(visible, std::memory_order_relaxed); }
535
536 [[nodiscard]]
537 auto debugWindowsVisible() const noexcept -> bool {
538 return m_debug_windows_visible.load(std::memory_order_relaxed);
539 }
540
541 void setApplicationFocused(bool focused) noexcept { m_application_focused.store(focused, std::memory_order_relaxed); }
542
543 [[nodiscard]]
544 auto applicationFocused() const noexcept -> bool {
545 return m_application_focused.load(std::memory_order_relaxed);
546 }
547
548 [[nodiscard]]
549 auto effectiveFrameRateLimit() const noexcept -> double {
550 const double user_limit = frameRateLimit();
551 if (applicationFocused()) {
552 return user_limit;
553 }
554 return user_limit > 0.0 && user_limit < k_background_frame_rate_limit ? user_limit : k_background_frame_rate_limit;
555 }
556
557 void setRenderingPaused(bool paused) noexcept;
558
559 [[nodiscard]]
560 auto renderingPaused() const noexcept -> bool {
561 return m_rendering_paused.load(std::memory_order_relaxed);
562 }
563
564 void stop();
565
566 void addRenderPass(std::unique_ptr<IRenderPass> pass);
567
568 void addComputePass(std::unique_ptr<IComputePass> pass);
569
571 void addPostProcessPass(std::unique_ptr<IPostProcessPass> pass);
572
573 auto setPostProcessPassEnabled(std::string_view name, bool enabled) -> bool;
574
575 [[nodiscard]]
576 auto isPostProcessPassEnabled(std::string_view name) const -> bool;
577
578 [[nodiscard]]
579 auto getSceneColorFormat() const noexcept -> vk::Format {
580 return m_scene_color_format;
581 }
582
583 [[nodiscard]]
584 auto getSceneColorView() const noexcept -> vk::ImageView {
585 return m_scene_color.view.has_value() ? **m_scene_color.view : vk::ImageView {};
586 }
587
589
590 [[nodiscard]]
591 auto getSceneNormalView() const noexcept -> vk::ImageView {
592 return m_scene_normal.view.has_value() ? **m_scene_normal.view : vk::ImageView {};
593 }
594
595 [[nodiscard]]
596 auto getSceneIndirectView() const noexcept -> vk::ImageView {
597 return m_scene_indirect.view.has_value() ? **m_scene_indirect.view : vk::ImageView {};
598 }
599
600 [[nodiscard]]
601 auto getSceneMotionView() const noexcept -> vk::ImageView {
602 return m_scene_motion.view.has_value() ? **m_scene_motion.view : vk::ImageView {};
603 }
604
605 [[nodiscard]]
606 auto getVoxelAlbedoView() const noexcept -> vk::ImageView {
607 return m_voxel_albedo.view.has_value() ? **m_voxel_albedo.view : vk::ImageView {};
608 }
609
610 [[nodiscard]]
611 auto getVoxelMaterialView() const noexcept -> vk::ImageView {
612 return m_voxel_material.view.has_value() ? **m_voxel_material.view : vk::ImageView {};
613 }
614
616 [[nodiscard]]
617 auto getVoxelFaceNormalView() const noexcept -> vk::ImageView {
618 return m_voxel_face_normal.view.has_value() ? **m_voxel_face_normal.view : vk::ImageView {};
619 }
620
622 void setVoxelCellBoxes(bool enabled) noexcept { m_voxel_cell_boxes.store(enabled, std::memory_order_relaxed); }
623
624 void setVoxelOcclusion(bool enabled) noexcept { m_voxel_occlusion.store(enabled, std::memory_order_relaxed); }
625
626 void setVoxelMeshShaders(bool enabled) noexcept { m_voxel_mesh_shaders.store(enabled, std::memory_order_relaxed); }
627
628 [[nodiscard]]
629 auto voxelMeshShaders() const noexcept -> bool {
630 return m_voxel_mesh_shaders.load(std::memory_order_relaxed);
631 }
632
633 [[nodiscard]]
634 auto voxelOcclusion() const noexcept -> bool {
635 return m_voxel_occlusion.load(std::memory_order_relaxed);
636 }
637
639 void setVoxelRasterPixels(float pixels) noexcept {
640 m_voxel_raster_pixels.store(std::max(pixels, 0.0f), std::memory_order_relaxed);
641 }
642
643 [[nodiscard]]
644 auto voxelRasterPixels() const noexcept -> float {
645 return m_voxel_raster_pixels.load(std::memory_order_relaxed);
646 }
647
648 [[nodiscard]]
649 auto voxelCellBoxes() const noexcept -> bool {
650 return m_voxel_cell_boxes.load(std::memory_order_relaxed);
651 }
652
653 void setVoxelNormalRounding(float voxels) noexcept {
654 m_voxel_normal_rounding.store(std::max(voxels, 0.0f), std::memory_order_relaxed);
655 }
656
657 [[nodiscard]]
658 auto voxelNormalRounding() const noexcept -> float {
659 return m_voxel_normal_rounding.load(std::memory_order_relaxed);
660 }
661
663 [[nodiscard]]
664 auto getDepthView() const noexcept -> vk::ImageView {
665 return m_depth_resources.view.has_value() ? **m_depth_resources.view : vk::ImageView {};
666 }
667
668 struct PassInfo {
669 std::string name;
670 bool enabled = true;
671 };
672
673 [[nodiscard]]
674 auto listPasses() -> std::vector<PassInfo>;
675
676 void setPassEnabled(std::string_view name, bool enabled);
677
678 [[nodiscard]]
679 auto getDefaultTextureView() const noexcept -> vk::ImageView {
680 return m_default_texture.getView();
681 }
682
683 [[nodiscard]]
684 auto getFailsafeTextureView(bool has_reference, const VulkanTexture* texture) const noexcept -> vk::ImageView;
685
686 [[nodiscard]]
687 auto getFailsafeSampler() const noexcept -> vk::Sampler {
688 return *m_failsafe_sampler;
689 }
690
691 [[nodiscard]]
692 auto getDefaultBlackTextureView() const noexcept -> vk::ImageView {
693 return m_default_black_texture.getView();
694 }
695
696 [[nodiscard]]
697 auto getDefaultNormalTextureView() const noexcept -> vk::ImageView {
698 return m_default_normal_texture.getView();
699 }
700
701 [[nodiscard]]
702 auto getDefaultSampler() const noexcept -> vk::Sampler {
703 return *m_default_sampler;
704 }
705
706 [[nodiscard]]
707 auto getDefaultShadowMapView() const noexcept -> vk::ImageView {
708 return *m_default_shadow_view;
709 }
710
711 [[nodiscard]]
712 auto getDefaultShadowSampler() const noexcept -> vk::Sampler {
713 return *m_default_shadow_sampler;
714 }
715
716 [[nodiscard]]
717 auto getDefaultCubeView() const noexcept -> vk::ImageView {
718 return *m_default_cube_view;
719 }
720
721 void applyResize(vk::Extent2D extent);
722
723 void queueResourceUpload(std::unique_ptr<PendingResourceUpload> upload);
724
725 [[nodiscard]]
726 auto getFrameUBORes(uint32_t current_frame) const -> const FrameResources* {
727 return &m_frame_ubo_res[current_frame];
728 }
729
730 [[nodiscard]]
731 auto getInstanceBuffer(uint32_t frame_index) const -> vk::Buffer {
732 return frame_index < m_instance_res.size() && m_instance_res[frame_index].gpu_buffer.has_value()
733 ? **m_instance_res[frame_index].gpu_buffer
734 : vk::Buffer {};
735 }
736
737 [[nodiscard]]
738 auto getShadowInstanceBuffer(uint32_t frame_index) const -> vk::Buffer {
739 return frame_index < m_shadow_instance_res.size() && m_shadow_instance_res[frame_index].gpu_buffer.has_value()
740 ? **m_shadow_instance_res[frame_index].gpu_buffer
741 : vk::Buffer {};
742 }
743
744 [[nodiscard]]
745 auto getJointMatrixBuffer(uint32_t frame_index) const -> vk::Buffer {
746 return frame_index < m_joint_matrix_res.size() && m_joint_matrix_res[frame_index].gpu_buffer.has_value()
747 ? **m_joint_matrix_res[frame_index].gpu_buffer
748 : vk::Buffer {};
749 }
750
751 [[nodiscard]]
752 auto getDescriptorPoolHandle() const noexcept -> vk::DescriptorPool {
753 return *m_descriptor_pool;
754 }
755
756 void setClusterLightingPass(const ClusterLightingPass* pass) noexcept { m_cluster_lighting_pass = pass; }
757
758 [[nodiscard]]
759 auto getClusterLightingPass() const noexcept -> const ClusterLightingPass* {
760 return m_cluster_lighting_pass;
761 }
762
763 void setShadowPass(const ShadowPass* pass) noexcept { m_shadow_pass = pass; }
764
765 [[nodiscard]]
766 auto getShadowPass() const noexcept -> const ShadowPass* {
767 return m_shadow_pass;
768 }
769
770 void setEnvironmentPass(EnvironmentPass* pass) noexcept { m_environment_pass = pass; }
771
772 [[nodiscard]]
773 auto getEnvironmentPass() const noexcept -> const EnvironmentPass* {
774 return m_environment_pass;
775 }
776
778 void setVoxelPass(VoxelPass* pass) noexcept { m_voxel_pass = pass; }
779
780 void setDebrisPass(DebrisPass* pass) noexcept { m_debris_pass = pass; }
781
782 [[nodiscard]]
783 auto voxelCollisionWorld() const -> std::shared_ptr<const VoxelCollisionWorld>;
784
785 void setReflectionProbePass(ReflectionProbePass* pass) noexcept { m_reflection_probe_pass = pass; }
786
787 [[nodiscard]]
788 auto getReflectionProbePass() const noexcept -> ReflectionProbePass* {
789 return m_reflection_probe_pass;
790 }
791
792 void requestReflectionProbeBake() noexcept { m_probe_bake_requested.store(true, std::memory_order_release); }
793
794 void requestIrradianceBake() noexcept { m_irradiance_bake_requested.store(true, std::memory_order_release); }
795
796 [[nodiscard]]
797 auto getIrradianceProbeCount() const noexcept -> uint32_t {
798 return m_irradiance_probe_total;
799 }
800
801 [[nodiscard]]
802 auto getStaleIrradianceVolumeCount() const noexcept -> uint32_t {
803 return m_irradiance_stale_count.load(std::memory_order_relaxed);
804 }
805
806 [[nodiscard]]
807 auto getOutOfOrderFrameCount() const noexcept -> uint32_t {
808 return m_out_of_order_frames.load(std::memory_order_relaxed);
809 }
810
811 void getFrameTimeStats(float& avg_ms, float& min_ms, float& max_ms) const noexcept {
812 avg_ms = m_frame_time_avg_ms.load(std::memory_order_relaxed);
813 min_ms = m_frame_time_min_ms.load(std::memory_order_relaxed);
814 max_ms = m_frame_time_max_ms.load(std::memory_order_relaxed);
815 }
816
817 [[nodiscard]]
818 auto getSkippedBuildCount() const noexcept -> uint32_t {
819 return m_skipped_builds.load(std::memory_order_relaxed);
820 }
821
822 [[nodiscard]]
823 auto getDroppedFrameCount() const noexcept -> uint32_t {
824 return m_dropped_frames.load(std::memory_order_relaxed);
825 }
826
827 [[nodiscard]]
828 auto getVisibleInstanceCount() const noexcept -> uint32_t {
829 return m_visible_instance_count.load(std::memory_order_relaxed);
830 }
831
832 void setCullDebugDraw(bool enabled) noexcept { m_cull_debug_draw.store(enabled, std::memory_order_relaxed); }
833
834 [[nodiscard]]
835 auto getCullDebugDraw() const noexcept -> bool {
836 return m_cull_debug_draw.load(std::memory_order_relaxed);
837 }
838
839 void setCullFreeze(bool frozen) noexcept { m_cull_freeze.store(frozen, std::memory_order_relaxed); }
840
841 [[nodiscard]]
842 auto getCullFreeze() const noexcept -> bool {
843 return m_cull_freeze.load(std::memory_order_relaxed);
844 }
845
846 [[nodiscard]]
847 auto getStaleReflectionProbeCount() const noexcept -> uint32_t {
848 return m_probe_stale_count.load(std::memory_order_relaxed);
849 }
850
851 void requestMaterialFrameSetRebuild();
852
854 [[nodiscard]]
855 auto getEnvironmentPassMutable() const noexcept -> EnvironmentPass* {
856 return m_environment_pass;
857 }
858
859 [[nodiscard]]
860 auto getRayTracingScene() const noexcept -> RayTracingScene* {
861 return m_ray_tracing_scene.get();
862 }
863
864 [[nodiscard]]
865 auto getPrepassDrawnCount() const noexcept -> uint32_t;
866
867 [[nodiscard]]
868 auto getSkinningPass() const noexcept -> SkinningPass* {
869 return m_skinning_pass.get();
870 }
871
872 [[nodiscard]]
873 auto getPosedVertexBuffer(uint32_t frame_index) const -> vk::Buffer;
874
875 [[nodiscard]]
876 auto getSkinnedBlasPool() const noexcept -> SkinnedBlasPool* {
877 return m_skinned_blas_pool.get();
878 }
879
880 [[nodiscard]]
881 auto materialUsesCutout(assets::Material* material) const -> bool;
882
883 [[nodiscard]]
884 auto getReflectionProbePassMutable() const noexcept -> ReflectionProbePass* {
885 return m_reflection_probe_pass;
886 }
887
888 [[nodiscard]]
889 void setActiveCamera(toast::Camera* camera);
890
891 void forgetCamera(const toast::Camera* camera);
892
893 [[nodiscard]]
894 auto getCore() -> const VulkanCore& {
895 return *m_core;
896 }
897
898 [[nodiscard]]
899 auto getActiveCamera() -> toast::Camera* {
900 return m_camera;
901 }
902
903 void setGizmoState(const GizmoState& state) noexcept { m_gizmo_state = state; }
904
905 void setDebugDrawEnabled(bool enabled) noexcept { m_debug_draw_enabled.store(enabled, std::memory_order_relaxed); }
906
907 [[nodiscard]]
908 auto debugDrawEnabled() const noexcept -> bool {
909 return m_debug_draw_enabled.load(std::memory_order_relaxed);
910 }
911
913 uint64_t ticks = 0;
914 uint64_t frames_built = 0;
915 uint64_t slot_wait_ns = 0;
916 uint64_t draws = 0;
917 uint64_t repeated_draws = 0;
918 uint64_t draw_work_ns = 0;
919
920 uint64_t upload_ns = 0;
921 uint64_t gpu_wait_ns = 0;
922 uint64_t acquire_ns = 0;
923 uint64_t record_ns = 0;
924 uint64_t submit_ns = 0;
925 uint64_t present_ns = 0;
926 uint64_t frame_wait_ns = 0;
927 uint64_t pacing_ns = 0;
928 uint64_t build_ns = 0;
929 };
930
931 [[nodiscard]]
932 auto perfCounters() const noexcept -> PerfCounters {
933 return {
934 .ticks = m_perf_ticks.load(std::memory_order_relaxed),
935 .frames_built = m_perf_frames_built.load(std::memory_order_relaxed),
936 .slot_wait_ns = m_perf_slot_wait_ns.load(std::memory_order_relaxed),
937 .draws = m_perf_draws.load(std::memory_order_relaxed),
938 .repeated_draws = m_perf_repeated_draws.load(std::memory_order_relaxed),
939 .draw_work_ns = m_perf_draw_work_ns.load(std::memory_order_relaxed),
940 .upload_ns = m_perf_upload_ns.load(std::memory_order_relaxed),
941 .gpu_wait_ns = m_perf_gpu_wait_ns.load(std::memory_order_relaxed),
942 .acquire_ns = m_perf_acquire_ns.load(std::memory_order_relaxed),
943 .record_ns = m_perf_record_ns.load(std::memory_order_relaxed),
944 .submit_ns = m_perf_submit_ns.load(std::memory_order_relaxed),
945 .present_ns = m_perf_present_ns.load(std::memory_order_relaxed),
946 .frame_wait_ns = m_perf_frame_wait_ns.load(std::memory_order_relaxed),
947 .pacing_ns = m_perf_pacing_ns.load(std::memory_order_relaxed),
948 .build_ns = m_perf_build_ns.load(std::memory_order_relaxed),
949 };
950 }
951
952 struct UploadStats {
953 uint32_t slots_in_flight = 0;
954 uint32_t slot_count = 0;
955 size_t waiting_jobs = 0;
956 size_t batches_in_flight = 0;
957 vk::DeviceSize host_bytes = 0;
958 vk::DeviceSize host_budget = 0;
959 };
960
962 [[nodiscard]]
963 auto uploadStats() -> UploadStats;
964
966 [[nodiscard]]
967 auto uploadTimeline() const noexcept -> vk::Semaphore {
968 return *m_upload_timeline;
969 }
970
972 [[nodiscard]]
973 auto uploadTimelineValue() const noexcept -> uint64_t {
974 return m_upload_timeline_value;
975 }
976
978 void retire(std::shared_ptr<void> object);
979
981 [[nodiscard]]
982 auto gpuTimer() const noexcept -> const GpuTimer* {
983 return m_gpu_timer.get();
984 }
985
986 void setTracedShadowsEnabled(bool enabled) noexcept { m_traced_shadows_enabled.store(enabled, std::memory_order_relaxed); }
987
988 [[nodiscard]]
989 auto tracedShadowsEnabled() const noexcept -> bool {
990 return m_traced_shadows_enabled.load(std::memory_order_relaxed);
991 }
992
993 void setTaaEnabled(bool enabled) noexcept { m_taa_enabled.store(enabled, std::memory_order_relaxed); }
994
995 [[nodiscard]]
996 auto taaEnabled() const noexcept -> bool {
997 return m_taa_enabled.load(std::memory_order_relaxed);
998 }
999
1000 void setTaaHistoryWeight(float weight) noexcept { m_taa_history_weight.store(weight, std::memory_order_relaxed); }
1001
1002 [[nodiscard]]
1003 auto taaHistoryWeight() const noexcept -> float {
1004 return m_taa_history_weight.load(std::memory_order_relaxed);
1005 }
1006
1007 void setPostProcessSettings(const PostProcessSettings& settings) noexcept { m_post_process_settings = settings; }
1008
1009 [[nodiscard]]
1010 auto getPostProcessSettings() const noexcept -> const PostProcessSettings& {
1011 return m_post_process_settings;
1012 }
1013
1015 void requestPostProcessSettings(const PostProcessSettings& settings) {
1016 const std::lock_guard lock(m_pending_post_settings_mutex);
1017 m_pending_post_settings = settings;
1018 }
1019
1022 if (m_render_owner_filter == owner) {
1023 return;
1024 }
1025 m_render_owner_filter = owner;
1026 cancelIrradianceBake();
1027 }
1028
1033 void setVoxelHighlight(const toast::VoxelNode* node, const voxel::Volume* highlight) {
1034 std::scoped_lock lock(m_voxel_proxy_mutex);
1035 if (highlight == nullptr) {
1036 m_voxel_highlights.erase(node);
1037 } else {
1038 m_voxel_highlights[node] = highlight;
1039 }
1040 }
1041
1042 void setSecondaryOwner(const toast::INodeOwner* owner, const glm::mat4& transform, const toast::Node* hidden) {
1043 m_secondary_owner = owner;
1044 m_secondary_transform = transform;
1045 m_secondary_hidden = hidden;
1046 }
1047
1048 [[nodiscard]]
1049 auto renderingFrame() const -> const RenderFrame* {
1050 return m_rendering_frame;
1051 }
1052
1053 [[nodiscard]]
1054 auto getRenderDocAPI() const noexcept -> const RENDERDOC_API_1_6_0* {
1055 return m_core->getRenderDocAPI();
1056 }
1057
1058 [[nodiscard]]
1059 auto getOutputTarget() const noexcept -> const IOutputTarget& {
1060 return *m_output_target;
1061 }
1062
1063 static VulkanRenderer* instance;
1064
1065private:
1066 void drawFrame(RenderFrame& frame_data);
1067
1068 void mainRenderThread();
1069
1070 std::atomic_bool m_running {false};
1071
1072 std::thread m_render_thread;
1073
1074 std::array<RenderFrame, k_render_frames> m_render_frames;
1075 std::atomic<uint32_t> m_write_index = 0;
1076
1077 std::mutex m_queue_mutex;
1078
1079 std::condition_variable m_frame_cv;
1080
1081 std::queue<uint32_t> m_ready_frames;
1082 const RenderFrame* m_rendering_frame = nullptr;
1083
1084 std::counting_semaphore<k_render_frames> m_free_frames {k_render_frames};
1085
1086 struct DepthResources {
1087 std::optional<vma::raii::Image> image;
1088 std::optional<vk::raii::ImageView> view;
1089 sync::ImageState state;
1090 };
1091
1092 struct SceneColorResources {
1093 std::optional<vma::raii::Image> image;
1094 std::optional<vk::raii::ImageView> view;
1095 sync::ImageState state;
1096 };
1097
1098 void createGraphicsCommandPool();
1099 void createTransferCommandPool();
1100 void createFrameContexts();
1101
1102 void createPerImageSync();
1103 void createDepthResources();
1104 void createSceneColorResources();
1105
1107 auto recordVoxelScopes(FrameContext& frame, const vk::Viewport& viewport, const vk::Rect2D& scissor, bool depth_loaded) -> bool;
1108
1109 void publishCompletedFrames();
1110
1111 void recordAccelerationStructureBuilds(FrameContext& frame);
1112
1113 void createPresentResources();
1114 void createDescriptorPool();
1115 void createDefaultTexture();
1116
1117 void createFailsafeTextures();
1118 void createDefaultShadowMap();
1119 void createDefaultCubemap();
1120
1121 void ensureMaterialPasses(RenderFrame& frame_data);
1123 void hideInstancesWithoutPipeline(RenderFrame& frame_data);
1124
1125 void recordFrame(FrameContext& frame, uint32_t image_index) noexcept;
1126
1128 void recordMeshScene(vk::CommandBuffer cmd, uint32_t image_index);
1129
1130 struct PunctualShadowCandidate {
1131 size_t light_index = 0;
1132 float distance_squared = 0.0f;
1133 float resolution_scale = 1.0f;
1134 bool visible = true;
1135 uint64_t key = 0;
1136 float importance = 0.0f;
1137 };
1138
1139 void fitShadowViews(
1140 RenderFrame& frame, float aspect, int32_t shadow_caster_index, const glm::vec3& shadow_caster_direction,
1141 const std::vector<PunctualShadowCandidate>& spot_candidates, const std::vector<PunctualShadowCandidate>& point_candidates
1142 );
1143
1145 void assignShadowSlots(const std::vector<PunctualShadowCandidate>& candidates, std::span<shadow_slots::Slot> slots);
1146
1147 void releaseShadowSlots();
1148
1150 void finalizeLights(RenderFrame& frame);
1151
1152 struct UploadSlot {
1153 vk::raii::CommandBuffer command_buffer = nullptr;
1155 uint64_t timeline_value = 0;
1156 bool in_flight = false;
1157 };
1158
1159 static constexpr uint32_t k_upload_slots = 4;
1160
1161 struct BatchedUploadGroup {
1162 std::vector<std::unique_ptr<PendingResourceUpload>> jobs;
1163 uint32_t slot = 0;
1164 };
1165
1166 vk::raii::CommandPool m_command_pool = nullptr;
1167 vk::raii::CommandPool m_transfer_command_pool = nullptr;
1168 vk::raii::DescriptorPool m_descriptor_pool = nullptr;
1169
1170 std::vector<UploadSlot> m_upload_slots;
1171 uint32_t m_next_upload_slot = 0;
1172
1173 vk::raii::Semaphore m_upload_timeline = nullptr;
1174 uint64_t m_upload_timeline_value = 0;
1176 uint64_t m_upload_acquired_value = 0;
1177 uint64_t m_upload_waited_value = 0;
1178
1179 static constexpr vk::DeviceSize k_upload_host_budget = 256ull << 20;
1180
1182 std::deque<std::unique_ptr<PendingResourceUpload>> m_upload_waiting;
1183 std::atomic<vk::DeviceSize> m_upload_host_bytes {0};
1184
1185 std::vector<std::unique_ptr<PendingResourceUpload>> m_upload_staging;
1186 std::queue<BatchedUploadGroup> m_pending_uploads;
1187 void createUploadRing();
1188 void pumpUploadQueue();
1189 void processPendingUploads();
1190 void flushResourceUploads();
1191
1193 void buildVoxelProxies(RenderFrame& frame);
1194 std::mutex m_upload_mutex;
1195
1196 std::atomic<int> m_pending_upload_builds {0};
1197
1198 const VulkanCore* m_core = nullptr;
1199
1200 std::unique_ptr<IOutputTarget> m_output_target;
1201 std::vector<std::unique_ptr<IRenderPass>> m_render_passes;
1202 std::vector<std::unique_ptr<IComputePass>> m_compute_passes;
1203 std::vector<std::unique_ptr<IPostProcessPass>> m_post_process_passes;
1204
1205 std::unordered_map<assets::Material*, std::unique_ptr<MaterialPass>> m_material_passes;
1207 mutable std::mutex m_pass_mutex;
1208 std::atomic_bool m_pending_material_pass_clear {false};
1209 event::Listener m_asset_listener;
1210
1211 assets::Handle<assets::Material> m_default_material;
1212 bool m_default_material_warned = false;
1213
1214 VulkanTexture m_default_texture;
1215 VulkanTexture m_fail_load_texture;
1216 VulkanTexture m_fail_gpu_texture;
1217 VulkanTexture m_missing_texture;
1218 vk::raii::Sampler m_failsafe_sampler = nullptr;
1219 VulkanTexture m_default_black_texture;
1220 VulkanTexture m_default_normal_texture;
1221 vk::raii::Sampler m_default_sampler = nullptr;
1222
1223 std::optional<vma::raii::Image> m_default_shadow_image;
1224 vk::raii::ImageView m_default_shadow_view = nullptr;
1225 vk::raii::Sampler m_default_shadow_sampler = nullptr;
1226
1227 std::optional<vma::raii::Image> m_default_cube_image;
1228 vk::raii::ImageView m_default_cube_view = nullptr;
1229
1230 const ClusterLightingPass* m_cluster_lighting_pass = nullptr;
1231
1232 const ShadowPass* m_shadow_pass = nullptr;
1233
1234 EnvironmentPass* m_environment_pass = nullptr;
1235
1236 ReflectionProbePass* m_reflection_probe_pass = nullptr;
1237
1238 VoxelPass* m_voxel_pass = nullptr;
1239
1240 DebrisPass* m_debris_pass = nullptr;
1241
1242 std::atomic_bool m_probe_bake_requested {false};
1243 std::atomic<uint32_t> m_probe_stale_count {0};
1245 int32_t m_probe_bake_cursor = -1;
1246 bool m_probe_auto_baked = false;
1247 vk::Format m_depth_format = vk::Format::eUndefined;
1248 DepthResources m_depth_resources;
1249
1250 static constexpr vk::Format m_scene_color_format = vk::Format::eR16G16B16A16Sfloat;
1251 SceneColorResources m_scene_color;
1252
1253 static constexpr vk::Format m_scene_normal_format = renderer::k_scene_normal_format;
1254 SceneColorResources m_scene_normal;
1255
1256 static constexpr vk::Format m_scene_indirect_format = renderer::k_scene_indirect_format;
1257 SceneColorResources m_scene_indirect;
1258
1259 static constexpr vk::Format m_scene_motion_format = renderer::k_scene_motion_format;
1260 SceneColorResources m_scene_motion;
1261
1262 SceneColorResources m_voxel_albedo;
1263 SceneColorResources m_voxel_material;
1264 SceneColorResources m_voxel_face_normal;
1265
1266 std::atomic<float> m_voxel_normal_rounding {0.3f};
1267 std::atomic_bool m_voxel_cell_boxes {true};
1268 std::atomic_bool m_voxel_occlusion {true};
1269 std::atomic_bool m_voxel_mesh_shaders {true};
1270 std::atomic<float> m_voxel_raster_pixels {4.0f};
1271
1272 ShaderLayout m_present_layout;
1273 VulkanPipeline m_present_pipeline;
1274 vk::raii::Sampler m_present_sampler = nullptr;
1275 std::vector<vk::raii::DescriptorSet> m_present_sets;
1276 std::vector<vk::ImageView> m_present_bound_views;
1277
1278 TracyVkCtx m_tracy_vk_ctx = nullptr;
1279 std::unique_ptr<GpuTimer> m_gpu_timer;
1280
1281 std::vector<FrameContext> m_frames;
1282 std::vector<vk::raii::Semaphore> m_render_finished_per_image;
1283 std::vector<vk::Fence> m_images_in_flight;
1284 std::vector<sync::Usage> m_output_image_usage;
1285
1287 sync::BarrierBatch m_pending_acquires;
1288 uint32_t m_current_frame = 0;
1289
1290 toast::Camera* m_camera = nullptr;
1291
1292 GizmoState m_gizmo_state;
1293
1294 [[nodiscard]]
1295 auto blendPostProcessVolumes(const glm::vec3& camera_position) -> PostProcessSettings;
1296
1297 PostProcessSettings m_post_process_settings;
1298
1299 std::atomic_bool m_traced_shadows_enabled {false};
1301 bool m_traced_shadows_voxel_fallback_logged = false;
1302
1303 std::atomic_bool m_taa_enabled {true};
1304 std::atomic<float> m_taa_history_weight {0.9f};
1305
1307 struct TaaHistory {
1308 glm::mat4 previous_view_projection {1.0f};
1309 const toast::Camera* camera = nullptr;
1310 uint64_t jitter_index = 0;
1311 bool has_previous = false;
1312 bool was_active = false;
1313 };
1314
1315 TaaHistory m_taa_history;
1316
1317 std::atomic_bool m_debug_draw_enabled {false};
1318 std::mutex m_debug_line_mutex; // what in the actual fuck is this vro
1319 std::vector<debug::Vertex> m_pending_debug_line_vertices;
1320 bool m_collecting_debug_lines = false;
1321
1322 void beginDebugLineCollection(RenderFrame& frame);
1323 void endDebugLineCollection();
1324
1325 std::mutex m_pending_post_settings_mutex;
1326 std::optional<PostProcessSettings> m_pending_post_settings;
1327
1328 const toast::INodeOwner* m_render_owner_filter = nullptr;
1329 std::unordered_map<const toast::VoxelNode*, const voxel::Volume*> m_voxel_highlights;
1330 const toast::INodeOwner* m_secondary_owner = nullptr;
1331 glm::mat4 m_secondary_transform {1.0f};
1332 const toast::Node* m_secondary_hidden = nullptr;
1333
1334 [[nodiscard]]
1335 auto ownerTransform(const toast::Node& node) const -> const glm::mat4*;
1336
1337 event::Listener m_capture_listener;
1338 std::atomic_bool m_capture_frame_requested {false};
1339
1340 event::Listener m_imgui_input_listener;
1341 glm::vec2 m_imgui_mouse_pos {-1.0f, -1.0f};
1342 std::array<bool, 3> m_imgui_mouse_down {};
1343 float m_imgui_wheel_x_accum = 0.0f;
1344 float m_imgui_wheel_y_accum = 0.0f;
1345 std::vector<ImGuiKeyEvent> m_imgui_key_events_accum;
1346 std::vector<uint32_t> m_imgui_char_events_accum;
1347
1348 event::Listener m_render_mode_listener;
1349 uint32_t m_render_mode = 0;
1350
1351 UIFrameBuilder m_ui_frame_builder;
1352
1353 std::mutex m_mesh_proxy_mutex;
1354 std::vector<toast::MeshNode*> m_mesh_proxy_nodes;
1355 std::vector<std::pair<toast::Node3D*, void (*)(toast::Node3D&)>> m_debug_nodes;
1356
1357 std::mutex m_voxel_proxy_mutex;
1358 std::vector<toast::VoxelNode*> m_voxel_proxy_nodes;
1359 std::vector<toast::VoxelNode*> m_tick_voxel_nodes;
1360
1361 std::shared_ptr<VoxelGpuStorage> m_voxel_storage;
1362 std::shared_ptr<VoxelGpuStorage> m_voxel_storage_pending;
1363
1364 mutable std::mutex m_voxel_collision_mutex;
1365 std::shared_ptr<const VoxelCollisionWorld> m_voxel_collision_world;
1366
1367 struct VoxelSceneKey {
1368 uint64_t node_rid = 0;
1369 uint32_t revision = 0;
1370 uint32_t content = 0;
1371 const voxel::Palette* palette = nullptr;
1372
1373 uint32_t palette_revision = 0;
1374
1375 [[nodiscard]]
1376 auto operator==(const VoxelSceneKey&) const -> bool = default;
1377 };
1378
1379 std::vector<VoxelSceneKey> m_voxel_scene_key;
1380
1381 VoxelChangeTracker m_voxel_change_tracker;
1382
1384 VoxelScenePatcher m_voxel_patcher;
1385
1386 std::unordered_map<uint64_t, glm::mat4> m_voxel_previous_models;
1387
1389 std::unordered_map<uint64_t, glm::mat4> m_voxel_drawn_models;
1390 std::unordered_map<uint64_t, glm::mat4> m_mesh_previous_models;
1391 bool m_voxel_upload_failed_warned = false;
1392
1394 bool m_voxel_mirror_kept = false;
1395 uint64_t m_voxel_upload_sequence = 0;
1396
1397 std::mutex m_light_proxy_mutex;
1398 std::vector<toast::Light*> m_light_proxy_nodes;
1399
1400 std::mutex m_camera_proxy_mutex;
1401 std::vector<toast::Camera*> m_camera_proxy_nodes;
1402
1403 std::mutex m_reflection_probe_mutex;
1404 std::vector<toast::ReflectionProbe*> m_reflection_probe_nodes;
1405
1407 std::vector<toast::IrradianceVolume*> m_irradiance_volume_nodes;
1408
1410 std::vector<toast::PostProcessVolume*> m_post_process_volume_nodes;
1411
1412 std::vector<toast::MeshNode*> m_tick_mesh_nodes;
1413 std::vector<toast::Light*> m_tick_light_nodes;
1414 std::vector<toast::Camera*> m_tick_camera_nodes;
1415 std::vector<toast::ReflectionProbe*> m_tick_probe_nodes;
1416 std::vector<std::pair<uint32_t, const toast::ReflectionProbe*>> m_tick_probes_by_distance;
1417 std::vector<PunctualShadowCandidate> m_tick_spot_candidates;
1418 std::vector<PunctualShadowCandidate> m_tick_point_candidates;
1419
1421 std::array<shadow_slots::Slot, shadows::k_max_spot_shadows> m_spot_slots {};
1422 std::array<shadow_slots::Slot, shadows::k_max_point_shadows> m_point_slots {};
1423 uint64_t m_shadow_displaced_total = 0;
1424
1426 struct CascadeAnchor {
1427 glm::vec3 center {0.0f};
1428 float radius = 0.0f;
1429 glm::vec3 light_direction {0.0f};
1430 uint32_t resolution = 0;
1431 };
1432
1433 std::array<CascadeAnchor, shadows::k_cascade_count> m_cascade_anchors {};
1434 std::vector<shadow_slots::Candidate> m_tick_slot_candidates;
1435 std::vector<int32_t> m_tick_slot_of;
1436
1438 struct LightMeta {
1439 float importance = 0.0f;
1440 float depth_reach = 0.0f;
1441 bool visible = false;
1442 };
1443
1444 std::vector<LightMeta> m_tick_light_meta;
1445 std::vector<uint32_t> m_tick_light_order;
1446 std::vector<GpuLight> m_tick_light_scratch;
1447 std::vector<toast::PostProcessVolume*> m_tick_post_volumes;
1448
1449 std::vector<toast::IrradianceVolume*> m_irradiance_published_nodes;
1450
1451 uint32_t m_irradiance_probe_total = 0;
1452
1453 std::unordered_set<toast::IrradianceVolume*> m_irradiance_load_attempted;
1454
1455 std::unordered_map<toast::IrradianceVolume*, ShGridKey> m_irradiance_baked_keys;
1456
1457 std::atomic<uint32_t> m_irradiance_stale_count {0};
1458
1459 std::atomic<uint32_t> m_visible_instance_count {0};
1460
1461 uint64_t m_frame_boundary_counter = 0;
1462
1463 uint64_t m_frame_sequence_counter = 0;
1464
1465 std::deque<uint32_t> m_pending_publish;
1466
1467 std::unique_ptr<DepthPrepass> m_depth_prepass;
1468
1469 std::unique_ptr<SkinningPass> m_skinning_pass;
1470
1471 std::unique_ptr<RayTracingScene> m_ray_tracing_scene;
1472
1473 std::unique_ptr<SkinnedBlasPool> m_skinned_blas_pool;
1474
1475 std::atomic<uint64_t> m_last_drawn_sequence {0};
1476 std::atomic<uint32_t> m_out_of_order_frames {0};
1477 std::atomic<uint32_t> m_dropped_frames {0};
1478
1479 std::atomic<uint32_t> m_skipped_builds {0};
1480
1481 std::atomic<float> m_frame_time_avg_ms {0.0f};
1482 std::atomic<float> m_frame_time_min_ms {0.0f};
1483 std::atomic<float> m_frame_time_max_ms {0.0f};
1484
1485 std::atomic<uint64_t> m_perf_ticks {0};
1486 std::atomic<uint64_t> m_perf_frames_built {0};
1487 std::atomic<uint64_t> m_perf_slot_wait_ns {0};
1488 std::atomic<uint64_t> m_perf_draws {0};
1489 std::atomic<uint64_t> m_perf_repeated_draws {0};
1490 std::atomic<uint64_t> m_perf_draw_work_ns {0};
1491 std::atomic<uint64_t> m_perf_upload_ns {0};
1492 std::atomic<uint64_t> m_perf_gpu_wait_ns {0};
1493 std::atomic<uint64_t> m_perf_acquire_ns {0};
1494 std::atomic<uint64_t> m_perf_record_ns {0};
1495 std::atomic<uint64_t> m_perf_submit_ns {0};
1496 std::atomic<uint64_t> m_perf_present_ns {0};
1497 std::atomic<uint64_t> m_perf_frame_wait_ns {0};
1498 std::atomic<uint64_t> m_perf_pacing_ns {0};
1499 std::atomic<uint64_t> m_perf_build_ns {0};
1500
1501 std::atomic_bool m_cull_debug_draw {false};
1502
1503 std::atomic_bool m_cull_freeze {false};
1504 glm::mat4 m_frozen_cull_view_projection {1.0f};
1505 bool m_has_frozen_cull = false;
1506
1507 int32_t m_irradiance_bake_cursor = -1;
1508 std::atomic_bool m_irradiance_bake_requested {false};
1509
1510 std::atomic_bool m_bake_active {false};
1511 std::unordered_set<toast::ReflectionProbe*> m_probe_load_attempted;
1512
1513 std::vector<FrameUBO> m_frame_ubos;
1514 std::vector<FrameResources> m_frame_ubo_res;
1515 std::vector<FrameResources> m_joint_matrix_res;
1516 std::vector<FrameResources> m_instance_res;
1517 std::vector<FrameResources> m_shadow_instance_res;
1518 bool m_joint_matrix_pool_exhausted_warned = false;
1519
1520 void createFrameResources();
1521 void updateFrameResources(uint32_t frame_index, RenderFrame& frame_data);
1522
1523 void applyResizeInternal(vk::Extent2D extent);
1524
1525 static constexpr uint64_t k_no_pending_resize = 0;
1526 std::atomic<uint64_t> m_pending_resize_packed {k_no_pending_resize};
1527
1528 std::atomic<double> m_frame_rate_limit_hz {0.0};
1529 std::atomic_bool m_application_focused {true};
1530 std::atomic_bool m_debug_windows_visible {true};
1531 std::atomic_bool m_rendering_paused {false};
1532 std::atomic_bool m_clamp_to_simulation {true};
1533};
1534
1535inline void start() {
1536 VulkanRenderer::instance->start();
1537}
1538
1539inline void stop() {
1540 VulkanRenderer::instance->stop();
1541}
1542
1543inline auto beginFrameBuild() -> VulkanRenderer::RenderFrame& {
1544 return VulkanRenderer::instance->getCurrentFrameBuild();
1545}
1546
1547inline void submitFrame() {
1548 VulkanRenderer::instance->submitFrame();
1549}
1550
1551inline auto getActiveCamera() -> toast::Camera* {
1552 return VulkanRenderer::instance->getActiveCamera();
1553}
1554
1555inline void setActiveCamera(toast::Camera& camera) {
1556 VulkanRenderer::instance->setActiveCamera(&camera);
1557}
1558
1559inline void setActiveCamera(toast::Camera* camera) {
1560 if (VulkanRenderer::instance != nullptr) {
1561 VulkanRenderer::instance->setActiveCamera(camera);
1562 }
1563}
1564
1565inline void forgetCamera(const toast::Camera* camera) {
1566 if (VulkanRenderer::instance != nullptr) {
1567 VulkanRenderer::instance->forgetCamera(camera);
1568 }
1569}
1570
1571inline void registerMeshNodeProxy(toast::MeshNode* node) {
1572 VulkanRenderer::instance->registerMeshNodeProxy(node);
1573}
1574
1575inline void unregisterMeshNodeProxy(toast::MeshNode* node) {
1576 VulkanRenderer::instance->unregisterMeshNodeProxy(node);
1577}
1578
1579inline auto registerVoxelNodeProxy(toast::VoxelNode* node) -> bool {
1580 if (VulkanRenderer::instance == nullptr) {
1581 return false;
1582 }
1583 VulkanRenderer::instance->registerVoxelNodeProxy(node);
1584 return true;
1585}
1586
1587inline void unregisterVoxelNodeProxy(toast::VoxelNode* node) {
1588 if (VulkanRenderer::instance != nullptr) {
1589 VulkanRenderer::instance->unregisterVoxelNodeProxy(node);
1590 }
1591}
1592
1593inline void registerLightNodeProxy(toast::Light* node) {
1594 VulkanRenderer::instance->registerLightNodeProxy(node);
1595}
1596
1597inline void unregisterLightNodeProxy(toast::Light* node) {
1598 VulkanRenderer::instance->unregisterLightNodeProxy(node);
1599}
1600
1601inline void registerCameraNodeProxy(toast::Camera* node) {
1602 if (VulkanRenderer::instance != nullptr) {
1603 VulkanRenderer::instance->registerCameraNodeProxy(node);
1604 }
1605}
1606
1607inline void unregisterCameraNodeProxy(toast::Camera* node) {
1608 if (VulkanRenderer::instance != nullptr) {
1609 VulkanRenderer::instance->unregisterCameraNodeProxy(node);
1610 }
1611}
1612
1613inline void registerReflectionProbeProxy(toast::ReflectionProbe* node) {
1614 VulkanRenderer::instance->registerReflectionProbeProxy(node);
1615}
1616
1617inline void unregisterReflectionProbeProxy(toast::ReflectionProbe* node) {
1618 VulkanRenderer::instance->unregisterReflectionProbeProxy(node);
1619}
1620
1621inline void registerIrradianceVolumeProxy(toast::IrradianceVolume* node) {
1622 VulkanRenderer::instance->registerIrradianceVolumeProxy(node);
1623}
1624
1625inline void unregisterIrradianceVolumeProxy(toast::IrradianceVolume* node) {
1626 VulkanRenderer::instance->unregisterIrradianceVolumeProxy(node);
1627}
1628
1629inline void registerPostProcessVolumeProxy(toast::PostProcessVolume* node) {
1630 VulkanRenderer::instance->registerPostProcessVolumeProxy(node);
1631}
1632
1633inline void unregisterPostProcessVolumeProxy(toast::PostProcessVolume* node) {
1634 VulkanRenderer::instance->unregisterPostProcessVolumeProxy(node);
1635}
1636
1637inline void queueResourceUpload(std::unique_ptr<PendingResourceUpload> upload) {
1638 VulkanRenderer::instance->queueResourceUpload(std::move(upload));
1639}
1640
1641inline void applyResize(vk::Extent2D extent) {
1642 VulkanRenderer::instance->applyResize(extent);
1643}
1644
1645inline auto getOutputTarget() -> const IOutputTarget& {
1646 return VulkanRenderer::instance->getOutputTarget();
1647}
1648
1649inline auto getCore() -> const VulkanCore& {
1650 return VulkanRenderer::instance->getCore();
1651}
1652
1653inline auto getRenderDocAPI() -> const RENDERDOC_API_1_6_0* {
1654 return VulkanRenderer::instance->getRenderDocAPI();
1655}
1656
1658inline auto renderingFrame() -> const VulkanRenderer::RenderFrame* {
1659 return VulkanRenderer::instance->renderingFrame();
1660}
1661
1662} // namespace renderer
Shader-driven material asset.
Definition material.hpp:49
Definition cluster_lighting_pass.hpp:20
Definition debris_pass.hpp:26
Definition depth_prepass.hpp:21
Definition environment_pass.hpp:25
Definition gpu_timer.hpp:16
Definition output_target_base.hpp:20
Definition material_pass.hpp:28
Definition ray_tracing_scene.hpp:19
Definition reflection_probe_pass.hpp:26
Definition shadow_pass.hpp:31
Definition skinned_blas_pool.hpp:19
Definition skinning_pass.hpp:24
Deferred so lighting runs once per visible voxel pixel not once per march.
Definition voxel_pass.hpp:28
Definition vulkan_core.hpp:48
Definition vulkan_mesh.hpp:26
Definition vulkan_renderer.hpp:88
auto getDepthView() const noexcept -> vk::ImageView
Left in eDepthReadOnlyOptimal.
Definition vulkan_renderer.hpp:664
auto uploadTimeline() const noexcept -> vk::Semaphore
Signalled by every transfer submit so a submit needing an upload waits on its value.
Definition vulkan_renderer.hpp:967
void setClampToSimulation(bool clamp) noexcept
False repeats the last frame instead of waiting for a new one.
Definition vulkan_renderer.hpp:527
void setRenderOwnerFilter(const toast::INodeOwner *owner)
nullptr renders every owner. Main thread only
Definition vulkan_renderer.hpp:1021
auto uploadTimelineValue() const noexcept -> uint64_t
Render thread only.
Definition vulkan_renderer.hpp:973
void setVoxelHighlight(const toast::VoxelNode *node, const voxel::Volume *highlight)
Definition vulkan_renderer.hpp:1033
auto uploadStats() -> UploadStats
Definition vulkan_renderer.cpp:4414
void queueDebugLine(glm::vec3 a, glm::vec3 b, glm::vec4 color)
For the next frame when called before frame construction begins.
Definition vulkan_renderer.cpp:590
auto getEnvironmentPassMutable() const noexcept -> EnvironmentPass *
Definition vulkan_renderer.hpp:855
void setFrameRateLimit(double max_fps) noexcept
0 or negative is uncapped
Definition vulkan_renderer.hpp:517
void requestPostProcessSettings(const PostProcessSettings &settings)
Safe from any thread.
Definition vulkan_renderer.hpp:1015
void addPostProcessPass(std::unique_ptr< IPostProcessPass > pass)
Runs in registration order.
Definition vulkan_renderer.cpp:4370
void retire(std::shared_ptr< void > object)
Render thread and frees object once the frame being recorded finished on the GPU.
Definition vulkan_renderer.cpp:2031
auto gpuTimer() const noexcept -> const GpuTimer *
Definition vulkan_renderer.hpp:982
void setVoxelCellBoxes(bool enabled) noexcept
Edge rounding radius in voxels where 0 keeps the flat face normals.
Definition vulkan_renderer.hpp:622
void setVoxelRasterPixels(float pixels) noexcept
Smallest on screen voxel size in pixels that is rasterized instead of marched and 0 marches everythin...
Definition vulkan_renderer.hpp:639
auto getVoxelFaceNormalView() const noexcept -> vk::ImageView
Holds flat face normals not the rounded scene normal ones.
Definition vulkan_renderer.hpp:617
auto getSceneNormalView() const noexcept -> vk::ImageView
xyz world normal w roughness
Definition vulkan_renderer.hpp:591
void setVoxelPass(VoxelPass *pass) noexcept
Still registered with addRenderPass so it lists and toggles like any pass.
Definition vulkan_renderer.hpp:778
Definition vulkan_texture.hpp:16
Definition camera.hpp:25
Definition node_owner.hpp:38
A box filled with a regular grid of probes storing the light arriving at each point.
Definition irradiance_volume.hpp:35
Definition light.hpp:25
Definition mesh_node.hpp:24
Definition node_3d.hpp:25
Definition node.hpp:81
A region of the world that grades the post chain while the camera is inside it.
Definition post_process_volume.hpp:32
A volume in which reflections are reprojected onto local geometry instead of infinity.
Definition reflection_probe.hpp:33
Definition voxel_node.hpp:72
Definition voxel_volume.hpp:21
Base types for the asset system.
Geometry + hit-test constants shared between DebugPass (renders the gizmos) and Workspace (hit-tests/...
GizmoTool
Mirrors editor.WorkspaceViewModel.GizmoTool / proto SetGizmoTool - 0 Select, 1 Translate,...
Definition gizmo_layout.hpp:21
GizmoHandle
Which part of the active gizmo a ray/click is interacting with, axis_x/y/z and center are reused acro...
Definition gizmo_layout.hpp:32
Represents 3D geometry data, can be read from a .tmesh binary file.
Mirrored by shadow_depth.slang and mesh.slang.
Definition post_process_settings.hpp:14
Definition vulkan_renderer.hpp:130
Definition vulkan_renderer.hpp:97
std::vector< std::shared_ptr< void > > retired
Released once this frame fence was waited on again.
Definition vulkan_renderer.hpp:108
Definition vulkan_renderer.hpp:166
glm::mat4 jittered_view_projection
view_projection with the TAA jitter for world raster only
Definition vulkan_renderer.hpp:176
glm::mat4 previous_view_projection
Unjittered view_projection of the last presented frame.
Definition vulkan_renderer.hpp:179
Definition vulkan_renderer.hpp:340
Mirrors GpuLight in lighting.slang and cluster_lighting.slang.
Definition vulkan_renderer.hpp:228
Definition vulkan_renderer.hpp:359
Definition vulkan_renderer.hpp:354
Mirrors mesh.slang depth_prepass.slang and shadow_depth.slang InstanceData.
Definition vulkan_renderer.hpp:120
Definition vulkan_renderer.hpp:152
glm::uvec4 counts_base
xyz probe counts w first probe index in the SH buffer
Definition vulkan_renderer.hpp:156
glm::vec4 extents_intensity
xyz half extents w intensity
Definition vulkan_renderer.hpp:158
glm::uvec4 baked_pad
x 1 once the SH slice of this volume is baked
Definition vulkan_renderer.hpp:161
glm::vec4 min_corner_spacing
xyz grid min corner w probe spacing
Definition vulkan_renderer.hpp:154
Definition vulkan_renderer.hpp:368
Definition vulkan_renderer.hpp:262
Definition vulkan_renderer.hpp:668
Definition vulkan_renderer.hpp:912
Definition vulkan_renderer.hpp:137
Definition vulkan_renderer.hpp:377
float near_plane
Camera near plane distance so voxel cells it clips draw with the camera inside variant.
Definition vulkan_renderer.hpp:388
bool taa_reset
History is stale since the camera switched or TAA just turned on.
Definition vulkan_renderer.hpp:429
std::vector< VoxelVolumeProxy > voxel_instances
Sorted front to back.
Definition vulkan_renderer.hpp:383
bool taa_active
Jittered and resolved by TaaPass which then skips FxaaPass.
Definition vulkan_renderer.hpp:426
Definition vulkan_renderer.hpp:255
std::vector< glm::mat4 > matrices
matrices[i] belongs to views[i]
Definition vulkan_renderer.hpp:259
Definition vulkan_renderer.hpp:244
Definition vulkan_renderer.hpp:320
Definition vulkan_renderer.hpp:327
Definition vulkan_renderer.hpp:314
Definition vulkan_renderer.hpp:952
Definition vulkan_renderer.hpp:286
bool has_previous
False the first frame this node_uid is drawn so the shader reports motion as not written instead of z...
Definition vulkan_renderer.hpp:297