diff --git a/Resources/Shaders/g_buffer.frag b/Resources/Shaders/g_buffer.frag index b4757f2f..0e8e5836 100644 --- a/Resources/Shaders/g_buffer.frag +++ b/Resources/Shaders/g_buffer.frag @@ -26,7 +26,7 @@ void main() if (material.AlbedoMap < INVALID_MAP_HANDLE) { - uint texId = uint(material.AlbedoMap); + uint texId = material.AlbedoMap; vec4 albedoSample = texture(sampler2D(TextureArray[nonuniformEXT(texId)], LinearWrapSampler), TexCoord); albedo = albedoSample.rgb; alpha = albedoSample.a; @@ -37,7 +37,7 @@ void main() if (material.NormalMap < INVALID_MAP_HANDLE) { - uint texId = uint(material.NormalMap); + uint texId = material.NormalMap; vec3 normalSample = texture(sampler2D(TextureArray[nonuniformEXT(texId)], LinearWrapSampler), TexCoord).rgb; normal = perturbNormal(normalize(WorldNormal), WorldPos, normalSample, TexCoord); } @@ -45,7 +45,7 @@ void main() if (material.SpecularMap < INVALID_MAP_HANDLE) { // glTF metallicRoughnessTexture: R=occlusion, G=roughness, B=metallic - uint texId = uint(material.SpecularMap); + uint texId = material.SpecularMap; vec3 orm = texture(sampler2D(TextureArray[nonuniformEXT(texId)], LinearWrapSampler), TexCoord).rgb; ao = orm.r; roughness = orm.g; @@ -54,7 +54,7 @@ void main() if (material.EmissiveMap < INVALID_MAP_HANDLE) { - uint texId = uint(material.EmissiveMap); + uint texId = material.EmissiveMap; emissive = texture(sampler2D(TextureArray[nonuniformEXT(texId)], LinearWrapSampler), TexCoord).r; } diff --git a/Resources/Shaders/material.glsl b/Resources/Shaders/material.glsl index d1974bae..31457dc3 100644 --- a/Resources/Shaders/material.glsl +++ b/Resources/Shaders/material.glsl @@ -1,7 +1,6 @@ // Material bindings and accessor — fragment-stage only. // Declares set=1 (TextureArray, LinearWrapSampler) and MatSB. // Does NOT pull in geometry buffers so no duplicate-binding conflicts with vertex stage. -#extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable #include "material_types.glsl" #include "texture_bindings.glsl" diff --git a/Resources/Shaders/material_types.glsl b/Resources/Shaders/material_types.glsl index 9dd0970c..4c4c65b2 100644 --- a/Resources/Shaders/material_types.glsl +++ b/Resources/Shaders/material_types.glsl @@ -1,21 +1,19 @@ // Material data structure. // No descriptor bindings — include wherever MaterialData is needed. -// Note: MaterialData uses uint64_t; include material.glsl (not this file directly) -// in shader stages that need the full material pipeline — it enables Int64 there. - +// Texture map fields use uint (32-bit bindless indices) — no shaderInt64 required. struct MaterialData { - vec4 Ambient; - vec4 Emissive; - vec4 Albedo; - vec4 Specular; - vec4 Roughness; - vec4 Factors; // {x : transparency, y : Metallic, z : AlphaTest, w : _padding} + vec4 Ambient; + vec4 Emissive; + vec4 Albedo; + vec4 Specular; + vec4 Roughness; + vec4 Factors; // {x : transparency, y : Metallic, z : AlphaTest, w : _padding} - uint64_t EmissiveMap; - uint64_t AlbedoMap; - uint64_t SpecularMap; - uint64_t NormalMap; - uint64_t OpacityMap; - uint64_t _padding; + uint EmissiveMap; + uint AlbedoMap; + uint SpecularMap; + uint NormalMap; + uint OpacityMap; + uint _pad; }; diff --git a/Tetragrama/Panels/ViewportPanel.cpp b/Tetragrama/Panels/ViewportPanel.cpp index bb77f030..a09c6c63 100644 --- a/Tetragrama/Panels/ViewportPanel.cpp +++ b/Tetragrama/Panels/ViewportPanel.cpp @@ -265,52 +265,98 @@ namespace Tetragrama::Panels if (!ZEngine::Importers::AssetCodec::ReadAssetMeshFileHeader(native_path, header)) return; - // The registry's auto-ingest path is dead code (see #755) — ingest - // explicitly instead, matching every other real consumer. Idempotent. - { - auto scratch = ZGetScratch(&ctx->AssetArena); - ZEngine::Importers::AssetMesh mesh_data{}; - ZEngine::Importers::AssetNodeHierarchy hier_data{}; - ZEngine::Importers::AssetCodec::DeserializeMeshAssetFile(scratch.Arena, native_path, mesh_data, hier_data); - - // IngestMesh only loads geometry/hierarchy — ingest each submesh's material - // (and, transitively, its textures) first, before mesh_data is moved below. - for (uint32_t i = 0; i < mesh_data.SubMeshes.size(); ++i) - { - const auto& mat_uuid = mesh_data.SubMeshes[i].MaterialUUID; - if (!mat_uuid.is_nil()) - ZEngine::Managers::AssetManager::IngestMaterialFromUUID(scratch.Arena, mat_uuid); - } + std::string mesh_path = native_path; + std::string drop_path = m_pending_mesh_drop; + auto* layer_ptr = m_layer; - ZEngine::Managers::AssetManager::IngestMesh(std::move(mesh_data), std::move(hier_data)); - ZReleaseScratch(scratch); - } + // Deserialize + material ingest on a worker thread — both are synchronous file + // reads that block the render loop. A dedicated arena (4× file size + 8 MB) + // outlives the lambda; the main-thread callback owns and shuts it down after use. - char iname[256] = {}; - auto pr = VFSPath::Parse(m_pending_mesh_drop); - if (pr.Succeeded()) + uint64_t file_bytes = 0; + if (FILE* f = fopen(mesh_path.c_str(), "rb")) { - auto s = pr.Value().Stem(); - snprintf(iname, sizeof(iname), "%.*s", (int) s.Length, s.Data); + fseek(f, 0, SEEK_END); + file_bytes = static_cast(ftell(f)); + fclose(f); } - uint32_t render_id = scene->AddMeshInstance(header.Id, iname); - - using namespace ZEngine::ECS::Components; - ZEngine::ECS::ActorHandle handle = ctx->ActorManager->Create(); - ZEngine::ECS::Actor* actor = ctx->ActorManager->Access(handle); - if (actor) + struct MeshPayload { - NameComponent nc = {}; - secure_strncpy(nc.Value, sizeof(nc.Value), iname, secure_strlen(iname)); - actor->AddComponent(nc); - actor->AddComponent({}); - - MeshComponent mc = {}; - mc.MeshUUID = header.Id; - mc.RenderInstanceId = render_id; - actor->AddComponent(mc); - } + ZEngine::Core::Memory::ArenaAllocator* Arena = nullptr; + ZEngine::Importers::AssetMesh Mesh = {}; + ZEngine::Importers::AssetNodeHierarchy Hierarchy = {}; + uuids::uuid MeshId = {}; + std::string DropPath = {}; + void* Layer = nullptr; + }; + + auto* payload = new MeshPayload(); + payload->Arena = new ZEngine::Core::Memory::ArenaAllocator{}; + payload->Arena->Initialize(file_bytes * 4 + (8u << 20), 0); + payload->MeshId = header.Id; + payload->DropPath = drop_path; + payload->Layer = layer_ptr; + + ZEngine::Helpers::ThreadPoolHelper::Submit([payload, mesh_path]() mutable { + ZEngine::Importers::AssetCodec::DeserializeMeshAssetFile(payload->Arena, mesh_path.c_str(), payload->Mesh, payload->Hierarchy); + + // IngestMaterialFromUUID is also file I/O — keep it on the worker. + auto* ctx = ZEngine::Engine::GetContext(); + if (ctx) + { + auto scratch = ZGetScratch(&ctx->AssetArena); + for (uint32_t i = 0; i < payload->Mesh.SubMeshes.size(); ++i) + { + const auto& mat_uuid = payload->Mesh.SubMeshes[i].MaterialUUID; + if (!mat_uuid.is_nil()) + ZEngine::Managers::AssetManager::IngestMaterialFromUUID(scratch.Arena, mat_uuid); + } + ZReleaseScratch(scratch); + } + + ZEngine::Core::MainThreadScheduler::Post(payload, [](void* raw) { + auto* p = static_cast(raw); + auto* ctx = ZEngine::Engine::GetContext(); + auto* app = p->Layer ? reinterpret_cast(reinterpret_cast(p->Layer)->CurrentApp) : nullptr; + auto* scene = app ? reinterpret_cast(app->CurrentScene) : nullptr; + + if (ctx && scene && ctx->ActorManager) + { + ZEngine::Managers::AssetManager::IngestMesh(std::move(p->Mesh), std::move(p->Hierarchy)); + + char iname[256] = {}; + auto pr = VFSPath::Parse(p->DropPath.c_str()); + if (pr.Succeeded()) + { + auto s = pr.Value().Stem(); + snprintf(iname, sizeof(iname), "%.*s", (int) s.Length, s.Data); + } + + uint32_t render_id = scene->AddMeshInstance(p->MeshId, iname); + + using namespace ZEngine::ECS::Components; + ZEngine::ECS::ActorHandle handle = ctx->ActorManager->Create(); + ZEngine::ECS::Actor* actor = ctx->ActorManager->Access(handle); + if (actor) + { + NameComponent nc = {}; + secure_strncpy(nc.Value, sizeof(nc.Value), iname, secure_strlen(iname)); + actor->AddComponent(nc); + actor->AddComponent({}); + + MeshComponent mc = {}; + mc.MeshUUID = p->MeshId; + mc.RenderInstanceId = render_id; + actor->AddComponent(mc); + } + } + + p->Arena->Shutdown(); + delete p->Arena; + delete p; + }); + }); } // OpenDroppedScene (main-thread only) diff --git a/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp b/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp index 8f68678e..59e6a054 100644 --- a/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp +++ b/ZEngine/ZEngine/Applications/AppRenderPipeline.cpp @@ -145,15 +145,16 @@ namespace ZEngine::Applications void AppRenderPipeline::EndFrame() { if (Device->RRM) - { - auto* rrm = static_cast(Device->RRM); - rrm->EndFrame(); - rrm->SubmitAsyncUploads(); - } + static_cast(Device->RRM)->EndFrame(); + Device->CommandBufferMgr->EnqueueBuffer(CurrentCmdBuf); Device->CommandBufferMgr->EndEnqueuedBuffers(); + // Present before SubmitAsyncUploads: texture upload ops go into the deferred + // queues and are waited on by the next frame's submit_1, not the current one. Device->SwapchainPtr->Present(); + if (Device->RRM) + static_cast(Device->RRM)->SubmitAsyncUploads(); } void AppRenderPipeline::RenderScene(Rendering::Cameras::CameraPtr camera, Rendering::Scenes::RenderScenePtr scene) @@ -239,8 +240,8 @@ namespace ZEngine::Applications for (uint32_t sub_i = 0; sub_i < static_cast(mesh->SubMeshes.size()); ++sub_i) { const auto& sub = mesh->SubMeshes[sub_i]; - auto* mat = Managers::AssetManager::GetAsset(sub.MaterialUUID); - uint32_t mat_idx = mat ? static_cast(mat - mgr->Materials.data()) : 0; + uint32_t* mat_slot = mgr ? mgr->UUIDToMaterialSlot.find(sub.MaterialUUID) : nullptr; + uint32_t mat_idx = mat_slot ? *mat_slot : 0; uint32_t draw_idx = static_cast(allocs.size()); Rendering::Meshes::SubMeshAllocation alloc = {}; diff --git a/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.cpp b/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.cpp index 484cb112..970b2e11 100644 --- a/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.cpp +++ b/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.cpp @@ -33,7 +33,7 @@ namespace ZEngine::Hardwares // real failure, and the device may already be unsafe to keep calling into. if (!m_device->QueueSubmit(job.Buffer, job.Timeline, job.WaitFlag, job.SignalValue, job.WaitValue, job.WaitTimeline)) break; - m_device->EnqueueAsyncGPUOperation({job.WaitFlag, job.SignalValue, job.Timeline}); + m_device->EnqueueDeferredAsyncGPUOperation({job.WaitFlag, job.SignalValue, job.Timeline}); } } diff --git a/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.h b/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.h index c7f38670..49396e34 100644 --- a/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.h +++ b/ZEngine/ZEngine/Hardwares/AsyncUploadQueue.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include namespace ZEngine::Rendering::Primitives @@ -18,7 +19,7 @@ namespace ZEngine::Hardwares CommandBuffer* Buffer = nullptr; Rendering::Primitives::Semaphore* Timeline = nullptr; Rendering::Primitives::Semaphore* WaitTimeline = nullptr; - uint32_t WaitFlag = 0; + VkPipelineStageFlags2 WaitFlag = 0; uint64_t SignalValue = 0; uint64_t WaitValue = UINT64_MAX; }; diff --git a/ZEngine/ZEngine/Hardwares/DeviceSwapchain.cpp b/ZEngine/ZEngine/Hardwares/DeviceSwapchain.cpp index 2d7fb464..b640b3c2 100644 --- a/ZEngine/ZEngine/Hardwares/DeviceSwapchain.cpp +++ b/ZEngine/ZEngine/Hardwares/DeviceSwapchain.cpp @@ -346,6 +346,7 @@ namespace ZEngine::Hardwares void DeviceSwapchain::Present() { + if (Recreation == RecreationState::FrameAborted) { // OOD at acquire: semaphore not signalled, no GPU work submitted. @@ -354,16 +355,23 @@ namespace ZEngine::Hardwares return; } - // The device can go lost mid-frame, inside AppRenderPipeline::EndFrame's own - // SubmitAsyncUploads() call, before Present() runs — continuing into more Vulkan - // calls (including the unchecked vkGetSemaphoreCounterValue below) here would just - // add cascade errors on top of an already-lost device. + // The device can go lost mid-frame before Present() runs — continuing into more + // Vulkan calls here would just add cascade errors on top of the real failure. if (Device->IsDeviceLost.load(std::memory_order_acquire)) { Device->CommandBufferMgr->ResetEnqueuedBufferIndex(); return; } + // Promote last frame's deferred upload ops so submit_1 can wait on them. + // SubmitAsyncUploads runs after Present(), so DeferredAsyncGPUOperations only + // holds ops from prior frames when we reach this point. + { + Hardwares::AsyncGPUOperationHandle deferred_op = {}; + while (Device->DeferredAsyncGPUOperations.pop(deferred_op)) + Device->AsyncGPUOperations.Enqueue(deferred_op); + } + { // Watermark — warn once when live texture slots exceed 75% of pool capacity. static bool s_watermark_warned = false; @@ -454,13 +462,16 @@ namespace ZEngine::Hardwares } } - auto scratch = ZGetScratch(&Arena); + auto scratch = ZGetScratch(&Arena); - Array buffer = {}; - buffer.init(scratch.Arena, Device->CommandBufferMgr->EnqueuedCommandBufferIndex, Device->CommandBufferMgr->EnqueuedCommandBufferIndex); - for (int i = 0; i < buffer.size(); ++i) + Array cmd_infos = {}; + cmd_infos.init(scratch.Arena, Device->CommandBufferMgr->EnqueuedCommandBufferIndex, Device->CommandBufferMgr->EnqueuedCommandBufferIndex); + for (int i = 0; i < cmd_infos.size(); ++i) { - buffer[i] = Device->CommandBufferMgr->EnqueuedCommandBuffers[i]->GetHandle(); + cmd_infos[i] = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .commandBuffer = Device->CommandBufferMgr->EnqueuedCommandBuffers[i]->GetHandle(), + }; } auto render_complete = RenderCompletes[CurrentFrame->ImageIndex]; @@ -471,40 +482,39 @@ namespace ZEngine::Hardwares if (CurrentFrame->Fence->GetState() == Rendering::Primitives::FenceState::Submitted) CurrentFrame->Fence->Wait(UINT64_MAX); - QueueView queue = Device->GetQueue(Rendering::QueueType::GRAPHIC_QUEUE); - - // for the rendering and presentation, we use the 3-submit pattern - // This is due to Intel drivers bug that deosn't support well the combinaison of Timeline + Binary Semaphore. - // - // 1 - Acquire bridge - // 2 - Rendering work - // 3 - Present bridge - - // 1- Binary Acquire to a Timeline value - uint64_t frame_start_value = ++RenderTimelineNextValue; - uint64_t ignored_wait_val = 0; - VkTimelineSemaphoreSubmitInfo timeline_info0 = { - .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, - .waitSemaphoreValueCount = 1, // must match waitSemaphoreCount - .pWaitSemaphoreValues = &ignored_wait_val, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &frame_start_value, - }; + QueueView queue = Device->GetQueue(Rendering::QueueType::GRAPHIC_QUEUE); - VkPipelineStageFlags acquire_wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - VkSemaphore acquire_wait_semaphores[] = {CurrentFrame->Acquired->GetHandle()}; - VkSemaphore acquire_signal_semaphores[] = {RenderTimeline->GetHandle()}; - VkSubmitInfo submit_0 = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = &timeline_info0, - .waitSemaphoreCount = 1, - .pWaitSemaphores = acquire_wait_semaphores, - .pWaitDstStageMask = &acquire_wait_stage, - .commandBufferCount = 0, - .signalSemaphoreCount = 1, - .pSignalSemaphores = acquire_signal_semaphores, + // 3-submit pattern using vkQueueSubmit2: + // 1 - Acquire bridge: binary Acquired → timeline RenderTimeline + // 2 - Render work: timeline waits (async GPU ops) → timeline RenderTimeline + // 3 - Present bridge: timeline RenderTimeline → binary render_complete + // vkQueueSubmit2 uses per-semaphore VkSemaphoreSubmitInfo structs, eliminating + // the parallel-array count ambiguity that caused Intel driver corruption with + // the old VkTimelineSemaphoreSubmitInfo + vkQueueSubmit path. + + uint64_t frame_start_value = ++RenderTimelineNextValue; + + VkSemaphoreSubmitInfo acquire_wait_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = CurrentFrame->Acquired->GetHandle(), + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + }; + VkSemaphoreSubmitInfo frame_start_signal = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = RenderTimeline->GetHandle(), + .value = frame_start_value, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, }; - VkResult r0 = vkQueueSubmit(queue.Handle, 1, &submit_0, VK_NULL_HANDLE); + VkSubmitInfo2 submit_0 = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = 1, + .pWaitSemaphoreInfos = &acquire_wait_info, + .commandBufferInfoCount = 0, + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &frame_start_signal, + }; + VkResult r0 = vkQueueSubmit2(queue.Handle, 1, &submit_0, VK_NULL_HANDLE); if (Device->CheckDeviceLost(r0, "Present: acquire bridge submit")) { ZReleaseScratch(scratch); @@ -514,32 +524,24 @@ namespace ZEngine::Hardwares struct TimelineAggregate { - uint64_t MaxValue = 0; - VkPipelineStageFlags StageMask = 0; + uint64_t MaxValue = 0; + VkPipelineStageFlags2 StageMask = 0; }; - Array wait_semaphores = {}; - Array wait_values = {}; - Array stage_flags = {}; + Array wait_sem_infos = {}; UnorderedHashMap max_val_timeline_semaphores = {}; - wait_semaphores.init(scratch.Arena, 10); - stage_flags.init(scratch.Arena, 10); - wait_values.init(scratch.Arena, 10); + wait_sem_infos.init(scratch.Arena, 10); max_val_timeline_semaphores.init(scratch.Arena); - // Seed with RenderTimeline's own acquire-bridge value rather than pushing it - // directly — an AsyncGPUOperation can also target RenderTimeline (e.g. RRM's mesh - // batch upload), and pushing both separately would put the same semaphore twice - // in one submit's wait list with two different values. - max_val_timeline_semaphores.insert(RenderTimeline, {frame_start_value, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}); + // DO NOT seed with RenderTimeline here — removing the self-wait on RenderTimeline + // in submit_1. See commit message for full explanation. + // For NVIDIA: m_tex_transfer_timelines are included via AsyncGPUOperations drain below. { Hardwares::AsyncGPUOperationHandle op; while (Device->AsyncGPUOperations.Pop(op)) { - ZENGINE_CORE_TRACE("[Present] AsyncGPUOperation: timeline={} signal_value={} stage_flags={:#x}", (void*) op.Timeline->GetHandle(), op.SignalValue, op.StageFlags) - if (!max_val_timeline_semaphores.contains(op.Timeline)) { max_val_timeline_semaphores.insert(op.Timeline, {op.SignalValue, op.StageFlags}); @@ -553,36 +555,35 @@ namespace ZEngine::Hardwares for (auto [sem, val] : max_val_timeline_semaphores) { - wait_semaphores.push(sem->GetHandle()); - wait_values.push(val.MaxValue); - stage_flags.push(val.StageMask); + wait_sem_infos.push({ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = sem->GetHandle(), + .value = val.MaxValue, + .stageMask = val.StageMask, + }); } - uint64_t work_complete_value = ++RenderTimelineNextValue; - VkSemaphore work_signal_semaphores[] = {RenderTimeline->GetHandle()}; - VkTimelineSemaphoreSubmitInfo timeline_info_1 = { - .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, - .waitSemaphoreValueCount = (uint32_t) wait_values.size(), - .pWaitSemaphoreValues = wait_values.data(), - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &work_complete_value, - }; + uint64_t work_complete_value = ++RenderTimelineNextValue; - VkSubmitInfo submit_info_1 = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = &timeline_info_1, - .waitSemaphoreCount = (uint32_t) wait_semaphores.size(), - .pWaitSemaphores = wait_semaphores.data(), - .pWaitDstStageMask = stage_flags.data(), - .commandBufferCount = (uint32_t) buffer.size(), - .pCommandBuffers = buffer.data(), - .signalSemaphoreCount = 1, - .pSignalSemaphores = work_signal_semaphores, + VkSemaphoreSubmitInfo work_complete_signal = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = RenderTimeline->GetHandle(), + .value = work_complete_value, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + }; + VkSubmitInfo2 submit_info_1 = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = (uint32_t) wait_sem_infos.size(), + .pWaitSemaphoreInfos = wait_sem_infos.data(), + .commandBufferInfoCount = (uint32_t) cmd_infos.size(), + .pCommandBufferInfos = cmd_infos.data(), + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &work_complete_signal, }; Device->FrameHeaps[CurrentFrame->Index].Flush(&Device->GpuMem); - auto submit = vkQueueSubmit(queue.Handle, 1, &(submit_info_1), CurrentFrame->Fence->GetHandle()); + auto submit = vkQueueSubmit2(queue.Handle, 1, &submit_info_1, CurrentFrame->Fence->GetHandle()); if (Device->CheckDeviceLost(submit, "Present: render work submit")) { ZReleaseScratch(scratch); @@ -595,30 +596,28 @@ namespace ZEngine::Hardwares Device->CommandBufferMgr->ResetEnqueuedBufferIndex(); CurrentFrame->Fence->SetState(Rendering::Primitives::FenceState::Submitted); - uint64_t dummy_signal_val = 0; - VkPipelineStageFlags present_wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - VkSemaphore present_wait_semaphores[] = {RenderTimeline->GetHandle()}; - VkSemaphore present_signal_semaphores[] = {render_complete->GetHandle()}; - VkTimelineSemaphoreSubmitInfo timeline_info2 = { - .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &work_complete_value, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &dummy_signal_val, + VkSemaphoreSubmitInfo present_wait_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = RenderTimeline->GetHandle(), + .value = work_complete_value, + .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, }; - - VkSubmitInfo submit2 = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = &timeline_info2, - .waitSemaphoreCount = 1, - .pWaitSemaphores = present_wait_semaphores, - .pWaitDstStageMask = &present_wait_stage, - .commandBufferCount = 0, - .signalSemaphoreCount = 1, - .pSignalSemaphores = present_signal_semaphores, + VkSemaphoreSubmitInfo present_signal_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = render_complete->GetHandle(), + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + }; + VkSubmitInfo2 submit2 = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = 1, + .pWaitSemaphoreInfos = &present_wait_info, + .commandBufferInfoCount = 0, + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &present_signal_info, }; - VkResult r2 = vkQueueSubmit(queue.Handle, 1, &submit2, present_complete->GetHandle()); + VkResult r2 = vkQueueSubmit2(queue.Handle, 1, &submit2, present_complete->GetHandle()); if (Device->CheckDeviceLost(r2, "Present: present bridge submit")) return; ZENGINE_VALIDATE_ASSERT(r2 == VK_SUCCESS, "Failed to submit present bridge") @@ -669,5 +668,33 @@ namespace ZEngine::Hardwares { Recreation = RecreationState::Pending; } + + // Drain new deferred descriptor updates after the frame is submitted. + // Writing the fallback here (render thread only) avoids concurrent vkUpdateDescriptorSets + // with Present()'s own descriptor batch. The real image follows next frame via + // TextureHandleToUpdates once submit_1 has waited for the upload to complete. + { + Rendering::Textures::TextureHandle deferred_handle = {}; + while (Device->DeferredTextureDescriptorUpdates.pop(deferred_handle)) + { + if (Device->FallbackDescriptorImageInfo.imageView != VK_NULL_HANDLE) + { + for (const auto& req : Device->BindlessTextureSlotRequests) + { + VkWriteDescriptorSet write = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = req.DstSet, + .dstBinding = req.Binding, + .dstArrayElement = (uint32_t) deferred_handle.Index, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .pImageInfo = &Device->FallbackDescriptorImageInfo, + }; + vkUpdateDescriptorSets(Device->LogicalDevice, 1, &write, 0, nullptr); + } + } + Device->TextureHandleToUpdates.Enqueue(deferred_handle); + } + } } } // namespace ZEngine::Hardwares diff --git a/ZEngine/ZEngine/Hardwares/VulkanDevice.cpp b/ZEngine/ZEngine/Hardwares/VulkanDevice.cpp index f48c431b..49b89a01 100644 --- a/ZEngine/ZEngine/Hardwares/VulkanDevice.cpp +++ b/ZEngine/ZEngine/Hardwares/VulkanDevice.cpp @@ -210,8 +210,12 @@ namespace ZEngine::Hardwares auto try_select_device = [&](VkPhysicalDeviceType preferred_type) { for (VkPhysicalDevice physical_device : physical_device_collection) { + VkPhysicalDeviceDriverProperties driver_props = {}; + driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + VkPhysicalDeviceVulkan12Properties vulkan_1_2_properties = {}; vulkan_1_2_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; + vulkan_1_2_properties.pNext = &driver_props; VkPhysicalDeviceProperties2 physical_device_properties = {}; physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; @@ -219,6 +223,18 @@ namespace ZEngine::Hardwares vkGetPhysicalDeviceProperties2(physical_device, &physical_device_properties); + // VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS: fences/semaphores signal before + // GPU execution completes — no reliable Vulkan workaround. Halt early. + if (driver_props.driverID == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) + { + ZENGINE_CORE_CRITICAL( + "[GPU] Unsupported Vulkan driver detected: Intel HD/UHD Graphics (Windows proprietary). " + "This driver has known Vulkan synchronization bugs that cause GPU device loss. " + "Please update to the latest Intel graphics driver from https://www.intel.com/content/www/us/en/download-center/home.html " + "or wait for the DirectX 12 backend.") + ZENGINE_VALIDATE_ASSERT(false, "Intel HD/UHD Windows proprietary Vulkan driver is not supported — see engine log.") + } + VkPhysicalDeviceVulkan12Features vulkan_1_2_features = {}; vulkan_1_2_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; @@ -349,13 +365,19 @@ namespace ZEngine::Hardwares vulkan_1_2_features.timelineSemaphore = VK_TRUE; } - VkPhysicalDeviceFeatures2 device_features_2 = {}; - device_features_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - device_features_2.features.drawIndirectFirstInstance = PhysicalDeviceFeature.features.drawIndirectFirstInstance; - device_features_2.features.multiDrawIndirect = PhysicalDeviceFeature.features.multiDrawIndirect; - device_features_2.features.samplerAnisotropy = PhysicalDeviceFeature.features.samplerAnisotropy; + VkPhysicalDeviceFeatures2 device_features_2 = {}; + device_features_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + device_features_2.features.drawIndirectFirstInstance = PhysicalDeviceFeature.features.drawIndirectFirstInstance; + device_features_2.features.multiDrawIndirect = PhysicalDeviceFeature.features.multiDrawIndirect; + device_features_2.features.samplerAnisotropy = PhysicalDeviceFeature.features.samplerAnisotropy; // Required for MaterialData.AlbedoMap / NormalMap etc. (uint64_t handles in g_buffer.frag) - device_features_2.features.shaderInt64 = PhysicalDeviceFeature.features.shaderInt64; + // shaderInt64 no longer required — material map indices use uint32 in shader and CPU struct. + + // synchronization2 is required for vkQueueSubmit2 (used for all timeline semaphore submits). + VkPhysicalDeviceSynchronization2Features sync2_features = {}; + sync2_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; + sync2_features.synchronization2 = VK_TRUE; + device_features_2.pNext = &sync2_features; if (PhysicalDeviceSupportSampledImageBindless || PhysicalDeviceSupportStorageBufferBindless) { @@ -369,7 +391,11 @@ namespace ZEngine::Hardwares vulkan_1_2_features.descriptorBindingPartiallyBound = VK_TRUE; vulkan_1_2_features.runtimeDescriptorArray = VK_TRUE; - device_features_2.pNext = &vulkan_1_2_features; + sync2_features.pNext = &vulkan_1_2_features; + } + else if (PhysicalDeviceSupportTimelineSemaphore) + { + sync2_features.pNext = &vulkan_1_2_features; } device_create_info.pNext = &device_features_2; @@ -751,43 +777,44 @@ namespace ZEngine::Hardwares Instance = VK_NULL_HANDLE; } - bool VulkanDevice::QueueSubmit(CommandBuffer* const command_buffer, Rendering::Primitives::Semaphore* const signal_semaphore, uint32_t wait_flag, uint64_t signal_value, uint64_t wait_value, Rendering::Primitives::Semaphore* const wait_semaphore) + bool VulkanDevice::QueueSubmit(CommandBuffer* const command_buffer, Rendering::Primitives::Semaphore* const signal_semaphore, VkPipelineStageFlags2 wait_flag, uint64_t signal_value, uint64_t wait_value, Rendering::Primitives::Semaphore* const wait_semaphore) { ZENGINE_VALIDATE_ASSERT(command_buffer->GetState() == CommandBufferState::Executable, "Command buffer must be in executable state to be submitted.") ZENGINE_VALIDATE_ASSERT(signal_semaphore->IsTimeline == true, "Signal semaphore must be a timeline semaphore.") - bool has_wait = (wait_semaphore != nullptr && wait_value != UINT64_MAX); - - VkPipelineStageFlags flag = VkPipelineStageFlagBits(wait_flag); + bool has_wait = (wait_semaphore != nullptr && wait_value != UINT64_MAX); - VkCommandBuffer command_buffers[] = {command_buffer->GetHandle()}; - VkSemaphore semaphores[] = {signal_semaphore->GetHandle()}; - VkSemaphore wait_sems[] = {has_wait ? wait_semaphore->GetHandle() : VK_NULL_HANDLE}; - uint64_t wait_values[] = {wait_value}; - uint64_t signal_values[] = {signal_value}; - - VkTimelineSemaphoreSubmitInfo timeline_semaphore_submit_info = { - .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreValueCount = has_wait ? 1u : 0u, - .pWaitSemaphoreValues = has_wait ? wait_values : nullptr, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = signal_values, + VkCommandBufferSubmitInfo cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .commandBuffer = command_buffer->GetHandle(), }; - - VkSubmitInfo submit_info = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = &timeline_semaphore_submit_info, - .waitSemaphoreCount = has_wait ? 1u : 0u, - .pWaitSemaphores = has_wait ? wait_sems : nullptr, - .pWaitDstStageMask = has_wait ? &flag : nullptr, - .commandBufferCount = 1, - .pCommandBuffers = command_buffers, - .signalSemaphoreCount = 1, - .pSignalSemaphores = semaphores, + VkSemaphoreSubmitInfo signal_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = signal_semaphore->GetHandle(), + .value = signal_value, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + }; + VkSemaphoreSubmitInfo wait_info = {}; + if (has_wait) + { + wait_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = wait_semaphore->GetHandle(), + .value = wait_value, + .stageMask = (VkPipelineStageFlags2) wait_flag, + }; + } + VkSubmitInfo2 submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = has_wait ? 1u : 0u, + .pWaitSemaphoreInfos = has_wait ? &wait_info : nullptr, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &cmd_info, + .signalSemaphoreInfoCount = 1, + .pSignalSemaphoreInfos = &signal_info, }; - VkResult submit_result = vkQueueSubmit(GetQueue(command_buffer->QueueType).Handle, 1, &submit_info, VK_NULL_HANDLE); + VkResult submit_result = vkQueueSubmit2(GetQueue(command_buffer->QueueType).Handle, 1, &submit_info, VK_NULL_HANDLE); if (CheckDeviceLost(submit_result, "QueueSubmit (timeline)")) return false; ZENGINE_VALIDATE_ASSERT(submit_result == VK_SUCCESS, "Failed to submit queue") @@ -1849,4 +1876,17 @@ namespace ZEngine::Hardwares AsyncGPUOperations.Enqueue(operation); } + void VulkanDevice::EnqueueDeferredAsyncGPUOperation(const AsyncGPUOperationHandle& operation) + { + DeferredAsyncGPUOperations.push(operation); + } + + void VulkanDevice::RequestDeferredDescriptorUpdate(const Rendering::Textures::TextureHandle& handle) + { + // Push to the deferred queue only — the render thread drains this and writes + // the fallback then the real image, both from a single thread so vkUpdateDescriptorSets + // is never called concurrently with Present()'s own descriptor batch. + DeferredTextureDescriptorUpdates.push(handle); + } + } // namespace ZEngine::Hardwares diff --git a/ZEngine/ZEngine/Hardwares/VulkanDevice.h b/ZEngine/ZEngine/Hardwares/VulkanDevice.h index 08518e29..cb639f9f 100644 --- a/ZEngine/ZEngine/Hardwares/VulkanDevice.h +++ b/ZEngine/ZEngine/Hardwares/VulkanDevice.h @@ -200,7 +200,7 @@ namespace ZEngine::Hardwares */ struct AsyncGPUOperationHandle { - uint32_t StageFlags = 0; + VkPipelineStageFlags2 StageFlags = 0; uint64_t SignalValue = 0; Rendering::Primitives::Semaphore* Timeline = nullptr; }; @@ -274,8 +274,11 @@ namespace ZEngine::Hardwares Rendering::Textures::TextureHandleManager GlobalTextures = {}; Helpers::HandleManager ImageBufferManager = {}; Helpers::ThreadSafeQueue TextureHandleToUpdates = {}; + Core::Containers::SPSCQueue DeferredTextureDescriptorUpdates = {}; TextureDisposeQueue TextureHandleToDispose = {}; Helpers::ThreadSafeQueue AsyncGPUOperations = {}; + Core::Containers::SPSCQueue DeferredAsyncGPUOperations = {}; + VkDescriptorImageInfo FallbackDescriptorImageInfo = {}; Helpers::HandleManager ShaderManager = {}; std::mutex Mutex = {}; Windows::CoreWindow* CurrentWindow = nullptr; @@ -285,7 +288,7 @@ namespace ZEngine::Hardwares void Initialize(ZEngine::Core::Memory::ArenaAllocator* arena, Windows::CoreWindow* const window, uint32_t worker_thread_count); void Deinitialize(); void Dispose(); - bool QueueSubmit(CommandBuffer* const command_buffer, Rendering::Primitives::Semaphore* const signal_semaphore, uint32_t wait_flag, uint64_t signal_value, uint64_t wait_value, Rendering::Primitives::Semaphore* const wait_timeline); + bool QueueSubmit(CommandBuffer* const command_buffer, Rendering::Primitives::Semaphore* const signal_semaphore, VkPipelineStageFlags2 wait_flag, uint64_t signal_value, uint64_t wait_value, Rendering::Primitives::Semaphore* const wait_timeline); bool QueueSubmit(const VkPipelineStageFlags wait_stage_flag, CommandBuffer* const command_buffer, Rendering::Primitives::Semaphore* const signal_semaphore = nullptr, Rendering::Primitives::Fence* const fence = nullptr); /// @brief If result is VK_ERROR_DEVICE_LOST, sets IsDeviceLost (logging once, on the /// first caller to observe it) and returns true so the caller can bail out @@ -293,6 +296,7 @@ namespace ZEngine::Hardwares /// @param where Short description of the call site, for the one-time log line. bool CheckDeviceLost(VkResult result, const char* where); void EnqueueAsyncGPUOperation(const AsyncGPUOperationHandle& handle); + void EnqueueDeferredAsyncGPUOperation(const AsyncGPUOperationHandle& handle); QueueView GetQueue(Rendering::QueueType type); void QueueWait(Rendering::QueueType type); void QueueWaitAll(); @@ -319,6 +323,7 @@ namespace ZEngine::Hardwares /// @brief Dirty this handle's bindless descriptor for the next Present() to refresh. void RequestDescriptorUpdate(const Rendering::Textures::TextureHandle& handle); + void RequestDeferredDescriptorUpdate(const Rendering::Textures::TextureHandle& handle); /// @brief Timeline-gated disposal. Render-thread only. void DestroyTexture(const Rendering::Textures::TextureHandle& handle); diff --git a/ZEngine/ZEngine/Rendering/Meshes/Mesh.h b/ZEngine/ZEngine/Rendering/Meshes/Mesh.h index db2c2426..4b3eebc6 100644 --- a/ZEngine/ZEngine/Rendering/Meshes/Mesh.h +++ b/ZEngine/ZEngine/Rendering/Meshes/Mesh.h @@ -52,12 +52,12 @@ namespace ZEngine::Rendering::Meshes ZEngine::Core::Maths::Vec4f SpecularColor = {1.f, 1.f, 1.f, 1.f}; ZEngine::Core::Maths::Vec4f RoughnessColor = {1.f, 1.f, 1.f, 1.f}; ZEngine::Core::Maths::Vec4f Factors = {1.f, 1.f, 1.f, 1.f}; // {x : transparency, y : Metallic, z : AlphaTest, w : _padding} - uint64_t EmissiveMap = INVALID_MAP_HANDLE; - uint64_t AlbedoMap = INVALID_MAP_HANDLE; - uint64_t SpecularMap = INVALID_MAP_HANDLE; - uint64_t NormalMap = INVALID_MAP_HANDLE; - uint64_t OpacityMap = INVALID_MAP_HANDLE; - uint64_t _padding = INVALID_MAP_HANDLE; + uint32_t EmissiveMap = INVALID_MAP_HANDLE; + uint32_t AlbedoMap = INVALID_MAP_HANDLE; + uint32_t SpecularMap = INVALID_MAP_HANDLE; + uint32_t NormalMap = INVALID_MAP_HANDLE; + uint32_t OpacityMap = INVALID_MAP_HANDLE; + uint32_t _pad = 0; }; struct MaterialFile diff --git a/ZEngine/ZEngine/Rendering/RenderResourceManager.cpp b/ZEngine/ZEngine/Rendering/RenderResourceManager.cpp index 2f21ca24..5ec75984 100644 --- a/ZEngine/ZEngine/Rendering/RenderResourceManager.cpp +++ b/ZEngine/ZEngine/Rendering/RenderResourceManager.cpp @@ -532,10 +532,14 @@ namespace ZEngine::Rendering { uint64_t completed = 0; vkGetSemaphoreCounterValue(m_device->LogicalDevice, m_batch_timeline->GetHandle(), &completed); + for (uint32_t i = 0; i < m_batch_frames.size(); ++i) { BatchFrameState& frame = m_batch_frames[i]; - if (frame.LastSignal == 0 || frame.LastSignal > completed || frame.StagingCount == 0) + if (frame.StagingCount == 0) + continue; + uint64_t gate = frame.LastSignal; + if (gate == 0 || gate > completed) continue; for (uint32_t j = 0; j < frame.StagingCount; ++j) m_device->GpuMem.FreeBuffer(frame.StagingBuffers[j]); @@ -565,12 +569,11 @@ namespace ZEngine::Rendering if (frame.LastSignal != 0) m_batch_timeline->Wait(frame.LastSignal, UINT64_MAX); - // Guaranteed-safe fallback free, in case RetireBatchStagings' poll hasn't caught up - // yet — the wait above proves this frame index's previous batch is done either way. - for (uint32_t i = 0; i < frame.StagingCount; ++i) - m_device->GpuMem.FreeBuffer(frame.StagingBuffers[i]); - frame.StagingCount = 0; - + // RetireBatchStagings (called earlier in BeginFrame) handles staging cleanup via + // the RenderTimeline gate — it always runs before this point. If stagings remain + // here it means RetireBatchStagings hasn't confirmed GPU completion yet (render + // timeline hasn't reached SafeRetireAfterRenderValue). Leave them for the next poll + // rather than freeing while the command buffer may still be tracked as in-use. m_batch_cmd->ResetState(); vkResetCommandBuffer(m_batch_cmd->GetHandle(), 0); m_batch_cmd->Begin(); @@ -585,19 +588,13 @@ namespace ZEngine::Rendering // of submitted-and-blocked-on here, so a mesh drop never stalls the render thread. // Signals m_batch_timeline — a dedicated semaphore with exactly one writer (this // function) — rather than DeviceSwapchain::RenderTimeline, which Present() also - // drives independently; sharing it produced a timeline value Intel's Windows driver - // treats as non-monotonic. + // drives independently. uint64_t signal_value = ++m_batch_next_value; m_batch_frames[m_batch_frame_index].LastSignal = signal_value; - Hardwares::AsyncUploadJob job; - job.Buffer = m_batch_cmd; - job.Timeline = m_batch_timeline; - job.SignalValue = signal_value; - // Stages that actually consume the global vertex/index buffers, matching - // RecordGlobalBufferCopy's own barrier — so Present() waits at the right point. - job.WaitFlag = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - m_async_uploads.Enqueue(job); + VkPipelineStageFlags2 wait_flag = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + m_device->QueueSubmit(m_batch_cmd, m_batch_timeline, wait_flag, signal_value, UINT64_MAX, nullptr); + m_device->EnqueueAsyncGPUOperation({wait_flag, signal_value, m_batch_timeline}); // Left in m_batch_frames[m_batch_frame_index] for RetireBatchStagings (or the next // BeginBatchUpload for this same frame index) to free once m_batch_timeline proves @@ -1225,7 +1222,7 @@ namespace ZEngine::Rendering uint64_t graphics_val = m_tex_next_values[pool_index].fetch_add(1, std::memory_order_acq_rel); retire_values[acquire_slot] = graphics_val; - m_async_uploads.Enqueue({acquire_cmd, m_tex_timelines[pool_index], m_tex_transfer_timelines[pool_index], (uint32_t) release.DestinationStageMask, graphics_val, transfer_val}); + m_async_uploads.Enqueue({acquire_cmd, m_tex_timelines[pool_index], m_tex_transfer_timelines[pool_index], (VkPipelineStageFlags2) release.DestinationStageMask, graphics_val, transfer_val}); } else { @@ -1283,7 +1280,7 @@ namespace ZEngine::Rendering retire_values[i] = signal_value; if (staging) m_tex_retire_staging[pool_index][i] = staging; - m_async_uploads.Enqueue({cmd, m_tex_timelines[pool_index], nullptr, (uint32_t) to_final.DestinationStageMask, signal_value, UINT64_MAX}); + m_async_uploads.Enqueue({cmd, m_tex_timelines[pool_index], nullptr, (VkPipelineStageFlags2) to_final.DestinationStageMask, signal_value, UINT64_MAX}); img_buf->Layout = to_final.NewLayout; } return handle; @@ -1414,6 +1411,7 @@ namespace ZEngine::Rendering void RenderResourceManager::RetireTextureSlots(uint8_t frame_index, uint8_t thread_index) { uint32_t pool_index = (frame_index * m_device->CommandBufferMgr->TotalThreadCount) + thread_index; + uint64_t graphics_value = 0; vkGetSemaphoreCounterValue(m_device->LogicalDevice, m_tex_timelines[pool_index]->GetHandle(), &graphics_value); @@ -1749,7 +1747,7 @@ namespace ZEngine::Rendering deferral.Slab = slab; deferral.TexHandle = captured_handle; EnqueueTextureDeferral(std::move(deferral)); - m_device->RequestDescriptorUpdate(captured_handle); + m_device->RequestDeferredDescriptorUpdate(captured_handle); }); return tex_handle; @@ -1775,7 +1773,21 @@ namespace ZEngine::Rendering stbi_write_png(kFallbackPath, W, H, 4, pixels, W * 4); } - return SubmitTextureFile(kFallbackPath); + auto result = SubmitTextureFile(kFallbackPath); + if (result.Valid()) + { + auto texture = m_device->GlobalTextures.Access(result); + if (texture) + { + auto img_buf = m_device->ImageBufferManager.Access(texture->BufferHandle); + if (img_buf) + { + m_device->FallbackDescriptorImageInfo = img_buf->GetDescriptorImageInfo(); + m_device->FallbackDescriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + } + } + return result; } } // namespace ZEngine::Rendering diff --git a/ZEngine/ZEngine/Rendering/RenderResourceManager.h b/ZEngine/ZEngine/Rendering/RenderResourceManager.h index e014fd8e..7ea9294b 100644 --- a/ZEngine/ZEngine/Rendering/RenderResourceManager.h +++ b/ZEngine/ZEngine/Rendering/RenderResourceManager.h @@ -434,6 +434,7 @@ namespace ZEngine::Rendering struct BatchFrameState { uint64_t LastSignal = 0; + uint64_t SafeRetireAfterRenderValue = 0; // RenderTimeline value after which stagings are safe to free Core::Memory::BufferView StagingBuffers[MAX_PENDING * 2] = {}; uint32_t StagingCount = 0; };