91 static auto selectDepthFormat(
const VulkanCore& core) -> vk::Format;
93 static constexpr uint32_t k_frames_in_flight = 3;
95 static constexpr uint8_t k_render_frames = 3;
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;
105 std::vector<vma::raii::Buffer> blas_scratch;
111 static constexpr uint32_t k_max_directional_lights = 4;
113 static constexpr uint32_t k_max_joint_matrices = 4096;
115 static constexpr uint32_t k_max_instances = 16384;
117 static constexpr uint32_t k_instance_no_motion = 1u << 0;
121 glm::mat4 model {1.0f};
122 glm::mat4 previous_model {1.0f};
123 uint32_t joint_offset = 0;
125 std::array<uint32_t, 2> pad0 {0, 0};
128 static_assert(
sizeof(
InstanceData) == 144,
"InstanceData stride must match mesh.slang's std430 layout");
132 glm::vec4 color_intensity;
135 static constexpr uint32_t k_max_reflection_probes = 8;
138 glm::vec4 position_radius;
140 glm::vec4 box_extents_intensity;
143 glm::vec4 params {0.0f};
146 static constexpr uint32_t k_max_irradiance_volumes = 4;
148 static constexpr uint32_t k_max_irradiance_probes = 4096;
150 static constexpr uint32_t k_irradiance_capture_size = 64;
167 glm::mat4 view {1.0f};
168 glm::mat4 projection {1.0f};
169 glm::mat4 view_projection {1.0f};
171 glm::vec3 camera_position {};
181 glm::vec4 ambient_color_intensity {0.0f};
184 glm::uvec4 directional_light_count_pad {0};
186 std::array<DirectionalLightData, k_max_directional_lights> directional_lights {};
188 glm::uvec4 render_mode_pad {0};
190 std::array<glm::mat4, shadows::k_cascade_count> cascade_view_projection {};
192 glm::vec4 cascade_splits {0.0f};
194 glm::vec4 cascade_texel_world_size {0.0f};
196 glm::vec4 cascade_depth_bias {0.0f};
199 glm::vec4 shadow_params {0.0f};
202 glm::vec4 environment_params {0.0f};
204 glm::uvec4 reflection_probe_count_pad {0};
206 std::array<ReflectionProbeData, k_max_reflection_probes> reflection_probes {};
208 glm::uvec4 irradiance_volume_count_pad {0};
210 std::array<IrradianceVolumeData, k_max_irradiance_volumes> irradiance_volumes {};
213 glm::vec4 traced_shadow_params {0.0f};
217 offsetof(
FrameUBO, jittered_view_projection) == 208,
218 "FrameUBO::jittered_view_projection must match lighting.slang and depth_prepass.slang"
221 offsetof(
FrameUBO, irradiance_volumes) == 1264,
"FrameUBO::irradiance_volumes must match lighting.slang's std140 layout"
224 offsetof(
FrameUBO, traced_shadow_params) == 1520,
"FrameUBO::traced_shadow_params must match lighting.slang's std140 layout"
229 glm::vec4 world_pos_range;
230 glm::vec4 view_pos_type;
231 glm::vec4 view_direction;
232 glm::vec4 color_intensity;
233 glm::vec4 direction_pad;
234 glm::vec4 cone_angles;
237 glm::vec4 shadow_atlas {1.0f, 1.0f, 0.0f, 0.0f};
239 glm::mat4 shadow_view_projection {1.0f};
242 static_assert(
sizeof(
GpuLight) == 176,
"GpuLight must match the stride in lighting.slang and cluster_lighting.slang");
245 glm::mat4 view_projection {1.0f};
247 glm::vec4 cull_sphere {0.0f};
250 bool directional =
false;
252 uint32_t resolution = 0;
256 std::vector<ShadowView> views;
266 glm::mat4 model = glm::mat4(1.0f);
269 uint32_t joint_offset = 0;
270 uint32_t joint_count = 0;
272 glm::vec3 bounds_center {0.0f};
273 float bounds_radius = 0.0f;
277 uint32_t instance_index = 0;
279 static constexpr uint32_t k_no_posed_vertices = ~0u;
281 uint32_t posed_vertex_offset = k_no_posed_vertices;
283 uint64_t node_rid = 0;
287 uint32_t record_index = 0;
289 glm::uvec3 brick_dims {0};
291 glm::mat4 model {1.0f};
292 glm::mat4 inverse_model {1.0f};
294 glm::mat4 previous_model {1.0f};
299 glm::vec3 bounds_center {0.0f};
300 float bounds_radius = 0.0f;
302 float view_distance = 0.0f;
304 bool camera_inside =
false;
306 bool mirrored =
false;
310 uint64_t node_rid = 0;
311 uint32_t highlight_record = 0;
315 vk::ImageView view =
nullptr;
316 glm::mat4 model = glm::mat4(1.0f);
317 bool always_on_top =
false;
321 glm::vec3 world_position {0.0f};
325 static constexpr size_t k_max_size_handles = 6;
328 bool visible =
false;
330 glm::mat4 model {1.0f};
333 float drag_scale_factor = 1.0f;
335 std::array<SizeHandleDraw, k_max_size_handles> size_handles {};
336 uint32_t size_handle_count = 0;
337 float size_handle_scale = 1.0f;
341 bool visible =
false;
343 glm::vec3 origin {0.0f};
344 glm::quat orientation {1.0f, 0.0f, 0.0f, 0.0f};
347 float drag_scale_factor = 1.0f;
349 std::array<SizeHandleDraw, k_max_size_handles> size_handles {};
350 uint32_t size_handle_count = 0;
351 float size_handle_scale = 1.0f;
360 glm::vec2 mouse_pos {-1.0f, -1.0f};
361 std::array<bool, 3> mouse_down {};
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;
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;
380 std::vector<MeshInstanceProxy> mesh_instances;
385 std::shared_ptr<const VoxelGpuStorage> voxel_storage;
390 std::vector<assets::HandleBase> asset_refs;
392 std::vector<GpuLight> lights;
396 std::vector<glm::mat4> joint_matrices;
398 std::vector<InstanceData> instance_data;
400 std::vector<InstanceData> shadow_instance_data;
408 std::vector<MaterialRange> material_ranges;
410 glm::vec2 viewport_extent {0.0f};
411 float camera_near = 0.01f;
412 float camera_far = 5000.0f;
414 float cluster_near = 0.1f;
415 float cluster_far = 5000.0f;
421 uint32_t render_mode = 0;
423 uint64_t sequence = 0;
431 PostProcessSettings post_process;
433 int32_t probe_capture_index = -1;
434 uint32_t probe_capture_face = 0;
436 int32_t irradiance_capture_index = -1;
437 uint32_t irradiance_capture_face = 0;
439 uint32_t capture_extent = 0;
441 std::vector<debug::Vertex> debug_line_vertices;
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;
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;
455 using UIFrameBuilder = std::function<void(
RenderFrame&)>;
457 void setUIFrameBuilder(UIFrameBuilder builder) { m_ui_frame_builder = std::move(builder); }
468 void start() noexcept;
471 auto getCurrentFrameBuild() noexcept -> RenderFrame& {
472 return m_render_frames[m_write_index];
475 void submitFrame() noexcept;
480 void tick(
float time) noexcept;
482 void registerMeshNodeProxy(toast::MeshNode* node);
483 void registerDebugDraw(toast::Node3D* node,
void (*draw)(toast::Node3D&));
484 void unregisterDebugDraw(toast::Node3D* node);
486 void unregisterMeshNodeProxy(toast::MeshNode* node);
488 void registerVoxelNodeProxy(toast::VoxelNode* node);
490 void unregisterVoxelNodeProxy(toast::VoxelNode* node);
492 void registerLightNodeProxy(toast::Light* node);
494 void unregisterLightNodeProxy(toast::Light* node);
496 void registerCameraNodeProxy(toast::Camera* node);
498 void unregisterCameraNodeProxy(toast::Camera* node);
500 void registerReflectionProbeProxy(toast::ReflectionProbe* node);
502 void unregisterReflectionProbeProxy(toast::ReflectionProbe* node);
504 void registerIrradianceVolumeProxy(toast::IrradianceVolume* node);
506 void registerPostProcessVolumeProxy(toast::PostProcessVolume* node);
508 void unregisterPostProcessVolumeProxy(toast::PostProcessVolume* node);
510 void unregisterIrradianceVolumeProxy(toast::IrradianceVolume* node);
513 void cancelIrradianceBake();
517 void setFrameRateLimit(
double max_fps) noexcept { m_frame_rate_limit_hz.store(max_fps, std::memory_order_relaxed); }
520 auto frameRateLimit() const noexcept ->
double {
521 return m_frame_rate_limit_hz.load(std::memory_order_relaxed);
524 static constexpr double k_background_frame_rate_limit = 30.0;
527 void setClampToSimulation(
bool clamp)
noexcept { m_clamp_to_simulation.store(clamp, std::memory_order_relaxed); }
530 auto clampToSimulation() const noexcept ->
bool {
531 return m_clamp_to_simulation.load(std::memory_order_relaxed);
534 void setDebugWindowsVisible(
bool visible)
noexcept { m_debug_windows_visible.store(visible, std::memory_order_relaxed); }
537 auto debugWindowsVisible() const noexcept ->
bool {
538 return m_debug_windows_visible.load(std::memory_order_relaxed);
541 void setApplicationFocused(
bool focused)
noexcept { m_application_focused.store(focused, std::memory_order_relaxed); }
544 auto applicationFocused() const noexcept ->
bool {
545 return m_application_focused.load(std::memory_order_relaxed);
549 auto effectiveFrameRateLimit() const noexcept ->
double {
550 const double user_limit = frameRateLimit();
551 if (applicationFocused()) {
554 return user_limit > 0.0 && user_limit < k_background_frame_rate_limit ? user_limit : k_background_frame_rate_limit;
557 void setRenderingPaused(
bool paused)
noexcept;
560 auto renderingPaused() const noexcept ->
bool {
561 return m_rendering_paused.load(std::memory_order_relaxed);
566 void addRenderPass(std::unique_ptr<IRenderPass> pass);
568 void addComputePass(std::unique_ptr<IComputePass> pass);
573 auto setPostProcessPassEnabled(std::string_view name,
bool enabled) -> bool;
576 auto isPostProcessPassEnabled(std::string_view name)
const -> bool;
579 auto getSceneColorFormat() const noexcept -> vk::Format {
580 return m_scene_color_format;
584 auto getSceneColorView() const noexcept -> vk::ImageView {
585 return m_scene_color.view.has_value() ? **m_scene_color.view : vk::ImageView {};
592 return m_scene_normal.view.has_value() ? **m_scene_normal.view : vk::ImageView {};
596 auto getSceneIndirectView() const noexcept -> vk::ImageView {
597 return m_scene_indirect.view.has_value() ? **m_scene_indirect.view : vk::ImageView {};
601 auto getSceneMotionView() const noexcept -> vk::ImageView {
602 return m_scene_motion.view.has_value() ? **m_scene_motion.view : vk::ImageView {};
606 auto getVoxelAlbedoView() const noexcept -> vk::ImageView {
607 return m_voxel_albedo.view.has_value() ? **m_voxel_albedo.view : vk::ImageView {};
611 auto getVoxelMaterialView() const noexcept -> vk::ImageView {
612 return m_voxel_material.view.has_value() ? **m_voxel_material.view : vk::ImageView {};
618 return m_voxel_face_normal.view.has_value() ? **m_voxel_face_normal.view : vk::ImageView {};
622 void setVoxelCellBoxes(
bool enabled)
noexcept { m_voxel_cell_boxes.store(enabled, std::memory_order_relaxed); }
624 void setVoxelOcclusion(
bool enabled)
noexcept { m_voxel_occlusion.store(enabled, std::memory_order_relaxed); }
626 void setVoxelMeshShaders(
bool enabled)
noexcept { m_voxel_mesh_shaders.store(enabled, std::memory_order_relaxed); }
629 auto voxelMeshShaders() const noexcept ->
bool {
630 return m_voxel_mesh_shaders.load(std::memory_order_relaxed);
634 auto voxelOcclusion() const noexcept ->
bool {
635 return m_voxel_occlusion.load(std::memory_order_relaxed);
640 m_voxel_raster_pixels.store(std::max(pixels, 0.0f), std::memory_order_relaxed);
644 auto voxelRasterPixels() const noexcept ->
float {
645 return m_voxel_raster_pixels.load(std::memory_order_relaxed);
649 auto voxelCellBoxes() const noexcept ->
bool {
650 return m_voxel_cell_boxes.load(std::memory_order_relaxed);
653 void setVoxelNormalRounding(
float voxels)
noexcept {
654 m_voxel_normal_rounding.store(std::max(voxels, 0.0f), std::memory_order_relaxed);
658 auto voxelNormalRounding() const noexcept ->
float {
659 return m_voxel_normal_rounding.load(std::memory_order_relaxed);
665 return m_depth_resources.view.has_value() ? **m_depth_resources.view : vk::ImageView {};
674 auto listPasses() -> std::vector<PassInfo>;
676 void setPassEnabled(std::string_view name,
bool enabled);
679 auto getDefaultTextureView() const noexcept -> vk::ImageView {
680 return m_default_texture.getView();
684 auto getFailsafeTextureView(
bool has_reference,
const VulkanTexture* texture)
const noexcept -> vk::ImageView;
687 auto getFailsafeSampler() const noexcept -> vk::Sampler {
688 return *m_failsafe_sampler;
692 auto getDefaultBlackTextureView() const noexcept -> vk::ImageView {
693 return m_default_black_texture.getView();
697 auto getDefaultNormalTextureView() const noexcept -> vk::ImageView {
698 return m_default_normal_texture.getView();
702 auto getDefaultSampler() const noexcept -> vk::Sampler {
703 return *m_default_sampler;
707 auto getDefaultShadowMapView() const noexcept -> vk::ImageView {
708 return *m_default_shadow_view;
712 auto getDefaultShadowSampler() const noexcept -> vk::Sampler {
713 return *m_default_shadow_sampler;
717 auto getDefaultCubeView() const noexcept -> vk::ImageView {
718 return *m_default_cube_view;
721 void applyResize(vk::Extent2D extent);
723 void queueResourceUpload(std::unique_ptr<PendingResourceUpload> upload);
726 auto getFrameUBORes(uint32_t current_frame)
const ->
const FrameResources* {
727 return &m_frame_ubo_res[current_frame];
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
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
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
752 auto getDescriptorPoolHandle() const noexcept -> vk::DescriptorPool {
753 return *m_descriptor_pool;
756 void setClusterLightingPass(
const ClusterLightingPass* pass)
noexcept { m_cluster_lighting_pass = pass; }
759 auto getClusterLightingPass() const noexcept -> const ClusterLightingPass* {
760 return m_cluster_lighting_pass;
763 void setShadowPass(
const ShadowPass* pass)
noexcept { m_shadow_pass = pass; }
766 auto getShadowPass() const noexcept -> const ShadowPass* {
767 return m_shadow_pass;
770 void setEnvironmentPass(EnvironmentPass* pass)
noexcept { m_environment_pass = pass; }
773 auto getEnvironmentPass() const noexcept -> const EnvironmentPass* {
774 return m_environment_pass;
780 void setDebrisPass(
DebrisPass* pass)
noexcept { m_debris_pass = pass; }
783 auto voxelCollisionWorld() const -> std::shared_ptr<const VoxelCollisionWorld>;
785 void setReflectionProbePass(ReflectionProbePass* pass) noexcept { m_reflection_probe_pass = pass; }
788 auto getReflectionProbePass() const noexcept -> ReflectionProbePass* {
789 return m_reflection_probe_pass;
792 void requestReflectionProbeBake() noexcept { m_probe_bake_requested.store(
true, std::memory_order_release); }
794 void requestIrradianceBake() noexcept { m_irradiance_bake_requested.store(
true, std::memory_order_release); }
797 auto getIrradianceProbeCount() const noexcept -> uint32_t {
798 return m_irradiance_probe_total;
802 auto getStaleIrradianceVolumeCount() const noexcept -> uint32_t {
803 return m_irradiance_stale_count.load(std::memory_order_relaxed);
807 auto getOutOfOrderFrameCount() const noexcept -> uint32_t {
808 return m_out_of_order_frames.load(std::memory_order_relaxed);
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);
818 auto getSkippedBuildCount() const noexcept -> uint32_t {
819 return m_skipped_builds.load(std::memory_order_relaxed);
823 auto getDroppedFrameCount() const noexcept -> uint32_t {
824 return m_dropped_frames.load(std::memory_order_relaxed);
828 auto getVisibleInstanceCount() const noexcept -> uint32_t {
829 return m_visible_instance_count.load(std::memory_order_relaxed);
832 void setCullDebugDraw(
bool enabled)
noexcept { m_cull_debug_draw.store(enabled, std::memory_order_relaxed); }
835 auto getCullDebugDraw() const noexcept ->
bool {
836 return m_cull_debug_draw.load(std::memory_order_relaxed);
839 void setCullFreeze(
bool frozen)
noexcept { m_cull_freeze.store(frozen, std::memory_order_relaxed); }
842 auto getCullFreeze() const noexcept ->
bool {
843 return m_cull_freeze.load(std::memory_order_relaxed);
847 auto getStaleReflectionProbeCount() const noexcept -> uint32_t {
848 return m_probe_stale_count.load(std::memory_order_relaxed);
851 void requestMaterialFrameSetRebuild();
856 return m_environment_pass;
861 return m_ray_tracing_scene.get();
865 auto getPrepassDrawnCount() const noexcept -> uint32_t;
868 auto getSkinningPass() const noexcept -> SkinningPass* {
869 return m_skinning_pass.get();
873 auto getPosedVertexBuffer(uint32_t frame_index)
const -> vk::Buffer;
876 auto getSkinnedBlasPool() const noexcept -> SkinnedBlasPool* {
877 return m_skinned_blas_pool.get();
881 auto materialUsesCutout(assets::Material* material)
const -> bool;
884 auto getReflectionProbePassMutable() const noexcept -> ReflectionProbePass* {
885 return m_reflection_probe_pass;
889 void setActiveCamera(toast::Camera* camera);
891 void forgetCamera(
const toast::Camera* camera);
894 auto getCore() ->
const VulkanCore& {
899 auto getActiveCamera() -> toast::Camera* {
903 void setGizmoState(
const GizmoState& state)
noexcept { m_gizmo_state = state; }
905 void setDebugDrawEnabled(
bool enabled)
noexcept { m_debug_draw_enabled.store(enabled, std::memory_order_relaxed); }
908 auto debugDrawEnabled() const noexcept ->
bool {
909 return m_debug_draw_enabled.load(std::memory_order_relaxed);
914 uint64_t frames_built = 0;
915 uint64_t slot_wait_ns = 0;
917 uint64_t repeated_draws = 0;
918 uint64_t draw_work_ns = 0;
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;
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),
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;
968 return *m_upload_timeline;
974 return m_upload_timeline_value;
978 void retire(std::shared_ptr<void>
object);
983 return m_gpu_timer.get();
986 void setTracedShadowsEnabled(
bool enabled)
noexcept { m_traced_shadows_enabled.store(enabled, std::memory_order_relaxed); }
989 auto tracedShadowsEnabled() const noexcept ->
bool {
990 return m_traced_shadows_enabled.load(std::memory_order_relaxed);
993 void setTaaEnabled(
bool enabled)
noexcept { m_taa_enabled.store(enabled, std::memory_order_relaxed); }
996 auto taaEnabled() const noexcept ->
bool {
997 return m_taa_enabled.load(std::memory_order_relaxed);
1000 void setTaaHistoryWeight(
float weight)
noexcept { m_taa_history_weight.store(weight, std::memory_order_relaxed); }
1003 auto taaHistoryWeight() const noexcept ->
float {
1004 return m_taa_history_weight.load(std::memory_order_relaxed);
1007 void setPostProcessSettings(
const PostProcessSettings& settings)
noexcept { m_post_process_settings = settings; }
1010 auto getPostProcessSettings() const noexcept -> const PostProcessSettings& {
1011 return m_post_process_settings;
1016 const std::lock_guard lock(m_pending_post_settings_mutex);
1017 m_pending_post_settings = settings;
1022 if (m_render_owner_filter == owner) {
1025 m_render_owner_filter = owner;
1026 cancelIrradianceBake();
1034 std::scoped_lock lock(m_voxel_proxy_mutex);
1035 if (highlight ==
nullptr) {
1036 m_voxel_highlights.erase(node);
1038 m_voxel_highlights[node] = highlight;
1043 m_secondary_owner = owner;
1044 m_secondary_transform = transform;
1045 m_secondary_hidden = hidden;
1049 auto renderingFrame() const -> const RenderFrame* {
1050 return m_rendering_frame;
1054 auto getRenderDocAPI() const noexcept -> const RENDERDOC_API_1_6_0* {
1055 return m_core->getRenderDocAPI();
1059 auto getOutputTarget() const noexcept -> const IOutputTarget& {
1060 return *m_output_target;
1063 static VulkanRenderer* instance;
1068 void mainRenderThread();
1070 std::atomic_bool m_running {
false};
1072 std::thread m_render_thread;
1074 std::array<RenderFrame, k_render_frames> m_render_frames;
1075 std::atomic<uint32_t> m_write_index = 0;
1077 std::mutex m_queue_mutex;
1079 std::condition_variable m_frame_cv;
1081 std::queue<uint32_t> m_ready_frames;
1084 std::counting_semaphore<k_render_frames> m_free_frames {k_render_frames};
1086 struct DepthResources {
1087 std::optional<vma::raii::Image> image;
1088 std::optional<vk::raii::ImageView> view;
1089 sync::ImageState state;
1092 struct SceneColorResources {
1093 std::optional<vma::raii::Image> image;
1094 std::optional<vk::raii::ImageView> view;
1095 sync::ImageState state;
1098 void createGraphicsCommandPool();
1099 void createTransferCommandPool();
1100 void createFrameContexts();
1102 void createPerImageSync();
1103 void createDepthResources();
1104 void createSceneColorResources();
1107 auto recordVoxelScopes(
FrameContext& frame,
const vk::Viewport& viewport,
const vk::Rect2D& scissor,
bool depth_loaded) -> bool;
1109 void publishCompletedFrames();
1111 void recordAccelerationStructureBuilds(
FrameContext& frame);
1113 void createPresentResources();
1114 void createDescriptorPool();
1115 void createDefaultTexture();
1117 void createFailsafeTextures();
1118 void createDefaultShadowMap();
1119 void createDefaultCubemap();
1121 void ensureMaterialPasses(
RenderFrame& frame_data);
1123 void hideInstancesWithoutPipeline(
RenderFrame& frame_data);
1125 void recordFrame(
FrameContext& frame, uint32_t image_index)
noexcept;
1128 void recordMeshScene(vk::CommandBuffer cmd, uint32_t image_index);
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;
1136 float importance = 0.0f;
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
1145 void assignShadowSlots(
const std::vector<PunctualShadowCandidate>& candidates, std::span<shadow_slots::Slot> slots);
1147 void releaseShadowSlots();
1153 vk::raii::CommandBuffer command_buffer =
nullptr;
1155 uint64_t timeline_value = 0;
1156 bool in_flight =
false;
1159 static constexpr uint32_t k_upload_slots = 4;
1161 struct BatchedUploadGroup {
1162 std::vector<std::unique_ptr<PendingResourceUpload>> jobs;
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;
1170 std::vector<UploadSlot> m_upload_slots;
1171 uint32_t m_next_upload_slot = 0;
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;
1179 static constexpr vk::DeviceSize k_upload_host_budget = 256ull << 20;
1182 std::deque<std::unique_ptr<PendingResourceUpload>> m_upload_waiting;
1183 std::atomic<vk::DeviceSize> m_upload_host_bytes {0};
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();
1194 std::mutex m_upload_mutex;
1196 std::atomic<int> m_pending_upload_builds {0};
1198 const VulkanCore* m_core =
nullptr;
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;
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;
1211 assets::Handle<assets::Material> m_default_material;
1212 bool m_default_material_warned =
false;
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;
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;
1227 std::optional<vma::raii::Image> m_default_cube_image;
1228 vk::raii::ImageView m_default_cube_view =
nullptr;
1230 const ClusterLightingPass* m_cluster_lighting_pass =
nullptr;
1232 const ShadowPass* m_shadow_pass =
nullptr;
1234 EnvironmentPass* m_environment_pass =
nullptr;
1236 ReflectionProbePass* m_reflection_probe_pass =
nullptr;
1238 VoxelPass* m_voxel_pass =
nullptr;
1240 DebrisPass* m_debris_pass =
nullptr;
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;
1250 static constexpr vk::Format m_scene_color_format = vk::Format::eR16G16B16A16Sfloat;
1251 SceneColorResources m_scene_color;
1253 static constexpr vk::Format m_scene_normal_format = renderer::k_scene_normal_format;
1254 SceneColorResources m_scene_normal;
1256 static constexpr vk::Format m_scene_indirect_format = renderer::k_scene_indirect_format;
1257 SceneColorResources m_scene_indirect;
1259 static constexpr vk::Format m_scene_motion_format = renderer::k_scene_motion_format;
1260 SceneColorResources m_scene_motion;
1262 SceneColorResources m_voxel_albedo;
1263 SceneColorResources m_voxel_material;
1264 SceneColorResources m_voxel_face_normal;
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};
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;
1278 TracyVkCtx m_tracy_vk_ctx =
nullptr;
1279 std::unique_ptr<GpuTimer> m_gpu_timer;
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;
1287 sync::BarrierBatch m_pending_acquires;
1288 uint32_t m_current_frame = 0;
1290 toast::Camera* m_camera =
nullptr;
1295 auto blendPostProcessVolumes(
const glm::vec3& camera_position) -> PostProcessSettings;
1297 PostProcessSettings m_post_process_settings;
1299 std::atomic_bool m_traced_shadows_enabled {
false};
1301 bool m_traced_shadows_voxel_fallback_logged =
false;
1303 std::atomic_bool m_taa_enabled {
true};
1304 std::atomic<float> m_taa_history_weight {0.9f};
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;
1315 TaaHistory m_taa_history;
1317 std::atomic_bool m_debug_draw_enabled {
false};
1318 std::mutex m_debug_line_mutex;
1319 std::vector<debug::Vertex> m_pending_debug_line_vertices;
1320 bool m_collecting_debug_lines =
false;
1322 void beginDebugLineCollection(
RenderFrame& frame);
1323 void endDebugLineCollection();
1325 std::mutex m_pending_post_settings_mutex;
1326 std::optional<PostProcessSettings> m_pending_post_settings;
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;
1335 auto ownerTransform(
const toast::Node& node)
const ->
const glm::mat4*;
1337 event::Listener m_capture_listener;
1338 std::atomic_bool m_capture_frame_requested {
false};
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;
1348 event::Listener m_render_mode_listener;
1349 uint32_t m_render_mode = 0;
1351 UIFrameBuilder m_ui_frame_builder;
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;
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;
1361 std::shared_ptr<VoxelGpuStorage> m_voxel_storage;
1362 std::shared_ptr<VoxelGpuStorage> m_voxel_storage_pending;
1364 mutable std::mutex m_voxel_collision_mutex;
1365 std::shared_ptr<const VoxelCollisionWorld> m_voxel_collision_world;
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;
1373 uint32_t palette_revision = 0;
1376 auto operator==(
const VoxelSceneKey&)
const ->
bool =
default;
1379 std::vector<VoxelSceneKey> m_voxel_scene_key;
1381 VoxelChangeTracker m_voxel_change_tracker;
1384 VoxelScenePatcher m_voxel_patcher;
1386 std::unordered_map<uint64_t, glm::mat4> m_voxel_previous_models;
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;
1394 bool m_voxel_mirror_kept =
false;
1395 uint64_t m_voxel_upload_sequence = 0;
1397 std::mutex m_light_proxy_mutex;
1398 std::vector<toast::Light*> m_light_proxy_nodes;
1400 std::mutex m_camera_proxy_mutex;
1401 std::vector<toast::Camera*> m_camera_proxy_nodes;
1403 std::mutex m_reflection_probe_mutex;
1404 std::vector<toast::ReflectionProbe*> m_reflection_probe_nodes;
1407 std::vector<toast::IrradianceVolume*> m_irradiance_volume_nodes;
1410 std::vector<toast::PostProcessVolume*> m_post_process_volume_nodes;
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;
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;
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;
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;
1439 float importance = 0.0f;
1440 float depth_reach = 0.0f;
1441 bool visible =
false;
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;
1449 std::vector<toast::IrradianceVolume*> m_irradiance_published_nodes;
1451 uint32_t m_irradiance_probe_total = 0;
1453 std::unordered_set<toast::IrradianceVolume*> m_irradiance_load_attempted;
1455 std::unordered_map<toast::IrradianceVolume*, ShGridKey> m_irradiance_baked_keys;
1457 std::atomic<uint32_t> m_irradiance_stale_count {0};
1459 std::atomic<uint32_t> m_visible_instance_count {0};
1461 uint64_t m_frame_boundary_counter = 0;
1463 uint64_t m_frame_sequence_counter = 0;
1465 std::deque<uint32_t> m_pending_publish;
1467 std::unique_ptr<DepthPrepass> m_depth_prepass;
1469 std::unique_ptr<SkinningPass> m_skinning_pass;
1471 std::unique_ptr<RayTracingScene> m_ray_tracing_scene;
1473 std::unique_ptr<SkinnedBlasPool> m_skinned_blas_pool;
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};
1479 std::atomic<uint32_t> m_skipped_builds {0};
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};
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};
1501 std::atomic_bool m_cull_debug_draw {
false};
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;
1507 int32_t m_irradiance_bake_cursor = -1;
1508 std::atomic_bool m_irradiance_bake_requested {
false};
1510 std::atomic_bool m_bake_active {
false};
1511 std::unordered_set<toast::ReflectionProbe*> m_probe_load_attempted;
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;
1520 void createFrameResources();
1521 void updateFrameResources(uint32_t frame_index,
RenderFrame& frame_data);
1523 void applyResizeInternal(vk::Extent2D extent);
1525 static constexpr uint64_t k_no_pending_resize = 0;
1526 std::atomic<uint64_t> m_pending_resize_packed {k_no_pending_resize};
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};