From d8a1f1fd5f390edf82e52085eaca68e72820499b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 5 Jul 2024 18:34:12 +0200 Subject: [PATCH 001/148] Upgrade Nabla's IMGUI extension backend to use indirect indexed draw call --- include/nbl/ext/ImGui/ImGui.h | 9 +- src/nbl/ext/ImGui/ImGui.cpp | 363 ++++++++++++++++-------- src/nbl/ext/ImGui/shaders/common.hlsl | 18 ++ src/nbl/ext/ImGui/shaders/fragment.hlsl | 14 +- src/nbl/ext/ImGui/shaders/vertex.hlsl | 2 +- 5 files changed, 280 insertions(+), 126 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 521d1c2986..a9ba811fb0 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -6,10 +6,10 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - UI(core::smart_refctd_ptr device, int maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); + UI(core::smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); ~UI() override; - bool Render(nbl::video::IGPUCommandBuffer* commandBuffer, int frameIndex); + bool Render(nbl::video::IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex); void Update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events void BeginWindow(char const* windowName); void EndWindow(); @@ -42,7 +42,7 @@ class UI final : public core::IReferenceCounted void CreatePipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); void CreateFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void UpdateDescriptorSets(); + void UpdateDescriptorSets(asset::SBufferRange mdie = {}); void createSystem(); void CreateFontSampler(); void CreateDescriptorPool(); @@ -60,8 +60,9 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontTexture; core::smart_refctd_ptr m_window; - std::vector> m_vertexBuffers, m_indexBuffers; + std::vector> m_mdiBuffers; bool hasFocus = false; + uint32_t maxFramesInFlight; // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; struct Subscriber { diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 3d9977120e..40bbd74a43 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -11,7 +11,6 @@ #include "shaders/common.hlsl" #include "ext/imgui/spirv/builtin/builtinResources.h" #include "ext/imgui/spirv/builtin/CArchive.h" - // 3rdparty #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" @@ -27,12 +26,19 @@ namespace nbl::ext::imgui { nbl::video::IGPUDescriptorSetLayout::SBinding bindings[] = { { - .binding = 0, + .binding = 0u, .type = asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, .stageFlags = IShader::ESS_FRAGMENT, .count = 1, .samplers = nullptr // TODO: m_fontSampler? + }, + { + .binding = 1u, + .type = asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER, + .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = asset::IShader::ESS_VERTEX | asset::IShader::ESS_FRAGMENT, + .count = 1u } }; @@ -44,7 +50,7 @@ namespace nbl::ext::imgui // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix SPushConstantRange pushConstantRanges[] = { { - .stageFlags = IShader::ESS_VERTEX, + .stageFlags = IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, .offset = 0, .size = sizeof(PushConstants) } @@ -323,23 +329,36 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::UpdateDescriptorSets() + void UI::UpdateDescriptorSets(asset::SBufferRange mdie) { - IGPUDescriptorSet::SDescriptorInfo info; + _NBL_STATIC_INLINE_CONSTEXPR auto NBL_RESOURCES_AMOUNT = 2u; + + IGPUDescriptorSet::SDescriptorInfo info[NBL_RESOURCES_AMOUNT]; { - info.desc = m_fontTexture; - info.info.image.sampler = m_fontSampler; - info.info.image.imageLayout = nbl::asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; + auto& font = info[0u]; + font.desc = m_fontTexture; + font.info.image.sampler = m_fontSampler; + font.info.image.imageLayout = nbl::asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; + + auto& ssbo = info[1u]; + ssbo.desc = mdie.buffer; + ssbo.info.buffer.size = mdie.size; + ssbo.info.buffer.offset = mdie.offset; } - IGPUDescriptorSet::SWriteDescriptorSet writeDescriptorSet{}; - writeDescriptorSet.dstSet = m_gpuDescriptorSet.get(); - writeDescriptorSet.binding = 0; - writeDescriptorSet.arrayElement = 0; - writeDescriptorSet.count = 1; - writeDescriptorSet.info = &info; + IGPUDescriptorSet::SWriteDescriptorSet write[NBL_RESOURCES_AMOUNT]; + for (auto i = 0u; i < NBL_RESOURCES_AMOUNT; ++i) + { + auto& w = write[i]; + + w.dstSet = m_gpuDescriptorSet.get(); + w.binding = 0; + w.arrayElement = 0; + w.count = 1; + w.info = info + i; + } - m_device->updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr); + m_device->updateDescriptorSets(mdie.buffer ? NBL_RESOURCES_AMOUNT : 1u, write, 0, nullptr); } void UI::CreateFontSampler() @@ -358,9 +377,9 @@ namespace nbl::ext::imgui void UI::CreateDescriptorPool() { - static constexpr int TotalSetCount = 1; IDescriptorPool::SCreateInfo createInfo = {}; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)] = TotalSetCount; + createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)] = 1u; + createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER)] = 1u; createInfo.maxSets = 1; createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_NONE; @@ -412,8 +431,8 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr device, int maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) - : m_device(core::smart_refctd_ptr(device)), m_window(core::smart_refctd_ptr(window)) + UI::UI(smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) + : m_device(core::smart_refctd_ptr(device)), m_window(core::smart_refctd_ptr(window)), maxFramesInFlight(_maxFramesInFlight) { createSystem(); struct @@ -498,8 +517,7 @@ namespace nbl::ext::imgui io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - m_vertexBuffers.resize(maxFramesInFlight); - m_indexBuffers.resize(maxFramesInFlight); + m_mdiBuffers.resize(maxFramesInFlight); } UI::~UI() = default; @@ -546,8 +564,15 @@ namespace nbl::ext::imgui //} //} - bool UI::Render(IGPUCommandBuffer* commandBuffer, int const frameIndex) + bool UI::Render(IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex) { + const bool validFramesRange = frameIndex >= 0 && frameIndex < maxFramesInFlight; + if (!validFramesRange) + { + logger->log("Requested frame index is OOB!", system::ILogger::ELL_ERROR); + assert(false); + } + ImGuiIO& io = ImGui::GetIO(); if (!io.Fonts->IsBuilt()) @@ -570,82 +595,221 @@ namespace nbl::ext::imgui float const frameBufferHeight = drawData->DisplaySize.y * drawData->FramebufferScale.y; if (frameBufferWidth > 0 && frameBufferHeight > 0 && drawData->TotalVtxCount > 0) { - // Create or resize the vertex/index buffers - size_t const vertexSize = drawData->TotalVtxCount * sizeof(ImDrawVert); - size_t const indexSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); + const struct + { + ImVec2 off; // (0,0) unless using multi-viewports + ImVec2 scale; // (1,1) unless using retina display which are often (2,2) + ImVec2 framebuffer; // width, height - IGPUBuffer::SCreationParams vertexCreationParams = {}; - vertexCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - vertexCreationParams.size = vertexSize; + // Project scissor/clipping rectangles into frame-buffer space + ImVec4 getClipRectangle(const ImDrawCmd* cmd) const + { + assert(cmd); + + ImVec4 rectangle; + rectangle.x = (cmd->ClipRect.x - off.x) * scale.x; + rectangle.y = (cmd->ClipRect.y - off.y) * scale.y; + rectangle.z = (cmd->ClipRect.z - off.x) * scale.x; + rectangle.w = (cmd->ClipRect.w - off.y) * scale.y; + + return rectangle; + } - auto & vertexBuffer = m_vertexBuffers[frameIndex]; + VkRect2D getScissor(ImVec4 clipRectangle) const + { + // Negative offsets are illegal for vkCmdSetScissor + if (clipRectangle.x < 0.0f) + clipRectangle.x = 0.0f; + if (clipRectangle.y < 0.0f) + clipRectangle.y = 0.0f; + + {// Apply scissor/clipping rectangle + VkRect2D scissor {}; + scissor.offset.x = static_cast(clipRectangle.x); + scissor.offset.y = static_cast(clipRectangle.y); + scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); + scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); + + return scissor; + } + } + + bool prepass(const ImDrawCmd* cmd) const + { + const auto rectangle = getClipRectangle(cmd); + + return prepass(rectangle); + } - if (static_cast(vertexBuffer) == false || vertexBuffer->getSize() < vertexSize) + bool prepass(const ImVec4& clipRectangle) const + { + return clipRectangle.x < framebuffer.x && clipRectangle.y < framebuffer.y && clipRectangle.z >= 0.0f && clipRectangle.w >= 0.0f; + } + + } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; + + struct { - vertexBuffer = m_device->createBuffer(std::move(vertexCreationParams)); + // sizes in bytes + size_t vBufferSize = {}, iBufferSize = {}, + vPadding = {}; // indicies can break our alignment so small padding may be required before vertices in memory - video::IDeviceMemoryBacked::SDeviceMemoryRequirements memReq = vertexBuffer->getMemoryReqs(); - memReq.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto memOffset = m_device->allocate(memReq, vertexBuffer.get()); - assert(memOffset.isValid()); - } + std::vector indirects; + std::vector elements; + + // buffer layout is [Draw Indirect structures] [Element Data] [All the Indices] [All the vertices] + size_t getMDIBufferSize() const + { + return getVerticesOffset() + vBufferSize; + } + + size_t getIndicesOffset() const + { + return getElementsOffset() + elements.size() * sizeof(PerObjectData); + } - IGPUBuffer::SCreationParams indexCreationParams = {}; - indexCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT - | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - indexCreationParams.size = indexSize; + size_t getVerticesOffset() const + { + return getIndicesOffset() + iBufferSize + vPadding; + } - auto & indexBuffer = m_indexBuffers[frameIndex]; + size_t getVkDrawIndexedIndirectCommandOffset() const + { + return 0u; + } - if (static_cast(indexBuffer) == false || indexBuffer->getSize() < indexSize) + size_t getElementsOffset() const + { + return elements.size() * sizeof(VkDrawIndexedIndirectCommand); + } + + bool validate() const + { + return indirects.size() == elements.size(); + } + + } requestData; + + { + /* + IMGUI Render command lists. + Wwe merged all buffers into a single one so we maintain our own offset into them, + we pre-loop to get request data for MDI buffer alocation request/update. + */ + + size_t global_idx_offset = {}, global_vtx_offset = {}, drawID = {}; + + for (int n = 0; n < drawData->CmdListsCount; n++) + { + const auto* cmd_list = drawData->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + const auto clipRectangle = clip.getClipRectangle(pcmd); + + // count draw invocations and allocate cpu indirect + element data for mdi buffer + if (clip.prepass(clipRectangle)) + { + // TODO: I guess we waste a lot of time on this emplace, we should reserve memory first or use some nice allocator probably? + auto& indirect = requestData.indirects.emplace_back(); + auto& element = requestData.elements.emplace_back(); + + indirect.firstIndex = pcmd->IdxOffset + global_idx_offset; + indirect.firstInstance = drawID; // use base instance as draw ID + indirect.indexCount = pcmd->ElemCount; + indirect.instanceCount = 1u; + indirect.vertexOffset = pcmd->VtxOffset + global_vtx_offset; + + element.scissor = clip.getScissor(clipRectangle); + // element.texId = 0; // use defaults from constructor + + ++drawID; + } + } + + global_idx_offset += cmd_list->IdxBuffer.Size; + global_vtx_offset += cmd_list->VtxBuffer.Size; + } + + requestData.iBufferSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); + requestData.vBufferSize = drawData->TotalVtxCount * sizeof(ImDrawVert); + requestData.vPadding = requestData.getVerticesOffset() % 4u; + assert(requestData.validate()); + } + + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; + mdiCreationParams.size = requestData.getMDIBufferSize(); + + auto mdiBuffer = m_mdiBuffers[frameIndex]; + const auto drawCount = requestData.elements.size(); + const auto mdicBSize = drawCount * sizeof(VkDrawIndexedIndirectCommand); + const auto elementsBSize = drawCount * sizeof(PerObjectData); + + // create or resize the mdi buffer + if (!static_cast(mdiBuffer) || mdiBuffer->getSize() < mdiCreationParams.size) { - indexBuffer = m_device->createBuffer(std::move(indexCreationParams)); + mdiBuffer = m_device->createBuffer(std::move(mdiCreationParams)); - video::IDeviceMemoryBacked::SDeviceMemoryRequirements memReq = indexBuffer->getMemoryReqs(); - memReq.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto memOffset = m_device->allocate(memReq, indexBuffer.get()); + auto mReqs = mdiBuffer->getMemoryReqs(); + mReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto memOffset = m_device->allocate(mReqs, mdiBuffer.get()); assert(memOffset.isValid()); + + SBufferRange mdie = { .offset = requestData.getElementsOffset(), .size = elementsBSize, .buffer = mdiBuffer }; + + UpdateDescriptorSets(mdie); } + // update mdi buffer { - auto vBinding = vertexBuffer->getBoundMemory(); - auto iBinding = indexBuffer->getBoundMemory(); + auto binding = mdiBuffer->getBoundMemory(); { - if (!vBinding.memory->map({ 0ull, vBinding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory for vertex buffer data!", system::ILogger::ELL_WARNING); + if (!binding.memory->map({ 0ull, binding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) + logger->log("Could not map device memory!", system::ILogger::ELL_WARNING); - assert(vBinding.memory->isCurrentlyMapped()); + assert(binding.memory->isCurrentlyMapped()); } + auto* mdiPointer = binding.memory->getMappedPointer(); + + auto getMDIPointer = [&mdiPointer](auto bOffset) { - if (!iBinding.memory->map({ 0ull, iBinding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory for index buffer data!", system::ILogger::ELL_WARNING); - - assert(iBinding.memory->isCurrentlyMapped()); - } + return reinterpret_cast(reinterpret_cast(mdiPointer) + bOffset); + }; + + auto* const indirectPointer = getMDIPointer.template operator() < VkDrawIndexedIndirectCommand > (requestData.getVkDrawIndexedIndirectCommandOffset()); + auto* const elementPointer = getMDIPointer.template operator() < PerObjectData > (requestData.getElementsOffset()); + auto* indicesPointer = getMDIPointer.template operator() < ImDrawIdx > (requestData.getIndicesOffset()); + auto* verticesPointer = getMDIPointer.template operator() < ImDrawVert > (requestData.getVerticesOffset()); - auto* vertex_ptr = static_cast(vBinding.memory->getMappedPointer()); - auto* index_ptr = static_cast(iBinding.memory->getMappedPointer()); + // fill indirect/element data + ::memcpy(indirectPointer, requestData.indirects.data(), mdicBSize); + ::memcpy(elementPointer, requestData.elements.data(), elementsBSize); + // fill vertex/index data for (int n = 0; n < drawData->CmdListsCount; n++) { - const ImDrawList* cmd = drawData->CmdLists[n]; - ::memcpy(vertex_ptr, cmd->VtxBuffer.Data, cmd->VtxBuffer.Size * sizeof(ImDrawVert)); - ::memcpy(index_ptr, cmd->IdxBuffer.Data, cmd->IdxBuffer.Size * sizeof(ImDrawIdx)); - vertex_ptr += cmd->VtxBuffer.Size; - index_ptr += cmd->IdxBuffer.Size; + const auto* cmd = drawData->CmdLists[n]; + + ::memcpy(verticesPointer, cmd->VtxBuffer.Data, cmd->VtxBuffer.Size * sizeof(ImDrawVert)); + ::memcpy(indicesPointer, cmd->IdxBuffer.Data, cmd->IdxBuffer.Size * sizeof(ImDrawIdx)); + + verticesPointer += cmd->VtxBuffer.Size; + indicesPointer += cmd->IdxBuffer.Size; } - vBinding.memory->unmap(); - iBinding.memory->unmap(); + binding.memory->unmap(); } { const asset::SBufferBinding binding = { - .offset = 0, - .buffer = core::smart_refctd_ptr(indexBuffer) + .offset = requestData.getIndicesOffset(), + .buffer = core::smart_refctd_ptr(mdiBuffer) }; if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) @@ -659,8 +823,8 @@ namespace nbl::ext::imgui const asset::SBufferBinding bindings[] = { { - .offset = 0, - .buffer = core::smart_refctd_ptr(vertexBuffer) + .offset = requestData.getVerticesOffset(), + .buffer = core::smart_refctd_ptr(mdiBuffer) } }; @@ -669,7 +833,6 @@ namespace nbl::ext::imgui logger->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); assert(false); } - } SViewport const viewport @@ -681,6 +844,7 @@ namespace nbl::ext::imgui .minDepth = 0.0f, .maxDepth = 1.0f, }; + commandBuffer->setViewport(0, 1, &viewport); // Setup scale and translation: @@ -691,62 +855,21 @@ namespace nbl::ext::imgui constants.scale[1] = 2.0f / drawData->DisplaySize.y; constants.translate[0] = -1.0f - drawData->DisplayPos.x * constants.scale[0]; constants.translate[1] = -1.0f - drawData->DisplayPos.y * constants.scale[1]; + constants.viewport[0] = viewport.x; + constants.viewport[1] = viewport.x; + constants.viewport[2] = viewport.width; + constants.viewport[3] = viewport.height; commandBuffer->pushConstants(pipeline->getLayout(), IShader::ESS_VERTEX, 0, sizeof(constants), &constants); } - // Will project scissor/clipping rectangles into frame-buffer space - ImVec2 const clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 const clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - int global_vtx_offset = 0; - int global_idx_offset = 0; - for (int n = 0; n < drawData->CmdListsCount; n++) + const asset::SBufferBinding binding = { - const ImDrawList* cmd_list = drawData->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - - // Project scissor/clipping rectangles into frame-buffer space - ImVec4 clip_rect; - clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; - clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; - clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; - clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; - - if (clip_rect.x < frameBufferWidth && clip_rect.y < frameBufferHeight && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) - { - // Negative offsets are illegal for vkCmdSetScissor - if (clip_rect.x < 0.0f) - clip_rect.x = 0.0f; - if (clip_rect.y < 0.0f) - clip_rect.y = 0.0f; - - {// Apply scissor/clipping rectangle - VkRect2D scissor{}; - scissor.offset.x = static_cast(clip_rect.x); - scissor.offset.y = static_cast(clip_rect.y); - scissor.extent.width = static_cast(clip_rect.z - clip_rect.x); - scissor.extent.height = static_cast(clip_rect.w - clip_rect.y); - commandBuffer->setScissor(0, 1, &scissor); - } + .offset = requestData.getVkDrawIndexedIndirectCommandOffset(), + .buffer = core::smart_refctd_ptr(mdiBuffer) + }; - // Draw - commandBuffer->drawIndexed( - pcmd->ElemCount, - 1, - pcmd->IdxOffset + global_idx_offset, - pcmd->VtxOffset + global_vtx_offset, - 0 - ); - } - } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; - } + commandBuffer->drawIndexedIndirect(binding, drawCount, sizeof(VkDrawIndexedIndirectCommand)); } return true; diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 320080a5b4..50788f7129 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -17,11 +17,29 @@ { float2 scale; float2 translate; + float4 viewport; + uint drawsCount; + + uint padding[3]; + }; + + struct PerObjectData + { + float4 scissor; + uint texId; }; #else struct PushConstants { float scale[2]; float translate[2]; + float viewport[4]; + size_t drawsCount; + }; + + struct PerObjectData + { + VkRect2D scissor; + uint32_t texId = 0; }; #endif // __HLSL_VERSION diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 19fe0da632..d089b6e14e 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -1,9 +1,21 @@ #include "common.hlsl" +[[vk::push_constant]] struct PushConstants pc; + +[[vk::binding(0, 1)]] StructuredBuffer perObject : register(t0); [[vk::combinedImageSampler]][[vk::binding(0, 0)]] Texture2D sampleTexture : register(t0); [[vk::combinedImageSampler]][[vk::binding(0, 0)]] SamplerState linearSampler : register(s0); -float4 PSMain(PSInput input) : SV_Target0 +float4 PSMain(PSInput input, uint drawID : SV_InstanceID) : SV_Target0 { + PerObjectData objectData = perObject[drawID]; + + // convert NDC coordinates to window coordinates + float2 windowPos = (input.position.xy * 0.5 + 0.5) * pc.viewport.zw + pc.viewport.xy; + + // scissor pass + if (windowPos.x < objectData.scissor.x || windowPos.x > (objectData.scissor.x + objectData.scissor.z) || windowPos.y < objectData.scissor.y || windowPos.y > (objectData.scissor.y + objectData.scissor.w)) + discard; + return input.color * sampleTexture.Sample(linearSampler, input.uv); } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index f3e7895d3f..d2e5f01423 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -2,7 +2,7 @@ [[vk::push_constant]] struct PushConstants pc; -PSInput VSMain(VSInput input) +PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) { PSInput output; output.color = input.color; From fd11a0e2fc9868f6f835a1c8e4f0a7a10816eadc Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 7 Jul 2024 08:32:33 +0200 Subject: [PATCH 002/148] update IMGUI to LTS version 1.90.9 --- 3rdparty/imgui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/imgui b/3rdparty/imgui index e489e40a85..cb16be3a3f 160000 --- a/3rdparty/imgui +++ b/3rdparty/imgui @@ -1 +1 @@ -Subproject commit e489e40a853426767de9ce0637bc0c9ceb431c1e +Subproject commit cb16be3a3fc1f9cd146ae24d52b615f8a05fa93d From 85d56810a13fcb27ef50d8ca902f8783e8bb3af6 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 7 Jul 2024 09:06:11 +0200 Subject: [PATCH 003/148] clean a bit Nabla IMGUI extension backend, remove a lot of useless wrappers, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 54 ++------ src/nbl/ext/ImGui/ImGui.cpp | 249 +++++++--------------------------- 3 files changed, 65 insertions(+), 240 deletions(-) diff --git a/examples_tests b/examples_tests index daea3f951b..da9f0dd6ab 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit daea3f951b59a4b53cec81c44786f5934994176e +Subproject commit da9f0dd6ab0d0df964af9e5cf0080558c74dba4e diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index a9ba811fb0..9cbf572589 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -9,63 +9,37 @@ class UI final : public core::IReferenceCounted UI(core::smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); ~UI() override; - bool Render(nbl::video::IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex); - void Update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events - void BeginWindow(char const* windowName); - void EndWindow(); - int Register(std::function const& listener); - bool UnRegister(int listenerId); - void SetNextItemWidth(float nextItemWidth); - void SetWindowSize(float width, float height); - void Text(char const* label, ...); - void InputFloat(char const* label, float* value); - void InputFloat2(char const* label, float* value); - void InputFloat3(char const* label, float* value); - void InputFloat4(char const* label, float* value); - void InputFloat3(char const* label, nbl::core::vector3df& value); - bool Combo(char const* label, int32_t* selectedItemIndex, char const** items, int32_t itemsCount); - bool Combo(const char* label, int* selectedItemIndex, std::vector& values); - void SliderInt(char const* label, int* value, int minValue, int maxValue); - void SliderFloat(char const* label, float* value, float minValue, float maxValue); - void Checkbox(char const* label, bool* value); - void Spacing(); - void Button(char const* label, std::function const& onPress); - void InputText(char const* label, std::string& outValue); - [[nodiscard]] bool HasFocus(); - [[nodiscard]] bool IsItemActive(); - [[nodiscard]] bool TreeNode(char const* name); - void TreePop(); + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex); + void update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events + int registerListener(std::function const& listener); + bool unregisterListener(int listenerId); private: - - core::smart_refctd_ptr CreateDescriptorSetLayout(); - - void CreatePipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); - void CreateFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void UpdateDescriptorSets(asset::SBufferRange mdie = {}); + core::smart_refctd_ptr createDescriptorSetLayout(); + void createPipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); + void createFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); + void updateDescriptorSets(asset::SBufferRange mdie = {}); void createSystem(); - void CreateFontSampler(); - void CreateDescriptorPool(); - void HandleMouseEvents(float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents) const; + void createFontSampler(); + void createDescriptorPool(); + void handleMouseEvents(float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents) const; core::smart_refctd_ptr system; core::smart_refctd_ptr logger; core::smart_refctd_ptr utilities; - core::smart_refctd_ptr m_device; core::smart_refctd_ptr m_fontSampler; core::smart_refctd_ptr m_descriptorPool; core::smart_refctd_ptr m_gpuDescriptorSet; - core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontTexture; core::smart_refctd_ptr m_window; std::vector> m_mdiBuffers; - bool hasFocus = false; - uint32_t maxFramesInFlight; + const uint32_t maxFramesInFlight; // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; - struct Subscriber { + struct Subscriber + { int id = -1; std::function listener = nullptr; }; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 40bbd74a43..ef3eb249df 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -4,14 +4,13 @@ #include #include -// internal & nabla #include "nbl/system/IApplicationFramework.h" #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" #include "ext/imgui/spirv/builtin/builtinResources.h" #include "ext/imgui/spirv/builtin/CArchive.h" -// 3rdparty + #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" @@ -22,9 +21,10 @@ using namespace nbl::ui; namespace nbl::ext::imgui { - smart_refctd_ptr UI::CreateDescriptorSetLayout() + smart_refctd_ptr UI::createDescriptorSetLayout() { - nbl::video::IGPUDescriptorSetLayout::SBinding bindings[] = { + nbl::video::IGPUDescriptorSetLayout::SBinding bindings[] = + { { .binding = 0u, .type = asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, @@ -45,10 +45,11 @@ namespace nbl::ext::imgui return m_device->createDescriptorSetLayout(bindings); } - void UI::CreatePipeline(video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) + void UI::createPipeline(video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix - SPushConstantRange pushConstantRanges[] = { + SPushConstantRange pushConstantRanges[] = + { { .stageFlags = IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, .offset = 0, @@ -56,7 +57,7 @@ namespace nbl::ext::imgui } }; - auto descriptorSetLayout = CreateDescriptorSetLayout(); + auto descriptorSetLayout = createDescriptorSetLayout(); m_gpuDescriptorSet = m_descriptorPool->createDescriptorSet(descriptorSetLayout); assert(m_gpuDescriptorSet); @@ -162,7 +163,7 @@ namespace nbl::ext::imgui } } - void UI::CreateFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) + void UI::createFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -255,7 +256,7 @@ namespace nbl::ext::imgui sInfo.queue = transfer; sInfo.waitSemaphores = {}; sInfo.commandBuffers = { &cmdInfo, 1 }; - sInfo.scratchSemaphore = // TODO: do I really need it? + sInfo.scratchSemaphore = { .semaphore = scratchSemaphore.get(), .value = 0, @@ -329,7 +330,7 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::UpdateDescriptorSets(asset::SBufferRange mdie) + void UI::updateDescriptorSets(asset::SBufferRange mdie) { _NBL_STATIC_INLINE_CONSTEXPR auto NBL_RESOURCES_AMOUNT = 2u; @@ -358,10 +359,10 @@ namespace nbl::ext::imgui w.info = info + i; } - m_device->updateDescriptorSets(mdie.buffer ? NBL_RESOURCES_AMOUNT : 1u, write, 0, nullptr); + m_device->updateDescriptorSets(mdie.buffer ? NBL_RESOURCES_AMOUNT : 1u, write, 0u, nullptr); } - void UI::CreateFontSampler() + void UI::createFontSampler() { // TODO: Recheck this settings IGPUSampler::SParams params{}; @@ -375,7 +376,7 @@ namespace nbl::ext::imgui m_fontSampler = m_device->createSampler(params); } - void UI::CreateDescriptorPool() + void UI::createDescriptorPool() { IDescriptorPool::SCreateInfo createInfo = {}; createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)] = 1u; @@ -387,12 +388,7 @@ namespace nbl::ext::imgui assert(m_descriptorPool); } - void UI::HandleMouseEvents( - float const mousePosX, - float const mousePosY, - size_t const mouseEventsCount, - SMouseEvent const * mouseEvents - ) const + void UI::handleMouseEvents(float const mousePosX, float const mousePosY, size_t const mouseEventsCount, SMouseEvent const * mouseEvents) const { auto& io = ImGui::GetIO(); @@ -405,16 +401,12 @@ namespace nbl::ext::imgui if(event.type == SMouseEvent::EET_CLICK) { int buttonIndex = -1; - if (event.clickEvent.mouseButton == EMB_LEFT_BUTTON) - { + if (event.clickEvent.mouseButton == EMB_LEFT_BUTTON) buttonIndex = 0; - } else if (event.clickEvent.mouseButton == EMB_RIGHT_BUTTON) - { + else if (event.clickEvent.mouseButton == EMB_RIGHT_BUTTON) buttonIndex = 1; - } else if (event.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) - { + else if (event.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) buttonIndex = 2; - } if (buttonIndex == -1) { @@ -422,11 +414,10 @@ namespace nbl::ext::imgui continue; } - if(event.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) { + if(event.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) io.MouseDown[buttonIndex] = true; - } else if (event.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) { + else if (event.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) io.MouseDown[buttonIndex] = false; - } } } } @@ -502,14 +493,14 @@ namespace nbl::ext::imgui IMGUI_CHECKVERSION(); ImGui::CreateContext(); - CreateFontSampler(); - CreateDescriptorPool(); - CreateDescriptorSetLayout(); - CreatePipeline(renderpass, pipelineCache); - CreateFontTexture(transistentCMD.get(), tQueue); + createFontSampler(); + createDescriptorPool(); + createDescriptorSetLayout(); + createPipeline(renderpass, pipelineCache); + createFontTexture(transistentCMD.get(), tQueue); prepareKeyMapForDesktop(); adjustGlobalFontScale(); - UpdateDescriptorSets(); + updateDescriptorSets(); } tQueue->endCapture(); @@ -564,7 +555,7 @@ namespace nbl::ext::imgui //} //} - bool UI::Render(IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex) + bool UI::render(IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex) { const bool validFramesRange = frameIndex >= 0 && frameIndex < maxFramesInFlight; if (!validFramesRange) @@ -650,15 +641,13 @@ namespace nbl::ext::imgui struct { - // sizes in bytes - size_t vBufferSize = {}, iBufferSize = {}, - vPadding = {}; // indicies can break our alignment so small padding may be required before vertices in memory + size_t vBufferSize = {}, iBufferSize = {}, // sizes in bytes + vPadding = {}; // indicies can break our alignment so small padding may be required before vertices in memory, vertices' offset must be multiple of 4 std::vector indirects; std::vector elements; - // buffer layout is [Draw Indirect structures] [Element Data] [All the Indices] [All the vertices] - size_t getMDIBufferSize() const + size_t getMDIBufferSize() const // buffer layout is [Draw Indirect structures] [Element Data] [All the Indices] [All the vertices] { return getVerticesOffset() + vBufferSize; } @@ -692,12 +681,12 @@ namespace nbl::ext::imgui { /* - IMGUI Render command lists. - Wwe merged all buffers into a single one so we maintain our own offset into them, - we pre-loop to get request data for MDI buffer alocation request/update. + IMGUI Render command lists. We merged all buffers into a single one so we + maintain our own offset into them, we pre-loop to get request data for + MDI buffer alocation request/update. */ - size_t global_idx_offset = {}, global_vtx_offset = {}, drawID = {}; + size_t globalIOffset = {}, globalVOffset = {}, drawID = {}; for (int n = 0; n < drawData->CmdListsCount; n++) { @@ -715,11 +704,11 @@ namespace nbl::ext::imgui auto& indirect = requestData.indirects.emplace_back(); auto& element = requestData.elements.emplace_back(); - indirect.firstIndex = pcmd->IdxOffset + global_idx_offset; + indirect.firstIndex = pcmd->IdxOffset + globalIOffset; indirect.firstInstance = drawID; // use base instance as draw ID indirect.indexCount = pcmd->ElemCount; indirect.instanceCount = 1u; - indirect.vertexOffset = pcmd->VtxOffset + global_vtx_offset; + indirect.vertexOffset = pcmd->VtxOffset + globalVOffset; element.scissor = clip.getScissor(clipRectangle); // element.texId = 0; // use defaults from constructor @@ -728,8 +717,8 @@ namespace nbl::ext::imgui } } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; + globalIOffset += cmd_list->IdxBuffer.Size; + globalVOffset += cmd_list->VtxBuffer.Size; } requestData.iBufferSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); @@ -760,7 +749,7 @@ namespace nbl::ext::imgui SBufferRange mdie = { .offset = requestData.getElementsOffset(), .size = elementsBSize, .buffer = mdiBuffer }; - UpdateDescriptorSets(mdie); + updateDescriptorSets(mdie); } // update mdi buffer @@ -847,8 +836,11 @@ namespace nbl::ext::imgui commandBuffer->setViewport(0, 1, &viewport); - // Setup scale and translation: - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + /* + Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to + draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + */ + { PushConstants constants{}; constants.scale[0] = 2.0f / drawData->DisplaySize.x; @@ -876,36 +868,23 @@ namespace nbl::ext::imgui } - void UI::Update(float const deltaTimeInSec, float const mousePosX, float const mousePosY, size_t const mouseEventsCount, ui::SMouseEvent const * mouseEvents) // TODO: Keyboard events + void UI::update(float const deltaTimeInSec, float const mousePosX, float const mousePosY, size_t const mouseEventsCount, ui::SMouseEvent const * mouseEvents) // TODO: Keyboard events { auto & io = ImGui::GetIO(); io.DeltaTime = deltaTimeInSec; io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); - HandleMouseEvents(mousePosX, mousePosY, mouseEventsCount, mouseEvents); + handleMouseEvents(mousePosX, mousePosY, mouseEventsCount, mouseEvents); ImGui::NewFrame(); - hasFocus = false; + for (auto const& subscriber : m_subscribers) subscriber.listener(); - ImGui::Render(); // note it doesn't touch GPU or graphics API at all, internal call - open the function def for more details - } - - void UI::BeginWindow(char const* windowName) - { - ImGui::Begin(windowName); - } - - void UI::EndWindow() - { - if (ImGui::IsWindowFocused()) - hasFocus = true; - - ImGui::End(); + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, internal call for IMGUI cpu geometry buffers update } - int UI::Register(std::function const& listener) + int UI::registerListener(std::function const& listener) { assert(listener != nullptr); static int NextId = 0; @@ -913,7 +892,7 @@ namespace nbl::ext::imgui return m_subscribers.back().id; } - bool UI::UnRegister(int const listenerId) + bool UI::unregisterListener(int const listenerId) { for (int i = m_subscribers.size() - 1; i >= 0; --i) { @@ -925,132 +904,4 @@ namespace nbl::ext::imgui } return false; } - - void UI::SetNextItemWidth(float const nextItemWidth) - { - ImGui::SetNextItemWidth(nextItemWidth); - } - - void UI::SetWindowSize(float const width, float const height) - { - ImGui::SetWindowSize(ImVec2(width, height)); - } - - void UI::Text(char const* label, ...) - { - va_list args; - va_start(args, label); - ImGui::TextV(label, args); - va_end(args); - } - - void UI::InputFloat(char const* label, float* value) - { - ImGui::InputFloat(label, value); - } - - void UI::InputFloat2(char const* label, float* value) - { - ImGui::InputFloat2(label, value); - } - - void UI::InputFloat3(char const* label, float* value) - { - ImGui::InputFloat3(label, value); - } - - void UI::InputFloat4(char const* label, float* value) - { - ImGui::InputFloat3(label, value); - } - - void UI::InputFloat3(char const* label, nbl::core::vector3df&value) - { - float tempValue[3]{ value.X, value.Y, value.Z }; - InputFloat3(label, tempValue); - - if (memcmp(tempValue, &value.X, sizeof(float) * 3) != 0) - { - memcpy(&value.X, tempValue, sizeof(float) * 3); - } - } - - bool UI::Combo(char const* label, int32_t* selectedItemIndex, char const** items, int32_t const itemsCount) - { - return ImGui::Combo(label, selectedItemIndex, items, itemsCount); - } - - //------------------------------------------------------------------------------------------------- - // Based on https://eliasdaler.github.io/using-imgui-with-sfml-pt2/ - static auto vector_getter = [](void* vec, int idx, const char** out_text) - { - auto const& vector = *static_cast*>(vec); - if (idx < 0 || idx >= static_cast(vector.size())) { return false; } - *out_text = vector.at(idx).c_str(); - return true; - }; - - bool UI::Combo(const char* label, int* selectedItemIndex, std::vector& values) - { - if (values.empty()) - return false; - - return ImGui::Combo(label, selectedItemIndex, vector_getter, &values, static_cast(values.size())); - } - - void UI::SliderInt(char const* label, int* value, int const minValue, int const maxValue) - { - ImGui::SliderInt(label, value, minValue, maxValue); - } - - void UI::SliderFloat(char const* label, float* value, float const minValue, float const maxValue) - { - ImGui::SliderFloat(label, value, minValue, maxValue); - } - - void UI::Checkbox(char const* label, bool* value) - { - ImGui::Checkbox(label, value); - } - - void UI::Spacing() - { - ImGui::Spacing(); - } - - void UI::Button(char const* label, std::function const& onPress) - { - if (ImGui::Button(label)) - { - assert(onPress != nullptr); - //SceneManager::AssignMainThreadTask([onPress]()->void{ - onPress(); - //}); - } - } - - void UI::InputText(char const* label, std::string& outValue) - { - ImGui::InputText(label, &outValue); - } - - bool UI::HasFocus() - { - return hasFocus; - } - - bool UI::IsItemActive() - { - return ImGui::IsItemActive(); - } - - bool UI::TreeNode(char const* name) - { - return ImGui::TreeNode(name); - } - - void UI::TreePop() - { - ImGui::TreePop(); - } } \ No newline at end of file From c921b9f8f47ffb197d8969fa720d85acdf527665 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 7 Jul 2024 18:44:01 +0200 Subject: [PATCH 004/148] Remove SSBO & use Buffer Device Address instead. Clean code, move clipping into vertex shader - TODO: fill clipping plane distances --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 84 ++++++++++++------------- src/nbl/ext/ImGui/shaders/common.hlsl | 9 +-- src/nbl/ext/ImGui/shaders/fragment.hlsl | 10 --- src/nbl/ext/ImGui/shaders/vertex.hlsl | 7 +++ 6 files changed, 56 insertions(+), 58 deletions(-) diff --git a/examples_tests b/examples_tests index da9f0dd6ab..74ff08fc88 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit da9f0dd6ab0d0df964af9e5cf0080558c74dba4e +Subproject commit 74ff08fc88bac706e962af4d563b3b51f19cc761 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 9cbf572589..8110f34bb4 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -18,7 +18,7 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr createDescriptorSetLayout(); void createPipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); void createFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void updateDescriptorSets(asset::SBufferRange mdie = {}); + void updateDescriptorSets(); void createSystem(); void createFontSampler(); void createDescriptorPool(); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index ef3eb249df..e6ee874a84 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -32,13 +32,6 @@ namespace nbl::ext::imgui .stageFlags = IShader::ESS_FRAGMENT, .count = 1, .samplers = nullptr // TODO: m_fontSampler? - }, - { - .binding = 1u, - .type = asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER, - .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, - .stageFlags = asset::IShader::ESS_VERTEX | asset::IShader::ESS_FRAGMENT, - .count = 1u } }; @@ -263,7 +256,8 @@ namespace nbl::ext::imgui .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS }; - const SMemoryBarrier toTransferBarrier = { + const SMemoryBarrier toTransferBarrier = + { .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT }; @@ -330,9 +324,9 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::updateDescriptorSets(asset::SBufferRange mdie) + void UI::updateDescriptorSets() { - _NBL_STATIC_INLINE_CONSTEXPR auto NBL_RESOURCES_AMOUNT = 2u; + _NBL_STATIC_INLINE_CONSTEXPR auto NBL_RESOURCES_AMOUNT = 1u; IGPUDescriptorSet::SDescriptorInfo info[NBL_RESOURCES_AMOUNT]; { @@ -340,11 +334,6 @@ namespace nbl::ext::imgui font.desc = m_fontTexture; font.info.image.sampler = m_fontSampler; font.info.image.imageLayout = nbl::asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; - - auto& ssbo = info[1u]; - ssbo.desc = mdie.buffer; - ssbo.info.buffer.size = mdie.size; - ssbo.info.buffer.offset = mdie.offset; } IGPUDescriptorSet::SWriteDescriptorSet write[NBL_RESOURCES_AMOUNT]; @@ -359,7 +348,7 @@ namespace nbl::ext::imgui w.info = info + i; } - m_device->updateDescriptorSets(mdie.buffer ? NBL_RESOURCES_AMOUNT : 1u, write, 0u, nullptr); + m_device->updateDescriptorSets(NBL_RESOURCES_AMOUNT, write, 0u, nullptr); } void UI::createFontSampler() @@ -455,6 +444,7 @@ namespace nbl::ext::imgui logger->log(onError.data(), system::ILogger::ELL_ERROR); assert(false); + return uint8_t(0); // silent warnings }; // get & validate families' capabilities @@ -571,10 +561,6 @@ namespace nbl::ext::imgui logger->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); assert(false); } - - auto* rawPipeline = pipeline.get(); - commandBuffer->bindGraphicsPipeline(rawPipeline); - commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &m_gpuDescriptorSet.get()); auto const* drawData = ImGui::GetDrawData(); @@ -728,13 +714,14 @@ namespace nbl::ext::imgui } IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; + mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; mdiCreationParams.size = requestData.getMDIBufferSize(); auto mdiBuffer = m_mdiBuffers[frameIndex]; - const auto drawCount = requestData.elements.size(); + const uint32_t drawCount = requestData.elements.size(); const auto mdicBSize = drawCount * sizeof(VkDrawIndexedIndirectCommand); const auto elementsBSize = drawCount * sizeof(PerObjectData); + const uint64_t elementsBOffset = requestData.getElementsOffset(); // create or resize the mdi buffer if (!static_cast(mdiBuffer) || mdiBuffer->getSize() < mdiCreationParams.size) @@ -743,15 +730,15 @@ namespace nbl::ext::imgui auto mReqs = mdiBuffer->getMemoryReqs(); mReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - - auto memOffset = m_device->allocate(mReqs, mdiBuffer.get()); + + auto memOffset = m_device->allocate(mReqs, mdiBuffer.get(), core::bitflag(IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS::EMAF_DEVICE_ADDRESS_BIT)); assert(memOffset.isValid()); - - SBufferRange mdie = { .offset = requestData.getElementsOffset(), .size = elementsBSize, .buffer = mdiBuffer }; - - updateDescriptorSets(mdie); } + auto* rawPipeline = pipeline.get(); + commandBuffer->bindGraphicsPipeline(rawPipeline); + commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &m_gpuDescriptorSet.get()); + // update mdi buffer { auto binding = mdiBuffer->getBoundMemory(); @@ -771,7 +758,7 @@ namespace nbl::ext::imgui }; auto* const indirectPointer = getMDIPointer.template operator() < VkDrawIndexedIndirectCommand > (requestData.getVkDrawIndexedIndirectCommandOffset()); - auto* const elementPointer = getMDIPointer.template operator() < PerObjectData > (requestData.getElementsOffset()); + auto* const elementPointer = getMDIPointer.template operator() < PerObjectData > (elementsBOffset); auto* indicesPointer = getMDIPointer.template operator() < ImDrawIdx > (requestData.getIndicesOffset()); auto* verticesPointer = getMDIPointer.template operator() < ImDrawVert > (requestData.getVerticesOffset()); @@ -779,7 +766,18 @@ namespace nbl::ext::imgui ::memcpy(indirectPointer, requestData.indirects.data(), mdicBSize); ::memcpy(elementPointer, requestData.elements.data(), elementsBSize); - // fill vertex/index data + // flush elements data range if required + { + auto eData = mdiBuffer->getBoundMemory(); + + //if (eData.memory->haveToMakeVisible()) + { + const ILogicalDevice::MappedMemoryRange range(eData.memory, eData.offset, mdiBuffer->getSize()); + m_device->flushMappedMemoryRanges(1, &range); + } + } + + // fill vertex/index data, no flush request for (int n = 0; n < drawData->CmdListsCount; n++) { const auto* cmd = drawData->CmdLists[n]; @@ -835,6 +833,10 @@ namespace nbl::ext::imgui }; commandBuffer->setViewport(0, 1, &viewport); + { + VkRect2D scissor[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; + commandBuffer->setScissor(scissor); // cover whole viewport (only to not throw validation errors) + } /* Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to @@ -842,17 +844,16 @@ namespace nbl::ext::imgui */ { - PushConstants constants{}; - constants.scale[0] = 2.0f / drawData->DisplaySize.x; - constants.scale[1] = 2.0f / drawData->DisplaySize.y; - constants.translate[0] = -1.0f - drawData->DisplayPos.x * constants.scale[0]; - constants.translate[1] = -1.0f - drawData->DisplayPos.y * constants.scale[1]; - constants.viewport[0] = viewport.x; - constants.viewport[1] = viewport.x; - constants.viewport[2] = viewport.width; - constants.viewport[3] = viewport.height; - - commandBuffer->pushConstants(pipeline->getLayout(), IShader::ESS_VERTEX, 0, sizeof(constants), &constants); + PushConstants constants + { + .elementBDA = { mdiBuffer->getDeviceAddress() + elementsBOffset }, + .elementCount = { drawCount }, + .scale = { 2.0f / drawData->DisplaySize.x, 2.0f / drawData->DisplaySize.y }, + .translate = { -1.0f - drawData->DisplayPos.x * constants.scale[0u], -1.0f - drawData->DisplayPos.y * constants.scale[1u] }, + .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } + }; + + commandBuffer->pushConstants(pipeline->getLayout(), IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, 0u, sizeof(constants), &constants); } const asset::SBufferBinding binding = @@ -865,7 +866,6 @@ namespace nbl::ext::imgui } return true; - } void UI::update(float const deltaTimeInSec, float const mousePosX, float const mousePosY, size_t const mouseEventsCount, ui::SMouseEvent const * mouseEvents) // TODO: Keyboard events diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 50788f7129..3828d186b4 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -11,16 +11,16 @@ float4 position : SV_Position; float4 color : COLOR0; float2 uv : TEXCOORD0; + float clip[2] : SV_ClipDistance; }; struct PushConstants { + uint64_t elementBDA; + uint64_t elementCount; float2 scale; float2 translate; float4 viewport; - uint drawsCount; - - uint padding[3]; }; struct PerObjectData @@ -31,10 +31,11 @@ #else struct PushConstants { + uint64_t elementBDA; + uint64_t elementCount; float scale[2]; float translate[2]; float viewport[4]; - size_t drawsCount; }; struct PerObjectData diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index d089b6e14e..63ade940ba 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -2,20 +2,10 @@ [[vk::push_constant]] struct PushConstants pc; -[[vk::binding(0, 1)]] StructuredBuffer perObject : register(t0); [[vk::combinedImageSampler]][[vk::binding(0, 0)]] Texture2D sampleTexture : register(t0); [[vk::combinedImageSampler]][[vk::binding(0, 0)]] SamplerState linearSampler : register(s0); float4 PSMain(PSInput input, uint drawID : SV_InstanceID) : SV_Target0 { - PerObjectData objectData = perObject[drawID]; - - // convert NDC coordinates to window coordinates - float2 windowPos = (input.position.xy * 0.5 + 0.5) * pc.viewport.zw + pc.viewport.xy; - - // scissor pass - if (windowPos.x < objectData.scissor.x || windowPos.x > (objectData.scissor.x + objectData.scissor.z) || windowPos.y < objectData.scissor.y || windowPos.y > (objectData.scissor.y + objectData.scissor.w)) - discard; - return input.color * sampleTexture.Sample(linearSampler, input.uv); } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index d2e5f01423..7b6684c6c0 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -8,6 +8,13 @@ PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) output.color = input.color; output.uv = input.uv; output.position = float4(input.position * pc.scale + pc.translate, 0, 1); + + // BDA for requesting object data + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); + + // TODO + output.clip[0] = 69; + output.clip[1] = 69; return output; } \ No newline at end of file From b995627fa7d139f1991d6eb2f8a8edf4a5635ed9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 8 Jul 2024 15:24:55 +0200 Subject: [PATCH 005/148] comment out matrix inverse & unpackSnorm2x16 from glsl_compat/core.hlsl and spirv_intrinsics/core.hlsl because of https://github.com/microsoft/DirectXShaderCompiler/issues/6751 --- include/nbl/builtin/hlsl/glsl_compat/core.hlsl | 11 +++++++++++ include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl index b2871795a8..709e650612 100644 --- a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl +++ b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl @@ -107,12 +107,23 @@ enable_if_t, T> atomicCompSwap(Ptr_T ptr, T value) /** * GLSL extended math */ + + // COMMENTING OUT BECAUSE OF https://github.com/microsoft/DirectXShaderCompiler/issues/6751, bring back when fixed! + /* + + template // NBL_REQUIRES() extents are square SquareMatrix inverse(NBL_CONST_REF_ARG(SquareMatrix) mat) { return spirv::matrixInverse(mat); } +float32_t2 unpackSnorm2x16(uint32_t p) +{ + return spirv::unpackSnorm2x16(p); +} + +*/ /** * For Vertex Shaders diff --git a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl index cdbf90b59f..3e2159269b 100644 --- a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl +++ b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl @@ -175,10 +175,19 @@ template enable_if_t, void> store(P pointer, T obj, [[vk::ext_literal]] uint32_t __aligned = /*Aligned*/0x00000002, [[vk::ext_literal]] uint32_t __alignment = alignment); //! Std 450 Extended set operations + +// COMMENTING OUT BECAUSE OF https://github.com/microsoft/DirectXShaderCompiler/issues/6751, bring back when fixed! +/* + template [[vk::ext_instruction(GLSLstd450MatrixInverse)]] SquareMatrix matrixInverse(NBL_CONST_REF_ARG(SquareMatrix) mat); +[[vk::ext_instruction(GLSLstd450UnpackSnorm2x16)]] +float32_t2 unpackSnorm2x16(uint32_t p); + +*/ + // Memory Semantics link here: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Memory_Semantics_-id- // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_memory_semantics_id From e6ecff6d5fc4dbf99967191369f2acb25fff7779 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 8 Jul 2024 17:44:30 +0200 Subject: [PATCH 006/148] handle imgui clipping correctly, introduce emulated_snorm16_t2 and use it --- src/nbl/ext/ImGui/ImGui.cpp | 40 +++++++++++++++++++++++++-- src/nbl/ext/ImGui/shaders/common.hlsl | 34 +++++++++++++++-------- src/nbl/ext/ImGui/shaders/vertex.hlsl | 13 ++++++--- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index e6ee874a84..7e07ea912e 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -625,6 +625,27 @@ namespace nbl::ext::imgui } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; + struct TRS + { + core::vector2df_SIMD scale; + core::vector2df_SIMD translate; + + core::vector2df_SIMD toNDC(core::vector2df_SIMD in) const + { + return in * scale + translate; + } + }; + + const TRS trs = [&]() + { + TRS retV; + + retV.scale = core::vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; + retV.translate = core::vector2df_SIMD { -1.0f, -1.0f } - core::vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; + + return std::move(retV); + }(); + struct { size_t vBufferSize = {}, iBufferSize = {}, // sizes in bytes @@ -696,7 +717,20 @@ namespace nbl::ext::imgui indirect.instanceCount = 1u; indirect.vertexOffset = pcmd->VtxOffset + globalVOffset; - element.scissor = clip.getScissor(clipRectangle); + const auto scissor = clip.getScissor(clipRectangle); + + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; + + const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + + element.aabbMin.x = packSnorm16(vMin.x); + element.aabbMin.y = packSnorm16(vMin.y); + element.aabbMax.x = packSnorm16(vMax.x); + element.aabbMax.y = packSnorm16(vMax.y); // element.texId = 0; // use defaults from constructor ++drawID; @@ -848,8 +882,8 @@ namespace nbl::ext::imgui { .elementBDA = { mdiBuffer->getDeviceAddress() + elementsBOffset }, .elementCount = { drawCount }, - .scale = { 2.0f / drawData->DisplaySize.x, 2.0f / drawData->DisplaySize.y }, - .translate = { -1.0f - drawData->DisplayPos.x * constants.scale[0u], -1.0f - drawData->DisplayPos.y * constants.scale[1u] }, + .scale = { trs.scale[0u], trs.scale[1u] }, + .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } }; diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 3828d186b4..00b8f485a3 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -11,7 +11,7 @@ float4 position : SV_Position; float4 color : COLOR0; float2 uv : TEXCOORD0; - float clip[2] : SV_ClipDistance; + float clip[4] : SV_ClipDistance; }; struct PushConstants @@ -23,11 +23,6 @@ float4 viewport; }; - struct PerObjectData - { - float4 scissor; - uint texId; - }; #else struct PushConstants { @@ -37,10 +32,25 @@ float translate[2]; float viewport[4]; }; - - struct PerObjectData - { - VkRect2D scissor; - uint32_t texId = 0; - }; #endif // __HLSL_VERSION + +struct emulated_snorm16_t2 +{ + #ifdef __HLSL_VERSION + float32_t2 unpack() // returns in NDC [-1, 1] range + { + return clamp(float32_t2(x, y) / 32767.0f, -1.f, +1.f); + } + #endif + + int16_t x; + int16_t y; +}; + +struct PerObjectData +{ + emulated_snorm16_t2 aabbMin; + emulated_snorm16_t2 aabbMax; + uint32_t texId; + uint32_t padding; +}; \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index 7b6684c6c0..4c8b31a332 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -7,14 +7,19 @@ PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) PSInput output; output.color = input.color; output.uv = input.uv; - output.position = float4(input.position * pc.scale + pc.translate, 0, 1); // BDA for requesting object data const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); - // TODO - output.clip[0] = 69; - output.clip[1] = 69; + // NDC [-1, 1] range + output.position = float4(input.position * pc.scale + pc.translate, 0, 1); + const float2 vMin = self.aabbMin.unpack(); + const float2 vMax = self.aabbMax.unpack(); + + output.clip[0] = output.position.x - vMin.x; + output.clip[1] = output.position.y - vMin.y; + output.clip[2] = vMax.x - output.position.x; + output.clip[3] = vMax.y - output.position.y; return output; } \ No newline at end of file From 55031d9445b64b3b925cd65b45d99e5ff9df083b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 9 Jul 2024 09:23:51 +0200 Subject: [PATCH 007/148] eliminate some Nabla IMGUI extension validation errors, auto submit pipeline barier to change the font image's layout after the buffer update from TRANSFER_DST_OPTIMAL to READ_ONLY_OPTIMAL --- src/nbl/ext/ImGui/ImGui.cpp | 63 +++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 7e07ea912e..56c64e721b 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -192,7 +192,7 @@ namespace nbl::ext::imgui params.mipLevels = 1; params.arrayLayers = 1u; params.samples = IImage::ESCF_1_BIT; - params.usage |= IGPUImage::EUF_TRANSFER_DST_BIT | IGPUImage::EUF_SAMPLED_BIT | IGPUImage::E_USAGE_FLAGS::EUF_TRANSFER_SRC_BIT; + params.usage |= asset::IImage::EUF_SAMPLED_BIT | asset::IImage::EUF_TRANSFER_DST_BIT; struct { @@ -256,26 +256,57 @@ namespace nbl::ext::imgui .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS }; - const SMemoryBarrier toTransferBarrier = + // barier with TRANSFER_DST_OPTIMAL image layout request for filling the image's buffer { - .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, - .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT - }; + const SMemoryBarrier toTransferBarrier = + { + .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, + .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT + }; - cmdBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - const IGPUCommandBuffer::SImageMemoryBarrier barriers[] = - { + cmdBuffer->begin(IGPUCommandBuffer::USAGE::SIMULTANEOUS_USE_BIT); + const IGPUCommandBuffer::SImageMemoryBarrier barriers[] = { - .barrier = { .dep = toTransferBarrier }, - .image = image.get(), - .subresourceRange = regions.subresource, - .newLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL - } - }; + { + .barrier = {.dep = toTransferBarrier }, + .image = image.get(), + .subresourceRange = regions.subresource, + .newLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL + } + }; - cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { .imgBarriers = barriers }); + cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { .imgBarriers = barriers }); + } + // fill gpu image given buffer data and submit utilities->updateImageViaStagingBufferAutoSubmit(sInfo, buffer->getPointer(), NBL_FORMAT_FONT, image.get(), IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL, regions.range); + + // barier with READ_ONLY_OPTIMAL image layout request after the image's buffer update with auto submit + { + utilities->autoSubmit(sInfo, + [&](SIntendedSubmitInfo& nextSubmit) -> bool + { + const SMemoryBarrier toTransferBarrier = + { + .dstStageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS, + .dstAccessMask = ACCESS_FLAGS::TRANSFER_READ_BIT + }; + + const IGPUCommandBuffer::SImageMemoryBarrier barriers[] = + { + { + .barrier = {.dep = toTransferBarrier }, + .image = image.get(), + .subresourceRange = regions.subresource, + .oldLayout = IImage::LAYOUT::TRANSFER_DST_OPTIMAL, + .newLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL, + } + }; + + return nextSubmit.getScratchCommandBuffer()->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { .imgBarriers = barriers }); + } + ); + } } { @@ -804,7 +835,7 @@ namespace nbl::ext::imgui { auto eData = mdiBuffer->getBoundMemory(); - //if (eData.memory->haveToMakeVisible()) + if (eData.memory->haveToMakeVisible()) { const ILogicalDevice::MappedMemoryRange range(eData.memory, eData.offset, mdiBuffer->getSize()); m_device->flushMappedMemoryRanges(1, &range); From 1bdb90273c78499eba1c56ad09676d4ace0c22c5 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 9 Jul 2024 10:12:12 +0200 Subject: [PATCH 008/148] add 3rdparty/imgui_test_engine submodule --- .gitmodules | 3 +++ 3rdparty/imgui_test_engine | 1 + 2 files changed, 4 insertions(+) create mode 160000 3rdparty/imgui_test_engine diff --git a/.gitmodules b/.gitmodules index ea533a6736..a396587980 100644 --- a/.gitmodules +++ b/.gitmodules @@ -99,3 +99,6 @@ [submodule "3rdparty/imguizmo"] path = 3rdparty/imguizmo url = git@github.com:Devsh-Graphics-Programming/ImGuizmo.git +[submodule "3rdparty/imgui_test_engine"] + path = 3rdparty/imgui_test_engine + url = git@github.com:ocornut/imgui_test_engine.git diff --git a/3rdparty/imgui_test_engine b/3rdparty/imgui_test_engine new file mode 160000 index 0000000000..60b295cc83 --- /dev/null +++ b/3rdparty/imgui_test_engine @@ -0,0 +1 @@ +Subproject commit 60b295cc83a2dd4b530ab2eb5bf37f009a443619 From fcca8e05e4508d0094b4f446dd51e8bfd2b83915 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 9 Jul 2024 17:11:44 +0200 Subject: [PATCH 009/148] Create Nabla Imgui Test Engine Tool - integrate 3rdparty/imgui_test_engine for this purpose with full test suite. Enter runtime issues, I think it's because of how I currently share resources across modules and the fact ImGUI context is static --- 3rdparty/CMakeLists.txt | 19 +- include/nbl/ext/ImGui/ImGui.h | 4 + src/nbl/ext/ImGui/ImGui.cpp | 11 + tools/CMakeLists.txt | 4 + tools/nite/CMakeLists.txt | 41 ++++ tools/nite/main.cpp | 386 ++++++++++++++++++++++++++++++++++ 6 files changed, 458 insertions(+), 7 deletions(-) create mode 100644 tools/nite/CMakeLists.txt create mode 100644 tools/nite/main.cpp diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 09939e98f0..f29ab2bc8b 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -274,19 +274,24 @@ if(NBL_BUILD_IMGUI) PUBLIC "imgui/backends" ) - # ImPlot + set(NBL_IMGUI_TEST_ENGINE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui_test_engine/imgui_test_suite") + set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_ENGINE_ROOT}/thirdparty/implot") + add_library(implot STATIC - "implot/implot.h" - "implot/implot_internal.h" - "implot/implot.cpp" - "implot/implot_items.cpp" + "${NBL_IMPLOT_ROOT}/implot.h" + "${NBL_IMPLOT_ROOT}/implot_internal.h" + "${NBL_IMPLOT_ROOT}/implot.cpp" + "${NBL_IMPLOT_ROOT}/implot_items.cpp" + "${NBL_IMPLOT_ROOT}/implot_demo.cpp" ) + target_include_directories(implot - PUBLIC "imgui" + PUBLIC imgui + PUBLIC "${NBL_IMPLOT_ROOT}" ) target_link_libraries(implot PUBLIC imgui) target_compile_definitions(implot PUBLIC IMPLOT_DEBUG IMPLOT_DLL_EXPORT) - set_property(TARGET implot PROPERTY CXX_STANDARD 11) + set_property(TARGET implot PROPERTY CXX_STANDARD 20) if(MSVC) target_compile_options(implot PRIVATE /MT /W4 /WX /arch:AVX2 /fp:fast /permissive-) else() diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 8110f34bb4..d4a2443347 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -3,6 +3,7 @@ namespace nbl::ext::imgui { + class UI final : public core::IReferenceCounted { public: @@ -13,6 +14,9 @@ class UI final : public core::IReferenceCounted void update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events int registerListener(std::function const& listener); bool unregisterListener(int listenerId); + + void* getContext(); + void setContext(void* imguiContext); private: core::smart_refctd_ptr createDescriptorSetLayout(); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 56c64e721b..8b67bd6777 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -969,4 +969,15 @@ namespace nbl::ext::imgui } return false; } + + void* UI::getContext() + { + return reinterpret_cast(ImGui::GetCurrentContext()); + } + + void UI::setContext(void* imguiContext) + { + ImGui::SetCurrentContext(reinterpret_cast(imguiContext)); + } + } \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 0a739871a2..1761ea9f07 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,2 +1,6 @@ add_subdirectory(nsc) add_subdirectory(xxHash256) + +if(NBL_BUILD_IMGUI) + add_subdirectory(nite) +endif() \ No newline at end of file diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt new file mode 100644 index 0000000000..2f3f2d27c4 --- /dev/null +++ b/tools/nite/CMakeLists.txt @@ -0,0 +1,41 @@ +set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") +set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") +set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") + +set(NBL_EXTRA_INCLUDES + "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}" + "${NBL_IMGUI_TEST_ENGINE_ROOT}" + "${NBL_IMGUI_TEST_SUITE_ROOT}" + $ + $ +) + +file(GLOB_RECURSE NBL_EXTRA_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_ENGINE_ROOT}/*.cpp") +file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") +file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") + +add_library(imtestsuite STATIC + ${NBL_TEST_SUITE_SOURCES} +) +target_include_directories(imtestsuite + PUBLIC ${NBL_EXTRA_INCLUDES} +) +nbl_handle_runtime_lib_properties(imtestsuite) +set_property(TARGET imtestsuite PROPERTY CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE + +target_precompile_headers(imtestsuite + PUBLIC "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test_suite_imconfig.h" # force include config header into test suite sources +) +target_link_libraries(imtestsuite PUBLIC implot) + +set(NBL_EXTRA_OPTIONS + # TODO: add if required +) + +set(NBL_EXTRA_LIBS + imgui + imtestsuite + "${NBL_EXT_IMGUI_UI_LIB}" # Nabla IMGUI backend +) + +nbl_create_executable_project("${NBL_EXTRA_SOURCES}" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") \ No newline at end of file diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp new file mode 100644 index 0000000000..6835ae9af0 --- /dev/null +++ b/tools/nite/main.cpp @@ -0,0 +1,386 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#define _NBL_STATIC_LIB_ +#include +#include "nbl/video/utilities/CSimpleResizeSurface.h" + +#include "../common/SimpleWindowedApplication.hpp" +#include "../common/InputSystem.hpp" + +#include "nbl/ext/ImGui/ImGui.h" +#include "nbl/ui/ICursorControl.h" + +// Test Engine +#include "imgui_test_suite_imconfig.h" +#include "imgui_test_suite.h" +#include "imgui_te_engine.h" +#include "imgui_te_ui.h" +#include "imgui_te_utils.h" + +using namespace nbl; +using namespace core; +using namespace hlsl; +using namespace system; +using namespace asset; +using namespace ui; +using namespace video; + +class CEventCallback : public ISimpleManagedSurface::ICallback +{ +public: + CEventCallback(nbl::core::smart_refctd_ptr&& m_inputSystem, nbl::system::logger_opt_smart_ptr&& logger) : m_inputSystem(std::move(m_inputSystem)), m_logger(std::move(logger)) {} + CEventCallback() {} + + void setLogger(nbl::system::logger_opt_smart_ptr& logger) + { + m_logger = logger; + } + void setInputSystem(nbl::core::smart_refctd_ptr&& m_inputSystem) + { + m_inputSystem = std::move(m_inputSystem); + } +private: + + void onMouseConnected_impl(nbl::core::smart_refctd_ptr&& mch) override + { + m_logger.log("A mouse %p has been connected", nbl::system::ILogger::ELL_INFO, mch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_mouse, std::move(mch)); + } + void onMouseDisconnected_impl(nbl::ui::IMouseEventChannel* mch) override + { + m_logger.log("A mouse %p has been disconnected", nbl::system::ILogger::ELL_INFO, mch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_mouse, mch); + } + void onKeyboardConnected_impl(nbl::core::smart_refctd_ptr&& kbch) override + { + m_logger.log("A keyboard %p has been connected", nbl::system::ILogger::ELL_INFO, kbch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_keyboard, std::move(kbch)); + } + void onKeyboardDisconnected_impl(nbl::ui::IKeyboardEventChannel* kbch) override + { + m_logger.log("A keyboard %p has been disconnected", nbl::system::ILogger::ELL_INFO, kbch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_keyboard, kbch); + } + +private: + nbl::core::smart_refctd_ptr m_inputSystem = nullptr; + nbl::system::logger_opt_smart_ptr m_logger = nullptr; +}; + +class NITETool final : public examples::SimpleWindowedApplication +{ + using device_base_t = examples::SimpleWindowedApplication; + using clock_t = std::chrono::steady_clock; + + _NBL_STATIC_INLINE_CONSTEXPR uint32_t WIN_W = 1280, WIN_H = 720, SC_IMG_COUNT = 3u, FRAMES_IN_FLIGHT = 5u; + static_assert(FRAMES_IN_FLIGHT > SC_IMG_COUNT); + + constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); + +public: + inline NITETool(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) + : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} + + inline core::vector getSurfaces() const override + { + if (!m_surface) + { + { + auto windowCallback = core::make_smart_refctd_ptr(smart_refctd_ptr(m_inputSystem), smart_refctd_ptr(m_logger)); + IWindow::SCreationParams params = {}; + params.callback = core::make_smart_refctd_ptr(); + params.width = WIN_W; + params.height = WIN_H; + params.x = 32; + params.y = 32; + params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_RESIZABLE; + params.windowCaption = "NITETool"; + params.callback = windowCallback; + const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); + } + + auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); + const_cast&>(m_surface) = nbl::video::CSimpleResizeSurface::create(std::move(surface)); + } + + if (m_surface) + return { {m_surface->getSurface()/*,EQF_NONE*/} }; + + return {}; + } + + inline bool onAppInitialized(smart_refctd_ptr&& system) override + { + m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); + + if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + + m_semaphore = m_device->createSemaphore(m_submitIx); + if (!m_semaphore) + return logFail("Failed to Create a Semaphore!"); + + ISwapchain::SCreationParams swapchainParams = { .surface = m_surface->getSurface() }; + if (!swapchainParams.deduceFormat(m_physicalDevice)) + return logFail("Could not choose a Surface Format for the Swapchain!"); + + const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = + { + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = + { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COPY_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + }, + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = + { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + + auto scResources = std::make_unique(m_device.get(), swapchainParams.surfaceFormat.format, dependencies); + auto* renderpass = scResources->getRenderpass(); + + if (!renderpass) + return logFail("Failed to create Renderpass!"); + + auto gQueue = getGraphicsQueue(); + if (!m_surface || !m_surface->init(gQueue, std::move(scResources), swapchainParams.sharedParams)) + return logFail("Could not create Window & Surface or initialize the Surface!"); + + m_maxFramesInFlight = m_surface->getMaxFramesInFlight(); + if (FRAMES_IN_FLIGHT < m_maxFramesInFlight) + { + m_logger->log("Lowering frames in flight!", ILogger::ELL_WARNING); + m_maxFramesInFlight = FRAMES_IN_FLIGHT; + } + + m_cmdPool = m_device->createCommandPool(gQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + + for (auto i = 0u; i < m_maxFramesInFlight; i++) + { + if (!m_cmdPool) + return logFail("Couldn't create Command Pool!"); + if (!m_cmdPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data() + i, 1 })) + return logFail("Couldn't create Command Buffer!"); + } + + ui = core::make_smart_refctd_ptr(smart_refctd_ptr(m_device), (int)m_maxFramesInFlight, renderpass, nullptr, smart_refctd_ptr(m_window)); + auto* ctx = reinterpret_cast(ui->getContext()); + ImGui::SetCurrentContext(ctx); + + // Initialize Test Engine + engine = ImGuiTestEngine_CreateContext(); + ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine); + test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info; + test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug; + test_io.ConfigSavedSettings = false; + + // Register tests + RegisterTests_All(engine); + + // Start engine + ImGuiTestEngine_Start(engine, ctx); + ImGuiTestEngine_InstallDefaultCrashHandler(); + + ui->registerListener([this]() -> void + { + ImGuiTestEngine_ShowTestEngineWindows(engine, nullptr); + } + ); + + m_winMgr->setWindowSize(m_window.get(), WIN_W, WIN_H); + m_surface->recreateSwapchain(); + m_winMgr->show(m_window.get()); + + return true; + } + + inline void workLoopBody() override + { + static std::chrono::microseconds previousEventTimestamp{}; + // TODO: Use real deltaTime instead + float deltaTimeInSec = 0.1f; + + struct + { + std::vector mouse{}; + } capturedEvents; + + m_inputSystem->getDefaultMouse(&mouse); + m_inputSystem->getDefaultKeyboard(&keyboard); + + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + for (auto event : events) + { + if (event.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = event.timeStamp; + capturedEvents.mouse.push_back(event); + } + }, m_logger.get()); + + keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + // TOOD + }, m_logger.get()); + + const auto mousePosition = m_window->getCursorControl()->getPosition(); + ui->update(deltaTimeInSec, static_cast(mousePosition.x), static_cast(mousePosition.y), capturedEvents.mouse.size(), capturedEvents.mouse.data()); + + const auto resourceIx = m_realFrameIx % m_maxFramesInFlight; + + if (m_realFrameIx >= m_maxFramesInFlight) + { + const ISemaphore::SWaitInfo cbDonePending[] = + { + { + .semaphore = m_semaphore.get(), + .value = m_realFrameIx + 1 - m_maxFramesInFlight + } + }; + if (m_device->blockForSemaphores(cbDonePending) != ISemaphore::WAIT_RESULT::SUCCESS) + return; + } + + m_currentImageAcquire = m_surface->acquireNextImage(); + if (!m_currentImageAcquire) + return; + + auto* const cb = m_cmdBufs.data()[resourceIx].get(); + cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + cb->beginDebugMarker("NITETool Frame"); + + auto* queue = getGraphicsQueue(); + + asset::SViewport viewport; + { + viewport.minDepth = 1.f; + viewport.maxDepth = 0.f; + viewport.x = 0u; + viewport.y = 0u; + viewport.width = WIN_W; + viewport.height = WIN_H; + } + cb->setViewport(0u, 1u, &viewport); + { + const VkRect2D currentRenderArea = + { + .offset = {0,0}, + .extent = {m_window->getWidth(),m_window->getHeight()} + }; + + const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + auto scRes = static_cast(m_surface->getSwapchainResources()); + const IGPUCommandBuffer::SRenderpassBeginInfo info = + { + .framebuffer = scRes->getFramebuffer(m_currentImageAcquire.imageIndex), + .colorClearValues = &clearValue, + .depthStencilClearValues = nullptr, + .renderArea = currentRenderArea + }; + cb->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + } + + ui->render(cb, resourceIx); + cb->endRenderPass(); + cb->end(); + { + const IQueue::SSubmitInfo::SSemaphoreInfo rendered[] = + { + { + .semaphore = m_semaphore.get(), + .value = ++m_submitIx, + .stageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT + } + }; + { + { + const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[] = + { + {.cmdbuf = cb } + }; + + const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = + { + { + .semaphore = m_currentImageAcquire.semaphore, + .value = m_currentImageAcquire.acquireCount, + .stageMask = PIPELINE_STAGE_FLAGS::NONE + } + }; + const IQueue::SSubmitInfo infos[] = + { + { + .waitSemaphores = acquired, + .commandBuffers = commandBuffers, + .signalSemaphores = rendered + } + }; + + if (queue->submit(infos) != IQueue::RESULT::SUCCESS) + m_submitIx--; + } + } + + m_window->setCaption("IMGUI Nabla Backend Test"); + m_surface->present(m_currentImageAcquire.imageIndex, rendered); + + // Post swap Test Engine + ImGuiTestEngine_PostSwap(engine); + } + } + + inline bool keepRunning() override + { + if (m_surface->irrecoverable()) + return false; + + return true; + } + + inline bool onAppTerminated() override + { + ImGuiTestEngine_Stop(engine); + + return device_base_t::onAppTerminated(); + } + +private: + smart_refctd_ptr m_window; + smart_refctd_ptr> m_surface; + smart_refctd_ptr m_pipeline; + smart_refctd_ptr m_semaphore; + smart_refctd_ptr m_cmdPool; + uint64_t m_realFrameIx : 59 = 0; + uint64_t m_submitIx : 59 = 0; + uint64_t m_maxFramesInFlight : 5; + std::array, ISwapchain::MaxImages> m_cmdBufs; + ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; + + nbl::core::smart_refctd_ptr ui; + core::smart_refctd_ptr m_inputSystem; + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + + // Test engine + ImGuiTestEngine* engine = nullptr; +}; + +NBL_MAIN_FUNC(NITETool) \ No newline at end of file From badbdafbcd0c94b243a96ce2571b11b7e2818570 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 12:52:12 +0200 Subject: [PATCH 010/148] Correct IMGUI Integration, use Test Engine's configuration and eliminate memory corruption issues. Make NITE work, now we just need to take care of imguizmo which doesn't like the configuration --- 3rdparty/CMakeLists.txt | 72 ++++++++++++++++++++++---------- cmake/common.cmake | 1 + src/nbl/ext/ImGui/CMakeLists.txt | 11 +++-- tools/nite/CMakeLists.txt | 25 ----------- 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index f29ab2bc8b..c33be4f15b 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -253,29 +253,38 @@ add_library(spirv_cross OBJECT target_compile_definitions(spirv_cross PUBLIC SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS) if(NBL_BUILD_IMGUI) + set(NBL_IMGUI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui") + add_library(imgui STATIC - "imgui/imconfig.h" - "imgui/imgui_demo.cpp" - "imgui/imgui_draw.cpp" - "imgui/imgui_internal.h" - "imgui/imgui_tables.cpp" - "imgui/imgui_widgets.cpp" - "imgui/imgui.cpp" - "imgui/imgui.h" - "imgui/misc/cpp/imgui_stdlib.cpp" - "imgui/misc/cpp/imgui_stdlib.h" - "imgui/imstb_rectpack.h" - "imgui/imstb_textedit.h" - "imgui/imstb_truetype.h" + "${NBL_IMGUI_ROOT}/imconfig.h" + "${NBL_IMGUI_ROOT}/imgui_demo.cpp" + "${NBL_IMGUI_ROOT}/imgui_draw.cpp" + "${NBL_IMGUI_ROOT}/imgui_internal.h" + "${NBL_IMGUI_ROOT}/imgui_tables.cpp" + "${NBL_IMGUI_ROOT}/imgui_widgets.cpp" + "${NBL_IMGUI_ROOT}/imgui.cpp" + "${NBL_IMGUI_ROOT}/imgui.h" + "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.cpp" + "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.h" + "${NBL_IMGUI_ROOT}/imstb_rectpack.h" + "${NBL_IMGUI_ROOT}/imstb_textedit.h" + "${NBL_IMGUI_ROOT}/imstb_truetype.h" ) target_include_directories(imgui - PUBLIC "imgui" - PUBLIC "imgui/misc/cpp" - PUBLIC "imgui/backends" + PUBLIC "${NBL_IMGUI_ROOT}" + PUBLIC "${NBL_IMGUI_ROOT}/misc/cpp" + PUBLIC "${NBL_IMGUI_ROOT}/backends" ) - set(NBL_IMGUI_TEST_ENGINE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui_test_engine/imgui_test_suite") - set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_ENGINE_ROOT}/thirdparty/implot") + set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") + set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") + set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") + set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_SUITE_ROOT}/thirdparty/implot") + + # USER DEFINED IMGUI CONFIG, IMPACTS THE BUILD + target_compile_definitions(imgui PUBLIC + IMGUI_USER_CONFIG="${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test_suite_imconfig.h" + ) add_library(implot STATIC "${NBL_IMPLOT_ROOT}/implot.h" @@ -286,17 +295,38 @@ if(NBL_BUILD_IMGUI) ) target_include_directories(implot - PUBLIC imgui + PUBLIC $ PUBLIC "${NBL_IMPLOT_ROOT}" ) + target_link_libraries(implot PUBLIC imgui) target_compile_definitions(implot PUBLIC IMPLOT_DEBUG IMPLOT_DLL_EXPORT) - set_property(TARGET implot PROPERTY CXX_STANDARD 20) + set_target_properties(implot PROPERTIES CXX_STANDARD 20) + if(MSVC) target_compile_options(implot PRIVATE /MT /W4 /WX /arch:AVX2 /fp:fast /permissive-) else() target_compile_options(implot PRIVATE -Wall -Wextra -pedantic -Werror -mavx2 -Ofast) endif() + + file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") + file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") + + add_library(imtestsuite STATIC + ${NBL_TEST_SUITE_SOURCES} + ) + + target_include_directories(imtestsuite PUBLIC + "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}" + "${NBL_IMGUI_TEST_ENGINE_ROOT}" + "${NBL_IMGUI_TEST_SUITE_ROOT}" + $ + $ + ) + nbl_handle_runtime_lib_properties(imtestsuite) + set_property(TARGET imtestsuite PROPERTY CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE + + target_link_libraries(imtestsuite PUBLIC implot) set(IMGUIZMO_BUILD_EXAMPLE OFF) add_subdirectory(imguizmo EXCLUDE_FROM_ALL) @@ -392,7 +422,7 @@ if (NBL_BUILD_MITSUBA_LOADER) list(APPEND NBL_3RDPARTY_TARGETS expat) endif() if (NBL_BUILD_IMGUI) - list(APPEND NBL_3RDPARTY_TARGETS imgui implot imguizmo) + list(APPEND NBL_3RDPARTY_TARGETS imgui implot imtestsuite imguizmo) endif() if(ENABLE_HLSL) list(APPEND NBL_3RDPARTY_TARGETS HLSL) diff --git a/cmake/common.cmake b/cmake/common.cmake index 6bfa6cba96..5c68599620 100755 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -258,6 +258,7 @@ macro(nbl_create_executable_project _EXTRA_SOURCES _EXTRA_OPTIONS _EXTRA_INCLUDE nbl_project_process_test_module() endmacro() +# TODO this macro needs more love macro(nbl_create_ext_library_project EXT_NAME LIB_HEADERS LIB_SOURCES LIB_INCLUDES LIB_OPTIONS DEF_OPTIONS) set(LIB_NAME "NblExt${EXT_NAME}") project(${LIB_NAME}) diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index 05144787e9..ed839953eb 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -1,5 +1,3 @@ -include(${NBL_ROOT_PATH}/cmake/common.cmake) - set(NBL_EXT_INTERNAL_INCLUDE_DIR "${NBL_ROOT_PATH}/include") set(NBL_EXT_IMGUI_H @@ -7,12 +5,11 @@ set(NBL_EXT_IMGUI_H ) set(NBL_EXT_IMGUI_SRC - ImGui.cpp + "${CMAKE_CURRENT_SOURCE_DIR}/ImGui.cpp" ) -set(NBL_EXT_IMGUI_EXTERNAL_INCLUDE - "${NBL_ROOT_PATH}/3rdparty" - "${NBL_EXT_INTERNAL_INCLUDE_DIR}" +set(NBL_EXT_IMGUI_INCLUDE_SEARCH_DIRECTORIES + $ ) nbl_create_ext_library_project( @@ -24,6 +21,8 @@ nbl_create_ext_library_project( "" ) +target_link_libraries(${LIB_NAME} PUBLIC imtestsuite) + # shaders IO directories set(NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shaders") get_filename_component(_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_ "${CMAKE_CURRENT_BINARY_DIR}/shaders/include" ABSOLUTE) diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt index 2f3f2d27c4..d2e2275c1b 100644 --- a/tools/nite/CMakeLists.txt +++ b/tools/nite/CMakeLists.txt @@ -2,38 +2,13 @@ set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_eng set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") -set(NBL_EXTRA_INCLUDES - "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}" - "${NBL_IMGUI_TEST_ENGINE_ROOT}" - "${NBL_IMGUI_TEST_SUITE_ROOT}" - $ - $ -) - file(GLOB_RECURSE NBL_EXTRA_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_ENGINE_ROOT}/*.cpp") -file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") -file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") - -add_library(imtestsuite STATIC - ${NBL_TEST_SUITE_SOURCES} -) -target_include_directories(imtestsuite - PUBLIC ${NBL_EXTRA_INCLUDES} -) -nbl_handle_runtime_lib_properties(imtestsuite) -set_property(TARGET imtestsuite PROPERTY CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE - -target_precompile_headers(imtestsuite - PUBLIC "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test_suite_imconfig.h" # force include config header into test suite sources -) -target_link_libraries(imtestsuite PUBLIC implot) set(NBL_EXTRA_OPTIONS # TODO: add if required ) set(NBL_EXTRA_LIBS - imgui imtestsuite "${NBL_EXT_IMGUI_UI_LIB}" # Nabla IMGUI backend ) From 54e370f848e038582d43e0b7ef776d20ff5c5c7e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 13:41:07 +0200 Subject: [PATCH 011/148] fail nite's exit code if any test fails --- tools/nite/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index 6835ae9af0..10f126b1f6 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -357,9 +357,13 @@ class NITETool final : public examples::SimpleWindowedApplication inline bool onAppTerminated() override { + int tested = 0, successed = 0; + ImGuiTestEngine_GetResult(engine, tested, successed); + const bool good = tested == successed; + ImGuiTestEngine_Stop(engine); - return device_base_t::onAppTerminated(); + return device_base_t::onAppTerminated() && good; } private: From 003aae41dd0c46af5cec3aac1bd1a66c0de8921e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 15:06:33 +0200 Subject: [PATCH 012/148] Reorganize imgui dependency & utility libraries, create our own imconfig header on fly with CMake and share across imgui projects. TODO: add NBL_IMGUI_WITH_TEST_ENGINE enabled by default which makes all imgui projects use our configuration with embedding the test engine and enables the nite tool, otherwise disables stuff and limits the imgui build to minimum --- 3rdparty/CMakeLists.txt | 51 +++++++++++++++++++++++--------- src/nbl/ext/ImGui/CMakeLists.txt | 4 +-- tools/nite/CMakeLists.txt | 6 ++-- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index c33be4f15b..a05fbaa54b 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -254,6 +254,11 @@ target_compile_definitions(spirv_cross PUBLIC SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIO if(NBL_BUILD_IMGUI) set(NBL_IMGUI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui") + set(NBL_IMGUI_USER_CONFIG_ISD "${CMAKE_CURRENT_BINARY_DIR}/imgui/config/include") + set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") + set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") + set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") + set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_SUITE_ROOT}/thirdparty/implot") add_library(imgui STATIC "${NBL_IMGUI_ROOT}/imconfig.h" @@ -274,16 +279,7 @@ if(NBL_BUILD_IMGUI) PUBLIC "${NBL_IMGUI_ROOT}" PUBLIC "${NBL_IMGUI_ROOT}/misc/cpp" PUBLIC "${NBL_IMGUI_ROOT}/backends" - ) - - set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") - set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") - set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") - set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_SUITE_ROOT}/thirdparty/implot") - - # USER DEFINED IMGUI CONFIG, IMPACTS THE BUILD - target_compile_definitions(imgui PUBLIC - IMGUI_USER_CONFIG="${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test_suite_imconfig.h" + PUBLIC "${NBL_IMGUI_TEST_SUITE_ROOT}" ) add_library(implot STATIC @@ -323,13 +319,40 @@ if(NBL_BUILD_IMGUI) $ $ ) - nbl_handle_runtime_lib_properties(imtestsuite) - set_property(TARGET imtestsuite PROPERTY CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE - + + set_target_properties(imtestsuite PROPERTIES CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE target_link_libraries(imtestsuite PUBLIC implot) + file(GLOB_RECURSE NBL_TEST_ENGINE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_ENGINE_ROOT}/*.cpp") + + add_library(imtestengine STATIC + ${NBL_TEST_ENGINE_SOURCES} + ) + + target_include_directories(imtestengine PUBLIC + $ + ) + + target_link_libraries(imtestengine PUBLIC imtestsuite) + set(IMGUIZMO_BUILD_EXAMPLE OFF) add_subdirectory(imguizmo EXCLUDE_FROM_ALL) + + # override test engine config with our own +string(APPEND NBL_IMPL_IMCONFIG_CONTENT +[=[ +#include "imgui_test_suite_imconfig.h" // use test engine's config + +#undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things +]=] +) + set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_USER_CONFIG_ISD}/nabla_imconfig.h") + file(WRITE "${NBL_IMGUI_USER_CONFIG_FILEPATH}" "${NBL_IMPL_IMCONFIG_CONTENT}") + + # USER DEFINED IMGUI CONFIG, IMPACTS THE BUILD + target_compile_definitions(imgui PUBLIC + IMGUI_USER_CONFIG="${NBL_IMGUI_USER_CONFIG_FILEPATH}" + ) endif() add_library(aesGladman OBJECT @@ -422,7 +445,7 @@ if (NBL_BUILD_MITSUBA_LOADER) list(APPEND NBL_3RDPARTY_TARGETS expat) endif() if (NBL_BUILD_IMGUI) - list(APPEND NBL_3RDPARTY_TARGETS imgui implot imtestsuite imguizmo) + list(APPEND NBL_3RDPARTY_TARGETS imgui implot imtestsuite imtestengine imguizmo) endif() if(ENABLE_HLSL) list(APPEND NBL_3RDPARTY_TARGETS HLSL) diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index ed839953eb..481c873a95 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -9,7 +9,7 @@ set(NBL_EXT_IMGUI_SRC ) set(NBL_EXT_IMGUI_INCLUDE_SEARCH_DIRECTORIES - $ + $ ) nbl_create_ext_library_project( @@ -21,7 +21,7 @@ nbl_create_ext_library_project( "" ) -target_link_libraries(${LIB_NAME} PUBLIC imtestsuite) +target_link_libraries(${LIB_NAME} PUBLIC imtestengine) # shaders IO directories set(NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shaders") diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt index d2e2275c1b..6fb01849c6 100644 --- a/tools/nite/CMakeLists.txt +++ b/tools/nite/CMakeLists.txt @@ -2,15 +2,13 @@ set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_eng set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") -file(GLOB_RECURSE NBL_EXTRA_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_ENGINE_ROOT}/*.cpp") - set(NBL_EXTRA_OPTIONS # TODO: add if required ) set(NBL_EXTRA_LIBS - imtestsuite + imtestengine "${NBL_EXT_IMGUI_UI_LIB}" # Nabla IMGUI backend ) -nbl_create_executable_project("${NBL_EXTRA_SOURCES}" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") \ No newline at end of file +nbl_create_executable_project("" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") \ No newline at end of file From e932e23e0e752ca175f366fd008c47d72e8f26fb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 15:08:09 +0200 Subject: [PATCH 013/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 74ff08fc88..4ee9b1ba78 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 74ff08fc88bac706e962af4d563b3b51f19cc761 +Subproject commit 4ee9b1ba7847fa505afeb9935ab19a0466a35368 From 8eace54c83d5f8d99564488cc0662bb85605784c Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 18:35:28 +0200 Subject: [PATCH 014/148] Add CTest for Nabla IMGUI Test Engine, update the tool to use argparse and use running modes --- tools/nite/CMakeLists.txt | 22 ++++++++++++- tools/nite/main.cpp | 66 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt index 6fb01849c6..4e7467e2ed 100644 --- a/tools/nite/CMakeLists.txt +++ b/tools/nite/CMakeLists.txt @@ -11,4 +11,24 @@ set(NBL_EXTRA_LIBS "${NBL_EXT_IMGUI_UI_LIB}" # Nabla IMGUI backend ) -nbl_create_executable_project("" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") \ No newline at end of file +nbl_create_executable_project("" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") + +add_dependencies(${EXECUTABLE_NAME} argparse) +target_include_directories(${EXECUTABLE_NAME} PUBLIC + $ +) + +nbl_adjust_flags(MAP_RELEASE Release MAP_RELWITHDEBINFO RelWithDebInfo MAP_DEBUG Debug) +nbl_adjust_definitions() + +enable_testing() + +add_test(NAME NBL_NITE_RUN_SUITE_BASIC_TESTS + COMMAND "$" --mode cmd --group test + COMMAND_EXPAND_LISTS +) + +add_test(NAME NBL_NITE_RUN_SUITE_PERF_TESTS + COMMAND "$" --mode cmd --group perf + COMMAND_EXPAND_LISTS +) \ No newline at end of file diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index 10f126b1f6..2f0f50c7f2 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -19,6 +19,9 @@ #include "imgui_te_ui.h" #include "imgui_te_utils.h" +// Argparse +#include + using namespace nbl; using namespace core; using namespace hlsl; @@ -113,6 +116,33 @@ class NITETool final : public examples::SimpleWindowedApplication inline bool onAppInitialized(smart_refctd_ptr&& system) override { + + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_MODE_ARG = "--mode"; // "cmd" || "gui" + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_GROUP_ARG = "--group"; // "test" || "perf" + + argparse::ArgumentParser program("[NITE]: Performs Suite Test for Nabla IMGUI backend"); + + program.add_argument(NBL_MODE_ARG.data()) + .default_value("gui") + .help("Running mode, use \"cmd\" for running from command line and \"gui\" for GUI (default)"); + + program.add_argument(NBL_GROUP_ARG.data()) + .default_value("test") + .help("Running group, use \"test\" (default) for running basic tests and \"perf\" for performance tests"); + + try + { + program.parse_args({ argv.data(), argv.data() + argv.size() }); + } + catch (const std::exception& err) + { + std::cerr << err.what() << std::endl << program; + return 1; + } + + const auto pModeArg = program.get(NBL_MODE_ARG.data()); + const auto pGroupArg = program.get(NBL_GROUP_ARG.data()); + m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) @@ -196,6 +226,22 @@ class NITETool final : public examples::SimpleWindowedApplication ImGuiTestEngine_Start(engine, ctx); ImGuiTestEngine_InstallDefaultCrashHandler(); + ImGuiTestGroup group = ImGuiTestGroup_Unknown; + { + if (pGroupArg == "test") + group = ImGuiTestGroup_Tests; + else if (pGroupArg == "perf") + group = ImGuiTestGroup_Perfs; + } + ImGuiTestRunFlags flags = ImGuiTestRunFlags_None; + { + if (pModeArg == "cmd") + flags = ImGuiTestRunFlags_RunFromCommandLine; + else if (pModeArg == "gui") + flags = ImGuiTestRunFlags_RunFromGui; + } + ImGuiTestEngine_QueueTests(engine, group, nullptr, flags); + ui->registerListener([this]() -> void { ImGuiTestEngine_ShowTestEngineWindows(engine, nullptr); @@ -265,7 +311,7 @@ class NITETool final : public examples::SimpleWindowedApplication auto* const cb = m_cmdBufs.data()[resourceIx].get(); cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - cb->beginDebugMarker("NITETool Frame"); + cb->beginDebugMarker("NITE Tool Frame"); auto* queue = getGraphicsQueue(); @@ -339,7 +385,7 @@ class NITETool final : public examples::SimpleWindowedApplication } } - m_window->setCaption("IMGUI Nabla Backend Test"); + m_window->setCaption("[Nabla IMGUI Test Engine]"); m_surface->present(m_currentImageAcquire.imageIndex, rendered); // Post swap Test Engine @@ -361,6 +407,22 @@ class NITETool final : public examples::SimpleWindowedApplication ImGuiTestEngine_GetResult(engine, tested, successed); const bool good = tested == successed; + ImVector* tests = _NBL_NEW(ImVector); + ImGuiTestEngine_GetTestList(engine, tests); + + if (successed < tested) + { + m_logger->log("Failing Tests: ", ILogger::ELL_ERROR); + + for (auto* test : *tests) + if (test->Output.Status == ImGuiTestStatus_Error) + m_logger->log("- " + std::string(test->Name), ILogger::ELL_ERROR); + } + + m_logger->log(std::string("Tests Result: ") + (good ? "PASSING" : "FAILING"), (good ? ILogger::ELL_PERFORMANCE : ILogger::ELL_ERROR)); + m_logger->log(std::string("(") + std::to_string(successed) + "/" + std::to_string(tested) + " tests passed)", ILogger::ELL_PERFORMANCE); + + _NBL_DELETE(tests); ImGuiTestEngine_Stop(engine); return device_base_t::onAppTerminated() && good; From fe5dc46b4e81ea887d2d97e7b1b89766e6ca5c4a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 10 Jul 2024 18:55:06 +0200 Subject: [PATCH 015/148] add --queue nite argument which toggles whether tests should be queued and executed immediately after startup or should let user browse GUI and execute stuff by hand, use queued mode with CTest --- tools/nite/CMakeLists.txt | 4 ++-- tools/nite/main.cpp | 43 +++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt index 4e7467e2ed..91ee8e3a47 100644 --- a/tools/nite/CMakeLists.txt +++ b/tools/nite/CMakeLists.txt @@ -24,11 +24,11 @@ nbl_adjust_definitions() enable_testing() add_test(NAME NBL_NITE_RUN_SUITE_BASIC_TESTS - COMMAND "$" --mode cmd --group test + COMMAND "$" --mode cmd --group test --queued COMMAND_EXPAND_LISTS ) add_test(NAME NBL_NITE_RUN_SUITE_PERF_TESTS - COMMAND "$" --mode cmd --group perf + COMMAND "$" --mode cmd --group perf --queued COMMAND_EXPAND_LISTS ) \ No newline at end of file diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index 2f0f50c7f2..b8afa5ec76 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -117,18 +117,23 @@ class NITETool final : public examples::SimpleWindowedApplication inline bool onAppInitialized(smart_refctd_ptr&& system) override { - _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_MODE_ARG = "--mode"; // "cmd" || "gui" - _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_GROUP_ARG = "--group"; // "test" || "perf" + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_QUEUE_ARG = "--queued"; // flag + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_MODE_ARG = "--mode"; // "cmd" || "gui" value + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_GROUP_ARG = "--group"; // "test" || "perf" value argparse::ArgumentParser program("[NITE]: Performs Suite Test for Nabla IMGUI backend"); + program.add_argument(NBL_QUEUE_ARG.data()) + .flag() + .help("use this argument to queue execution of tests depending on --group argument, otherwise (default) you can browse GUI freely"); + program.add_argument(NBL_MODE_ARG.data()) .default_value("gui") - .help("Running mode, use \"cmd\" for running from command line and \"gui\" for GUI (default)"); + .help("use \"cmd\" for running from command line and \"gui\" for GUI (default)"); program.add_argument(NBL_GROUP_ARG.data()) .default_value("test") - .help("Running group, use \"test\" (default) for running basic tests and \"perf\" for performance tests"); + .help("use \"test\" (default) for running basic tests and \"perf\" for performance tests"); try { @@ -140,6 +145,7 @@ class NITETool final : public examples::SimpleWindowedApplication return 1; } + const auto pQueueArg = program.get(NBL_QUEUE_ARG.data()); const auto pModeArg = program.get(NBL_MODE_ARG.data()); const auto pGroupArg = program.get(NBL_GROUP_ARG.data()); @@ -226,21 +232,24 @@ class NITETool final : public examples::SimpleWindowedApplication ImGuiTestEngine_Start(engine, ctx); ImGuiTestEngine_InstallDefaultCrashHandler(); - ImGuiTestGroup group = ImGuiTestGroup_Unknown; - { - if (pGroupArg == "test") - group = ImGuiTestGroup_Tests; - else if (pGroupArg == "perf") - group = ImGuiTestGroup_Perfs; - } - ImGuiTestRunFlags flags = ImGuiTestRunFlags_None; + if (pQueueArg) { - if (pModeArg == "cmd") - flags = ImGuiTestRunFlags_RunFromCommandLine; - else if (pModeArg == "gui") - flags = ImGuiTestRunFlags_RunFromGui; + ImGuiTestGroup group = ImGuiTestGroup_Unknown; + { + if (pGroupArg == "test") + group = ImGuiTestGroup_Tests; + else if (pGroupArg == "perf") + group = ImGuiTestGroup_Perfs; + } + ImGuiTestRunFlags flags = ImGuiTestRunFlags_None; + { + if (pModeArg == "cmd") + flags = ImGuiTestRunFlags_RunFromCommandLine; + else if (pModeArg == "gui") + flags = ImGuiTestRunFlags_RunFromGui; + } + ImGuiTestEngine_QueueTests(engine, group, nullptr, flags); } - ImGuiTestEngine_QueueTests(engine, group, nullptr, flags); ui->registerListener([this]() -> void { From d755e83c899ff5f3de20d5736f03e5334d2398ca Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 11 Jul 2024 08:40:07 +0200 Subject: [PATCH 016/148] create tools/nite/README.md --- tools/nite/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tools/nite/README.md diff --git a/tools/nite/README.md b/tools/nite/README.md new file mode 100644 index 0000000000..99eb6b921b --- /dev/null +++ b/tools/nite/README.md @@ -0,0 +1,21 @@ +# Nabla IMGUI Test Engine + +Performs suite tests for Nabla IMGUI backend. + +## CTest + +Build the target with desired configuration eg. `Debug`, open command line in the target's build directory and execute + +```bash +ctest -C Debug --progress --stop-on-failure +``` + + + +CTest will execute `NBL_NITE_RUN_SUITE_BASIC_TESTS` first then `NBL_NITE_RUN_SUITE_PERF_TESTS`. Once we pass the first we can say we have 100% working backend! (currently we get SegFault somewhere in half of `NBL_NITE_RUN_SUITE_BASIC_TESTS` because our backend is still WIP) + +## GUI + +If you want to pick individual tests and browse GUI just execute the target's executable with no arguments. + + From f2f89f8ec8c3b7a9dd512306b0a9311cd2e9cb04 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz <34793522+AnastaZIuk@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:00:39 +0200 Subject: [PATCH 017/148] Update README.md, use github uploads for artifacts stored on their aws --- tools/nite/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/nite/README.md b/tools/nite/README.md index 99eb6b921b..6a8d56e400 100644 --- a/tools/nite/README.md +++ b/tools/nite/README.md @@ -10,7 +10,7 @@ Build the target with desired configuration eg. `Debug`, open command line in th ctest -C Debug --progress --stop-on-failure ``` - +https://github.com/Devsh-Graphics-Programming/Nabla/assets/34793522/2b739994-9900-4789-ba54-6435daded632 CTest will execute `NBL_NITE_RUN_SUITE_BASIC_TESTS` first then `NBL_NITE_RUN_SUITE_PERF_TESTS`. Once we pass the first we can say we have 100% working backend! (currently we get SegFault somewhere in half of `NBL_NITE_RUN_SUITE_BASIC_TESTS` because our backend is still WIP) @@ -18,4 +18,5 @@ CTest will execute `NBL_NITE_RUN_SUITE_BASIC_TESTS` first then `NBL_NITE_RUN_SUI If you want to pick individual tests and browse GUI just execute the target's executable with no arguments. - +https://github.com/Devsh-Graphics-Programming/Nabla/assets/34793522/79876d13-cf4e-4476-9ab5-7d9b56dc695e + From a7102f08b969a8b5743da24819ebe9dca59c0e8a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 11 Jul 2024 11:57:05 +0200 Subject: [PATCH 018/148] update Nabla IMGUI backend to use separated immutable sampler with font atlas as 2D array texture, use BDA for requesting texture index. Add comments regarding our base instance indexing as replacement for gl_DrawID and a few more comments + minor changes --- include/nbl/ext/ImGui/ImGui.h | 6 +- src/nbl/ext/ImGui/ImGui.cpp | 87 +++++++++++++------------ src/nbl/ext/ImGui/shaders/common.hlsl | 2 +- src/nbl/ext/ImGui/shaders/fragment.hlsl | 16 ++++- src/nbl/ext/ImGui/shaders/vertex.hlsl | 7 ++ 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d4a2443347..be1576e61d 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -21,10 +21,10 @@ class UI final : public core::IReferenceCounted private: core::smart_refctd_ptr createDescriptorSetLayout(); void createPipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); - void createFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); + void createFontAtlas2DArrayTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); void updateDescriptorSets(); void createSystem(); - void createFontSampler(); + void createFontAtlasSampler(); void createDescriptorPool(); void handleMouseEvents(float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents) const; @@ -36,7 +36,7 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr m_descriptorPool; core::smart_refctd_ptr m_gpuDescriptorSet; core::smart_refctd_ptr pipeline; - core::smart_refctd_ptr m_fontTexture; + core::smart_refctd_ptr m_fontAtlas2DArrayTexture; core::smart_refctd_ptr m_window; std::vector> m_mdiBuffers; const uint32_t maxFramesInFlight; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 8b67bd6777..74c1f525fe 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -23,15 +23,23 @@ namespace nbl::ext::imgui { smart_refctd_ptr UI::createDescriptorSetLayout() { - nbl::video::IGPUDescriptorSetLayout::SBinding bindings[] = - { + const IGPUDescriptorSetLayout::SBinding bindings[] = + { { .binding = 0u, - .type = asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::ESS_FRAGMENT, + .count = 1u, + .immutableSamplers = &m_fontSampler + }, + { + .binding = 1u, + .type = IDescriptor::E_TYPE::ET_SAMPLER, .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, .stageFlags = IShader::ESS_FRAGMENT, - .count = 1, - .samplers = nullptr // TODO: m_fontSampler? + .count = 1u, + .immutableSamplers = &m_fontSampler } }; @@ -45,7 +53,7 @@ namespace nbl::ext::imgui { { .stageFlags = IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, - .offset = 0, + .offset = 0u, .size = sizeof(PushConstants) } }; @@ -141,7 +149,7 @@ namespace nbl::ext::imgui IGPUGraphicsPipeline::SCreationParams params[1]; { - auto& param = params[0]; + auto& param = params[0u]; param.layout = pipelineLayout.get(); param.shaders = specs; param.renderpass = renderpass; @@ -156,7 +164,7 @@ namespace nbl::ext::imgui } } - void UI::createFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) + void UI::createFontAtlas2DArrayTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -215,7 +223,7 @@ namespace nbl::ext::imgui region->imageSubresource = {}; region->imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; region->imageSubresource.layerCount = 1u; - region->imageOffset = { 0, 0, 0 }; + region->imageOffset = { 0u, 0u, 0u }; region->imageExtent = { params.extent.width, params.extent.height, 1u }; } @@ -233,7 +241,7 @@ namespace nbl::ext::imgui assert(false); } - image->setObjectDebugName("Nabla IMGUI extension Font Image"); + image->setObjectDebugName("Nabla IMGUI extension Font Atlas"); { IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; SIntendedSubmitInfo sInfo; @@ -248,11 +256,11 @@ namespace nbl::ext::imgui sInfo.queue = transfer; sInfo.waitSemaphores = {}; - sInfo.commandBuffers = { &cmdInfo, 1 }; + sInfo.commandBuffers = { &cmdInfo, 1u }; sInfo.scratchSemaphore = { .semaphore = scratchSemaphore.get(), - .value = 0, + .value = 0u, .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS }; @@ -312,11 +320,11 @@ namespace nbl::ext::imgui { IGPUImageView::SCreationParams params; params.format = image->getCreationParameters().format; - params.viewType = IImageView::ET_2D; + params.viewType = IImageView::ET_2D_ARRAY; params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontTexture = m_device->createImageView(std::move(params)); + m_fontAtlas2DArrayTexture = m_device->createImageView(std::move(params)); } } @@ -357,32 +365,26 @@ namespace nbl::ext::imgui void UI::updateDescriptorSets() { - _NBL_STATIC_INLINE_CONSTEXPR auto NBL_RESOURCES_AMOUNT = 1u; - - IGPUDescriptorSet::SDescriptorInfo info[NBL_RESOURCES_AMOUNT]; - { - auto& font = info[0u]; - font.desc = m_fontTexture; - font.info.image.sampler = m_fontSampler; - font.info.image.imageLayout = nbl::asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; - } - - IGPUDescriptorSet::SWriteDescriptorSet write[NBL_RESOURCES_AMOUNT]; - for (auto i = 0u; i < NBL_RESOURCES_AMOUNT; ++i) - { - auto& w = write[i]; - - w.dstSet = m_gpuDescriptorSet.get(); - w.binding = 0; - w.arrayElement = 0; - w.count = 1; - w.info = info + i; - } + // texture atlas, note we don't create info & write for the font sampler because ours is immutable and baked into DS layout + IGPUDescriptorSet::SDescriptorInfo iInfo = {}; + iInfo.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + iInfo.desc = m_fontAtlas2DArrayTexture; + + const IGPUDescriptorSet::SWriteDescriptorSet writes[] = + { + { + .dstSet = m_gpuDescriptorSet.get(), + .binding = 0u, + .arrayElement = 0u, + .count = 1u, + .info = &iInfo + } + }; - m_device->updateDescriptorSets(NBL_RESOURCES_AMOUNT, write, 0u, nullptr); + m_device->updateDescriptorSets(writes, {}); } - void UI::createFontSampler() + void UI::createFontAtlasSampler() { // TODO: Recheck this settings IGPUSampler::SParams params{}; @@ -394,14 +396,15 @@ namespace nbl::ext::imgui params.TextureWrapW = ISampler::ETC_REPEAT; m_fontSampler = m_device->createSampler(params); + m_fontSampler->setObjectDebugName("Nabla Font Atlas Sampler"); } void UI::createDescriptorPool() { IDescriptorPool::SCreateInfo createInfo = {}; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)] = 1u; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER)] = 1u; - createInfo.maxSets = 1; + createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = 1u; + createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = 1u; + createInfo.maxSets = 1u; createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_NONE; m_descriptorPool = m_device->createDescriptorPool(std::move(createInfo)); @@ -514,11 +517,11 @@ namespace nbl::ext::imgui IMGUI_CHECKVERSION(); ImGui::CreateContext(); - createFontSampler(); + createFontAtlasSampler(); createDescriptorPool(); createDescriptorSetLayout(); createPipeline(renderpass, pipelineCache); - createFontTexture(transistentCMD.get(), tQueue); + createFontAtlas2DArrayTexture(transistentCMD.get(), tQueue); prepareKeyMapForDesktop(); adjustGlobalFontScale(); updateDescriptorSets(); diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 00b8f485a3..2b2718e55a 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -9,8 +9,8 @@ struct PSInput { float4 position : SV_Position; - float4 color : COLOR0; float2 uv : TEXCOORD0; + float4 color : COLOR0; float clip[4] : SV_ClipDistance; }; diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 63ade940ba..cfe5bdc3a2 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -2,10 +2,20 @@ [[vk::push_constant]] struct PushConstants pc; -[[vk::combinedImageSampler]][[vk::binding(0, 0)]] Texture2D sampleTexture : register(t0); -[[vk::combinedImageSampler]][[vk::binding(0, 0)]] SamplerState linearSampler : register(s0); +// separable image samplers, one to handle all textures +[[vk::binding(0,0)]] Texture2DArray texture; +[[vk::binding(1,0)]] SamplerState samplerState; + +/* + we use Indirect Indexed draw call to render whole GUI, note we do a cross + platform trick and use base instance index as replacement for gl_DrawID + to request per object data with BDA +*/ float4 PSMain(PSInput input, uint drawID : SV_InstanceID) : SV_Target0 { - return input.color * sampleTexture.Sample(linearSampler, input.uv); + // BDA for requesting object data + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); + + return input.color * texture.Sample(samplerState, float32_t3(input.uv, self.texId)); } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index 4c8b31a332..c69136b362 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -2,6 +2,12 @@ [[vk::push_constant]] struct PushConstants pc; +/* + we use Indirect Indexed draw call to render whole GUI, note we do a cross + platform trick and use base instance index as replacement for gl_DrawID + to request per object data with BDA +*/ + PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) { PSInput output; @@ -16,6 +22,7 @@ PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) const float2 vMin = self.aabbMin.unpack(); const float2 vMax = self.aabbMax.unpack(); + // clip planes calculations, axis aligned output.clip[0] = output.position.x - vMin.x; output.clip[1] = output.position.y - vMin.y; output.clip[2] = vMax.x - output.position.x; From c8198be9d403a5a153f53dfa2f5c5dade4d261c9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 11 Jul 2024 16:29:33 +0200 Subject: [PATCH 019/148] upgrade Nabla IMGUI backend key IO, use IMGUI_DISABLE_OBSOLETE_KEYIO define, refactor current legacy mouse handle & add key events handle --- 3rdparty/CMakeLists.txt | 1 + include/nbl/ext/ImGui/ImGui.h | 9 +- include/nbl/ui/KeyCodes.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 265 ++++++++++++++++++++++++++-------- tools/nite/main.cpp | 30 +++- 5 files changed, 237 insertions(+), 70 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index a05fbaa54b..d1d4755675 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -343,6 +343,7 @@ string(APPEND NBL_IMPL_IMCONFIG_CONTENT [=[ #include "imgui_test_suite_imconfig.h" // use test engine's config +#define IMGUI_DISABLE_OBSOLETE_KEYIO #undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things ]=] ) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index be1576e61d..3c0c32b36c 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -11,9 +11,9 @@ class UI final : public core::IReferenceCounted ~UI() override; bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex); - void update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events + void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); int registerListener(std::function const& listener); - bool unregisterListener(int listenerId); + bool unregisterListener(uint32_t id); void* getContext(); void setContext(void* imguiContext); @@ -26,7 +26,8 @@ class UI final : public core::IReferenceCounted void createSystem(); void createFontAtlasSampler(); void createDescriptorPool(); - void handleMouseEvents(float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents) const; + void handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const; + void handleKeyEvents(const core::SRange& events) const; core::smart_refctd_ptr system; core::smart_refctd_ptr logger; @@ -44,7 +45,7 @@ class UI final : public core::IReferenceCounted // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; struct Subscriber { - int id = -1; + uint32_t id = 0; std::function listener = nullptr; }; std::vector m_subscribers{}; diff --git a/include/nbl/ui/KeyCodes.h b/include/nbl/ui/KeyCodes.h index 98490d0d6f..b6d05aed36 100644 --- a/include/nbl/ui/KeyCodes.h +++ b/include/nbl/ui/KeyCodes.h @@ -139,7 +139,7 @@ enum E_KEY_CODE : uint8_t EKC_COUNT, }; -inline char keyCodeToChar(E_KEY_CODE code, bool shiftPressed) +constexpr char keyCodeToChar(E_KEY_CODE code, bool shiftPressed) { char result = 0; if (!shiftPressed) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 74c1f525fe..c9ed349d2d 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -328,35 +328,6 @@ namespace nbl::ext::imgui } } - void prepareKeyMapForDesktop() - { - ImGuiIO& io = ImGui::GetIO(); - // TODO: - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. - //io.KeyMap[ImGuiKey_Tab] = MSDL::SDL_SCANCODE_TAB; - //io.KeyMap[ImGuiKey_LeftArrow] = MSDL::SDL_SCANCODE_LEFT; - //io.KeyMap[ImGuiKey_RightArrow] = MSDL::SDL_SCANCODE_RIGHT; - //io.KeyMap[ImGuiKey_UpArrow] = MSDL::SDL_SCANCODE_UP; - //io.KeyMap[ImGuiKey_DownArrow] = MSDL::SDL_SCANCODE_DOWN; - //io.KeyMap[ImGuiKey_PageUp] = MSDL::SDL_SCANCODE_PAGEUP; - //io.KeyMap[ImGuiKey_PageDown] = MSDL::SDL_SCANCODE_PAGEDOWN; - //io.KeyMap[ImGuiKey_Home] = MSDL::SDL_SCANCODE_HOME; - //io.KeyMap[ImGuiKey_End] = MSDL::SDL_SCANCODE_END; - //io.KeyMap[ImGuiKey_Insert] = MSDL::SDL_SCANCODE_INSERT; - //io.KeyMap[ImGuiKey_Delete] = MSDL::SDL_SCANCODE_DELETE; - //io.KeyMap[ImGuiKey_Backspace] = MSDL::SDL_SCANCODE_BACKSPACE; - //io.KeyMap[ImGuiKey_Space] = MSDL::SDL_SCANCODE_SPACE; - //io.KeyMap[ImGuiKey_Enter] = MSDL::SDL_SCANCODE_RETURN; - //io.KeyMap[ImGuiKey_Escape] = MSDL::SDL_SCANCODE_ESCAPE; - //io.KeyMap[ImGuiKey_KeyPadEnter] = MSDL::SDL_SCANCODE_KP_ENTER; - //io.KeyMap[ImGuiKey_A] = MSDL::SDL_SCANCODE_A; - //io.KeyMap[ImGuiKey_C] = MSDL::SDL_SCANCODE_C; - //io.KeyMap[ImGuiKey_V] = MSDL::SDL_SCANCODE_V; - //io.KeyMap[ImGuiKey_X] = MSDL::SDL_SCANCODE_X; - //io.KeyMap[ImGuiKey_Y] = MSDL::SDL_SCANCODE_Y; - //io.KeyMap[ImGuiKey_Z] = MSDL::SDL_SCANCODE_Z; - } - static void adjustGlobalFontScale() { ImGuiIO& io = ImGui::GetIO(); @@ -410,41 +381,217 @@ namespace nbl::ext::imgui m_descriptorPool = m_device->createDescriptorPool(std::move(createInfo)); assert(m_descriptorPool); } - - void UI::handleMouseEvents(float const mousePosX, float const mousePosY, size_t const mouseEventsCount, SMouseEvent const * mouseEvents) const + + void UI::handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const { auto& io = ImGui::GetIO(); - io.MousePos.x = mousePosX - m_window->getX(); - io.MousePos.y = mousePosY - m_window->getY(); + const auto position = mousePosition - nbl::hlsl::float32_t2(m_window->getX(), m_window->getY()); + + io.AddMousePosEvent(position.x, position.y); - for (size_t i = 0; i < mouseEventsCount; ++i) + for (const auto& e : events) { - auto const & event = mouseEvents[i]; - if(event.type == SMouseEvent::EET_CLICK) - { - int buttonIndex = -1; - if (event.clickEvent.mouseButton == EMB_LEFT_BUTTON) - buttonIndex = 0; - else if (event.clickEvent.mouseButton == EMB_RIGHT_BUTTON) - buttonIndex = 1; - else if (event.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) - buttonIndex = 2; - - if (buttonIndex == -1) - { - assert(false); + switch (e.type) + { + case SMouseEvent::EET_CLICK: + { + ImGuiMouseButton_ button = ImGuiMouseButton_COUNT; + if (e.clickEvent.mouseButton == EMB_LEFT_BUTTON) + button = ImGuiMouseButton_Left; + else if (e.clickEvent.mouseButton == EMB_RIGHT_BUTTON) + button = ImGuiMouseButton_Right; + else if (e.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) + button = ImGuiMouseButton_Middle; + + if (button == ImGuiMouseButton_COUNT) continue; - } - if(event.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) - io.MouseDown[buttonIndex] = true; - else if (event.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) - io.MouseDown[buttonIndex] = false; + if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) + io.AddMouseButtonEvent(button, true); + else if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) + io.AddMouseButtonEvent(button, false); + } break; + + case SMouseEvent::EET_SCROLL: + { + _NBL_STATIC_INLINE_CONSTEXPR auto scalar = 0.02f; + const auto wheel = nbl::hlsl::float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; + + io.AddMouseWheelEvent(wheel.x, wheel.y); + } break; + + case SMouseEvent::EET_MOVEMENT: + + default: + break; } } } + struct NBL_TO_IMGUI_KEY_BIND + { + ImGuiKey target; + char physicalSmall; + char physicalBig; + }; + + // maps Nabla keys to IMGUIs + _NBL_STATIC_INLINE_CONSTEXPR std::array createKeyMap() + { + std::array map = { { NBL_TO_IMGUI_KEY_BIND{ImGuiKey_None, '0', '0'} } }; + + #define NBL_REGISTER_KEY(__NBL_KEY__, __IMGUI_KEY__) \ + map[__NBL_KEY__] = NBL_TO_IMGUI_KEY_BIND{__IMGUI_KEY__, keyCodeToChar(__NBL_KEY__, false), keyCodeToChar(__NBL_KEY__, true)}; + + NBL_REGISTER_KEY(EKC_BACKSPACE, ImGuiKey_Backspace); + NBL_REGISTER_KEY(EKC_TAB, ImGuiKey_Tab); + NBL_REGISTER_KEY(EKC_ENTER, ImGuiKey_Enter); + NBL_REGISTER_KEY(EKC_LEFT_SHIFT, ImGuiKey_LeftShift); + NBL_REGISTER_KEY(EKC_RIGHT_SHIFT, ImGuiKey_RightShift); + NBL_REGISTER_KEY(EKC_LEFT_CONTROL, ImGuiKey_LeftCtrl); + NBL_REGISTER_KEY(EKC_RIGHT_CONTROL, ImGuiKey_RightCtrl); + NBL_REGISTER_KEY(EKC_LEFT_ALT, ImGuiKey_LeftAlt); + NBL_REGISTER_KEY(EKC_RIGHT_ALT, ImGuiKey_RightAlt); + NBL_REGISTER_KEY(EKC_PAUSE, ImGuiKey_Pause); + NBL_REGISTER_KEY(EKC_CAPS_LOCK, ImGuiKey_CapsLock); + NBL_REGISTER_KEY(EKC_ESCAPE, ImGuiKey_Escape); + NBL_REGISTER_KEY(EKC_SPACE, ImGuiKey_Space); + NBL_REGISTER_KEY(EKC_PAGE_UP, ImGuiKey_PageUp); + NBL_REGISTER_KEY(EKC_PAGE_DOWN, ImGuiKey_PageDown); + NBL_REGISTER_KEY(EKC_END, ImGuiKey_End); + NBL_REGISTER_KEY(EKC_HOME, ImGuiKey_Home); + NBL_REGISTER_KEY(EKC_LEFT_ARROW, ImGuiKey_LeftArrow); + NBL_REGISTER_KEY(EKC_RIGHT_ARROW, ImGuiKey_RightArrow); + NBL_REGISTER_KEY(EKC_DOWN_ARROW, ImGuiKey_DownArrow); + NBL_REGISTER_KEY(EKC_UP_ARROW, ImGuiKey_UpArrow); + NBL_REGISTER_KEY(EKC_PRINT_SCREEN, ImGuiKey_PrintScreen); + NBL_REGISTER_KEY(EKC_INSERT, ImGuiKey_Insert); + NBL_REGISTER_KEY(EKC_DELETE, ImGuiKey_Delete); + NBL_REGISTER_KEY(EKC_APPS, ImGuiKey_Menu); + NBL_REGISTER_KEY(EKC_COMMA, ImGuiKey_Comma); + NBL_REGISTER_KEY(EKC_PERIOD, ImGuiKey_Period); + NBL_REGISTER_KEY(EKC_SEMICOLON, ImGuiKey_Semicolon); + NBL_REGISTER_KEY(EKC_OPEN_BRACKET, ImGuiKey_LeftBracket); + NBL_REGISTER_KEY(EKC_CLOSE_BRACKET, ImGuiKey_RightBracket); + NBL_REGISTER_KEY(EKC_BACKSLASH, ImGuiKey_Backslash); + NBL_REGISTER_KEY(EKC_APOSTROPHE, ImGuiKey_Apostrophe); + NBL_REGISTER_KEY(EKC_ADD, ImGuiKey_KeypadAdd); + NBL_REGISTER_KEY(EKC_SUBTRACT, ImGuiKey_KeypadSubtract); + NBL_REGISTER_KEY(EKC_MULTIPLY, ImGuiKey_KeypadMultiply); + NBL_REGISTER_KEY(EKC_DIVIDE, ImGuiKey_KeypadDivide); + NBL_REGISTER_KEY(EKC_0, ImGuiKey_0); + NBL_REGISTER_KEY(EKC_1, ImGuiKey_1); + NBL_REGISTER_KEY(EKC_2, ImGuiKey_2); + NBL_REGISTER_KEY(EKC_3, ImGuiKey_3); + NBL_REGISTER_KEY(EKC_4, ImGuiKey_4); + NBL_REGISTER_KEY(EKC_5, ImGuiKey_5); + NBL_REGISTER_KEY(EKC_6, ImGuiKey_6); + NBL_REGISTER_KEY(EKC_7, ImGuiKey_7); + NBL_REGISTER_KEY(EKC_8, ImGuiKey_8); + NBL_REGISTER_KEY(EKC_9, ImGuiKey_9); + NBL_REGISTER_KEY(EKC_A, ImGuiKey_A); + NBL_REGISTER_KEY(EKC_B, ImGuiKey_B); + NBL_REGISTER_KEY(EKC_C, ImGuiKey_C); + NBL_REGISTER_KEY(EKC_D, ImGuiKey_D); + NBL_REGISTER_KEY(EKC_E, ImGuiKey_E); + NBL_REGISTER_KEY(EKC_F, ImGuiKey_F); + NBL_REGISTER_KEY(EKC_G, ImGuiKey_G); + NBL_REGISTER_KEY(EKC_H, ImGuiKey_H); + NBL_REGISTER_KEY(EKC_I, ImGuiKey_I); + NBL_REGISTER_KEY(EKC_J, ImGuiKey_J); + NBL_REGISTER_KEY(EKC_K, ImGuiKey_K); + NBL_REGISTER_KEY(EKC_L, ImGuiKey_L); + NBL_REGISTER_KEY(EKC_M, ImGuiKey_M); + NBL_REGISTER_KEY(EKC_N, ImGuiKey_N); + NBL_REGISTER_KEY(EKC_O, ImGuiKey_O); + NBL_REGISTER_KEY(EKC_P, ImGuiKey_P); + NBL_REGISTER_KEY(EKC_Q, ImGuiKey_Q); + NBL_REGISTER_KEY(EKC_R, ImGuiKey_R); + NBL_REGISTER_KEY(EKC_S, ImGuiKey_S); + NBL_REGISTER_KEY(EKC_T, ImGuiKey_T); + NBL_REGISTER_KEY(EKC_U, ImGuiKey_U); + NBL_REGISTER_KEY(EKC_V, ImGuiKey_V); + NBL_REGISTER_KEY(EKC_W, ImGuiKey_W); + NBL_REGISTER_KEY(EKC_X, ImGuiKey_X); + NBL_REGISTER_KEY(EKC_Y, ImGuiKey_Y); + NBL_REGISTER_KEY(EKC_Z, ImGuiKey_Z); + NBL_REGISTER_KEY(EKC_NUMPAD_0, ImGuiKey_Keypad0); + NBL_REGISTER_KEY(EKC_NUMPAD_1, ImGuiKey_Keypad1); + NBL_REGISTER_KEY(EKC_NUMPAD_2, ImGuiKey_Keypad2); + NBL_REGISTER_KEY(EKC_NUMPAD_3, ImGuiKey_Keypad3); + NBL_REGISTER_KEY(EKC_NUMPAD_4, ImGuiKey_Keypad4); + NBL_REGISTER_KEY(EKC_NUMPAD_5, ImGuiKey_Keypad5); + NBL_REGISTER_KEY(EKC_NUMPAD_6, ImGuiKey_Keypad6); + NBL_REGISTER_KEY(EKC_NUMPAD_7, ImGuiKey_Keypad7); + NBL_REGISTER_KEY(EKC_NUMPAD_8, ImGuiKey_Keypad8); + NBL_REGISTER_KEY(EKC_NUMPAD_9, ImGuiKey_Keypad9); + NBL_REGISTER_KEY(EKC_F1, ImGuiKey_F1); + NBL_REGISTER_KEY(EKC_F2, ImGuiKey_F2); + NBL_REGISTER_KEY(EKC_F3, ImGuiKey_F3); + NBL_REGISTER_KEY(EKC_F4, ImGuiKey_F4); + NBL_REGISTER_KEY(EKC_F5, ImGuiKey_F5); + NBL_REGISTER_KEY(EKC_F6, ImGuiKey_F6); + NBL_REGISTER_KEY(EKC_F7, ImGuiKey_F7); + NBL_REGISTER_KEY(EKC_F8, ImGuiKey_F8); + NBL_REGISTER_KEY(EKC_F9, ImGuiKey_F9); + NBL_REGISTER_KEY(EKC_F10, ImGuiKey_F10); + NBL_REGISTER_KEY(EKC_F11, ImGuiKey_F11); + NBL_REGISTER_KEY(EKC_F12, ImGuiKey_F12); + NBL_REGISTER_KEY(EKC_F13, ImGuiKey_F13); + NBL_REGISTER_KEY(EKC_F14, ImGuiKey_F14); + NBL_REGISTER_KEY(EKC_F15, ImGuiKey_F15); + NBL_REGISTER_KEY(EKC_F16, ImGuiKey_F16); + NBL_REGISTER_KEY(EKC_F17, ImGuiKey_F17); + NBL_REGISTER_KEY(EKC_F18, ImGuiKey_F18); + NBL_REGISTER_KEY(EKC_F19, ImGuiKey_F19); + NBL_REGISTER_KEY(EKC_F20, ImGuiKey_F20); + NBL_REGISTER_KEY(EKC_F21, ImGuiKey_F21); + NBL_REGISTER_KEY(EKC_F22, ImGuiKey_F22); + NBL_REGISTER_KEY(EKC_F23, ImGuiKey_F23); + NBL_REGISTER_KEY(EKC_F24, ImGuiKey_F24); + NBL_REGISTER_KEY(EKC_NUM_LOCK, ImGuiKey_NumLock); + NBL_REGISTER_KEY(EKC_SCROLL_LOCK, ImGuiKey_ScrollLock); + NBL_REGISTER_KEY(EKC_VOLUME_MUTE, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_UP, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_DOWN, ImGuiKey_None); + + return map; + } + + void UI::handleKeyEvents(const core::SRange& events) const + { + auto& io = ImGui::GetIO(); + + _NBL_STATIC_INLINE_CONSTEXPR auto keyMap = createKeyMap(); + + const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc + { + for (const auto& e : events) + if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) + return true; + + return false; + }(); + + for (const auto& e : events) + { + const auto& bind = keyMap[e.keyCode]; + const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; + + if(bind.target == ImGuiKey_None) + logger->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); + else + if (e.action == SKeyboardEvent::ECA_PRESSED) + { + io.AddKeyEvent(bind.target, true); + io.AddInputCharacter(iCharacter); + } + else if (e.action == SKeyboardEvent::ECA_RELEASED) + io.AddKeyEvent(bind.target, false); + } + } + UI::UI(smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) : m_device(core::smart_refctd_ptr(device)), m_window(core::smart_refctd_ptr(window)), maxFramesInFlight(_maxFramesInFlight) { @@ -522,7 +669,6 @@ namespace nbl::ext::imgui createDescriptorSetLayout(); createPipeline(renderpass, pipelineCache); createFontAtlas2DArrayTexture(transistentCMD.get(), tQueue); - prepareKeyMapForDesktop(); adjustGlobalFontScale(); updateDescriptorSets(); } @@ -531,6 +677,7 @@ namespace nbl::ext::imgui auto & io = ImGui::GetIO(); io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + io.BackendUsingLegacyKeyArrays = 0; // 0: using AddKeyEvent() [new way of handling events in imgui] m_mdiBuffers.resize(maxFramesInFlight); } @@ -936,13 +1083,15 @@ namespace nbl::ext::imgui return true; } - void UI::update(float const deltaTimeInSec, float const mousePosX, float const mousePosY, size_t const mouseEventsCount, ui::SMouseEvent const * mouseEvents) // TODO: Keyboard events + void UI::update(float const deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents) { auto & io = ImGui::GetIO(); + io.DeltaTime = deltaTimeInSec; io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); - handleMouseEvents(mousePosX, mousePosY, mouseEventsCount, mouseEvents); + handleMouseEvents(mousePosition, mouseEvents); + handleKeyEvents(keyboardEvents); ImGui::NewFrame(); @@ -960,11 +1109,11 @@ namespace nbl::ext::imgui return m_subscribers.back().id; } - bool UI::unregisterListener(int const listenerId) + bool UI::unregisterListener(const uint32_t id) { for (int i = m_subscribers.size() - 1; i >= 0; --i) { - if (m_subscribers[i].id == listenerId) + if (m_subscribers[i].id == id) { m_subscribers.erase(m_subscribers.begin() + i); return true; diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index b8afa5ec76..3c739d7368 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -272,7 +272,8 @@ class NITETool final : public examples::SimpleWindowedApplication struct { - std::vector mouse{}; + std::vector mouse {}; + std::vector keyboard {}; } capturedEvents; m_inputSystem->getDefaultMouse(&mouse); @@ -280,23 +281,38 @@ class NITETool final : public examples::SimpleWindowedApplication mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - for (auto event : events) + for (const auto& e : events) { - if (event.timeStamp < previousEventTimestamp) + if (e.timeStamp < previousEventTimestamp) continue; - previousEventTimestamp = event.timeStamp; - capturedEvents.mouse.push_back(event); + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); } }, m_logger.get()); keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void { - // TOOD + for (const auto& e : events) + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } }, m_logger.get()); const auto mousePosition = m_window->getCursorControl()->getPosition(); - ui->update(deltaTimeInSec, static_cast(mousePosition.x), static_cast(mousePosition.y), capturedEvents.mouse.size(), capturedEvents.mouse.data()); + + // C++ no instance of constructor matches the argument list argument types are: (std::_Vector_const_iterator>>, std::_Vector_const_iterator>>) + // const nbl::ui::IMouseEventChannel::range_t mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + // const nbl::ui::IKeyboardEventChannel::range_t keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + + core::SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + core::SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + + ui->update(deltaTimeInSec, { mousePosition.x , mousePosition.y}, mouseEvents, keyboardEvents); const auto resourceIx = m_realFrameIx % m_maxFramesInFlight; From b52077ada06afad47d11d439c1bb46ba6ce6d05c Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 12 Jul 2024 13:59:35 +0200 Subject: [PATCH 020/148] use FreeType to build and rasterize the imgui font atlas instead of stb_truetype --- 3rdparty/CMakeLists.txt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 8a25c846f2..fc0a26a762 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -287,6 +287,7 @@ if(NBL_BUILD_IMGUI) "${NBL_IMGUI_ROOT}/imgui_tables.cpp" "${NBL_IMGUI_ROOT}/imgui_widgets.cpp" "${NBL_IMGUI_ROOT}/imgui.cpp" + "${NBL_IMGUI_ROOT}/misc/freetype/imgui_freetype.cpp" "${NBL_IMGUI_ROOT}/imgui.h" "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.cpp" "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.h" @@ -294,11 +295,15 @@ if(NBL_BUILD_IMGUI) "${NBL_IMGUI_ROOT}/imstb_textedit.h" "${NBL_IMGUI_ROOT}/imstb_truetype.h" ) - target_include_directories(imgui - PUBLIC "${NBL_IMGUI_ROOT}" - PUBLIC "${NBL_IMGUI_ROOT}/misc/cpp" - PUBLIC "${NBL_IMGUI_ROOT}/backends" - PUBLIC "${NBL_IMGUI_TEST_SUITE_ROOT}" + + target_link_libraries(imgui PUBLIC freetype) + + target_include_directories(imgui PUBLIC + "${NBL_IMGUI_ROOT}" + "${NBL_IMGUI_ROOT}/misc/cpp" + "${NBL_IMGUI_ROOT}/backends" + "${NBL_IMGUI_TEST_SUITE_ROOT}" + $ ) add_library(implot STATIC @@ -362,6 +367,7 @@ string(APPEND NBL_IMPL_IMCONFIG_CONTENT [=[ #include "imgui_test_suite_imconfig.h" // use test engine's config +#define IMGUI_ENABLE_FREETYPE #define IMGUI_DISABLE_OBSOLETE_KEYIO #undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things ]=] From b3dc0cada0370d7abd174887a88dcfcc423ed933 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 12 Jul 2024 18:16:50 +0200 Subject: [PATCH 021/148] actually pass descriptor binding flags to Vulkan --- include/nbl/asset/IDescriptorSetLayout.h | 8 ++++---- include/nbl/video/CVulkanCommon.h | 10 ++++++++++ include/nbl/video/ILogicalDevice.h | 1 + src/nbl/video/CVulkanLogicalDevice.cpp | 20 ++++++++++++++++---- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/include/nbl/asset/IDescriptorSetLayout.h b/include/nbl/asset/IDescriptorSetLayout.h index 0e980b9bea..ab870c0fae 100644 --- a/include/nbl/asset/IDescriptorSetLayout.h +++ b/include/nbl/asset/IDescriptorSetLayout.h @@ -46,10 +46,10 @@ class IDescriptorSetLayout : public virtual core::IReferenceCounted // TODO: tr enum class E_CREATE_FLAGS : uint8_t { ECF_NONE = 0, - ECF_UPDATE_AFTER_BIND_BIT = 1u << 1, - ECF_UPDATE_UNUSED_WHILE_PENDING_BIT = 1u << 2, - ECF_PARTIALLY_BOUND_BIT = 1u << 3, - ECF_VARIABLE_DESCRIPTOR_COUNT_BIT = 1u << 4 + ECF_UPDATE_AFTER_BIND_BIT = 1u << 0, + ECF_UPDATE_UNUSED_WHILE_PENDING_BIT = 1u << 1, + ECF_PARTIALLY_BOUND_BIT = 1u << 2, + ECF_VARIABLE_DESCRIPTOR_COUNT_BIT = 1u << 3 }; uint32_t binding; diff --git a/include/nbl/video/CVulkanCommon.h b/include/nbl/video/CVulkanCommon.h index 1af4dd886e..0509115928 100644 --- a/include/nbl/video/CVulkanCommon.h +++ b/include/nbl/video/CVulkanCommon.h @@ -1027,6 +1027,16 @@ inline constexpr VkDescriptorType getVkDescriptorTypeFromDescriptorType(const as } } +inline VkDescriptorBindingFlagBits getVkDescriptorBindingFlagsFrom(const core::bitflag flags) +{ + // Wait for C++23 + //static_assert(std::to_underlying(IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT)==VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT); + //static_assert(IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT==VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT); + //static_assert(IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT==VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT); + //static_assert(IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT==VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT); + return static_cast(flags.value); +} + inline IPhysicalDevice::E_DRIVER_ID getDriverIdFromVkDriverId(const VkDriverId in) { if(in == VK_DRIVER_ID_AMD_PROPRIETARY) return IPhysicalDevice::E_DRIVER_ID::EDI_AMD_PROPRIETARY; diff --git a/include/nbl/video/ILogicalDevice.h b/include/nbl/video/ILogicalDevice.h index 84557c2edb..2203a28c1a 100644 --- a/include/nbl/video/ILogicalDevice.h +++ b/include/nbl/video/ILogicalDevice.h @@ -631,6 +631,7 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe { if (layout) { + // TODO: when creating the layouts, cache if they have any update after bindingings, and patch `createInfo.flags` with that here const auto setCount = setCounts ? *(setCountsIt):1u; createInfo.maxSets += setCount; for (uint32_t t=0; t(asset::IDescriptor::E_TYPE::ET_COUNT); ++t) diff --git a/src/nbl/video/CVulkanLogicalDevice.cpp b/src/nbl/video/CVulkanLogicalDevice.cpp index 332a46b3ec..c72805e359 100644 --- a/src/nbl/video/CVulkanLogicalDevice.cpp +++ b/src/nbl/video/CVulkanLogicalDevice.cpp @@ -539,9 +539,12 @@ core::smart_refctd_ptr CVulkanLogicalDevice::createDesc { std::vector vk_samplers; std::vector vk_dsLayoutBindings; + std::vector vk_bindingFlags; vk_samplers.reserve(maxSamplersCount); // Reserve to avoid resizing and pointer change while iterating vk_dsLayoutBindings.reserve(bindings.size()); + vk_bindingFlags.reserve(bindings.size()); + bool updateAfterBindFound = false; for (const auto& binding : bindings) { auto& vkDescSetLayoutBinding = vk_dsLayoutBindings.emplace_back(); @@ -560,13 +563,22 @@ core::smart_refctd_ptr CVulkanLogicalDevice::createDesc vk_samplers.push_back(static_cast(binding.immutableSamplers[i].get())->getInternalObject()); vkDescSetLayoutBinding.pImmutableSamplers = vk_samplers.data()+samplerOffset; } + + if (binding.createFlags.hasFlags(IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT)) + updateAfterBindFound = true; + vk_bindingFlags.emplace_back() = getVkDescriptorBindingFlagsFrom(binding.createFlags); } - VkDescriptorSetLayoutCreateInfo vk_createInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; - vk_createInfo.pNext = nullptr; // pNext of interest: VkDescriptorSetLayoutBindingFlagsCreateInfo - vk_createInfo.flags = 0; // Todo(achal): I would need to create a IDescriptorSetLayout::SCreationParams for this - vk_createInfo.bindingCount = vk_dsLayoutBindings.size(); + VkDescriptorSetLayoutBindingFlagsCreateInfo vk_bindingFlagsInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, nullptr }; + VkDescriptorSetLayoutCreateInfo vk_createInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, &vk_bindingFlagsInfo }; + // Todo(achal): I would need to create a IDescriptorSetLayout::SCreationParams for this + // Answer: We don't actually support any extensions/features that would necessitate exposing any other flag than update_after_bind + vk_createInfo.flags = 0; + if (updateAfterBindFound) + vk_createInfo.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; + vk_createInfo.bindingCount = vk_bindingFlagsInfo.bindingCount = vk_dsLayoutBindings.size(); vk_createInfo.pBindings = vk_dsLayoutBindings.data(); + vk_bindingFlagsInfo.pBindingFlags = vk_bindingFlags.data(); VkDescriptorSetLayout vk_dsLayout; if (m_devf.vk.vkCreateDescriptorSetLayout(m_vkdev,&vk_createInfo,nullptr,&vk_dsLayout)==VK_SUCCESS) From 38d80c34685424c176ccec98283b35799b595d19 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 12 Jul 2024 18:39:08 +0200 Subject: [PATCH 022/148] use Descriptor Indexing in the IMGUI backend. TODO: ds writes on new texture use request + no hardcoded ds layout etc --- 3rdparty/CMakeLists.txt | 3 + include/nbl/ext/ImGui/ImGui.h | 4 +- src/nbl/ext/ImGui/ImGui.cpp | 73 ++++++++++--------------- src/nbl/ext/ImGui/shaders/common.hlsl | 3 + src/nbl/ext/ImGui/shaders/fragment.hlsl | 7 ++- 5 files changed, 42 insertions(+), 48 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index fc0a26a762..e66af9fbd2 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -367,6 +367,9 @@ string(APPEND NBL_IMPL_IMCONFIG_CONTENT [=[ #include "imgui_test_suite_imconfig.h" // use test engine's config +// note we override it with default imguis (void*) type +#include +#define ImTextureID uint32_t #define IMGUI_ENABLE_FREETYPE #define IMGUI_DISABLE_OBSOLETE_KEYIO #undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 3c0c32b36c..9b6b192cc7 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -21,7 +21,7 @@ class UI final : public core::IReferenceCounted private: core::smart_refctd_ptr createDescriptorSetLayout(); void createPipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); - void createFontAtlas2DArrayTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); + void createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); void updateDescriptorSets(); void createSystem(); void createFontAtlasSampler(); @@ -37,7 +37,7 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr m_descriptorPool; core::smart_refctd_ptr m_gpuDescriptorSet; core::smart_refctd_ptr pipeline; - core::smart_refctd_ptr m_fontAtlas2DArrayTexture; + core::smart_refctd_ptr m_fontAtlasTexture; core::smart_refctd_ptr m_window; std::vector> m_mdiBuffers; const uint32_t maxFramesInFlight; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index c9ed349d2d..7d4e813d30 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -19,24 +19,28 @@ using namespace nbl::core; using namespace nbl::asset; using namespace nbl::ui; +// Nabla IMGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error +_NBL_STATIC_INLINE_CONSTEXPR ImTextureID NBL_FONT_ATLAS_TEX_ID = 0u; + namespace nbl::ext::imgui { smart_refctd_ptr UI::createDescriptorSetLayout() { + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + const IGPUDescriptorSetLayout::SBinding bindings[] = { { .binding = 0u, .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, - .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .createFlags = core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT, .stageFlags = IShader::ESS_FRAGMENT, - .count = 1u, - .immutableSamplers = &m_fontSampler + .count = NBL_MAX_IMGUI_TEXTURES }, { .binding = 1u, .type = IDescriptor::E_TYPE::ET_SAMPLER, - .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .createFlags = binding_flags_t::ECF_NONE, .stageFlags = IShader::ESS_FRAGMENT, .count = 1u, .immutableSamplers = &m_fontSampler @@ -164,7 +168,7 @@ namespace nbl::ext::imgui } } - void UI::createFontAtlas2DArrayTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) + void UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -184,6 +188,8 @@ namespace nbl::ext::imgui uint8_t* pixels = nullptr; int32_t width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + io.Fonts->SetTexID(NBL_FONT_ATLAS_TEX_ID); + assert(pixels != nullptr); assert(width > 0); assert(height > 0); @@ -320,11 +326,11 @@ namespace nbl::ext::imgui { IGPUImageView::SCreationParams params; params.format = image->getCreationParameters().format; - params.viewType = IImageView::ET_2D_ARRAY; + params.viewType = IImageView::ET_2D; params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontAtlas2DArrayTexture = m_device->createImageView(std::move(params)); + m_fontAtlasTexture = m_device->createImageView(std::move(params)); } } @@ -336,19 +342,24 @@ namespace nbl::ext::imgui void UI::updateDescriptorSets() { - // texture atlas, note we don't create info & write for the font sampler because ours is immutable and baked into DS layout - IGPUDescriptorSet::SDescriptorInfo iInfo = {}; - iInfo.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - iInfo.desc = m_fontAtlas2DArrayTexture; - + // texture atlas + user defined textures, note we don't create info & write pair for the font sampler because ours is immutable and baked into DS layout + std::array iInfo; + for (auto& it : iInfo) + { + it.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + it.desc = m_fontAtlasTexture; + } + + ImGuiIO& io = ImGui::GetIO(); + const IGPUDescriptorSet::SWriteDescriptorSet writes[] = { { .dstSet = m_gpuDescriptorSet.get(), .binding = 0u, - .arrayElement = 0u, - .count = 1u, - .info = &iInfo + .arrayElement = io.Fonts->TexID, // OK ITS TEMPORARY (filling all with font atlas index, I need to fill all with dummy texture to not throw validation errors which kills my app) + .count = 1,//NBL_MAX_IMGUI_TEXTURES, + .info = iInfo.data() } }; @@ -374,9 +385,9 @@ namespace nbl::ext::imgui { IDescriptorPool::SCreateInfo createInfo = {}; createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = 1u; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = 1u; + createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = NBL_MAX_IMGUI_TEXTURES; createInfo.maxSets = 1u; - createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_NONE; + createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; m_descriptorPool = m_device->createDescriptorPool(std::move(createInfo)); assert(m_descriptorPool); @@ -668,7 +679,7 @@ namespace nbl::ext::imgui createDescriptorPool(); createDescriptorSetLayout(); createPipeline(renderpass, pipelineCache); - createFontAtlas2DArrayTexture(transistentCMD.get(), tQueue); + createFontAtlasTexture(transistentCMD.get(), tQueue); adjustGlobalFontScale(); updateDescriptorSets(); } @@ -702,30 +713,6 @@ namespace nbl::ext::imgui } } - //------------------------------------------------------------------------------------------------- - // TODO: Handle mouse cursor for nabla - //static void UpdateMouseCursor() - //{ - //auto & io = ImGui::GetIO(); - - //if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) - //{ - // return; - //} - //ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); - //if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) - //{ - // // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor - // MSDL::SDL_ShowCursor(MSDL::SDL_FALSE); - //} - //else - //{ - // // Show OS mouse cursor - // MSDL::SDL_SetCursor(mouseCursors[imgui_cursor] ? mouseCursors[imgui_cursor] : mouseCursors[ImGuiMouseCursor_Arrow]); - // MSDL::SDL_ShowCursor(MSDL::SDL_TRUE); - //} - //} - bool UI::render(IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex) { const bool validFramesRange = frameIndex >= 0 && frameIndex < maxFramesInFlight; @@ -912,7 +899,7 @@ namespace nbl::ext::imgui element.aabbMin.y = packSnorm16(vMin.y); element.aabbMax.x = packSnorm16(vMax.x); element.aabbMax.y = packSnorm16(vMax.y); - // element.texId = 0; // use defaults from constructor + element.texId = pcmd->TextureId; ++drawID; } diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 2b2718e55a..bdf84933f6 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -1,3 +1,6 @@ +// temporary, maybe we should ask user for it and the layout BUT only for sampler + texture binding IDs + set IDs they belong to and size of the texture array? +#define NBL_MAX_IMGUI_TEXTURES 69 + #ifdef __HLSL_VERSION struct VSInput { diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index cfe5bdc3a2..3f20cb8232 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -2,8 +2,8 @@ [[vk::push_constant]] struct PushConstants pc; -// separable image samplers, one to handle all textures -[[vk::binding(0,0)]] Texture2DArray texture; +// single separable image sampler to handle all textures we descriptor-index +[[vk::binding(0,0)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES]; [[vk::binding(1,0)]] SamplerState samplerState; /* @@ -15,7 +15,8 @@ float4 PSMain(PSInput input, uint drawID : SV_InstanceID) : SV_Target0 { // BDA for requesting object data + // TODO: move this to vertex shader, then pass along as interpolant const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); - return input.color * texture.Sample(samplerState, float32_t3(input.uv, self.texId)); + return input.color * textures[NonUniformResourceIndex(self.texId)].Sample(samplerState, input.uv); } \ No newline at end of file From 3eb8fb1dec8376ce411b206bde5053d868d6f8a9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 16 Jul 2024 15:40:56 +0200 Subject: [PATCH 023/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 822b7330a0..bd438d4868 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 822b7330a0b0df89c6b2f87eff37467b0ad79dda +Subproject commit bd438d48685a51b489fd6487920aae372fb77bdb From ab1318cdf6bad8c5110841364475a550947de4db Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 17 Jul 2024 08:13:52 +0200 Subject: [PATCH 024/148] bring proper dxc submodule pointer back --- 3rdparty/dxc/dxc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/dxc/dxc b/3rdparty/dxc/dxc index 2a0f9968de..a08b6cbeb1 160000 --- a/3rdparty/dxc/dxc +++ b/3rdparty/dxc/dxc @@ -1 +1 @@ -Subproject commit 2a0f9968de8e554b6dc104e4bc0f7f7f7122f0cd +Subproject commit a08b6cbeb1038d14d0586d10a8cfa507b2fda8eb From 7385d2245e40652e5696dfe8bea467d933a7773f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 17 Jul 2024 08:19:00 +0200 Subject: [PATCH 025/148] remove openssl again --- 3rdparty/openssl/openssl | 1 - 1 file changed, 1 deletion(-) delete mode 160000 3rdparty/openssl/openssl diff --git a/3rdparty/openssl/openssl b/3rdparty/openssl/openssl deleted file mode 160000 index 12ad22dd16..0000000000 --- a/3rdparty/openssl/openssl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 12ad22dd16ffe47f8cde3cddb84a160e8cdb3e30 From 332fad460d4ef7cc05556250444e3a5e115b8a61 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 17 Jul 2024 08:20:08 +0200 Subject: [PATCH 026/148] remove 3rdparty/tcpp again --- 3rdparty/tcpp | 1 - 1 file changed, 1 deletion(-) delete mode 160000 3rdparty/tcpp diff --git a/3rdparty/tcpp b/3rdparty/tcpp deleted file mode 160000 index 1349103cd5..0000000000 --- a/3rdparty/tcpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1349103cd57c7c33ca3c8d4dd241c3eed63dd509 From 5b6465b3d3221556901d489e5f34f45c872e7386 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 17 Jul 2024 08:25:39 +0200 Subject: [PATCH 027/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index bd438d4868..32e06030ca 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit bd438d48685a51b489fd6487920aae372fb77bdb +Subproject commit 32e06030ca08e4a96dbe73416b278c2e723538ab From f6d8b89f352fcf079a90b1a34571d69bb057bd04 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 23 Jul 2024 15:35:19 +0200 Subject: [PATCH 028/148] fix compile issues after pulling master (scoped enums) --- src/nbl/ext/ImGui/ImGui.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5b0007ffdd..04178c56ca 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -34,14 +34,14 @@ namespace nbl::ext::imgui .binding = 0u, .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, .createFlags = core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT, - .stageFlags = IShader::ESS_FRAGMENT, + .stageFlags = IShader::E_SHADER_STAGE::ESS_FRAGMENT, .count = NBL_MAX_IMGUI_TEXTURES }, { .binding = 1u, .type = IDescriptor::E_TYPE::ET_SAMPLER, .createFlags = binding_flags_t::ECF_NONE, - .stageFlags = IShader::ESS_FRAGMENT, + .stageFlags = IShader::E_SHADER_STAGE::ESS_FRAGMENT, .count = 1u, .immutableSamplers = &m_fontSampler } @@ -56,7 +56,7 @@ namespace nbl::ext::imgui SPushConstantRange pushConstantRanges[] = { { - .stageFlags = IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, .offset = 0u, .size = sizeof(PushConstants) } @@ -88,8 +88,8 @@ namespace nbl::ext::imgui return m_device->createShader(shader.get()); }; - shaders.vertex = createShader(spirv.vertex, IShader::ESS_VERTEX); - shaders.fragment = createShader(spirv.fragment, IShader::ESS_FRAGMENT); + shaders.vertex = createShader(spirv.vertex, IShader::E_SHADER_STAGE::ESS_VERTEX); + shaders.fragment = createShader(spirv.fragment, IShader::E_SHADER_STAGE::ESS_FRAGMENT); } SVertexInputParams vertexInputParams{}; @@ -199,7 +199,6 @@ namespace nbl::ext::imgui _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; const auto buffer = core::make_smart_refctd_ptr< asset::CCustomAllocatorCPUBuffer, true> >(image_size, pixels, core::adopt_memory); - constexpr auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; IGPUImage::SCreationParams params; params.flags = static_cast(0u); params.type = IImage::ET_2D; @@ -1056,7 +1055,7 @@ namespace nbl::ext::imgui .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } }; - commandBuffer->pushConstants(pipeline->getLayout(), IShader::ESS_VERTEX | IShader::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + commandBuffer->pushConstants(pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); } const asset::SBufferBinding binding = From b76761770f7174113aa1ec1f59efb5aac0282696 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 24 Jul 2024 07:57:34 +0200 Subject: [PATCH 029/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index c228fddaae..4b68a56003 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit c228fddaae97e701306c780e95589f2f23bdefaf +Subproject commit 4b68a56003a1ab64da5cb10dd6a9fe6927277a90 From 256c63068fd1497380ebc607d42e5cdca324a670 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 24 Jul 2024 08:07:39 +0200 Subject: [PATCH 030/148] fix nite's timeline semaphore issues & sync --- tools/nite/main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index 3c739d7368..6c69f83e6e 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -154,7 +154,7 @@ class NITETool final : public examples::SimpleWindowedApplication if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) return false; - m_semaphore = m_device->createSemaphore(m_submitIx); + m_semaphore = m_device->createSemaphore(m_realFrameIx); if (!m_semaphore) return logFail("Failed to Create a Semaphore!"); @@ -377,7 +377,7 @@ class NITETool final : public examples::SimpleWindowedApplication { { .semaphore = m_semaphore.get(), - .value = ++m_submitIx, + .value = ++m_realFrameIx, .stageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT } }; @@ -406,7 +406,7 @@ class NITETool final : public examples::SimpleWindowedApplication }; if (queue->submit(infos) != IQueue::RESULT::SUCCESS) - m_submitIx--; + m_realFrameIx--; } } @@ -460,7 +460,6 @@ class NITETool final : public examples::SimpleWindowedApplication smart_refctd_ptr m_semaphore; smart_refctd_ptr m_cmdPool; uint64_t m_realFrameIx : 59 = 0; - uint64_t m_submitIx : 59 = 0; uint64_t m_maxFramesInFlight : 5; std::array, ISwapchain::MaxImages> m_cmdBufs; ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; From a86c21327d2ab0b78ce93ade6b1cae6b89dfa13c Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 7 Aug 2024 08:59:46 +0200 Subject: [PATCH 031/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 4b68a56003..b8fa395522 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 4b68a56003a1ab64da5cb10dd6a9fe6927277a90 +Subproject commit b8fa395522f6dac59a0bb9a89c5b3b1db5be5459 From 310369c75a1c55bac5961fd8c66bc1381b930782 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 7 Aug 2024 12:06:58 +0200 Subject: [PATCH 032/148] update examples_tests submodule & common.cmake --- cmake/common.cmake | 16 ++++++++++++++++ examples_tests | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cmake/common.cmake b/cmake/common.cmake index c4b1476136..7fcf9b32af 100755 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -1282,4 +1282,20 @@ endmacro() macro(write_source_definitions NBL_FILE NBL_WRAPPER_CODE_TO_WRITE) file(WRITE "${NBL_FILE}" "${NBL_WRAPPER_CODE_TO_WRITE}") +endmacro() + +function(NBL_GET_ALL_TARGETS NBL_OUTPUT_VAR) + set(NBL_TARGETS) + NBL_GET_ALL_TARGETS_RECURSIVE(NBL_TARGETS ${CMAKE_CURRENT_SOURCE_DIR}) + set(${NBL_OUTPUT_VAR} ${NBL_TARGETS} PARENT_SCOPE) +endfunction() + +macro(NBL_GET_ALL_TARGETS_RECURSIVE NBL_TARGETS NBL_DIRECTORY) + get_property(NBL_SUBDIRECTORIES DIRECTORY ${NBL_DIRECTORY} PROPERTY SUBDIRECTORIES) + foreach(NBL_SUBDIRECTORY ${NBL_SUBDIRECTORIES}) + NBL_GET_ALL_TARGETS_RECURSIVE(${NBL_TARGETS} ${NBL_SUBDIRECTORY}) + endforeach() + + get_property(NBL_GATHERED_TARGETS DIRECTORY ${NBL_DIRECTORY} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${NBL_TARGETS} ${NBL_GATHERED_TARGETS}) endmacro() \ No newline at end of file diff --git a/examples_tests b/examples_tests index b8fa395522..12dfe28312 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit b8fa395522f6dac59a0bb9a89c5b3b1db5be5459 +Subproject commit 12dfe283120efeacdf5bb862aa89124cecff4b59 From 80032c5249d6a03ab3f0e3493abebbb7526b057d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 7 Aug 2024 13:45:21 +0200 Subject: [PATCH 033/148] fix qualifier issue with builtins, update examples_tests submodule --- examples_tests | 2 +- src/nbl/builtin/utils.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples_tests b/examples_tests index 12dfe28312..d4b9939646 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 12dfe283120efeacdf5bb862aa89124cecff4b59 +Subproject commit d4b9939646084cbc13bd39bc83d1d71454e16685 diff --git a/src/nbl/builtin/utils.cmake b/src/nbl/builtin/utils.cmake index bc8a35f673..0a76d1c67e 100644 --- a/src/nbl/builtin/utils.cmake +++ b/src/nbl/builtin/utils.cmake @@ -191,7 +191,7 @@ function(ADD_CUSTOM_BUILTIN_RESOURCES _TARGET_NAME_ _BUNDLE_NAME_ _BUNDLE_SEARCH endif() if(NOT _NBL_INTERNAL_BR_CREATION_) - target_link_libraries(${_TARGET_NAME_} Nabla) + target_link_libraries(${_TARGET_NAME_} PUBLIC Nabla) endif() endif() From edf27cd303a7efe9c0acdf4c94c51ac03cfff6d0 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 8 Aug 2024 17:41:47 +0200 Subject: [PATCH 034/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index d4b9939646..b6aae0bc40 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d4b9939646084cbc13bd39bc83d1d71454e16685 +Subproject commit b6aae0bc401ad1e10a368c716d1fbbc5d64dc58a From f70053b7ede45d19171781f9c0098288be580e9a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 9 Aug 2024 12:18:59 +0200 Subject: [PATCH 035/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index b6aae0bc40..13496a3e61 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit b6aae0bc401ad1e10a368c716d1fbbc5d64dc58a +Subproject commit 13496a3e61921b5c01cd42dd2f10484b6e9f9365 From a4882040a4bb0eac7af48299f85044bfc47970ed Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 9 Aug 2024 17:22:13 +0200 Subject: [PATCH 036/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 13496a3e61..4e27d6ec0b 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 13496a3e61921b5c01cd42dd2f10484b6e9f9365 +Subproject commit 4e27d6ec0b56d91099291248169a505f64f70739 From a4f2dac26524659227145d851dbdee5ac367e2e9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 9 Aug 2024 18:03:21 +0200 Subject: [PATCH 037/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 4e27d6ec0b..841bae8213 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 4e27d6ec0b56d91099291248169a505f64f70739 +Subproject commit 841bae821386657f0c4fa695fa2ceacc3b9e1d89 From 47fc1b5315bca8d1b0903c04c3119275e98c053f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 12 Aug 2024 18:43:03 +0200 Subject: [PATCH 038/148] fix NSC include search directories given with CLI --- tools/nsc/main.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tools/nsc/main.cpp b/tools/nsc/main.cpp index 59ecf8b459..541598d76d 100644 --- a/tools/nsc/main.cpp +++ b/tools/nsc/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace nbl; using namespace nbl::system; @@ -46,6 +47,7 @@ class ShaderCompiler final : public system::IApplicationFramework } m_arguments = std::vector(argv.begin() + 1, argv.end()-1); // turn argv into vector for convenience + std::string file_to_compile = argv.back(); if (!m_system->exists(file_to_compile, IFileBase::ECF_READ)) { @@ -137,6 +139,13 @@ class ShaderCompiler final : public system::IApplicationFramework m_arguments.push_back("main"); } + for (size_t i = 0; i < m_arguments.size() - 1; ++i) // -I must be given with second arg, no need to include iteration over last one + { + const auto& arg = m_arguments[i]; + if (arg == "-I") + m_include_search_paths.emplace_back(m_arguments[i + 1]); + } + auto shader = open_shader_file(file_to_compile); if (shader->getContentType() != IShader::E_CONTENT_TYPE::ECT_HLSL) { @@ -173,8 +182,16 @@ class ShaderCompiler final : public system::IApplicationFramework options.stage = shader->getStage(); options.preprocessorOptions.sourceIdentifier = sourceIdentifier; options.preprocessorOptions.logger = m_logger.get(); + options.dxcOptions = std::span(m_arguments); + auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + auto includeLoader = includeFinder->getDefaultFileSystemLoader(); + + // because before real compilation we do preprocess the input it doesn't really matter we proxy include search direcotries further with dxcOptions since at the end all includes are resolved to single file + for(const auto& it : m_include_search_paths) + includeFinder->addSearchPath(it, includeLoader); + options.preprocessorOptions.includeFinder = includeFinder.get(); return hlslcompiler->compileToSPIRV((const char*)shader->getContent()->getPointer(), options); @@ -220,7 +237,7 @@ class ShaderCompiler final : public system::IApplicationFramework bool no_nbl_builtins{ false }; smart_refctd_ptr m_system; smart_refctd_ptr m_logger; - std::vector m_arguments; + std::vector m_arguments, m_include_search_paths; core::smart_refctd_ptr m_assetMgr; From 3071f65012044e04843a25807e34c2f770cb78bc Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 12 Aug 2024 19:31:33 +0200 Subject: [PATCH 039/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 841bae8213..0aec312ed7 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 841bae821386657f0c4fa695fa2ceacc3b9e1d89 +Subproject commit 0aec312ed72b3695210775922fc3eb14a9eb67a6 From d9624ee7b420f8ff4b25879e9c0b9ef25cc2fb24 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 13 Aug 2024 17:28:52 +0200 Subject: [PATCH 040/148] update DXC submodule --- 3rdparty/dxc/dxc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/dxc/dxc b/3rdparty/dxc/dxc index a08b6cbeb1..bcedaf749f 160000 --- a/3rdparty/dxc/dxc +++ b/3rdparty/dxc/dxc @@ -1 +1 @@ -Subproject commit a08b6cbeb1038d14d0586d10a8cfa507b2fda8eb +Subproject commit bcedaf749fb6325dc41f9b436f1f2ea0a660de5e From f57c274f3ffd8db628a4f162299673ae72c4f27b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 17 Aug 2024 09:07:53 +0200 Subject: [PATCH 041/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 0aec312ed7..d06eceb630 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 0aec312ed72b3695210775922fc3eb14a9eb67a6 +Subproject commit d06eceb63030078250a57a00414964f9a7825c0e From 9730eb053006eb9581406118126376d69edf3304 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 17 Aug 2024 16:33:41 +0200 Subject: [PATCH 042/148] update submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index d06eceb630..9678b06bc2 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d06eceb63030078250a57a00414964f9a7825c0e +Subproject commit 9678b06bc264aa7492f22d35adc17a4b587432e6 From b1f307b32a770521091fd1a66859dcaf8118ce0d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 19 Aug 2024 18:51:44 +0200 Subject: [PATCH 043/148] remove sampler, descriptor set & its pool creation from Nabla IMGUI extension, ask user for descriptor set layout at create time & let they pass descriptor set within render call --- include/nbl/ext/ImGui/ImGui.h | 18 +++--- src/nbl/ext/ImGui/ImGui.cpp | 110 ++++------------------------------ 2 files changed, 18 insertions(+), 110 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 69bbcf6f09..17159580d1 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -9,27 +9,27 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - UI(core::smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); + // Nabla IMGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error + _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; + + UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); ~UI() override; - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex); + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const nbl::video::IGPUDescriptorSet* const descriptorSet, const uint32_t frameIndex); void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); int registerListener(std::function const& listener); bool unregisterListener(uint32_t id); + inline nbl::core::smart_refctd_ptr getFontAtlasView() { return m_fontAtlasTexture; } void* getContext(); void setContext(void* imguiContext); private: - core::smart_refctd_ptr createDescriptorSetLayout(); - void createPipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); + void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); // TODO: just take an intended next submit instead of queue and cmdbuf, so we're consistent across utilities video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void updateDescriptorSets(); void createSystem(); - void createFontAtlasSampler(); - void createDescriptorPool(); void handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const; void handleKeyEvents(const core::SRange& events) const; @@ -37,9 +37,7 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr logger; core::smart_refctd_ptr utilities; core::smart_refctd_ptr m_device; - core::smart_refctd_ptr m_fontSampler; - core::smart_refctd_ptr m_descriptorPool; - core::smart_refctd_ptr m_gpuDescriptorSet; + core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontAtlasTexture; core::smart_refctd_ptr m_window; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 04178c56ca..e90238a519 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -19,38 +19,9 @@ using namespace nbl::core; using namespace nbl::asset; using namespace nbl::ui; -// Nabla IMGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error -_NBL_STATIC_INLINE_CONSTEXPR ImTextureID NBL_FONT_ATLAS_TEX_ID = 0u; - namespace nbl::ext::imgui { - smart_refctd_ptr UI::createDescriptorSetLayout() - { - using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - - const IGPUDescriptorSetLayout::SBinding bindings[] = - { - { - .binding = 0u, - .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, - .createFlags = core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT, - .stageFlags = IShader::E_SHADER_STAGE::ESS_FRAGMENT, - .count = NBL_MAX_IMGUI_TEXTURES - }, - { - .binding = 1u, - .type = IDescriptor::E_TYPE::ET_SAMPLER, - .createFlags = binding_flags_t::ECF_NONE, - .stageFlags = IShader::E_SHADER_STAGE::ESS_FRAGMENT, - .count = 1u, - .immutableSamplers = &m_fontSampler - } - }; - - return m_device->createDescriptorSetLayout(bindings); - } - - void UI::createPipeline(video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) + void UI::createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix SPushConstantRange pushConstantRanges[] = @@ -62,11 +33,7 @@ namespace nbl::ext::imgui } }; - auto descriptorSetLayout = createDescriptorSetLayout(); - m_gpuDescriptorSet = m_descriptorPool->createDescriptorSet(descriptorSetLayout); - assert(m_gpuDescriptorSet); - - auto pipelineLayout = m_device->createPipelineLayout(pushConstantRanges, std::move(descriptorSetLayout)); + auto pipelineLayout = m_device->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(descriptorSetLayout)); struct { @@ -338,59 +305,6 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::updateDescriptorSets() - { - // texture atlas + user defined textures, note we don't create info & write pair for the font sampler because ours is immutable and baked into DS layout - std::array iInfo; - for (auto& it : iInfo) - { - it.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - it.desc = m_fontAtlasTexture; - } - - ImGuiIO& io = ImGui::GetIO(); - - const IGPUDescriptorSet::SWriteDescriptorSet writes[] = - { - { - .dstSet = m_gpuDescriptorSet.get(), - .binding = 0u, - .arrayElement = io.Fonts->TexID, // OK ITS TEMPORARY (filling all with font atlas index, I need to fill all with dummy texture to not throw validation errors which kills my app) - .count = 1,//NBL_MAX_IMGUI_TEXTURES, - .info = iInfo.data() - } - }; - - m_device->updateDescriptorSets(writes, {}); - } - - void UI::createFontAtlasSampler() - { - // TODO: Recheck this settings - IGPUSampler::SParams params{}; - params.MinLod = -1000; - params.MaxLod = 1000; - params.AnisotropicFilter = 1.0f; - params.TextureWrapU = ISampler::ETC_REPEAT; - params.TextureWrapV = ISampler::ETC_REPEAT; - params.TextureWrapW = ISampler::ETC_REPEAT; - - m_fontSampler = m_device->createSampler(params); - m_fontSampler->setObjectDebugName("Nabla Font Atlas Sampler"); - } - - void UI::createDescriptorPool() - { - IDescriptorPool::SCreateInfo createInfo = {}; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = 1u; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = NBL_MAX_IMGUI_TEXTURES; - createInfo.maxSets = 1u; - createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; - - m_descriptorPool = m_device->createDescriptorPool(std::move(createInfo)); - assert(m_descriptorPool); - } - void UI::handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const { auto& io = ImGui::GetIO(); @@ -601,8 +515,8 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr device, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) - : m_device(core::smart_refctd_ptr(device)), m_window(core::smart_refctd_ptr(window)), maxFramesInFlight(_maxFramesInFlight) + UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) + : m_device(core::smart_refctd_ptr(_device)), m_window(core::smart_refctd_ptr(window)), maxFramesInFlight(_maxFramesInFlight) { createSystem(); struct @@ -613,7 +527,7 @@ namespace nbl::ext::imgui } id; } families; - const nbl::video::IPhysicalDevice* pDevice = device->getPhysicalDevice(); + const nbl::video::IPhysicalDevice* pDevice = m_device->getPhysicalDevice(); ILogicalDevice::SCreationParams params = {}; auto properties = pDevice->getQueueFamilyProperties(); @@ -642,7 +556,7 @@ namespace nbl::ext::imgui families.id.graphics = requestFamilyQueueId(IQueue::FAMILY_FLAGS::GRAPHICS_BIT, "Could not find any queue with GRAPHICS_BIT enabled!"); // allocate temporary command buffer - auto* tQueue = device->getThreadSafeQueue(families.id.transfer, 0); + auto* tQueue = m_device->getThreadSafeQueue(families.id.transfer, 0); if (!tQueue) { @@ -654,7 +568,7 @@ namespace nbl::ext::imgui { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; // need to be individually resettable such that we can form a valid SIntendedSubmit out of the commandbuffer allocated from the pool - smart_refctd_ptr pool = device->createCommandPool(families.id.transfer, pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + smart_refctd_ptr pool = m_device->createCommandPool(families.id.transfer, pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { logger->log("Could not create command pool!", system::ILogger::ELL_ERROR); @@ -675,13 +589,9 @@ namespace nbl::ext::imgui IMGUI_CHECKVERSION(); ImGui::CreateContext(); - createFontAtlasSampler(); - createDescriptorPool(); - createDescriptorSetLayout(); - createPipeline(renderpass, pipelineCache); + createPipeline(core::smart_refctd_ptr(_descriptorSetLayout), renderpass, pipelineCache); createFontAtlasTexture(transistentCMD.get(), tQueue); adjustGlobalFontScale(); - updateDescriptorSets(); } tQueue->endCapture(); @@ -713,7 +623,7 @@ namespace nbl::ext::imgui } } - bool UI::render(IGPUCommandBuffer* commandBuffer, const uint32_t frameIndex) + bool UI::render(IGPUCommandBuffer* commandBuffer, const IGPUDescriptorSet* const descriptorSet, const uint32_t frameIndex) { const bool validFramesRange = frameIndex >= 0 && frameIndex < maxFramesInFlight; if (!validFramesRange) @@ -939,7 +849,7 @@ namespace nbl::ext::imgui auto* rawPipeline = pipeline.get(); commandBuffer->bindGraphicsPipeline(rawPipeline); - commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &m_gpuDescriptorSet.get()); + commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); // update mdi buffer { From 7a655651c64fb67ce8aede29493274a7eaea9e20 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 19 Aug 2024 19:05:22 +0200 Subject: [PATCH 044/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 9678b06bc2..8d3ae4dabd 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 9678b06bc264aa7492f22d35adc17a4b587432e6 +Subproject commit 8d3ae4dabd61ac31ce578a24d6818b64fdb7626a From bfed39c7644ee77f6b1cb18dc1da535fc0de4def Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 23 Aug 2024 12:50:47 +0200 Subject: [PATCH 045/148] update examples_test pointer --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 8d3ae4dabd..da62c0218d 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8d3ae4dabd61ac31ce578a24d6818b64fdb7626a +Subproject commit da62c0218d836fd4b09713b80cc2020093a6d932 From 69a1f6563c939bf4e0a08c932b5e668e61124d00 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 23 Aug 2024 12:54:07 +0200 Subject: [PATCH 046/148] update examples_tests pointer --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index da62c0218d..dee3f452b3 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit da62c0218d836fd4b09713b80cc2020093a6d932 +Subproject commit dee3f452b37038a30ca47784adca2014a551ee8b From 66ade6a1030d929983ec91f0f73db6f7895816fb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 23 Aug 2024 15:20:23 +0200 Subject: [PATCH 047/148] update examples_tests + fix nasty bug with drawID (no more validation errors!) - turns out it must be passed to another stage --- examples_tests | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 2 +- src/nbl/ext/ImGui/shaders/common.hlsl | 1 + src/nbl/ext/ImGui/shaders/fragment.hlsl | 4 ++-- src/nbl/ext/ImGui/shaders/vertex.hlsl | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples_tests b/examples_tests index 8f9676b100..f9eb425b9c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8f9676b100ed2d5c112fe6e2dc2513952cb3a849 +Subproject commit f9eb425b9cda705504ac8e8cf08b6b5b6f147f6f diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 0699bbb9b4..d404d0027c 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -27,7 +27,7 @@ namespace nbl::ext::imgui SPushConstantRange pushConstantRanges[] = { { - .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX, + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, .offset = 0, .size = sizeof(PushConstants) } diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index bdf84933f6..427f1743d2 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -14,6 +14,7 @@ float4 position : SV_Position; float2 uv : TEXCOORD0; float4 color : COLOR0; + uint drawID : SV_InstanceID; float clip[4] : SV_ClipDistance; }; diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 3f20cb8232..bb14dd00eb 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -12,11 +12,11 @@ to request per object data with BDA */ -float4 PSMain(PSInput input, uint drawID : SV_InstanceID) : SV_Target0 +float4 PSMain(PSInput input) : SV_Target0 { // BDA for requesting object data // TODO: move this to vertex shader, then pass along as interpolant - const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* input.drawID); return input.color * textures[NonUniformResourceIndex(self.texId)].Sample(samplerState, input.uv); } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index c69136b362..18479a02db 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -13,6 +13,7 @@ PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) PSInput output; output.color = input.color; output.uv = input.uv; + output.drawID = drawID; // BDA for requesting object data const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); From 9415013e47dded58cb4c8e02ebc121c0d0998640 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 23 Aug 2024 18:28:31 +0200 Subject: [PATCH 048/148] update ImGui.cpp & ImGui/shaders/fragment.hlsl, update examples_tests submodule - turns out I have issues with blend equation, add a temporary solution which turns alpha to 1 when non-atlas font textures are sampled --- examples_tests | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 22 +++++++++++++--------- src/nbl/ext/ImGui/shaders/fragment.hlsl | 10 +++++++--- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/examples_tests b/examples_tests index f9eb425b9c..d6acb5120c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit f9eb425b9cda705504ac8e8cf08b6b5b6f147f6f +Subproject commit d6acb5120c0ada09f80be70ccfea0dd10b1e796f diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index d404d0027c..c9b366d1f9 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -88,22 +88,26 @@ namespace nbl::ext::imgui blendParams.logicOp = ELO_NO_OP; auto& param = blendParams.blendParams[0]; - param.srcColorFactor = EBF_SRC_ALPHA;//VK_BLEND_FACTOR_SRC_ALPHA; - param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA;//VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - param.colorBlendOp = EBO_ADD;//VK_BLEND_OP_ADD; - param.srcAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA;//VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - param.dstAlphaFactor = EBF_ZERO;//VK_BLEND_FACTOR_ZERO; - param.alphaBlendOp = EBO_ADD;//VK_BLEND_OP_ADD; - param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u);//VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + // color blending factors (for RGB) + param.srcColorFactor = EBF_SRC_ALPHA; + param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.colorBlendOp = EBO_ADD; + + // alpha blending factors (for A) + param.srcAlphaFactor = EBF_ONE; + param.dstAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.alphaBlendOp = EBO_ADD; + + // Write all components (R, G, B, A) + param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u); } SRasterizationParams rasterizationParams{}; { rasterizationParams.faceCullingMode = EFCM_NONE; - rasterizationParams.depthWriteEnable = false; // TODO: check if it disabled depth test? + rasterizationParams.depthWriteEnable = false; rasterizationParams.depthBoundsTestEnable = false; - // rasterizationParams.stencilTestEnable = false; // TODO: check if stencil test is disabled } SPrimitiveAssemblyParams primitiveAssemblyParams{}; diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index bb14dd00eb..4e844e3f3d 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -4,7 +4,7 @@ // single separable image sampler to handle all textures we descriptor-index [[vk::binding(0,0)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES]; -[[vk::binding(1,0)]] SamplerState samplerState; +[[vk::binding(1,0)]] SamplerState samplerStates[NBL_MAX_IMGUI_TEXTURES]; /* we use Indirect Indexed draw call to render whole GUI, note we do a cross @@ -15,8 +15,12 @@ float4 PSMain(PSInput input) : SV_Target0 { // BDA for requesting object data - // TODO: move this to vertex shader, then pass along as interpolant const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* input.drawID); - return input.color * textures[NonUniformResourceIndex(self.texId)].Sample(samplerState, input.uv); + float4 texel = textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.texId], input.uv) * input.color; + + if(self.texId != 0) // TMP! + texel.w = 1.f; + + return texel; } \ No newline at end of file From 9ec03aab60c33df882e917c3939c264b1b92ce28 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 24 Aug 2024 11:13:41 +0200 Subject: [PATCH 049/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index d6acb5120c..125e89494b 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d6acb5120c0ada09f80be70ccfea0dd10b1e796f +Subproject commit 125e89494b58fe0f9bd14b7da4f5be3aa1fe39b1 From 65e1b9adae6617a23431f31491903c8774365126 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 24 Aug 2024 16:57:07 +0200 Subject: [PATCH 050/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 63ae37f7ae..8a9e9903e9 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 63ae37f7ae582d49d30a7c16901277fad0437d51 +Subproject commit 8a9e9903e9b14f2dd0a67a024df5cbe41edaed7a From b1d5ca713ac020eb6257969715408aeae5d039a7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 24 Aug 2024 17:58:04 +0200 Subject: [PATCH 051/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 8a9e9903e9..60773070ac 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8a9e9903e9b14f2dd0a67a024df5cbe41edaed7a +Subproject commit 60773070ac56e2b56c8c1f85381a19fa78f755fa From 7a0e724131fbb9e5ce83faa29dfba06a60ef81da Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 24 Aug 2024 19:02:12 +0200 Subject: [PATCH 052/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 60773070ac..56bd238e9d 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 60773070ac56e2b56c8c1f85381a19fa78f755fa +Subproject commit 56bd238e9deec3311868ec12729b4a2d4f535761 From 23192cad910b9a4579f47b22a93d0c6d440efc90 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 26 Aug 2024 19:26:54 +0200 Subject: [PATCH 053/148] update ICPUDescriptorSetLayout.h, add std::span constructor --- include/nbl/asset/ICPUDescriptorSetLayout.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/nbl/asset/ICPUDescriptorSetLayout.h b/include/nbl/asset/ICPUDescriptorSetLayout.h index 437a3f4552..8f45a789ea 100644 --- a/include/nbl/asset/ICPUDescriptorSetLayout.h +++ b/include/nbl/asset/ICPUDescriptorSetLayout.h @@ -24,7 +24,8 @@ class ICPUDescriptorSetLayout : public IDescriptorSetLayout, public public: _NBL_STATIC_INLINE_CONSTEXPR uint32_t IMMUTABLE_SAMPLER_HIERARCHYLEVELS_BELOW = 1u; - ICPUDescriptorSetLayout(const SBinding* const _begin, const SBinding* const _end) : base_t({_begin,_end}) {} + [[deprecated("Use contructor with std::span!")]] ICPUDescriptorSetLayout(const SBinding* const _begin, const SBinding* const _end) : base_t({_begin,_end}) {} + ICPUDescriptorSetLayout(const std::span bindings) : base_t(bindings) {} core::smart_refctd_ptr clone(uint32_t _depth = ~0u) const override { From 99ffad4a37a2f704c4894b5a423dcd2636a6c7b2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 27 Aug 2024 11:28:36 +0200 Subject: [PATCH 054/148] define ICPURenderpass constructor + add IRenderpass's missing NBL_API2 to export/import it's constructor in dll mode --- include/nbl/asset/ICPURenderpass.h | 7 +++---- include/nbl/asset/IRenderpass.h | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/nbl/asset/ICPURenderpass.h b/include/nbl/asset/ICPURenderpass.h index 969ed93b46..b9cf31d127 100644 --- a/include/nbl/asset/ICPURenderpass.h +++ b/include/nbl/asset/ICPURenderpass.h @@ -1,11 +1,9 @@ #ifndef _NBL_I_CPU_RENDERPASS_H_INCLUDED_ #define _NBL_I_CPU_RENDERPASS_H_INCLUDED_ - #include "nbl/asset/IAsset.h" #include "nbl/asset/IRenderpass.h" - namespace nbl::asset { @@ -17,7 +15,8 @@ class ICPURenderpass : public IRenderpass, public IAsset const SCreationParamValidationResult validation = validateCreationParams(_params); if (!validation) return nullptr; - return core::smart_refctd_ptr(new ICPURenderpass(_params,validation), core::dont_grab); + + return core::smart_refctd_ptr(new ICPURenderpass(_params, validation), core::dont_grab); } inline core::smart_refctd_ptr clone(uint32_t _depth = ~0u) const override @@ -42,7 +41,7 @@ class ICPURenderpass : public IRenderpass, public IAsset inline size_t getDependantCount() const override {return 0ull;} protected: - using IRenderpass::IRenderpass; + inline ICPURenderpass(const SCreationParams& _params, const SCreationParamValidationResult& _validation) : IRenderpass(_params, _validation) {} inline ~ICPURenderpass() = default; inline IAsset* getDependant_impl(const size_t ix) override {return nullptr;} diff --git a/include/nbl/asset/IRenderpass.h b/include/nbl/asset/IRenderpass.h index 46e89eab05..7595911716 100644 --- a/include/nbl/asset/IRenderpass.h +++ b/include/nbl/asset/IRenderpass.h @@ -11,7 +11,7 @@ namespace nbl::asset { -class IRenderpass +class NBL_API2 IRenderpass { public: enum class LOAD_OP : uint8_t @@ -356,6 +356,7 @@ class IRenderpass protected: IRenderpass(const SCreationParams& params, const SCreationParamValidationResult& counts); + virtual ~IRenderpass() {} SCreationParams m_params; From b956b3c798bf173b46f06bf23e05e2dd42058b67 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 27 Aug 2024 11:29:07 +0200 Subject: [PATCH 055/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 8240926774..28a9b9b725 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8240926774e7b6e88ddfe7c2b4e335d7528b2524 +Subproject commit 28a9b9b725f43b9eb6fcd18854188e2b17bfd96d From 5e325e6e7f390909f6e4755872440314d8d70e1a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 27 Aug 2024 12:31:53 +0200 Subject: [PATCH 056/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 28a9b9b725..feec8b1257 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 28a9b9b725f43b9eb6fcd18854188e2b17bfd96d +Subproject commit feec8b1257636c6ebf82a25bec3dbccac19c4540 From f9f754e0fb01a1afcb1b5257fc7974e3b219fb38 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 27 Aug 2024 17:14:48 +0200 Subject: [PATCH 057/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index feec8b1257..9823840188 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit feec8b1257636c6ebf82a25bec3dbccac19c4540 +Subproject commit 98238401886751dce77bac65ee5b9a93fa0c81ad From 5e1cfb6b1065d725a500bb94998ca3fb5a70f586 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 28 Aug 2024 12:25:27 +0200 Subject: [PATCH 058/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 84850035b9..5d3d6dbed3 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 84850035b953b1b80baf757f543e0a1f9b428989 +Subproject commit 5d3d6dbed35e9d0d5d54bf6a08dbce4185f1e399 From f80832b2e36ecd19a184009464362d5a0b818ed7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 3 Sep 2024 09:59:10 +0200 Subject: [PATCH 059/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 5d3d6dbed3..37e037879e 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 5d3d6dbed35e9d0d5d54bf6a08dbce4185f1e399 +Subproject commit 37e037879eb3c7eaf73fc299d599cb6f2924448b From ef28cdd36fe510811de52aa754d414e2c2cb8ff1 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 3 Sep 2024 12:30:14 +0200 Subject: [PATCH 060/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 37e037879e..a2faef3098 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 37e037879eb3c7eaf73fc299d599cb6f2924448b +Subproject commit a2faef3098fe3f9223918ffca9cec1f012e60d8e From 7aaf7a9aad335097456851532eec8eac54e6e449 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 3 Sep 2024 14:05:17 +0200 Subject: [PATCH 061/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 2287ff9e5f..72950b41da 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 2287ff9e5f7b24dd353fe6281f8f4fa68b6fc859 +Subproject commit 72950b41dacc8ea2f252ff6a2dc48de3d9e91dd1 From 6de285f1dca7188c14f551c65bb80446292b5ddb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 3 Sep 2024 16:02:37 +0200 Subject: [PATCH 062/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 72950b41da..f9cd055b55 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 72950b41dacc8ea2f252ff6a2dc48de3d9e91dd1 +Subproject commit f9cd055b550222ca120db7416da7ef908a430284 From 1f34e333b693ba9dba5a3ace63bb7b0ac24d9177 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 4 Sep 2024 14:42:53 +0200 Subject: [PATCH 063/148] update Nabla ImGUI backend to use streaming buffer with multi allocations instead of std::vector objects, update examples_tests submodule. TODO: Instead of allocating at render call I need to start with preallocated like 2 MB buffer for which multi alloc requests will be performed & handle overflows --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 34 +++- src/nbl/ext/ImGui/ImGui.cpp | 311 +++++++++++++++------------------- 3 files changed, 172 insertions(+), 175 deletions(-) diff --git a/examples_tests b/examples_tests index f9cd055b55..eb1efe063c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit f9cd055b550222ca120db7416da7ef908a430284 +Subproject commit eb1efe063c864954e2fa75edf8f7d8ad2f76d507 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 17159580d1..d686d01891 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -12,10 +12,10 @@ class UI final : public core::IReferenceCounted // Nabla IMGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; - UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); + UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); ~UI() override; - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const nbl::video::IGPUDescriptorSet* const descriptorSet, const uint32_t frameIndex); + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const nbl::video::IGPUDescriptorSet* const descriptorSet); void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); int registerListener(std::function const& listener); bool unregisterListener(uint32_t id); @@ -41,8 +41,34 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontAtlasTexture; core::smart_refctd_ptr m_window; - std::vector> m_mdiBuffers; - const uint32_t maxFramesInFlight; + + struct MDI + { + enum E_BUFFER_CONTENT : uint8_t + { + EBC_DRAW_INDIRECT_STRUCTURES, + EBC_ELEMENT_STRUCTURES, + EBC_INDEX_BUFFERS, + EBC_VERTEX_BUFFERS, + + EBC_COUNT, + }; + + struct MULTI_ALLOC_PARAMS + { + static constexpr auto ALLOCATION_COUNT = EBC_COUNT; + + using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; + + std::array alignments = {}; + std::array byteSizes = {}; + std::array offsets = { COMPOSE_T::invalid_value, COMPOSE_T::invalid_value, COMPOSE_T::invalid_value, COMPOSE_T::invalid_value }; + }; + + nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [Draw Indirect structures] [Element structures] [Index buffers] [Vertex Buffers] + }; + + MDI m_mdi; // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; struct Subscriber diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index c9b366d1f9..cd7c814ec7 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -519,8 +519,8 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, uint32_t _maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) - : m_device(core::smart_refctd_ptr(_device)), m_window(core::smart_refctd_ptr(window)), maxFramesInFlight(_maxFramesInFlight) + UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) + : m_device(core::smart_refctd_ptr(_device)), m_window(core::smart_refctd_ptr(window)) { createSystem(); struct @@ -603,8 +603,6 @@ namespace nbl::ext::imgui io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); io.BackendUsingLegacyKeyArrays = 0; // 0: using AddKeyEvent() [new way of handling events in imgui] - - m_mdiBuffers.resize(maxFramesInFlight); } UI::~UI() = default; @@ -627,15 +625,8 @@ namespace nbl::ext::imgui } } - bool UI::render(IGPUCommandBuffer* commandBuffer, const IGPUDescriptorSet* const descriptorSet, const uint32_t frameIndex) + bool UI::render(IGPUCommandBuffer* commandBuffer, const IGPUDescriptorSet* const descriptorSet) { - const bool validFramesRange = frameIndex >= 0 && frameIndex < maxFramesInFlight; - if (!validFramesRange) - { - logger->log("Requested frame index is OOB!", system::ILogger::ELL_ERROR); - assert(false); - } - ImGuiIO& io = ImGui::GetIO(); if (!io.Fonts->IsBuilt()) @@ -692,19 +683,6 @@ namespace nbl::ext::imgui return scissor; } } - - bool prepass(const ImDrawCmd* cmd) const - { - const auto rectangle = getClipRectangle(cmd); - - return prepass(rectangle); - } - - bool prepass(const ImVec4& clipRectangle) const - { - return clipRectangle.x < framebuffer.x && clipRectangle.y < framebuffer.y && clipRectangle.z >= 0.0f && clipRectangle.w >= 0.0f; - } - } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; struct TRS @@ -727,47 +705,111 @@ namespace nbl::ext::imgui return std::move(retV); }(); + + MDI::MULTI_ALLOC_PARAMS multiAllocParams; + + multiAllocParams.alignments[MDI::EBC_DRAW_INDIRECT_STRUCTURES] = alignof(VkDrawIndexedIndirectCommand); + multiAllocParams.alignments[MDI::EBC_ELEMENT_STRUCTURES] = alignof(PerObjectData); + multiAllocParams.alignments[MDI::EBC_INDEX_BUFFERS] = alignof(ImDrawIdx); + multiAllocParams.alignments[MDI::EBC_VERTEX_BUFFERS] = alignof(ImDrawVert); - struct + // calculate upper bound byte size for each mdi's content + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { - size_t vBufferSize = {}, iBufferSize = {}, // sizes in bytes - vPadding = {}; // indicies can break our alignment so small padding may be required before vertices in memory, vertices' offset must be multiple of 4 + const ImDrawList* commandList = drawData->CmdLists[i]; - std::vector indirects; - std::vector elements; + multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); + multiAllocParams.byteSizes[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + multiAllocParams.byteSizes[MDI::EBC_INDEX_BUFFERS] += commandList->IdxBuffer.Size * sizeof(ImDrawIdx); + multiAllocParams.byteSizes[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); + } - size_t getMDIBufferSize() const // buffer layout is [Draw Indirect structures] [Element Data] [All the Indices] [All the vertices] - { - return getVerticesOffset() + vBufferSize; - } + // calculate upper bound byte size limit for mdi buffer with alignments taken into account + const auto mdiBufferByteSize = [&byteSizes = multiAllocParams.byteSizes, &alignments = multiAllocParams.alignments]() -> size_t + { + auto requestedByteSize = std::reduce(std::begin(byteSizes), std::end(byteSizes)); + auto maxAlignment = *std::max_element(std::begin(alignments), std::end(alignments)); + auto padding = requestedByteSize % maxAlignment; - size_t getIndicesOffset() const - { - return getElementsOffset() + elements.size() * sizeof(PerObjectData); - } + return requestedByteSize + padding; + }(); - size_t getVerticesOffset() const - { - return getIndicesOffset() + iBufferSize + vPadding; - } + // TOOD: lets start with preallocated buffer with like + // 2 MB or something and then handle overflows - size_t getVkDrawIndexedIndirectCommandOffset() const + // create/update mdi composed streaming buffer + auto updateMDIBuffer = [&](const uint32_t totalByteSize, const bool forceRecreate = false) -> void + { + if (forceRecreate || !m_mdi.streamingTDBufferST || totalByteSize > m_mdi.streamingTDBufferST->get_total_size()) { - return 0u; - } + constexpr static core::bitflag allocateFlags(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + constexpr static uint32_t minStreamingBufferAllocationSize = 4u, + maxStreamingBufferAllocationAlignment = 1024u * 64u; - size_t getElementsOffset() const - { - return elements.size() * sizeof(VkDrawIndexedIndirectCommand); + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; + mdiCreationParams.size = totalByteSize; + + auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto offset = m_device->allocate(memoryReqs, buffer.get(), allocateFlags); + auto memory = offset.memory; + const bool allocated = offset.isValid(); + assert(allocated); + + const auto properties = memory->getMemoryPropertyFlags(); + + core::bitflag accessFlags(IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) + accessFlags |= IDeviceMemoryAllocation::EMCAF_READ; + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) + accessFlags |= IDeviceMemoryAllocation::EMCAF_WRITE; + + assert(accessFlags.value); + + if (!memory->map({ 0ull, memoryReqs.size }, accessFlags)) + logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); + + m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); } + }; + + updateMDIBuffer(mdiBufferByteSize); + { + std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); - bool validate() const + m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), multiAllocParams.alignments.data()); + + const bool ok = [&offsets = multiAllocParams.offsets]() -> bool { - return indirects.size() == elements.size(); - } + for (const auto& it : offsets) + if (it == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value) + return false; + + return true; + }(); + + if(!ok) + logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + + // how to handle it properly on fail? TODO: take intended submit info, play with bariers, handle overflow + } - } requestData; + auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer(), dont_grab_t{}); + auto mdiBind = mdiBuffer->getBoundMemory(); + assert(mdiBind.memory->isCurrentlyMapped()); + + auto* const indirectsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); + auto* const elementsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); + auto* indicesPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); + auto* verticesPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + + uint32_t drawCount {}; { /* IMGUI Render command lists. We merged all buffers into a single one so we @@ -786,133 +828,63 @@ namespace nbl::ext::imgui const auto clipRectangle = clip.getClipRectangle(pcmd); - // count draw invocations and allocate cpu indirect + element data for mdi buffer - if (clip.prepass(clipRectangle)) - { - // TODO: I guess we waste a lot of time on this emplace, we should reserve memory first or use some nice allocator probably? - auto& indirect = requestData.indirects.emplace_back(); - auto& element = requestData.elements.emplace_back(); - - indirect.firstIndex = pcmd->IdxOffset + globalIOffset; - indirect.firstInstance = drawID; // use base instance as draw ID - indirect.indexCount = pcmd->ElemCount; - indirect.instanceCount = 1u; - indirect.vertexOffset = pcmd->VtxOffset + globalVOffset; - - const auto scissor = clip.getScissor(clipRectangle); - - auto packSnorm16 = [](float ndc) -> int16_t - { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils - }; - - const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - - element.aabbMin.x = packSnorm16(vMin.x); - element.aabbMin.y = packSnorm16(vMin.y); - element.aabbMax.x = packSnorm16(vMax.x); - element.aabbMax.y = packSnorm16(vMax.y); - element.texId = pcmd->TextureId; - - ++drawID; - } - } - - globalIOffset += cmd_list->IdxBuffer.Size; - globalVOffset += cmd_list->VtxBuffer.Size; - } + // update mdi's indirect & element structures + auto* indirect = indirectsPointer + drawID; + auto* element = elementsPointer + drawID; - requestData.iBufferSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); - requestData.vBufferSize = drawData->TotalVtxCount * sizeof(ImDrawVert); - requestData.vPadding = requestData.getVerticesOffset() % 4u; - assert(requestData.validate()); - } - - IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - mdiCreationParams.size = requestData.getMDIBufferSize(); - - auto mdiBuffer = m_mdiBuffers[frameIndex]; - const uint32_t drawCount = requestData.elements.size(); - const auto mdicBSize = drawCount * sizeof(VkDrawIndexedIndirectCommand); - const auto elementsBSize = drawCount * sizeof(PerObjectData); - const uint64_t elementsBOffset = requestData.getElementsOffset(); - - // create or resize the mdi buffer - if (!static_cast(mdiBuffer) || mdiBuffer->getSize() < mdiCreationParams.size) - { - mdiBuffer = m_device->createBuffer(std::move(mdiCreationParams)); + indirect->firstIndex = pcmd->IdxOffset + globalIOffset; + indirect->firstInstance = drawID; // use base instance as draw ID + indirect->indexCount = pcmd->ElemCount; + indirect->instanceCount = 1u; + indirect->vertexOffset = pcmd->VtxOffset + globalVOffset; - auto mReqs = mdiBuffer->getMemoryReqs(); - mReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - - auto memOffset = m_device->allocate(mReqs, mdiBuffer.get(), core::bitflag(IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS::EMAF_DEVICE_ADDRESS_BIT)); - assert(memOffset.isValid()); - } - - auto* rawPipeline = pipeline.get(); - commandBuffer->bindGraphicsPipeline(rawPipeline); - commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); - - // update mdi buffer - { - auto binding = mdiBuffer->getBoundMemory(); - - { - if (!binding.memory->map({ 0ull, binding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory!", system::ILogger::ELL_WARNING); - - assert(binding.memory->isCurrentlyMapped()); - } + const auto scissor = clip.getScissor(clipRectangle); - auto* mdiPointer = binding.memory->getMappedPointer(); + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; - auto getMDIPointer = [&mdiPointer](auto bOffset) - { - return reinterpret_cast(reinterpret_cast(mdiPointer) + bOffset); - }; + const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - auto* const indirectPointer = getMDIPointer.template operator() < VkDrawIndexedIndirectCommand > (requestData.getVkDrawIndexedIndirectCommandOffset()); - auto* const elementPointer = getMDIPointer.template operator() < PerObjectData > (elementsBOffset); - auto* indicesPointer = getMDIPointer.template operator() < ImDrawIdx > (requestData.getIndicesOffset()); - auto* verticesPointer = getMDIPointer.template operator() < ImDrawVert > (requestData.getVerticesOffset()); + element->aabbMin.x = packSnorm16(vMin.x); + element->aabbMin.y = packSnorm16(vMin.y); + element->aabbMax.x = packSnorm16(vMax.x); + element->aabbMax.y = packSnorm16(vMax.y); + element->texId = pcmd->TextureId; - // fill indirect/element data - ::memcpy(indirectPointer, requestData.indirects.data(), mdicBSize); - ::memcpy(elementPointer, requestData.elements.data(), elementsBSize); + ++drawID; + } - // flush elements data range if required - { - auto eData = mdiBuffer->getBoundMemory(); + // update mdi's vertex & index buffers + ::memcpy(indicesPointer + globalIOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + ::memcpy(verticesPointer + globalVOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - if (eData.memory->haveToMakeVisible()) - { - const ILogicalDevice::MappedMemoryRange range(eData.memory, eData.offset, mdiBuffer->getSize()); - m_device->flushMappedMemoryRanges(1, &range); - } + globalIOffset += cmd_list->IdxBuffer.Size; + globalVOffset += cmd_list->VtxBuffer.Size; } - // fill vertex/index data, no flush request - for (int n = 0; n < drawData->CmdListsCount; n++) - { - const auto* cmd = drawData->CmdLists[n]; - - ::memcpy(verticesPointer, cmd->VtxBuffer.Data, cmd->VtxBuffer.Size * sizeof(ImDrawVert)); - ::memcpy(indicesPointer, cmd->IdxBuffer.Data, cmd->IdxBuffer.Size * sizeof(ImDrawIdx)); + drawCount = drawID + 1u; + } - verticesPointer += cmd->VtxBuffer.Size; - indicesPointer += cmd->IdxBuffer.Size; + // flush mdi's memory range if required + { + if (mdiBind.memory->haveToMakeVisible()) + { + const ILogicalDevice::MappedMemoryRange range(mdiBind.memory, mdiBind.offset, mdiBuffer->getSize()); + m_device->flushMappedMemoryRanges(1, &range); } - - binding.memory->unmap(); } + auto* rawPipeline = pipeline.get(); + commandBuffer->bindGraphicsPipeline(rawPipeline); + commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); { const asset::SBufferBinding binding = { - .offset = requestData.getIndicesOffset(), - .buffer = core::smart_refctd_ptr(mdiBuffer) + .offset = mdiBind.offset + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], + .buffer = smart_refctd_ptr(mdiBuffer) }; if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) @@ -924,12 +896,10 @@ namespace nbl::ext::imgui { const asset::SBufferBinding bindings[] = - { - { - .offset = requestData.getVerticesOffset(), - .buffer = core::smart_refctd_ptr(mdiBuffer) - } - }; + {{ + .offset = mdiBind.offset + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], + .buffer = smart_refctd_ptr(mdiBuffer) + }}; if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) { @@ -950,6 +920,7 @@ namespace nbl::ext::imgui commandBuffer->setViewport(0, 1, &viewport); { + // TODO: remove VkRect2D scissor[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; commandBuffer->setScissor(scissor); // cover whole viewport (only to not throw validation errors) } @@ -962,7 +933,7 @@ namespace nbl::ext::imgui { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + elementsBOffset }, + .elementBDA = { mdiBuffer->getDeviceAddress() + mdiBind.offset + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]}, .elementCount = { drawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, @@ -974,7 +945,7 @@ namespace nbl::ext::imgui const asset::SBufferBinding binding = { - .offset = requestData.getVkDrawIndexedIndirectCommandOffset(), + .offset = multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES], .buffer = core::smart_refctd_ptr(mdiBuffer) }; From 4af890d4476b05079fff02894b6bcc53326bb540 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 4 Sep 2024 16:11:57 +0200 Subject: [PATCH 064/148] preallocate 1Mb MDI buffer in UI's constructor, pass correctly multi alloc but now I have white screen + swapchain present fails terminating my exe, I need to take care of intended submit + sync things first, also my queue submit seems to take much more time than before --- include/nbl/ext/ImGui/ImGui.h | 2 + src/nbl/ext/ImGui/ImGui.cpp | 122 ++++++++++++++-------------------- 2 files changed, 52 insertions(+), 72 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d686d01891..ad6bc40e86 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -28,6 +28,8 @@ class UI final : public core::IReferenceCounted void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); // TODO: just take an intended next submit instead of queue and cmdbuf, so we're consistent across utilities + + void createMDIBuffer(const uint32_t totalByteSize); video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); void createSystem(); void handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index cd7c814ec7..9f4eff576a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -599,6 +599,9 @@ namespace nbl::ext::imgui } tQueue->endCapture(); + static constexpr auto DEFAULT_MDI_SIZE = 1024u * 1024u; // 1 Mb + createMDIBuffer(DEFAULT_MDI_SIZE); + auto & io = ImGui::GetIO(); io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); @@ -625,6 +628,43 @@ namespace nbl::ext::imgui } } + void UI::createMDIBuffer(const uint32_t totalByteSize) + { + constexpr static core::bitflag allocateFlags(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + constexpr static uint32_t minStreamingBufferAllocationSize = 4u, + maxStreamingBufferAllocationAlignment = 1024u * 64u; + + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; + mdiCreationParams.size = totalByteSize; + + auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto offset = m_device->allocate(memoryReqs, buffer.get(), allocateFlags); + auto memory = offset.memory; + const bool allocated = offset.isValid(); + assert(allocated); + + const auto properties = memory->getMemoryPropertyFlags(); + + core::bitflag accessFlags(IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) + accessFlags |= IDeviceMemoryAllocation::EMCAF_READ; + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) + accessFlags |= IDeviceMemoryAllocation::EMCAF_WRITE; + + assert(accessFlags.value); + + if (!memory->map({ 0ull, memoryReqs.size }, accessFlags)) + logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); + + m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); + } + bool UI::render(IGPUCommandBuffer* commandBuffer, const IGPUDescriptorSet* const descriptorSet) { ImGuiIO& io = ImGui::GetIO(); @@ -734,75 +774,21 @@ namespace nbl::ext::imgui return requestedByteSize + padding; }(); - // TOOD: lets start with preallocated buffer with like - // 2 MB or something and then handle overflows - - // create/update mdi composed streaming buffer - auto updateMDIBuffer = [&](const uint32_t totalByteSize, const bool forceRecreate = false) -> void - { - if (forceRecreate || !m_mdi.streamingTDBufferST || totalByteSize > m_mdi.streamingTDBufferST->get_total_size()) - { - constexpr static core::bitflag allocateFlags(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - constexpr static uint32_t minStreamingBufferAllocationSize = 4u, - maxStreamingBufferAllocationAlignment = 1024u * 64u; - - IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - mdiCreationParams.size = totalByteSize; - - auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); - - auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - - auto offset = m_device->allocate(memoryReqs, buffer.get(), allocateFlags); - auto memory = offset.memory; - const bool allocated = offset.isValid(); - assert(allocated); - - const auto properties = memory->getMemoryPropertyFlags(); - - core::bitflag accessFlags(IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) - accessFlags |= IDeviceMemoryAllocation::EMCAF_READ; - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) - accessFlags |= IDeviceMemoryAllocation::EMCAF_WRITE; - - assert(accessFlags.value); - - if (!memory->map({ 0ull, memoryReqs.size }, accessFlags)) - logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); - - m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); - m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); - } - }; - - updateMDIBuffer(mdiBufferByteSize); { std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); - m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), multiAllocParams.alignments.data()); + const size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), multiAllocParams.alignments.data()); - const bool ok = [&offsets = multiAllocParams.offsets]() -> bool - { - for (const auto& it : offsets) - if (it == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value) - return false; - - return true; - }(); + const bool ok = unallocatedSize == 0u; - if(!ok) + if (!ok) + { logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); - - // how to handle it properly on fail? TODO: take intended submit info, play with bariers, handle overflow + exit(0x45); // TODO: handle overflows, unallocatedSize tells how much is missing + } } auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer(), dont_grab_t{}); - auto mdiBind = mdiBuffer->getBoundMemory(); - - assert(mdiBind.memory->isCurrentlyMapped()); auto* const indirectsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); auto* const elementsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); @@ -868,22 +854,13 @@ namespace nbl::ext::imgui drawCount = drawID + 1u; } - // flush mdi's memory range if required - { - if (mdiBind.memory->haveToMakeVisible()) - { - const ILogicalDevice::MappedMemoryRange range(mdiBind.memory, mdiBind.offset, mdiBuffer->getSize()); - m_device->flushMappedMemoryRanges(1, &range); - } - } - auto* rawPipeline = pipeline.get(); commandBuffer->bindGraphicsPipeline(rawPipeline); commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); { const asset::SBufferBinding binding = { - .offset = mdiBind.offset + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], + .offset = multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -897,7 +874,7 @@ namespace nbl::ext::imgui { const asset::SBufferBinding bindings[] = {{ - .offset = mdiBind.offset + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], + .offset = multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], .buffer = smart_refctd_ptr(mdiBuffer) }}; @@ -933,7 +910,7 @@ namespace nbl::ext::imgui { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + mdiBind.offset + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]}, + .elementBDA = { mdiBuffer->getDeviceAddress() + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]}, .elementCount = { drawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, @@ -951,6 +928,7 @@ namespace nbl::ext::imgui commandBuffer->drawIndexedIndirect(binding, drawCount, sizeof(VkDrawIndexedIndirectCommand)); } + assert(m_mdi.streamingTDBufferST->getBuffer()); // make sure no drop return true; } From e9af09313dfcfa0d382366c1386aeecc7c936d54 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 08:38:23 +0200 Subject: [PATCH 065/148] fix awful bugs - requested one extra indirect draw than I have + mistaken ImGUI Vertex with Indices types [typo] resulting in incorrect memory write. I see the scene nicely again, but something is decreasing my ref counted counter for mdi buffer which is proxy passed with dont_grab_t{}! (wut, inspecting it) --- src/nbl/ext/ImGui/ImGui.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 9f4eff576a..fda35bcb88 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -790,13 +790,21 @@ namespace nbl::ext::imgui auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer(), dont_grab_t{}); - auto* const indirectsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); - auto* const elementsPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); - auto* indicesPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); - auto* verticesPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); - - uint32_t drawCount {}; + const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); { + auto binding = mdiBuffer->getBoundMemory(); + + if(!binding.memory->isCurrentlyMapped()) + if (!binding.memory->map({ 0ull, binding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) + logger->log("Could not map device memory!", system::ILogger::ELL_WARNING); + + assert(binding.memory->isCurrentlyMapped()); + + auto* const indirectsMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); + auto* const elementsMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); + auto* indicesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); + auto* verticesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + /* IMGUI Render command lists. We merged all buffers into a single one so we maintain our own offset into them, we pre-loop to get request data for @@ -815,8 +823,8 @@ namespace nbl::ext::imgui const auto clipRectangle = clip.getClipRectangle(pcmd); // update mdi's indirect & element structures - auto* indirect = indirectsPointer + drawID; - auto* element = elementsPointer + drawID; + auto* indirect = indirectsMappedPointer + drawID; + auto* element = elementsMappedPointer + drawID; indirect->firstIndex = pcmd->IdxOffset + globalIOffset; indirect->firstInstance = drawID; // use base instance as draw ID @@ -844,14 +852,12 @@ namespace nbl::ext::imgui } // update mdi's vertex & index buffers - ::memcpy(indicesPointer + globalIOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - ::memcpy(verticesPointer + globalVOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + ::memcpy(indicesMappedPointer + globalIOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + ::memcpy(verticesMappedPointer + globalVOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); globalIOffset += cmd_list->IdxBuffer.Size; globalVOffset += cmd_list->VtxBuffer.Size; } - - drawCount = drawID + 1u; } auto* rawPipeline = pipeline.get(); From 73f77134f50bbae7885c872ca425848d71d61a42 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 08:55:56 +0200 Subject: [PATCH 066/148] correct another bug, indeed I don't want to "dont_grab{}" in order to keep it alive and not let it decrease reference counter to 0 --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index fda35bcb88..1d0ef36a3d 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -788,7 +788,7 @@ namespace nbl::ext::imgui } } - auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer(), dont_grab_t{}); + auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); { From cb83a58ab496c992c3da582de012ac493884b5ea Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 09:16:41 +0200 Subject: [PATCH 067/148] add multi alloc fail callback logs, time for intended submit + missing multi_deallocate --- src/nbl/ext/ImGui/ImGui.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1d0ef36a3d..1d0806fc7a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -764,12 +764,12 @@ namespace nbl::ext::imgui multiAllocParams.byteSizes[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); } - // calculate upper bound byte size limit for mdi buffer with alignments taken into account + // calculate upper bound byte size limit for mdi buffer const auto mdiBufferByteSize = [&byteSizes = multiAllocParams.byteSizes, &alignments = multiAllocParams.alignments]() -> size_t { auto requestedByteSize = std::reduce(std::begin(byteSizes), std::end(byteSizes)); auto maxAlignment = *std::max_element(std::begin(alignments), std::end(alignments)); - auto padding = requestedByteSize % maxAlignment; + auto padding = requestedByteSize % maxAlignment; // okay I don't need it at all but lets add a few bytes return requestedByteSize + padding; }(); @@ -784,6 +784,18 @@ namespace nbl::ext::imgui if (!ok) { logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + + auto callback = [&](const std::string_view section, const MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::value_type offset) + { + std::string value = offset == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); + logger->log("%s offset = %s", system::ILogger::ELL_ERROR, section.data(), value.c_str()); + }; + + callback("MDI::EBC_DRAW_INDIRECT_STRUCTURES", multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); + callback("MDI::EBC_ELEMENT_STRUCTURES", multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); + callback("MDI::EBC_INDEX_BUFFERS", multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); + callback("MDI::EBC_VERTEX_BUFFERS", multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + exit(0x45); // TODO: handle overflows, unallocatedSize tells how much is missing } } From 5cb70ef5e6e591439f2eb9f566e6e7d3bf68dcc0 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 12:00:06 +0200 Subject: [PATCH 068/148] use SIntendedSubmitInfo instead of raw command buffer in UI's render call, add more validation & logs, multi_deallocate at then end of the render call with future scratch semaphore (we treating it as wait semaphore), temporary terminate application on scratch semaphore-spotted counter overflow, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 62 ++++++++++++++++++++++++----------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/examples_tests b/examples_tests index eb1efe063c..7929932815 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit eb1efe063c864954e2fa75edf8f7d8ad2f76d507 +Subproject commit 792993281561ea1d67c07d712167857c44a4874a diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index ad6bc40e86..d0ae6a4ddb 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -15,7 +15,7 @@ class UI final : public core::IReferenceCounted UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); ~UI() override; - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const nbl::video::IGPUDescriptorSet* const descriptorSet); + bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); int registerListener(std::function const& listener); bool unregisterListener(uint32_t id); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1d0806fc7a..ee315aab09 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -665,14 +665,28 @@ namespace nbl::ext::imgui m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); } - bool UI::render(IGPUCommandBuffer* commandBuffer, const IGPUDescriptorSet* const descriptorSet) + bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet) { + if (!info.valid()) + { + logger->log("Invalid SIntendedSubmitInfo!", system::ILogger::ELL_ERROR); + return false; + } + + struct + { + const uint64_t oldie; + uint64_t newie; + } scratchSemaphoreCounters = { .oldie = info.scratchSemaphore.value, .newie = 0u }; + + auto* commandBuffer = info.getScratchCommandBuffer(); + ImGuiIO& io = ImGui::GetIO(); if (!io.Fonts->IsBuilt()) { logger->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); - assert(false); + return false; } auto const* drawData = ImGui::GetDrawData(); @@ -774,6 +788,7 @@ namespace nbl::ext::imgui return requestedByteSize + padding; }(); + auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); { std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); @@ -785,23 +800,23 @@ namespace nbl::ext::imgui { logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); - auto callback = [&](const std::string_view section, const MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::value_type offset) + auto getOffsetStr = [&](const MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::value_type offset) -> std::string { - std::string value = offset == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); - logger->log("%s offset = %s", system::ILogger::ELL_ERROR, section.data(), value.c_str()); + return offset == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); }; - callback("MDI::EBC_DRAW_INDIRECT_STRUCTURES", multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); - callback("MDI::EBC_ELEMENT_STRUCTURES", multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); - callback("MDI::EBC_INDEX_BUFFERS", multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); - callback("MDI::EBC_VERTEX_BUFFERS", multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + logger->log("[mdi streaming buffer size] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); + logger->log("[unallocated size] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); + + logger->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); + logger->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); + logger->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); + logger->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); - exit(0x45); // TODO: handle overflows, unallocatedSize tells how much is missing + exit(0x45); // TODO: handle OOB memory requests } } - auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); - const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); { auto binding = mdiBuffer->getBoundMemory(); @@ -817,12 +832,6 @@ namespace nbl::ext::imgui auto* indicesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); auto* verticesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); - /* - IMGUI Render command lists. We merged all buffers into a single one so we - maintain our own offset into them, we pre-loop to get request data for - MDI buffer alocation request/update. - */ - size_t globalIOffset = {}, globalVOffset = {}, drawID = {}; for (int n = 0; n < drawData->CmdListsCount; n++) @@ -945,9 +954,22 @@ namespace nbl::ext::imgui }; commandBuffer->drawIndexedIndirect(binding, drawCount, sizeof(VkDrawIndexedIndirectCommand)); - } - assert(m_mdi.streamingTDBufferST->getBuffer()); // make sure no drop + scratchSemaphoreCounters.newie = info.scratchSemaphore.value; + + if (scratchSemaphoreCounters.newie != scratchSemaphoreCounters.oldie) + { + const auto overflows = scratchSemaphoreCounters.newie - scratchSemaphoreCounters.oldie; + logger->log("%d overflows when rendering UI!\n", nbl::system::ILogger::ELL_PERFORMANCE, overflows); + + // TODO: handle them? + exit(0x45); + } + + auto waitInfo = info.getFutureScratchSemaphore(); + m_mdi.streamingTDBufferST->multi_deallocate(MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); + } + return true; } From 839f38f06f3ed323f7323006154a4f8fad64d683 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 12:08:29 +0200 Subject: [PATCH 069/148] let's boost default default UI mdi streaming buffer to 2Mb, I still have issues with multi alloc hence I guess something off with my wait semaphore --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index ee315aab09..ace528382e 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -599,7 +599,7 @@ namespace nbl::ext::imgui } tQueue->endCapture(); - static constexpr auto DEFAULT_MDI_SIZE = 1024u * 1024u; // 1 Mb + static constexpr auto DEFAULT_MDI_SIZE = 1024u * 1024u * 2u; // 2 Mb createMDIBuffer(DEFAULT_MDI_SIZE); auto & io = ImGui::GetIO(); From 4fbf379d5351b667d6f0285219f7e10590f23aa3 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 14:19:18 +0200 Subject: [PATCH 070/148] move MULTI_ALLOC_PARAMS to cpp, make mdi the alignment array constexpr, add more logs (+ tmp logs) - I have issue with deferred offset memory deallocation, it doesn't execute (maybe because of my wait semaphore?) --- include/nbl/ext/ImGui/ImGui.h | 15 +++---------- src/nbl/ext/ImGui/ImGui.cpp | 41 ++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 32 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d0ae6a4ddb..2586ec1fe4 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -46,6 +46,8 @@ class UI final : public core::IReferenceCounted struct MDI { + using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; + enum E_BUFFER_CONTENT : uint8_t { EBC_DRAW_INDIRECT_STRUCTURES, @@ -56,18 +58,7 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - struct MULTI_ALLOC_PARAMS - { - static constexpr auto ALLOCATION_COUNT = EBC_COUNT; - - using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; - - std::array alignments = {}; - std::array byteSizes = {}; - std::array offsets = { COMPOSE_T::invalid_value, COMPOSE_T::invalid_value, COMPOSE_T::invalid_value, COMPOSE_T::invalid_value }; - }; - - nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [Draw Indirect structures] [Element structures] [Index buffers] [Vertex Buffers] + nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] }; MDI m_mdi; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index ace528382e..27f089a93b 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -661,7 +661,7 @@ namespace nbl::ext::imgui if (!memory->map({ 0ull, memoryReqs.size }, accessFlags)) logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); - m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); } @@ -760,12 +760,17 @@ namespace nbl::ext::imgui return std::move(retV); }(); - MDI::MULTI_ALLOC_PARAMS multiAllocParams; + static constexpr auto MDI_ALLOCATION_COUNT = MDI::EBC_COUNT; + static auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); - multiAllocParams.alignments[MDI::EBC_DRAW_INDIRECT_STRUCTURES] = alignof(VkDrawIndexedIndirectCommand); - multiAllocParams.alignments[MDI::EBC_ELEMENT_STRUCTURES] = alignof(PerObjectData); - multiAllocParams.alignments[MDI::EBC_INDEX_BUFFERS] = alignof(ImDrawIdx); - multiAllocParams.alignments[MDI::EBC_VERTEX_BUFFERS] = alignof(ImDrawVert); + struct MULTI_ALLOC_PARAMS + { + std::array byteSizes = {}; + std::array offsets = {}; + }; + + MULTI_ALLOC_PARAMS multiAllocParams; + std::fill(multiAllocParams.offsets.data(), multiAllocParams.offsets.data() + MDI_ALLOCATION_COUNT, MDI::COMPOSE_T::invalid_value); // calculate upper bound byte size for each mdi's content for (uint32_t i = 0; i < drawData->CmdListsCount; i++) @@ -779,20 +784,13 @@ namespace nbl::ext::imgui } // calculate upper bound byte size limit for mdi buffer - const auto mdiBufferByteSize = [&byteSizes = multiAllocParams.byteSizes, &alignments = multiAllocParams.alignments]() -> size_t - { - auto requestedByteSize = std::reduce(std::begin(byteSizes), std::end(byteSizes)); - auto maxAlignment = *std::max_element(std::begin(alignments), std::end(alignments)); - auto padding = requestedByteSize % maxAlignment; // okay I don't need it at all but lets add a few bytes - - return requestedByteSize + padding; - }(); + const auto mdiBufferByteSize = std::reduce(std::begin(multiAllocParams.byteSizes), std::end(multiAllocParams.byteSizes)); auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); { std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); - const size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), multiAllocParams.alignments.data()); + const size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); const bool ok = unallocatedSize == 0u; @@ -800,13 +798,14 @@ namespace nbl::ext::imgui { logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); - auto getOffsetStr = [&](const MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::value_type offset) -> std::string + auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string { - return offset == MDI::MULTI_ALLOC_PARAMS::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); + return offset == MDI::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); }; - logger->log("[mdi streaming buffer size] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); - logger->log("[unallocated size] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); + logger->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); + logger->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); + logger->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); logger->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); logger->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); @@ -967,7 +966,9 @@ namespace nbl::ext::imgui } auto waitInfo = info.getFutureScratchSemaphore(); - m_mdi.streamingTDBufferST->multi_deallocate(MDI::MULTI_ALLOC_PARAMS::ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); + //logger->log("wait info semaphore value for deferred free latch \"%s\"", nbl::system::ILogger::ELL_PERFORMANCE, std::to_string(waitInfo.value).c_str()); + + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // TODO: why does it not free my offsets with deferred latch free? } return true; From 46a503957f7d8d9c835a7dc50b63b4cb92330a3d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 15:45:27 +0200 Subject: [PATCH 071/148] cull_frees with streaming buffer, handle failed allocation and make it work (Erfan thank you for help), update examples_tests submodule --- examples_tests | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 41 ++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/examples_tests b/examples_tests index 7929932815..9f615e2bc6 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 792993281561ea1d67c07d712167857c44a4874a +Subproject commit 9f615e2bc6bad50dd9609dd3d3a504ba71b2ea47 diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 27f089a93b..ab0954ff80 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -790,29 +790,34 @@ namespace nbl::ext::imgui { std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); - const size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); + size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); - const bool ok = unallocatedSize == 0u; - - if (!ok) + if (unallocatedSize != 0u) { - logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + // retry + m_mdi.streamingTDBufferST->cull_frees(); + unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); - auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string + if (unallocatedSize != 0u) { - return offset == MDI::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); - }; + logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + + auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string + { + return offset == MDI::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); + }; - logger->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); - logger->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); - logger->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); + logger->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); + logger->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); + logger->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); - logger->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); - logger->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); - logger->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); - logger->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); + logger->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); + logger->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); + logger->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); + logger->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); - exit(0x45); // TODO: handle OOB memory requests + exit(0x45); // TODO: handle OOB memory requests + } } } @@ -966,9 +971,7 @@ namespace nbl::ext::imgui } auto waitInfo = info.getFutureScratchSemaphore(); - //logger->log("wait info semaphore value for deferred free latch \"%s\"", nbl::system::ILogger::ELL_PERFORMANCE, std::to_string(waitInfo.value).c_str()); - - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // TODO: why does it not free my offsets with deferred latch free? + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // at some point a block would be needed anyway, cull frees but where? - so I just retry on failed allocation then cull free } return true; From 46c78a8313fcf26afb12b0806967ace18a731cbb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Sep 2024 17:24:29 +0200 Subject: [PATCH 072/148] add UI streaming buffer const getter, extra cull frees call, correct some typos, add comments & update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 5 +++-- src/nbl/ext/ImGui/ImGui.cpp | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/examples_tests b/examples_tests index 9f615e2bc6..63b30ae115 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 9f615e2bc6bad50dd9609dd3d3a504ba71b2ea47 +Subproject commit 63b30ae11595b54e1bd51605cc00bfd6bd6e8e94 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 2586ec1fe4..66e11571f2 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -19,10 +19,11 @@ class UI final : public core::IReferenceCounted void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); int registerListener(std::function const& listener); bool unregisterListener(uint32_t id); + void setContext(void* imguiContext); + inline nbl::core::smart_refctd_ptr getFontAtlasView() { return m_fontAtlasTexture; } - + inline auto getStreamingBuffer() -> decltype(auto) { return (std::as_const(m_mdi.streamingTDBufferST)); } void* getContext(); - void setContext(void* imguiContext); private: void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index ab0954ff80..5a21d3dd9b 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -761,7 +761,7 @@ namespace nbl::ext::imgui }(); static constexpr auto MDI_ALLOCATION_COUNT = MDI::EBC_COUNT; - static auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); struct MULTI_ALLOC_PARAMS { @@ -794,7 +794,7 @@ namespace nbl::ext::imgui if (unallocatedSize != 0u) { - // retry + // retry & cull frees m_mdi.streamingTDBufferST->cull_frees(); unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); @@ -816,7 +816,7 @@ namespace nbl::ext::imgui logger->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); logger->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); - exit(0x45); // TODO: handle OOB memory requests + exit(0x45); // TODO: handle OOB memory requests, probably need to extend the mdi buffer/let user pass more size at init } } } @@ -971,8 +971,10 @@ namespace nbl::ext::imgui } auto waitInfo = info.getFutureScratchSemaphore(); - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // at some point a block would be needed anyway, cull frees but where? - so I just retry on failed allocation then cull free + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); } + + m_mdi.streamingTDBufferST->cull_frees(); return true; } From 5be870d8b81e19365e967ad38ce5903af622b00a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 6 Sep 2024 07:44:29 +0200 Subject: [PATCH 073/148] I don't need to cull_frees myself, second allocation attempt fires deferred memory free anyway --- src/nbl/ext/ImGui/ImGui.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5a21d3dd9b..6addcf0072 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -794,8 +794,7 @@ namespace nbl::ext::imgui if (unallocatedSize != 0u) { - // retry & cull frees - m_mdi.streamingTDBufferST->cull_frees(); + // retry, second attempt cull frees and execute deferred memory deallocation of offsets no longer in use unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); if (unallocatedSize != 0u) @@ -973,8 +972,6 @@ namespace nbl::ext::imgui auto waitInfo = info.getFutureScratchSemaphore(); m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); } - - m_mdi.streamingTDBufferST->cull_frees(); return true; } From 4b8990bb918a344786f2da7786f776d13226ebf2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 7 Sep 2024 14:17:15 +0200 Subject: [PATCH 074/148] sort out how we handle window in ImGUI backend - it's really only needed at update stage, should never be kept & passed with constructor. Let user pass subpassIx while creating the UI for internal pipeline creation purposes, update examples_tests submodule, clean some code & add comments --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 35 +++++++++++++++++-------------- include/nbl/ui/IWindow.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 39 ++++++++++++++++++++--------------- src/nbl/ui/CWindowAndroid.h | 2 +- src/nbl/ui/CWindowWin32.h | 2 +- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/examples_tests b/examples_tests index 63b30ae115..b6ba8ea28c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 63b30ae11595b54e1bd51605cc00bfd6bd6e8e94 +Subproject commit b6ba8ea28ce52bba7febb6c7ee266b5f0dbba5ff diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 66e11571f2..c00c76bb29 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -5,35 +5,42 @@ namespace nbl::ext::imgui { - class UI final : public core::IReferenceCounted { public: - // Nabla IMGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error + UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache = nullptr, uint32_t mdiTotalByteSize = 1024u * 1024u * 2u /* 2Mb */); + ~UI() override; + + //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error [TODO: could have a setter & getter to control the default & currently hooked font texture ID and init 0u by default] _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; - UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); - ~UI() override; + //! update ImGUI internal state & cpu draw command lists, call it before this->render + bool update(const ui::IWindow* window, float deltaTimeInSec, const core::SRange mouseEvents, const core::SRange keyboardEvents); + //! updates mapped mdi buffer & records draw calls [TODO: maybe its a good idea to update mdi at the end of .update call instead of doing it here?] bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); - void update(float deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents); + + //! registers lambda listener in which ImGUI calls should be recorded int registerListener(std::function const& listener); bool unregisterListener(uint32_t id); + + //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context void setContext(void* imguiContext); - inline nbl::core::smart_refctd_ptr getFontAtlasView() { return m_fontAtlasTexture; } + //! image view getter to access default font texture + inline auto getFontAtlasView() -> decltype(auto) { return core::smart_refctd_ptr(m_fontAtlasTexture); } + + //! mdi streaming buffer getter inline auto getStreamingBuffer() -> decltype(auto) { return (std::as_const(m_mdi.streamingTDBufferST)); } - void* getContext(); + //! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); + void* getContext(); private: - void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); - - // TODO: just take an intended next submit instead of queue and cmdbuf, so we're consistent across utilities - + void createSystem(); + void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache); void createMDIBuffer(const uint32_t totalByteSize); video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void createSystem(); - void handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const; + void handleMouseEvents(const core::SRange& events, const ui::IWindow* window) const; void handleKeyEvents(const core::SRange& events) const; core::smart_refctd_ptr system; @@ -43,7 +50,6 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontAtlasTexture; - core::smart_refctd_ptr m_window; struct MDI { @@ -71,7 +77,6 @@ class UI final : public core::IReferenceCounted std::function listener = nullptr; }; std::vector m_subscribers{}; - }; } diff --git a/include/nbl/ui/IWindow.h b/include/nbl/ui/IWindow.h index b66f8dbd14..385f70d172 100644 --- a/include/nbl/ui/IWindow.h +++ b/include/nbl/ui/IWindow.h @@ -194,7 +194,7 @@ class IWindow : public core::IReferenceCounted inline int32_t getY() const { return m_y; } NBL_API2 virtual IClipboardManager* getClipboardManager() = 0; - NBL_API2 virtual ICursorControl* getCursorControl() = 0; + NBL_API2 virtual ICursorControl* getCursorControl() const = 0; NBL_API2 virtual IWindowManager* getManager() const = 0; inline IEventCallback* getEventCallback() const { return m_cb.get(); } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 6addcf0072..95d09793ed 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -5,6 +5,8 @@ #include #include "nbl/system/IApplicationFramework.h" +#include "nbl/ui/IWindow.h" +#include "nbl/ui/ICursorControl.h" #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" @@ -21,7 +23,7 @@ using namespace nbl::ui; namespace nbl::ext::imgui { - void UI::createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) + void UI::createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix SPushConstantRange pushConstantRanges[] = @@ -128,7 +130,7 @@ namespace nbl::ext::imgui param.layout = pipelineLayout.get(); param.shaders = specs; param.renderpass = renderpass; - param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = 0u }; // TODO: check "subpassIx" + param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = subpassIx }; }; if (!m_device->createGraphicsPipelines(pipelineCache, params, &pipeline)) @@ -309,13 +311,14 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::handleMouseEvents(const nbl::hlsl::float32_t2& mousePosition, const core::SRange& events) const + void UI::handleMouseEvents(const core::SRange& events, const ui::IWindow* window) const { auto& io = ImGui::GetIO(); - const auto position = mousePosition - nbl::hlsl::float32_t2(m_window->getX(), m_window->getY()); + const auto cursorPosition = window->getCursorControl()->getPosition(); + const auto mousePixelPosition = nbl::hlsl::float32_t2(cursorPosition.x, cursorPosition.y) - nbl::hlsl::float32_t2(window->getX(), window->getY()); - io.AddMousePosEvent(position.x, position.y); + io.AddMousePosEvent(mousePixelPosition.x, mousePixelPosition.y); for (const auto& e : events) { @@ -519,8 +522,8 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) - : m_device(core::smart_refctd_ptr(_device)), m_window(core::smart_refctd_ptr(window)) + UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache, uint32_t mdiTotalByteSize) + : m_device(core::smart_refctd_ptr(_device)) { createSystem(); struct @@ -571,7 +574,7 @@ namespace nbl::ext::imgui smart_refctd_ptr transistentCMD; { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - // need to be individually resettable such that we can form a valid SIntendedSubmit out of the commandbuffer allocated from the pool + smart_refctd_ptr pool = m_device->createCommandPool(families.id.transfer, pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { @@ -593,17 +596,15 @@ namespace nbl::ext::imgui IMGUI_CHECKVERSION(); ImGui::CreateContext(); - createPipeline(core::smart_refctd_ptr(_descriptorSetLayout), renderpass, pipelineCache); + createPipeline(core::smart_refctd_ptr(_descriptorSetLayout), renderpass, subpassIx, pipelineCache); createFontAtlasTexture(transistentCMD.get(), tQueue); adjustGlobalFontScale(); } tQueue->endCapture(); - static constexpr auto DEFAULT_MDI_SIZE = 1024u * 1024u * 2u; // 2 Mb - createMDIBuffer(DEFAULT_MDI_SIZE); + createMDIBuffer(mdiTotalByteSize); auto & io = ImGui::GetIO(); - io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); io.BackendUsingLegacyKeyArrays = 0; // 0: using AddKeyEvent() [new way of handling events in imgui] } @@ -931,7 +932,7 @@ namespace nbl::ext::imgui VkRect2D scissor[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; commandBuffer->setScissor(scissor); // cover whole viewport (only to not throw validation errors) } - + /* Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. @@ -976,14 +977,17 @@ namespace nbl::ext::imgui return true; } - void UI::update(float const deltaTimeInSec, const nbl::hlsl::float32_t2 mousePosition, const core::SRange mouseEvents, const core::SRange keyboardEvents) + bool UI::update(const ui::IWindow* window, float const deltaTimeInSec, const core::SRange mouseEvents, const core::SRange keyboardEvents) { + if (!window) + return false; + auto & io = ImGui::GetIO(); io.DeltaTime = deltaTimeInSec; - io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); + io.DisplaySize = ImVec2(window->getWidth(), window->getHeight()); - handleMouseEvents(mousePosition, mouseEvents); + handleMouseEvents(mouseEvents, window); handleKeyEvents(keyboardEvents); ImGui::NewFrame(); @@ -992,6 +996,8 @@ namespace nbl::ext::imgui subscriber.listener(); ImGui::Render(); // note it doesn't touch GPU or graphics API at all, internal call for IMGUI cpu geometry buffers update + + return true; } int UI::registerListener(std::function const& listener) @@ -1024,5 +1030,4 @@ namespace nbl::ext::imgui { ImGui::SetCurrentContext(reinterpret_cast(imguiContext)); } - } \ No newline at end of file diff --git a/src/nbl/ui/CWindowAndroid.h b/src/nbl/ui/CWindowAndroid.h index 18a364a4ee..f68dfc11fb 100644 --- a/src/nbl/ui/CWindowAndroid.h +++ b/src/nbl/ui/CWindowAndroid.h @@ -23,7 +23,7 @@ class CWindowAndroid : public IWindowAndroid } virtual inline IClipboardManager* getClipboardManager() override { return nullptr; } - virtual inline ICursorControl* getCursorControl() override { return nullptr; } + virtual inline ICursorControl* getCursorControl() const override { return nullptr; } inline const native_handle_t& getNativeHandle() const override { return m_native; } diff --git a/src/nbl/ui/CWindowWin32.h b/src/nbl/ui/CWindowWin32.h index 295e21e525..fcb675900e 100644 --- a/src/nbl/ui/CWindowWin32.h +++ b/src/nbl/ui/CWindowWin32.h @@ -31,7 +31,7 @@ class NBL_API2 CWindowWin32 final : public IWindowWin32 return m_clipboardManager.get(); } - inline ICursorControl* getCursorControl() override {return m_windowManager.get();} + inline ICursorControl* getCursorControl() const override {return m_windowManager.get();} inline IWindowManager* getManager() const override {return m_windowManager.get();} From bca9f86cfea7d6e4c57aee30e9004d09109e034d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 9 Sep 2024 14:54:22 +0200 Subject: [PATCH 075/148] bring back GLSLstd450MatrixInverse & GLSLstd450UnpackSnorm2x16 - set proper extended instruction set for our opcodes ( `ext_instruction(opcode[, set])` ) --- include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl index 139d21a3f0..ffe9100778 100644 --- a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl +++ b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl @@ -190,18 +190,13 @@ enable_if_t,void> store(P pointer, T obj); //! Std 450 Extended set operations -// COMMENTING OUT BECAUSE OF https://github.com/microsoft/DirectXShaderCompiler/issues/6751, bring back when fixed! -/* - template -[[vk::ext_instruction(GLSLstd450MatrixInverse)]] +[[vk::ext_instruction(GLSLstd450MatrixInverse, "GLSL.std.450")]] SquareMatrix matrixInverse(NBL_CONST_REF_ARG(SquareMatrix) mat); -[[vk::ext_instruction(GLSLstd450UnpackSnorm2x16)]] +[[vk::ext_instruction(GLSLstd450UnpackSnorm2x16, "GLSL.std.450")]] float32_t2 unpackSnorm2x16(uint32_t p); -*/ - // Memory Semantics link here: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Memory_Semantics_-id- // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_memory_semantics_id From 35af49d1be9171b9e200823952ec9bb9086066cb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 9 Sep 2024 18:57:33 +0200 Subject: [PATCH 076/148] fix IDeviceMemoryAllocation::map bug with setting m_currentMappingAccess --- include/nbl/video/IDeviceMemoryAllocation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/nbl/video/IDeviceMemoryAllocation.h b/include/nbl/video/IDeviceMemoryAllocation.h index 673f1834e6..9940080c5f 100644 --- a/include/nbl/video/IDeviceMemoryAllocation.h +++ b/include/nbl/video/IDeviceMemoryAllocation.h @@ -114,7 +114,7 @@ class IDeviceMemoryAllocation : public virtual core::IReferenceCounted if (m_mappedPtr) m_mappedPtr -= range.offset; m_mappedRange = m_mappedPtr ? range:MemoryRange{}; - m_currentMappingAccess = m_mappedPtr ? EMCAF_NO_MAPPING_ACCESS:accessHint; + m_currentMappingAccess = m_mappedPtr ? accessHint : EMCAF_NO_MAPPING_ACCESS; return m_mappedPtr; } // returns true on success, false on failure From 650e2e4a77229c500cf5264e26e365f5bed28fba Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 9 Sep 2024 19:01:34 +0200 Subject: [PATCH 077/148] update getters to return dumb pointers, let users pass MDI streaming buffer themselves & validate its creation params, memory, allocate + mapping flags, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 48 ++++++++++---------- src/nbl/ext/ImGui/ImGui.cpp | 84 ++++++++++++++++++++++++----------- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/examples_tests b/examples_tests index b6ba8ea28c..e9f9bbfdde 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit b6ba8ea28ce52bba7febb6c7ee266b5f0dbba5ff +Subproject commit e9f9bbfddeef5fed0ddf2f33c13f1795332f066c diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index c00c76bb29..d59959a530 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -8,7 +8,24 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache = nullptr, uint32_t mdiTotalByteSize = 1024u * 1024u * 2u /* 2Mb */); + struct MDI + { + using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; + + enum E_BUFFER_CONTENT : uint8_t + { + EBC_DRAW_INDIRECT_STRUCTURES, + EBC_ELEMENT_STRUCTURES, + EBC_INDEX_BUFFERS, + EBC_VERTEX_BUFFERS, + + EBC_COUNT, + }; + + nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + }; + + UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache = nullptr, nbl::core::smart_refctd_ptr _streamingMDIBuffer = nullptr); ~UI() override; //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error [TODO: could have a setter & getter to control the default & currently hooked font texture ID and init 0u by default] @@ -17,7 +34,7 @@ class UI final : public core::IReferenceCounted //! update ImGUI internal state & cpu draw command lists, call it before this->render bool update(const ui::IWindow* window, float deltaTimeInSec, const core::SRange mouseEvents, const core::SRange keyboardEvents); - //! updates mapped mdi buffer & records draw calls [TODO: maybe its a good idea to update mdi at the end of .update call instead of doing it here?] + //! updates mapped mdi buffer & records draw calls bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); //! registers lambda listener in which ImGUI calls should be recorded @@ -27,18 +44,18 @@ class UI final : public core::IReferenceCounted //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context void setContext(void* imguiContext); - //! image view getter to access default font texture - inline auto getFontAtlasView() -> decltype(auto) { return core::smart_refctd_ptr(m_fontAtlasTexture); } + //! image view default font texture + inline nbl::video::IGPUImageView* getFontAtlasView() { return m_fontAtlasTexture.get(); } - //! mdi streaming buffer getter - inline auto getStreamingBuffer() -> decltype(auto) { return (std::as_const(m_mdi.streamingTDBufferST)); } + //! mdi streaming buffer + inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.streamingTDBufferST.get(); } //! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: void createSystem(); void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache); - void createMDIBuffer(const uint32_t totalByteSize); + void createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer); video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); void handleMouseEvents(const core::SRange& events, const ui::IWindow* window) const; void handleKeyEvents(const core::SRange& events) const; @@ -51,23 +68,6 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontAtlasTexture; - struct MDI - { - using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; - - enum E_BUFFER_CONTENT : uint8_t - { - EBC_DRAW_INDIRECT_STRUCTURES, - EBC_ELEMENT_STRUCTURES, - EBC_INDEX_BUFFERS, - EBC_VERTEX_BUFFERS, - - EBC_COUNT, - }; - - nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] - }; - MDI m_mdi; // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 95d09793ed..5c7ed70c74 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -522,7 +522,7 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache, uint32_t mdiTotalByteSize) + UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache, nbl::core::smart_refctd_ptr _streamingMDIBuffer) : m_device(core::smart_refctd_ptr(_device)) { createSystem(); @@ -602,7 +602,7 @@ namespace nbl::ext::imgui } tQueue->endCapture(); - createMDIBuffer(mdiTotalByteSize); + createMDIBuffer(_streamingMDIBuffer); auto & io = ImGui::GetIO(); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); @@ -629,41 +629,71 @@ namespace nbl::ext::imgui } } - void UI::createMDIBuffer(const uint32_t totalByteSize) + void UI::createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer) { - constexpr static core::bitflag allocateFlags(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - constexpr static uint32_t minStreamingBufferAllocationSize = 4u, - maxStreamingBufferAllocationAlignment = 1024u * 64u; + constexpr static uint32_t minStreamingBufferAllocationSize = 4u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; + constexpr static auto requiredAllocateFlags = core::bitflag(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + constexpr static auto requiredUsageFlags = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - mdiCreationParams.size = totalByteSize; + auto getRequiredAccessFlags = [&](const core::bitflag& properties) + { + core::bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); + + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_READ; + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_WRITE; - auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); + return flags; + }; - auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + if (_streamingMDIBuffer) + m_mdi.streamingTDBufferST = core::smart_refctd_ptr(_streamingMDIBuffer); + else + { + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = requiredUsageFlags; + mdiCreationParams.size = mdiBufferDefaultSize; - auto offset = m_device->allocate(memoryReqs, buffer.get(), allocateFlags); - auto memory = offset.memory; - const bool allocated = offset.isValid(); - assert(allocated); + auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto allocation = m_device->allocate(memoryReqs, buffer.get(), requiredAllocateFlags); + { + const bool allocated = allocation.isValid(); + assert(allocated); + } + auto memory = allocation.memory; - const auto properties = memory->getMemoryPropertyFlags(); + if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) + logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); - core::bitflag accessFlags(IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) - accessFlags |= IDeviceMemoryAllocation::EMCAF_READ; - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) - accessFlags |= IDeviceMemoryAllocation::EMCAF_WRITE; + m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); + } - assert(accessFlags.value); + auto buffer = m_mdi.streamingTDBufferST->getBuffer(); + auto binding = buffer->getBoundMemory(); - if (!memory->map({ 0ull, memoryReqs.size }, accessFlags)) - logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); + const auto validation = std::to_array + ({ + std::make_pair(buffer->getCreationParams().usage.hasFlags(requiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(requiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) + std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") + }); - m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, totalByteSize, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); - m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); + for (const auto& [ok, error] : validation) + { + if (!ok) + { + logger->log(error, system::ILogger::ELL_ERROR); + assert(false); + } + } } bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet) From 1f19c37f7d587e048ed272cd57d8c02983086553 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 11:14:37 +0200 Subject: [PATCH 078/148] wrap the UI's .update args into S_UPDATE_PARAMETERS, require only the minimum (remove optional delta in secs, 1/60 by default), get rid of our IWindow & ICursor dependency from the UI, move ImGui::Render() from .update to .render, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 27 +++++++++++++++++++++++---- src/nbl/ext/ImGui/ImGui.cpp | 27 +++++++++------------------ 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/examples_tests b/examples_tests index e9f9bbfdde..6ea0637525 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit e9f9bbfddeef5fed0ddf2f33c13f1795332f066c +Subproject commit 6ea0637525363e773c413e1eed7fdbe3ce0e6dba diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d59959a530..deb0c781e8 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -25,16 +25,35 @@ class UI final : public core::IReferenceCounted nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] }; + //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) + struct S_UPDATE_PARAMETERS + { + //! what we pass to ImGuiIO::AddMousePosEvent + nbl::hlsl::float32_t2 mousePosition, + + //! main display size in pixels (generally == GetMainViewport()->Size) + displaySize; + + //! Nabla events you want to be handled with the backend + struct S_EVENTS + { + core::SRange mouse; + core::SRange keyboard; + }; + + S_EVENTS events; + }; + UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache = nullptr, nbl::core::smart_refctd_ptr _streamingMDIBuffer = nullptr); ~UI() override; //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error [TODO: could have a setter & getter to control the default & currently hooked font texture ID and init 0u by default] _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; - //! update ImGUI internal state & cpu draw command lists, call it before this->render - bool update(const ui::IWindow* window, float deltaTimeInSec, const core::SRange mouseEvents, const core::SRange keyboardEvents); + //! update ImGuiIO & record ImGUI *cpu* draw command lists, call it before .render + bool update(const S_UPDATE_PARAMETERS params); - //! updates mapped mdi buffer & records draw calls + //! updates mapped mdi buffer & records *gpu* draw commands, handles overflows for mdi allocation failure cases (pop & submit) bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); //! registers lambda listener in which ImGUI calls should be recorded @@ -57,7 +76,7 @@ class UI final : public core::IReferenceCounted void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache); void createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer); video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void handleMouseEvents(const core::SRange& events, const ui::IWindow* window) const; + void handleMouseEvents(const core::SRange& events, nbl::hlsl::float32_t2 mousePosition) const; void handleKeyEvents(const core::SRange& events) const; core::smart_refctd_ptr system; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5c7ed70c74..50e440cd1f 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -5,8 +5,6 @@ #include #include "nbl/system/IApplicationFramework.h" -#include "nbl/ui/IWindow.h" -#include "nbl/ui/ICursorControl.h" #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" @@ -311,14 +309,11 @@ namespace nbl::ext::imgui io.FontGlobalScale = 1.0f; } - void UI::handleMouseEvents(const core::SRange& events, const ui::IWindow* window) const + void UI::handleMouseEvents(const core::SRange& events, nbl::hlsl::float32_t2 mousePosition) const { auto& io = ImGui::GetIO(); - const auto cursorPosition = window->getCursorControl()->getPosition(); - const auto mousePixelPosition = nbl::hlsl::float32_t2(cursorPosition.x, cursorPosition.y) - nbl::hlsl::float32_t2(window->getX(), window->getY()); - - io.AddMousePosEvent(mousePixelPosition.x, mousePixelPosition.y); + io.AddMousePosEvent(mousePosition.x, mousePosition.y); for (const auto& e : events) { @@ -704,6 +699,8 @@ namespace nbl::ext::imgui return false; } + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() + struct { const uint64_t oldie; @@ -1007,26 +1004,20 @@ namespace nbl::ext::imgui return true; } - bool UI::update(const ui::IWindow* window, float const deltaTimeInSec, const core::SRange mouseEvents, const core::SRange keyboardEvents) + bool UI::update(const S_UPDATE_PARAMETERS params) { - if (!window) - return false; - auto & io = ImGui::GetIO(); + + io.DisplaySize = ImVec2(params.displaySize.x, params.displaySize.y); - io.DeltaTime = deltaTimeInSec; - io.DisplaySize = ImVec2(window->getWidth(), window->getHeight()); - - handleMouseEvents(mouseEvents, window); - handleKeyEvents(keyboardEvents); + handleMouseEvents(params.events.mouse, params.mousePosition); + handleKeyEvents(params.events.keyboard); ImGui::NewFrame(); for (auto const& subscriber : m_subscribers) subscriber.listener(); - ImGui::Render(); // note it doesn't touch GPU or graphics API at all, internal call for IMGUI cpu geometry buffers update - return true; } From 738ba75ed76e4e1536e62410eabf02cf824b62d8 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 12:20:38 +0200 Subject: [PATCH 079/148] fix bug with multi allocation timeout, call multi_deallocate right after multi_allocate, don't try to map MDI memory range in case its unmapped at .render --- src/nbl/ext/ImGui/ImGui.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 50e440cd1f..290cfa8aae 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -816,7 +816,7 @@ namespace nbl::ext::imgui auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); { - std::chrono::steady_clock::time_point timeout(std::chrono::seconds(0x45)); + auto timeout(std::chrono::steady_clock::now() + std::chrono::seconds(1u)); size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); @@ -846,16 +846,14 @@ namespace nbl::ext::imgui exit(0x45); // TODO: handle OOB memory requests, probably need to extend the mdi buffer/let user pass more size at init } } + + auto waitInfo = info.getFutureScratchSemaphore(); + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); } const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); { auto binding = mdiBuffer->getBoundMemory(); - - if(!binding.memory->isCurrentlyMapped()) - if (!binding.memory->map({ 0ull, binding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory!", system::ILogger::ELL_WARNING); - assert(binding.memory->isCurrentlyMapped()); auto* const indirectsMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); @@ -996,9 +994,6 @@ namespace nbl::ext::imgui // TODO: handle them? exit(0x45); } - - auto waitInfo = info.getFutureScratchSemaphore(); - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); } return true; From 70c5d584614904747d7bbbd04d6fd9343e67eeff Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 13:07:41 +0200 Subject: [PATCH 080/148] clean more UI's subscribers, remove EUF_INLINE_UPDATE_VIA_CMDBUF from required usage buffer flags, increase minStreamingBufferAllocationSize to 32u --- include/nbl/ext/ImGui/ImGui.h | 13 +++---------- src/nbl/ext/ImGui/ImGui.cpp | 29 +++++++++++++---------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index deb0c781e8..834805b112 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -57,8 +57,8 @@ class UI final : public core::IReferenceCounted bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); //! registers lambda listener in which ImGUI calls should be recorded - int registerListener(std::function const& listener); - bool unregisterListener(uint32_t id); + std::optional registerListener(std::function const& listener); + std::optional unregisterListener(size_t id); //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context void setContext(void* imguiContext); @@ -88,14 +88,7 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr m_fontAtlasTexture; MDI m_mdi; - - // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; - struct Subscriber - { - uint32_t id = 0; - std::function listener = nullptr; - }; - std::vector m_subscribers{}; + std::vector> m_subscribers {}; }; } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 290cfa8aae..d72a8c4fcc 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -626,9 +626,9 @@ namespace nbl::ext::imgui void UI::createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer) { - constexpr static uint32_t minStreamingBufferAllocationSize = 4u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; + constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; constexpr static auto requiredAllocateFlags = core::bitflag(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - constexpr static auto requiredUsageFlags = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; + constexpr static auto requiredUsageFlags = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; auto getRequiredAccessFlags = [&](const core::bitflag& properties) { @@ -674,7 +674,7 @@ namespace nbl::ext::imgui const auto validation = std::to_array ({ - std::make_pair(buffer->getCreationParams().usage.hasFlags(requiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT | IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF enabled!"), + std::make_pair(buffer->getCreationParams().usage.hasFlags(requiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), std::make_pair(binding.memory->getAllocateFlags().hasFlags(requiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) @@ -1011,30 +1011,27 @@ namespace nbl::ext::imgui ImGui::NewFrame(); for (auto const& subscriber : m_subscribers) - subscriber.listener(); + subscriber(); return true; } - int UI::registerListener(std::function const& listener) + std::optional UI::registerListener(const std::function& listener) { assert(listener != nullptr); - static int NextId = 0; - m_subscribers.emplace_back(NextId++, listener); - return m_subscribers.back().id; + m_subscribers.emplace_back(listener); + return m_subscribers.size() - 1; } - bool UI::unregisterListener(const uint32_t id) + std::optional UI::unregisterListener(size_t id) { - for (int i = m_subscribers.size() - 1; i >= 0; --i) + if (id < m_subscribers.size()) { - if (m_subscribers[i].id == id) - { - m_subscribers.erase(m_subscribers.begin() + i); - return true; - } + m_subscribers.erase(m_subscribers.begin() + id); + return id; } - return false; + + return std::nullopt; } void* UI::getContext() From 17faf3afeb1e8069aa58d427a0062fe4cf105f85 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 13:21:55 +0200 Subject: [PATCH 081/148] typo - registerListener needs to return the same type as unregister takes, also the design for the subscriber could be better however lets leave it currently --- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 834805b112..7dc4ec6870 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -57,7 +57,7 @@ class UI final : public core::IReferenceCounted bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); //! registers lambda listener in which ImGUI calls should be recorded - std::optional registerListener(std::function const& listener); + size_t registerListener(std::function const& listener); std::optional unregisterListener(size_t id); //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index d72a8c4fcc..8733ff878d 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1016,7 +1016,7 @@ namespace nbl::ext::imgui return true; } - std::optional UI::registerListener(const std::function& listener) + size_t UI::registerListener(const std::function& listener) { assert(listener != nullptr); m_subscribers.emplace_back(listener); From ee3fafe3dd03772da56743cf2a0a239fc483eef6 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 13:27:05 +0200 Subject: [PATCH 082/148] lower mdi allocation timeout time point offset to std::chrono::milliseconds --- src/nbl/ext/ImGui/ImGui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 8733ff878d..5b598344bc 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -816,7 +816,7 @@ namespace nbl::ext::imgui auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); { - auto timeout(std::chrono::steady_clock::now() + std::chrono::seconds(1u)); + auto timeout(std::chrono::steady_clock::now() + std::chrono::milliseconds(1u)); size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); @@ -1020,7 +1020,7 @@ namespace nbl::ext::imgui { assert(listener != nullptr); m_subscribers.emplace_back(listener); - return m_subscribers.size() - 1; + return m_subscribers.size() - 1u; } std::optional UI::unregisterListener(size_t id) From 2df883e1bc3b029b04e639b369b11866c54d687a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 17:38:20 +0200 Subject: [PATCH 083/148] add IUtilities::getLogger getter --- include/nbl/video/utilities/IUtilities.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/nbl/video/utilities/IUtilities.h b/include/nbl/video/utilities/IUtilities.h index bc83ad2da0..72641c1d60 100644 --- a/include/nbl/video/utilities/IUtilities.h +++ b/include/nbl/video/utilities/IUtilities.h @@ -144,6 +144,9 @@ class NBL_API2 IUtilities : public core::IReferenceCounted //! inline ILogicalDevice* getLogicalDevice() const { return m_device.get(); } + //! + inline system::ILogger* getLogger() const { return m_logger.getRaw(); } + //! inline StreamingTransientDataBufferMT<>* getDefaultUpStreamingBuffer() { From fe79eaf98875e1988fa04e7740845aca1249ba2a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Sep 2024 17:43:03 +0200 Subject: [PATCH 084/148] remove system creation from the UI, take everything it requires from the constructor, do also another clean-up, put stuff into nice creation parameters struct, remove unnecessary font adjusting static method + a few IO variables we were setting to their default values anyway, add more comments for users, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 37 +++--- src/nbl/ext/ImGui/ImGui.cpp | 221 ++++++++++++---------------------- 3 files changed, 99 insertions(+), 161 deletions(-) diff --git a/examples_tests b/examples_tests index 6ea0637525..ef9cf1e996 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 6ea0637525363e773c413e1eed7fdbe3ce0e6dba +Subproject commit ef9cf1e996e33296a444c11dd88a52fa1fa32f19 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 7dc4ec6870..8627f3c6b5 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -22,7 +22,18 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - nbl::core::smart_refctd_ptr streamingTDBufferST; // composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + nbl::core::smart_refctd_ptr streamingTDBufferST; //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + }; + + struct S_CREATION_PARAMETERS + { + video::IUtilities* const utilities; //! required + video::IQueue* const transfer; //! required + video::IGPURenderpass* const renderpass; //! required + uint32_t subpassIx = 0u; //! optional, default value if not provided + video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, default layout used if not provided [STILL TODO, currently its assumed its not nullptr!] + video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided + typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) @@ -44,17 +55,17 @@ class UI final : public core::IReferenceCounted S_EVENTS events; }; - UI(core::smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache = nullptr, nbl::core::smart_refctd_ptr _streamingMDIBuffer = nullptr); + UI(S_CREATION_PARAMETERS&& params); ~UI() override; //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error [TODO: could have a setter & getter to control the default & currently hooked font texture ID and init 0u by default] _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; //! update ImGuiIO & record ImGUI *cpu* draw command lists, call it before .render - bool update(const S_UPDATE_PARAMETERS params); + bool update(const S_UPDATE_PARAMETERS& params); //! updates mapped mdi buffer & records *gpu* draw commands, handles overflows for mdi allocation failure cases (pop & submit) - bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet); + bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet, const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded size_t registerListener(std::function const& listener); @@ -72,17 +83,13 @@ class UI final : public core::IReferenceCounted //! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: - void createSystem(); - void createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, video::IGPUPipelineCache* pipelineCache); - void createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer); - video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void handleMouseEvents(const core::SRange& events, nbl::hlsl::float32_t2 mousePosition) const; - void handleKeyEvents(const core::SRange& events) const; - - core::smart_refctd_ptr system; - core::smart_refctd_ptr logger; - core::smart_refctd_ptr utilities; - core::smart_refctd_ptr m_device; + void createPipeline(); + void createMDIBuffer(); + void handleMouseEvents(const S_UPDATE_PARAMETERS& params) const; + void handleKeyEvents(const S_UPDATE_PARAMETERS& params) const; + video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer); + + S_CREATION_PARAMETERS m_creationParams; core::smart_refctd_ptr pipeline; core::smart_refctd_ptr m_fontAtlasTexture; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5b598344bc..5f2a70894d 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -21,7 +21,7 @@ using namespace nbl::ui; namespace nbl::ext::imgui { - void UI::createPipeline(core::smart_refctd_ptr descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache) + void UI::createPipeline() { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix SPushConstantRange pushConstantRanges[] = @@ -33,7 +33,7 @@ namespace nbl::ext::imgui } }; - auto pipelineLayout = m_device->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(descriptorSetLayout)); + auto pipelineLayout = m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); struct { @@ -52,7 +52,7 @@ namespace nbl::ext::imgui const auto buffer = core::make_smart_refctd_ptr, true> >(in.size, /*this cast is awful but my custom buffer won't free it's memory*/ (void*)in.contents, core::adopt_memory); // no copy const auto shader = make_smart_refctd_ptr(core::smart_refctd_ptr(buffer), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, ""); - return m_device->createShader(shader.get()); + return m_creationParams.utilities->getLogicalDevice()->createShader(shader.get()); }; shaders.vertex = createShader(spirv.vertex, IShader::E_SHADER_STAGE::ESS_VERTEX); @@ -127,19 +127,19 @@ namespace nbl::ext::imgui auto& param = params[0u]; param.layout = pipelineLayout.get(); param.shaders = specs; - param.renderpass = renderpass; - param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = subpassIx }; + param.renderpass = m_creationParams.renderpass; + param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = m_creationParams.subpassIx }; }; - if (!m_device->createGraphicsPipelines(pipelineCache, params, &pipeline)) + if (!m_creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(m_creationParams.pipelineCache, params, &pipeline)) { - logger->log("Could not create pipeline!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create pipeline!", system::ILogger::ELL_ERROR); assert(false); } } } - ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) + ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -205,18 +205,18 @@ namespace nbl::ext::imgui region->imageExtent = { params.extent.width, params.extent.height, 1u }; } - auto image = m_device->createImage(std::move(params)); + auto image = m_creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); if (!image) { - logger->log("Could not create font image!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla IMGUI extension Font Image"); - if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) + if (!m_creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { - logger->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -226,15 +226,15 @@ namespace nbl::ext::imgui { IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; - auto scratchSemaphore = m_device->createSemaphore(0); + auto scratchSemaphore = m_creationParams.utilities->getLogicalDevice()->createSemaphore(0); if (!scratchSemaphore) { - logger->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - sInfo.queue = transfer; + sInfo.queue = m_creationParams.transfer; sInfo.waitSemaphores = {}; sInfo.commandBuffers = { &cmdInfo, 1 }; sInfo.scratchSemaphore = @@ -266,9 +266,9 @@ namespace nbl::ext::imgui cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!m_creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - logger->log("Could not upload font image contents", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -281,9 +281,9 @@ namespace nbl::ext::imgui cmdBuffer->end(); const auto submit = sInfo.popSubmit({}); - if (transfer->submit(submit)!=IQueue::RESULT::SUCCESS) + if (m_creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - logger->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -295,7 +295,7 @@ namespace nbl::ext::imgui params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontAtlasTexture = m_device->createImageView(std::move(params)); + m_fontAtlasTexture = m_creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -303,19 +303,13 @@ namespace nbl::ext::imgui return retval; } - static void adjustGlobalFontScale() - { - ImGuiIO& io = ImGui::GetIO(); - io.FontGlobalScale = 1.0f; - } - - void UI::handleMouseEvents(const core::SRange& events, nbl::hlsl::float32_t2 mousePosition) const + void UI::handleMouseEvents(const S_UPDATE_PARAMETERS& params) const { auto& io = ImGui::GetIO(); - io.AddMousePosEvent(mousePosition.x, mousePosition.y); + io.AddMousePosEvent(params.mousePosition.x, params.mousePosition.y); - for (const auto& e : events) + for (const auto& e : params.events.mouse) { switch (e.type) { @@ -484,7 +478,7 @@ namespace nbl::ext::imgui return map; } - void UI::handleKeyEvents(const core::SRange& events) const + void UI::handleKeyEvents(const S_UPDATE_PARAMETERS& params) const { auto& io = ImGui::GetIO(); @@ -492,20 +486,20 @@ namespace nbl::ext::imgui const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc { - for (const auto& e : events) + for (const auto& e : params.events.keyboard) if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) return true; return false; }(); - for (const auto& e : events) + for (const auto& e : params.events.keyboard) { const auto& bind = keyMap[e.keyCode]; const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; if(bind.target == ImGuiKey_None) - logger->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); else if (e.action == SKeyboardEvent::ECA_PRESSED) { @@ -517,114 +511,47 @@ namespace nbl::ext::imgui } } - UI::UI(smart_refctd_ptr _device, core::smart_refctd_ptr _descriptorSetLayout, video::IGPURenderpass* renderpass, uint32_t subpassIx, IGPUPipelineCache* pipelineCache, nbl::core::smart_refctd_ptr _streamingMDIBuffer) - : m_device(core::smart_refctd_ptr(_device)) + UI::UI(S_CREATION_PARAMETERS&& params) + : m_creationParams(std::move(params)) { - createSystem(); - struct - { - struct - { - uint8_t transfer, graphics; - } id; - } families; - - const nbl::video::IPhysicalDevice* pDevice = m_device->getPhysicalDevice(); - ILogicalDevice::SCreationParams params = {}; - - auto properties = pDevice->getQueueFamilyProperties(); - - auto requestFamilyQueueId = [&](IQueue::FAMILY_FLAGS requried, std::string_view onError) - { - uint8_t index = 0; - for (const auto& fProperty : properties) - { - if (fProperty.queueFlags.hasFlags(requried)) - { - ++params.queueParams[index].count; - - return index; - } - ++index; - } - - logger->log(onError.data(), system::ILogger::ELL_ERROR); - assert(false); - return uint8_t(0); // silent warnings - }; - - // get & validate families' capabilities - families.id.transfer = requestFamilyQueueId(IQueue::FAMILY_FLAGS::TRANSFER_BIT, "Could not find any queue with TRANSFER_BIT enabled!"); - families.id.graphics = requestFamilyQueueId(IQueue::FAMILY_FLAGS::GRAPHICS_BIT, "Could not find any queue with GRAPHICS_BIT enabled!"); - - // allocate temporary command buffer - auto* tQueue = m_device->getThreadSafeQueue(families.id.transfer, 0); - - if (!tQueue) - { - logger->log("Could not get queue!", system::ILogger::ELL_ERROR); - assert(false); - } + // TODO: could validate & log + assert(m_creationParams.utilities); + assert(m_creationParams.transfer); // and check if proper capability enabled + assert(m_creationParams.renderpass); smart_refctd_ptr transistentCMD; { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - smart_refctd_ptr pool = m_device->createCommandPool(families.id.transfer, pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + smart_refctd_ptr pool = m_creationParams.utilities->getLogicalDevice()->createCommandPool(m_creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { - logger->log("Could not create command pool!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create command pool!", system::ILogger::ELL_ERROR); assert(false); } - if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) { - logger->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); assert(false); } } - tQueue->startCapture(); - { - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); + // Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); - createPipeline(core::smart_refctd_ptr(_descriptorSetLayout), renderpass, subpassIx, pipelineCache); - createFontAtlasTexture(transistentCMD.get(), tQueue); - adjustGlobalFontScale(); - } - tQueue->endCapture(); - - createMDIBuffer(_streamingMDIBuffer); + createPipeline(); + createMDIBuffer(); + createFontAtlasTexture(transistentCMD.get()); auto & io = ImGui::GetIO(); - io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - io.BackendUsingLegacyKeyArrays = 0; // 0: using AddKeyEvent() [new way of handling events in imgui] + io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports } UI::~UI() = default; - void UI::createSystem() - { - system = system::IApplicationFramework::createSystem(); - const auto logLevel = nbl::core::bitflag(system::ILogger::ELL_ALL); - logger = core::make_smart_refctd_ptr(logLevel); - auto archive = core::make_smart_refctd_ptr<::ext::imgui::spirv::builtin::CArchive>(smart_refctd_ptr(logger)); - - system->mount(core::smart_refctd_ptr(archive)); - - utilities = make_smart_refctd_ptr(core::smart_refctd_ptr(m_device), core::smart_refctd_ptr(logger)); - - if (!utilities) - { - logger->log("Failed to create nbl::video::IUtilities!", system::ILogger::ELL_ERROR); - assert(false); - } - } - - void UI::createMDIBuffer(nbl::core::smart_refctd_ptr _streamingMDIBuffer) + void UI::createMDIBuffer() { constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; constexpr static auto requiredAllocateFlags = core::bitflag(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); @@ -642,20 +569,20 @@ namespace nbl::ext::imgui return flags; }; - if (_streamingMDIBuffer) - m_mdi.streamingTDBufferST = core::smart_refctd_ptr(_streamingMDIBuffer); + if (m_creationParams.streamingMDIBuffer) + m_mdi.streamingTDBufferST = core::smart_refctd_ptr(m_creationParams.streamingMDIBuffer); else { IGPUBuffer::SCreationParams mdiCreationParams = {}; mdiCreationParams.usage = requiredUsageFlags; mdiCreationParams.size = mdiBufferDefaultSize; - auto buffer = m_device->createBuffer(std::move(mdiCreationParams)); + auto buffer = m_creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + memoryReqs.memoryTypeBits &= m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto allocation = m_device->allocate(memoryReqs, buffer.get(), requiredAllocateFlags); + auto allocation = m_creationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), requiredAllocateFlags); { const bool allocated = allocation.isValid(); assert(allocated); @@ -663,7 +590,7 @@ namespace nbl::ext::imgui auto memory = allocation.memory; if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) - logger->log("Could not map device memory!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); @@ -675,7 +602,7 @@ namespace nbl::ext::imgui const auto validation = std::to_array ({ std::make_pair(buffer->getCreationParams().usage.hasFlags(requiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), - std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), std::make_pair(binding.memory->getAllocateFlags().hasFlags(requiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") @@ -685,17 +612,17 @@ namespace nbl::ext::imgui { if (!ok) { - logger->log(error, system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); assert(false); } } } - bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet) + bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet, const std::span scissors) { if (!info.valid()) { - logger->log("Invalid SIntendedSubmitInfo!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Invalid SIntendedSubmitInfo!", system::ILogger::ELL_ERROR); return false; } @@ -713,7 +640,7 @@ namespace nbl::ext::imgui if (!io.Fonts->IsBuilt()) { - logger->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); return false; } @@ -827,21 +754,21 @@ namespace nbl::ext::imgui if (unallocatedSize != 0u) { - logger->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string { return offset == MDI::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); }; - logger->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); - logger->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); - logger->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); + m_creationParams.utilities->getLogger()->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); + m_creationParams.utilities->getLogger()->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); + m_creationParams.utilities->getLogger()->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); - logger->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); - logger->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); - logger->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); - logger->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); + m_creationParams.utilities->getLogger()->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); + m_creationParams.utilities->getLogger()->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); + m_creationParams.utilities->getLogger()->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); + m_creationParams.utilities->getLogger()->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); exit(0x45); // TODO: handle OOB memory requests, probably need to extend the mdi buffer/let user pass more size at init } @@ -922,7 +849,7 @@ namespace nbl::ext::imgui if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) { - logger->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); assert(false); } } @@ -936,7 +863,7 @@ namespace nbl::ext::imgui if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) { - logger->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); assert(false); } } @@ -953,9 +880,13 @@ namespace nbl::ext::imgui commandBuffer->setViewport(0, 1, &viewport); { - // TODO: remove - VkRect2D scissor[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; - commandBuffer->setScissor(scissor); // cover whole viewport (only to not throw validation errors) + if (scissors.empty()) + { + VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; + commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) + } + else + commandBuffer->setScissor(scissors); } /* @@ -989,7 +920,7 @@ namespace nbl::ext::imgui if (scratchSemaphoreCounters.newie != scratchSemaphoreCounters.oldie) { const auto overflows = scratchSemaphoreCounters.newie - scratchSemaphoreCounters.oldie; - logger->log("%d overflows when rendering UI!\n", nbl::system::ILogger::ELL_PERFORMANCE, overflows); + m_creationParams.utilities->getLogger()->log("%d overflows when rendering UI!\n", nbl::system::ILogger::ELL_PERFORMANCE, overflows); // TODO: handle them? exit(0x45); @@ -999,14 +930,14 @@ namespace nbl::ext::imgui return true; } - bool UI::update(const S_UPDATE_PARAMETERS params) + bool UI::update(const S_UPDATE_PARAMETERS& params) { auto & io = ImGui::GetIO(); io.DisplaySize = ImVec2(params.displaySize.x, params.displaySize.y); - handleMouseEvents(params.events.mouse, params.mousePosition); - handleKeyEvents(params.events.keyboard); + handleMouseEvents(params); + handleKeyEvents(params); ImGui::NewFrame(); From be47096679aca8a17be66a69adca11662ce2e564 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 11 Sep 2024 11:06:38 +0200 Subject: [PATCH 085/148] add UI::S_CREATION_PARAMETERS validation --- src/nbl/ext/ImGui/ImGui.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5f2a70894d..af374bbc92 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -514,10 +514,20 @@ namespace nbl::ext::imgui UI::UI(S_CREATION_PARAMETERS&& params) : m_creationParams(std::move(params)) { - // TODO: could validate & log - assert(m_creationParams.utilities); - assert(m_creationParams.transfer); // and check if proper capability enabled - assert(m_creationParams.renderpass); + const auto validation = std::to_array + ({ + std::make_pair(bool(m_creationParams.utilities), "Invalid `m_creationParams.utilities` is nullptr!"), + std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), + std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), + (m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!") + }); + + for (const auto& [ok, error] : validation) + if (!ok) + { + m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + assert(false); + } smart_refctd_ptr transistentCMD; { @@ -609,13 +619,11 @@ namespace nbl::ext::imgui }); for (const auto& [ok, error] : validation) - { if (!ok) { m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); assert(false); } - } } bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet, const std::span scissors) From bfdbcf2a0a66eab5a53d7580820fc2c35250b506 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 11 Sep 2024 16:08:25 +0200 Subject: [PATCH 086/148] clean more code & a small test for sub allocation purposes --- src/nbl/ext/ImGui/ImGui.cpp | 65 ++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index af374bbc92..010f48af3a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -636,12 +636,6 @@ namespace nbl::ext::imgui ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() - struct - { - const uint64_t oldie; - uint64_t newie; - } scratchSemaphoreCounters = { .oldie = info.scratchSemaphore.value, .newie = 0u }; - auto* commandBuffer = info.getScratchCommandBuffer(); ImGuiIO& io = ImGui::GetIO(); @@ -760,6 +754,9 @@ namespace nbl::ext::imgui // retry, second attempt cull frees and execute deferred memory deallocation of offsets no longer in use unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); + // still no? then we will try suballocating smaller pieces of memory due to possible fragmentation issues & upstream in nice loop (if we get to a point when suballocation could not cover whole 1 sub draw call then we need to submit overflow & rerecord stuff + repeat) + // that's TODO but first a small test for not tightly packed index + vertex buffer + if (unallocatedSize != 0u) { m_creationParams.utilities->getLogger()->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); @@ -778,7 +775,7 @@ namespace nbl::ext::imgui m_creationParams.utilities->getLogger()->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); m_creationParams.utilities->getLogger()->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); - exit(0x45); // TODO: handle OOB memory requests, probably need to extend the mdi buffer/let user pass more size at init + exit(0x45); } } @@ -791,12 +788,17 @@ namespace nbl::ext::imgui auto binding = mdiBuffer->getBoundMemory(); assert(binding.memory->isCurrentlyMapped()); - auto* const indirectsMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); - auto* const elementsMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); - auto* indicesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); - auto* verticesMappedPointer = reinterpret_cast(reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()) + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + auto* const mdiData = reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()); - size_t globalIOffset = {}, globalVOffset = {}, drawID = {}; + auto* const indirectsMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); + auto* const elementsMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); + auto* const indicesMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); + auto* const verticesMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + + const auto indexObjectGlobalOffset = indicesMappedPointer - reinterpret_cast(mdiData); + const auto vertexObjectGlobalOffset = verticesMappedPointer - reinterpret_cast(mdiData); + + size_t cmdListIndexOffset = {}, cmdListVertexOffset = {}, drawID = {}; for (int n = 0; n < drawData->CmdListsCount; n++) { @@ -811,11 +813,17 @@ namespace nbl::ext::imgui auto* indirect = indirectsMappedPointer + drawID; auto* element = elementsMappedPointer + drawID; - indirect->firstIndex = pcmd->IdxOffset + globalIOffset; indirect->firstInstance = drawID; // use base instance as draw ID indirect->indexCount = pcmd->ElemCount; indirect->instanceCount = 1u; - indirect->vertexOffset = pcmd->VtxOffset + globalVOffset; + + indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; + indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; + + // TEST: I think this could be illegal in vulkan explaining why I get weird flickering but valid scene still I see (the geometry seems to be OK), + // could try BDA to get vertex + index instead and this is valid for sure + // indirect->firstIndex = indexObjectGlobalOffset + pcmd->IdxOffset + cmdListIndexOffset; + // indirect->vertexOffset = vertexObjectGlobalOffset + pcmd->VtxOffset + cmdListVertexOffset; const auto scissor = clip.getScissor(clipRectangle); @@ -837,21 +845,29 @@ namespace nbl::ext::imgui } // update mdi's vertex & index buffers - ::memcpy(indicesMappedPointer + globalIOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - ::memcpy(verticesMappedPointer + globalVOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + ::memcpy(indicesMappedPointer + cmdListIndexOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + ::memcpy(verticesMappedPointer + cmdListVertexOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - globalIOffset += cmd_list->IdxBuffer.Size; - globalVOffset += cmd_list->VtxBuffer.Size; + cmdListIndexOffset += cmd_list->IdxBuffer.Size; + cmdListVertexOffset += cmd_list->VtxBuffer.Size; } + + // TEST: flush + // const ILogicalDevice::MappedMemoryRange memoryRange(binding.memory, 0ull, binding.memory->getAllocationSize()); + // m_creationParams.utilities->getLogicalDevice()->flushMappedMemoryRanges(1u, &memoryRange); } auto* rawPipeline = pipeline.get(); commandBuffer->bindGraphicsPipeline(rawPipeline); commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); + + const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = { .offset = multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], + // TEST: start of MDI buffer + // .offset = offset, // 0u .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -866,6 +882,8 @@ namespace nbl::ext::imgui const asset::SBufferBinding bindings[] = {{ .offset = multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], + // TEST: start of MDI buffer + // .offset = offset, // 0u .buffer = smart_refctd_ptr(mdiBuffer) }}; @@ -922,17 +940,6 @@ namespace nbl::ext::imgui }; commandBuffer->drawIndexedIndirect(binding, drawCount, sizeof(VkDrawIndexedIndirectCommand)); - - scratchSemaphoreCounters.newie = info.scratchSemaphore.value; - - if (scratchSemaphoreCounters.newie != scratchSemaphoreCounters.oldie) - { - const auto overflows = scratchSemaphoreCounters.newie - scratchSemaphoreCounters.oldie; - m_creationParams.utilities->getLogger()->log("%d overflows when rendering UI!\n", nbl::system::ILogger::ELL_PERFORMANCE, overflows); - - // TODO: handle them? - exit(0x45); - } } return true; From fbec20bd1b867d19d2cabb35efe8b818185fc801 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Sep 2024 10:20:53 +0200 Subject: [PATCH 087/148] fix 3rdparty/msdfgen & 3rdparty/nlohmann_json submodule urls (to ssh) --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index d24b34d8ea..d8df409c51 100644 --- a/.gitmodules +++ b/.gitmodules @@ -72,7 +72,7 @@ url = git@github.com:Devsh-Graphics-Programming/Nabla-Examples-and-Tests.git [submodule "3rdparty/msdfgen"] path = 3rdparty/msdfgen - url = https://github.com/Chlumsky/msdfgen + url = git@github.com:Chlumsky/msdfgen.git branch = master [submodule "3rdparty/dxc/dxc"] path = 3rdparty/dxc/dxc @@ -95,7 +95,7 @@ url = git@github.com:p-ranav/argparse.git [submodule "3rdparty/nlohmann_json"] path = 3rdparty/nlohmann_json - url = https://github.com/Devsh-Graphics-Programming/json + url = git@github.com:Devsh-Graphics-Programming/json.git [submodule "3rdparty/blake"] path = 3rdparty/blake url = git@github.com:Devsh-Graphics-Programming/BLAKE3.git From ebe211d5c1376b5a58325e1f807f262c1fe5a62f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Sep 2024 10:37:57 +0200 Subject: [PATCH 088/148] dxc's submodules to ssh --- 3rdparty/dxc/dxc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/dxc/dxc b/3rdparty/dxc/dxc index bcedaf749f..7acfe6f4fc 160000 --- a/3rdparty/dxc/dxc +++ b/3rdparty/dxc/dxc @@ -1 +1 @@ -Subproject commit bcedaf749fb6325dc41f9b436f1f2ea0a660de5e +Subproject commit 7acfe6f4fc724265db8026256fad18afeb282b97 From 1e48c596f5f9c7b716f6061aa092dc8e7f2861fa Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 15 Sep 2024 14:33:44 +0200 Subject: [PATCH 089/148] turn OPENEXR_FORCE_INTERNAL_DEFLATE ON - fix legacy build system exr's deflate dependency, it should never look system wide but download --- 3rdparty/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 1cccb0027b..a7f6c0e8a2 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -214,7 +214,8 @@ set(BUILD_STATIC_LIBS OFF) set(BUILD_TESTING OFF) set(PYILMBASE_ENABLE OFF CACHE STRING "" FORCE) set(OPENEXR_BUILD_UTILS OFF CACHE STRING "" FORCE) -set(OPENEXR_FORCE_INTERNAL_IMATH ON CACHE STRING "" FORCE) # TODO: make it a submodule and force using it maybe instead of letting OpenEXR download it from it's github repository +set(OPENEXR_FORCE_INTERNAL_IMATH ON CACHE STRING "" FORCE) # TODO: make it a submodule and force using it maybe instead of letting OpenEXR download it from it's github repository [FIXED in newBuildSystem branch] +set(OPENEXR_FORCE_INTERNAL_DEFLATE ON CACHE STRING "" FORCE) # TODO: make it a submodule and force using it maybe instead of letting OpenEXR download it from it's github repository [FIXED in newBuildSystem branch] set(OPENEXR_BUILD_TOOLS OFF CACHE STRING "" FORCE) set(OPENEXR_INSTALL OFF CACHE STRING "" FORCE) set(OPENEXR_INSTALL_DOCS OFF CACHE STRING "" FORCE) From 9e7cbe293d7ac53bacc25f41667defa335e12835 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 15 Sep 2024 19:18:14 +0200 Subject: [PATCH 090/148] fix nullptr dereference bug in IShaderCompiler, add some comments to CHLSLCompiler --- include/nbl/asset/utils/CHLSLCompiler.h | 4 ++-- include/nbl/asset/utils/IShaderCompiler.h | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/nbl/asset/utils/CHLSLCompiler.h b/include/nbl/asset/utils/CHLSLCompiler.h index 82fed3ac3b..395c528ff6 100644 --- a/include/nbl/asset/utils/CHLSLCompiler.h +++ b/include/nbl/asset/utils/CHLSLCompiler.h @@ -32,7 +32,7 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler struct SOptions : IShaderCompiler::SCompilerOptions { - std::span dxcOptions; + std::span dxcOptions; // TODO: span is a VIEW to memory, so to something which we should treat immutable - why not span of string_view then? Since its span we force users to keep those std::strings alive anyway but now we cannnot even make nice constexpr & pass such expression here directly IShader::E_CONTENT_TYPE getCodeContentType() const override { return IShader::E_CONTENT_TYPE::ECT_HLSL; }; }; @@ -54,7 +54,7 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler std::string preprocessShader(std::string&& code, IShader::E_SHADER_STAGE& stage, const SPreprocessorOptions& preprocessOptions, std::vector& dxc_compile_flags_override, std::vector* dependencies = nullptr) const; void insertIntoStart(std::string& code, std::ostringstream&& ins) const override; - constexpr static inline const wchar_t* RequiredArguments[] = { + constexpr static inline const wchar_t* RequiredArguments[] = { // TODO: and if dxcOptions is span of std::string then why w_chars there? https://en.cppreference.com/w/cpp/string/basic_string L"-spirv", L"-Zpr", L"-enable-16bit-types", diff --git a/include/nbl/asset/utils/IShaderCompiler.h b/include/nbl/asset/utils/IShaderCompiler.h index ffc2f96a00..e92bc7dca9 100644 --- a/include/nbl/asset/utils/IShaderCompiler.h +++ b/include/nbl/asset/utils/IShaderCompiler.h @@ -442,6 +442,10 @@ class NBL_API2 IShaderCompiler : public core::IReferenceCounted return found; } auto retVal = compileToSPIRV_impl(code, options, options.writeCache ? &dependencies : nullptr); + + if (!retVal) + return nullptr; + // compute the SPIR-V shader content hash { auto backingBuffer = retVal->getContent(); From dc04722153c9517759e6a066047efd3a18d1a7fc Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 15 Sep 2024 19:25:40 +0200 Subject: [PATCH 091/148] add asset manager to nbl::ext::imgui::UI::S_CREATION_PARAMETERS, remove NSC from the ImGUI extension build system & embed shader sources instead (leave some comments), codegen & compile shaders on fly with respect to new optional S_RESOURCE_PARAMETERS (leave user freedom to specify set & resource bindings with certain required constraints), add more validation to the UI creation, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 23 +++-- src/nbl/ext/ImGui/CMakeLists.txt | 67 ++---------- src/nbl/ext/ImGui/ImGui.cpp | 132 +++++++++++++++++++++--- src/nbl/ext/ImGui/shaders/fragment.hlsl | 22 +++- 5 files changed, 162 insertions(+), 84 deletions(-) diff --git a/examples_tests b/examples_tests index ef9cf1e996..21326093ff 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit ef9cf1e996e33296a444c11dd88a52fa1fa32f19 +Subproject commit 21326093ff97db399582f83e83614beea6a24940 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 8627f3c6b5..585ce8c790 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -2,6 +2,7 @@ #define _NBL_EXT_IMGUI_UI_H_ #include "nbl/video/declarations.h" +#include "nbl/asset/IAssetManager.h" namespace nbl::ext::imgui { @@ -27,13 +28,21 @@ class UI final : public core::IReferenceCounted struct S_CREATION_PARAMETERS { - video::IUtilities* const utilities; //! required - video::IQueue* const transfer; //! required - video::IGPURenderpass* const renderpass; //! required - uint32_t subpassIx = 0u; //! optional, default value if not provided - video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, default layout used if not provided [STILL TODO, currently its assumed its not nullptr!] - video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided - typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided + struct S_RESOURCE_PARAMETERS + { + uint32_t setIx, bindingIx; + }; + + nbl::asset::IAssetManager* const assetManager; //! required + nbl::video::IUtilities* const utilities; //! required + nbl::video::IQueue* const transfer; //! required + nbl::video::IGPURenderpass* const renderpass; //! required + uint32_t subpassIx = 0u; //! optional, default value used if not provided + nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, default layout used if not provided [STILL TODO, currently its assumed its not nullptr!] + nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided + typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided + S_RESOURCE_PARAMETERS texturesInfo = { .setIx = 0u, .bindingIx = 0u }, //! optional, default values used if not provided + samplerStateInfo = { .setIx = 0u, .bindingIx = 1u }; //! optional, default values used if not provided }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index 481c873a95..10472c873d 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -23,62 +23,17 @@ nbl_create_ext_library_project( target_link_libraries(${LIB_NAME} PUBLIC imtestengine) -# shaders IO directories -set(NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shaders") -get_filename_component(_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_ "${CMAKE_CURRENT_BINARY_DIR}/shaders/include" ABSOLUTE) -get_filename_component(_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/builtin/include" ABSOLUTE) -get_filename_component(_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/builtin/src" ABSOLUTE) -set(NBL_EXT_IMGUI_OUTPUT_SPIRV_DIRECTORY "${_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_}/nbl/ext/imgui/spirv") +# (*) -> I wish we could just take NSC, offline-precompile to SPIRV, embed into builtin resource library (as we did!) but then be smart & adjust at runtime OpDecorate of our resources according to wishes - unfortunately no linker yet we have and we are not going to make one ourselves so we compile imgui shaders at runtime +set(_BR_TARGET_ extImguibuiltinResourceData) +set(_RESOURCE_DIR_ "shaders") -# list of input source shaders -set(NBL_EXT_IMGUI_INPUT_SHADERS - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/vertex.hlsl" - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/fragment.hlsl" -) - -set(NBL_EXT_IMGUI_INPUT_COMMONS - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/common.hlsl" -) - -include("${NBL_ROOT_PATH}/src/nbl/builtin/utils.cmake") - -foreach(NBL_INPUT_SHADER IN LISTS NBL_EXT_IMGUI_INPUT_SHADERS) - cmake_path(GET NBL_INPUT_SHADER STEM NBL_SHADER_STEM) - set(NBL_OUTPUT_SPIRV_FILENAME "${NBL_SHADER_STEM}.spv") - set(NBL_OUTPUT_SPIRV_PATH "${NBL_EXT_IMGUI_OUTPUT_SPIRV_DIRECTORY}/${NBL_OUTPUT_SPIRV_FILENAME}") +get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) +get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) +get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) - if(NBL_SHADER_STEM STREQUAL vertex) - set(NBL_NSC_COMPILE_OPTIONS -T vs_6_7 -E VSMain) - elseif(NBL_SHADER_STEM STREQUAL fragment) - set(NBL_NSC_COMPILE_OPTIONS -T ps_6_7 -E PSMain) - else() - message(FATAL_ERROR "internal error") - endif() - - set(NBL_NSC_COMPILE_COMMAND - "$" - -Fc "${NBL_OUTPUT_SPIRV_PATH}" - ${NBL_NSC_COMPILE_OPTIONS} # this should come from shader's [#pragma WAVE ] but our NSC doesn't seem to work properly currently - "${NBL_INPUT_SHADER}" - ) - - set(NBL_DEPENDS - "${NBL_INPUT_SHADER}" - ${NBL_EXT_IMGUI_INPUT_COMMONS} - ) - - add_custom_command(OUTPUT "${NBL_OUTPUT_SPIRV_PATH}" - COMMAND ${NBL_NSC_COMPILE_COMMAND} - DEPENDS ${NBL_DEPENDS} - WORKING_DIRECTORY "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}" - COMMENT "Generating \"${NBL_OUTPUT_SPIRV_PATH}\"" - VERBATIM - COMMAND_EXPAND_LISTS - ) - - list(APPEND NBL_EXT_IMGUI_OUTPUT_SPIRV_BUILTINS "${NBL_OUTPUT_SPIRV_PATH}") - LIST_BUILTIN_RESOURCE(IMGUI_EXT_SPIRV_RESOURCES_TO_EMBED "ext/imgui/spirv/${NBL_OUTPUT_SPIRV_FILENAME}") -endforeach() +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide pipeline layout + set/binding Ixs at runtime -ADD_CUSTOM_BUILTIN_RESOURCES(extImguiSpirvBuiltinResourceData IMGUI_EXT_SPIRV_RESOURCES_TO_EMBED "${_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_}" "nbl" "ext::imgui::spirv::builtin" "${_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_HEADER_}" "${_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_SOURCE_}" "STATIC" "INTERNAL") -LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} extImguiSpirvBuiltinResourceData) \ No newline at end of file +ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${_RESOURCE_DIR_}" "nbl::ext::imgui::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") +LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) \ No newline at end of file diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 010f48af3a..04c4e5fb67 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -8,9 +8,8 @@ #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" -#include "ext/imgui/spirv/builtin/builtinResources.h" -#include "ext/imgui/spirv/builtin/CArchive.h" - +#include "nbl/ext/ImGui/builtin/builtinResources.h" +#include "nbl/ext/ImGui/builtin/CArchive.h" #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" @@ -33,7 +32,25 @@ namespace nbl::ext::imgui } }; - auto pipelineLayout = m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + auto createPipelineLayout = [&](const uint32_t setIx) -> core::smart_refctd_ptr + { + switch (setIx) + { + case 0u: + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + case 1u: + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + case 2u: + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + case 3u: + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + default: + assert(false); + return nullptr; + } + }; + + auto pipelineLayout = createPipelineLayout(m_creationParams.texturesInfo.setIx); //! its okay to take the Ix from textures info because we force user to use the same set for both textures and samplers [also validated at this point] struct { @@ -41,22 +58,97 @@ namespace nbl::ext::imgui } shaders; { - struct + constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; + + auto system = smart_refctd_ptr(m_creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); + auto includeLoader = includeFinder->getDefaultFileSystemLoader(); + includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); + + auto createShader = [&]() -> core::smart_refctd_ptr { - const system::SBuiltinFile vertex = ::ext::imgui::spirv::builtin::get_resource<"ext/imgui/spirv/vertex.spv">(); - const system::SBuiltinFile fragment = ::ext::imgui::spirv::builtin::get_resource<"ext/imgui/spirv/fragment.spv">(); - } spirv; + asset::IAssetLoader::SAssetLoadParams params = {}; + params.logger = m_creationParams.utilities->getLogger(); + params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); - auto createShader = [&](const system::SBuiltinFile& in, asset::IShader::E_SHADER_STAGE stage) -> core::smart_refctd_ptr - { - const auto buffer = core::make_smart_refctd_ptr, true> >(in.size, /*this cast is awful but my custom buffer won't free it's memory*/ (void*)in.contents, core::adopt_memory); // no copy - const auto shader = make_smart_refctd_ptr(core::smart_refctd_ptr(buffer), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, ""); - - return m_creationParams.utilities->getLogicalDevice()->createShader(shader.get()); + auto bundle = m_creationParams.assetManager->getAsset(key.value, params); + const auto assets = bundle.getContents(); + + if (assets.empty()) + { + m_creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + return nullptr; + } + + const auto shader = IAsset::castDown(assets[0]); + + CHLSLCompiler::SOptions options = {}; + options.stage = stage; + options.preprocessorOptions.sourceIdentifier = key.value; + options.preprocessorOptions.logger = m_creationParams.utilities->getLogger(); + options.preprocessorOptions.includeFinder = includeFinder.get(); + + auto compileToSPIRV = [&]() -> core::smart_refctd_ptr + { + #define NBL_DEFAULT_OPTIONS "-spirv", "-Zpr", "-enable-16bit-types", "-fvk-use-scalar-layout", "-Wno-c++11-extensions", "-Wno-c++1z-extensions", "-Wno-c++14-extensions", "-Wno-gnu-static-float-init", "-fspv-target-env=vulkan1.3", "-HV", "202x" /* default required params, just to not throw warnings */ + const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); + + if constexpr (stage == IShader::E_SHADER_STAGE::ESS_VERTEX) + { + const auto VERTEX_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "vs_6_7", "-E", "VSMain", "-O3"}); + options.dxcOptions = VERTEX_COMPILE_OPTIONS; + + return compiler->compileToSPIRV(code.data(), options); // we good here - no code patching + } + else if (stage == IShader::E_SHADER_STAGE::ESS_FRAGMENT) + { + const auto FRAGMENT_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "ps_6_7", "-E", "PSMain", "-O3"}); + options.dxcOptions = FRAGMENT_COMPILE_OPTIONS; + + std::stringstream stream; + + stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" + << "#define NBL_TEXTURES_BINDING " << m_creationParams.texturesInfo.bindingIx << "\n" + << "#define NBL_TEXTURES_SET " << m_creationParams.texturesInfo.setIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.samplerStateInfo.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_SET " << m_creationParams.samplerStateInfo.setIx << "\n" + << "// <-\n\n"; + + const auto newCode = stream.str() + std::string(code); + return compiler->compileToSPIRV(newCode.c_str(), options); // but here we do patch the code with additional define directives for which values are taken from the creation parameters + } + else + { + static_assert(stage != IShader::E_SHADER_STAGE::ESS_UNKNOWN, "Unknown shader stage!"); + return nullptr; + } + }; + + auto spirv = compileToSPIRV(); + + if (!spirv) + { + m_creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + return nullptr; + } + + auto gpu = m_creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); + + if (!gpu) + m_creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", system::ILogger::ELL_ERROR, key.value); + + return gpu; }; - shaders.vertex = createShader(spirv.vertex, IShader::E_SHADER_STAGE::ESS_VERTEX); - shaders.fragment = createShader(spirv.fragment, IShader::E_SHADER_STAGE::ESS_FRAGMENT); + system->mount(smart_refctd_ptr(archive), NBL_ARCHIVE_ALIAS.data()); + shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); + shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); + system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); + + assert(shaders.vertex); + assert(shaders.fragment); } SVertexInputParams vertexInputParams{}; @@ -516,10 +608,16 @@ namespace nbl::ext::imgui { const auto validation = std::to_array ({ + std::make_pair(bool(m_creationParams.assetManager), "Invalid `m_creationParams.assetManager` is nullptr!"), + std::make_pair(bool(m_creationParams.assetManager->getSystem()), "Invalid `m_creationParams.assetManager->getSystem()` is nullptr!"), std::make_pair(bool(m_creationParams.utilities), "Invalid `m_creationParams.utilities` is nullptr!"), std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), - (m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!") + (m_creationParams.assetManager && m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), + std::make_pair(bool(m_creationParams.texturesInfo.setIx <= 3u), "Invalid `m_creationParams.texturesInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.samplerStateInfo.setIx <= 3u), "Invalid `m_creationParams.samplerStateInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.texturesInfo.setIx == m_creationParams.samplerStateInfo.setIx), "Invalid `m_creationParams.texturesInfo.setIx` is not equal to `m_creationParams.samplerStateInfo.setIx`!"), + std::make_pair(bool(m_creationParams.texturesInfo.bindingIx != m_creationParams.samplerStateInfo.bindingIx), "Invalid `m_creationParams.texturesInfo.bindingIx` is equal to `m_creationParams.samplerStateInfo.bindingIx`!") }); for (const auto& [ok, error] : validation) diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 4e844e3f3d..f1f571f496 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -1,10 +1,26 @@ +#ifndef NBL_TEXTURES_BINDING +#error "NBL_TEXTURES_BINDING must be defined!" +#endif + +#ifndef NBL_TEXTURES_SET +#error "NBL_TEXTURES_SET must be defined!" +#endif + +#ifndef NBL_SAMPLER_STATES_BINDING +#error "NBL_SAMPLER_STATES_BINDING must be defined!" +#endif + +#ifndef NBL_SAMPLER_STATES_SET +#error "NBL_SAMPLER_STATES_SET must be defined!" +#endif + #include "common.hlsl" [[vk::push_constant]] struct PushConstants pc; -// single separable image sampler to handle all textures we descriptor-index -[[vk::binding(0,0)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES]; -[[vk::binding(1,0)]] SamplerState samplerStates[NBL_MAX_IMGUI_TEXTURES]; +// separable image samplers to handle textures we do descriptor-index +[[vk::binding(NBL_TEXTURES_BINDING, NBL_TEXTURES_SET)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES]; +[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_SAMPLER_STATES_SET)]] SamplerState samplerStates[NBL_MAX_IMGUI_TEXTURES]; /* we use Indirect Indexed draw call to render whole GUI, note we do a cross From 90cec3b05b44c8a8e6255e2fab30a7e2f1f8ea80 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 15 Sep 2024 19:34:52 +0200 Subject: [PATCH 092/148] correct typo in comments --- src/nbl/ext/ImGui/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index 10472c873d..6f905f2ced 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -33,7 +33,7 @@ get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/in LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff -LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide pipeline layout + set/binding Ixs at runtime +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide external descriptor set layout + ImGUI textures & sampler state set/binding Ixs (for pipeline layout) at runtime ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${_RESOURCE_DIR_}" "nbl::ext::imgui::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) \ No newline at end of file From de4991bf4f76f71e28d9b3b91bdc6458c5399234 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 17 Sep 2024 13:59:42 +0200 Subject: [PATCH 093/148] update UI shader codegen - allow for even more freedom and gen NBL_TEXTURES_BINDING, NBL_SAMPLER_STATES_BINDING, NBL_RESOURCES_COUNT & NBL_RESOURCES_SET. Validate external descriptor set layout if given (redirects, validate required textures & samplers binding), support generating default one if nullptr passed. Add more comments for API usage, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 35 +++-- src/nbl/ext/ImGui/ImGui.cpp | 174 +++++++++++++++++++++--- src/nbl/ext/ImGui/shaders/fragment.hlsl | 16 +-- 4 files changed, 190 insertions(+), 37 deletions(-) diff --git a/examples_tests b/examples_tests index 21326093ff..34e2baeab5 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 21326093ff97db399582f83e83614beea6a24940 +Subproject commit 34e2baeab52bdabc5cf53d5dc890bc4fe135ca1d diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 585ce8c790..934efcad7e 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -23,26 +23,34 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - nbl::core::smart_refctd_ptr streamingTDBufferST; //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + nbl::core::smart_refctd_ptr streamingTDBufferST; }; struct S_CREATION_PARAMETERS { struct S_RESOURCE_PARAMETERS { - uint32_t setIx, bindingIx; + nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, if not provided then default layout will be created declaring: + const uint32_t setIx = 0u, // -> following set for ImGUI resources which consists of textures (ImGUI font atlas + optional user provided textures) & corresponding *immutable* samplers + count = 0x45u, // -> common amount of resources (since for a texture there is a sampler) + texturesBindingIx = 0u, // -> binding index for textures + samplersBindingIx = 1u; // -> binding index for samplers + + using binding_flags_t = nbl::video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + static constexpr auto TEXTURES_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT; //! required flags + static constexpr auto SAMPLERS_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_NONE); //! required flags + static constexpr auto RESOURCES_REQUIRED_STAGE_FLAGS = nbl::asset::IShader::E_SHADER_STAGE::ESS_FRAGMENT; //! required stage }; - nbl::asset::IAssetManager* const assetManager; //! required - nbl::video::IUtilities* const utilities; //! required - nbl::video::IQueue* const transfer; //! required - nbl::video::IGPURenderpass* const renderpass; //! required - uint32_t subpassIx = 0u; //! optional, default value used if not provided - nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, default layout used if not provided [STILL TODO, currently its assumed its not nullptr!] - nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided - typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided - S_RESOURCE_PARAMETERS texturesInfo = { .setIx = 0u, .bindingIx = 0u }, //! optional, default values used if not provided - samplerStateInfo = { .setIx = 0u, .bindingIx = 1u }; //! optional, default values used if not provided + nbl::asset::IAssetManager* const assetManager; //! required + nbl::video::IUtilities* const utilities; //! required + nbl::video::IQueue* const transfer; //! required + nbl::video::IGPURenderpass* const renderpass; //! required + uint32_t subpassIx = 0u; //! optional, default value used if not provided + S_RESOURCE_PARAMETERS resources; //! optional, default parameters used if not provided + nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided + typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) @@ -89,6 +97,9 @@ class UI final : public core::IReferenceCounted //! mdi streaming buffer inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.streamingTDBufferST.get(); } + //! ImGUI graphics pipeline + inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); } + //! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 04c4e5fb67..3036329787 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -32,25 +32,93 @@ namespace nbl::ext::imgui } }; - auto createPipelineLayout = [&](const uint32_t setIx) -> core::smart_refctd_ptr + auto createPipelineLayout = [&](const uint32_t setIx, core::smart_refctd_ptr descriptorSetLayout) -> core::smart_refctd_ptr { switch (setIx) { case 0u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, smart_refctd_ptr(descriptorSetLayout)); case 1u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, smart_refctd_ptr(descriptorSetLayout)); case 2u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout)); case 3u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, core::smart_refctd_ptr(m_creationParams.descriptorSetLayout)); + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout)); default: assert(false); return nullptr; } }; - auto pipelineLayout = createPipelineLayout(m_creationParams.texturesInfo.setIx); //! its okay to take the Ix from textures info because we force user to use the same set for both textures and samplers [also validated at this point] + auto pipelineLayout = createPipelineLayout(m_creationParams.resources.setIx, + [&]() -> smart_refctd_ptr + { + if (m_creationParams.resources.descriptorSetLayout) + return smart_refctd_ptr(m_creationParams.resources.descriptorSetLayout); // provided? good we just use it, we are validated at this point + else + { + //! if default descriptor set layout is not provided, we create it here + smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + { + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ISampler::ETC_REPEAT; + params.TextureWrapV = ISampler::ETC_REPEAT; + params.TextureWrapW = ISampler::ETC_REPEAT; + + fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); + } + + { + IGPUSampler::SParams params; + params.MinLod = 0.f; + params.MaxLod = 0.f; + params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; + + userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler"); + } + + //! note we use immutable separate samplers and they are part of the descriptor set layout + std::vector> immutableSamplers(m_creationParams.resources.count); + for (auto& it : immutableSamplers) + it = smart_refctd_ptr(userTexturesSampler); + + immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler); + + const IGPUDescriptorSetLayout::SBinding bindings[] = + { + { + .binding = m_creationParams.resources.texturesBindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count + }, + { + .binding = m_creationParams.resources.samplersBindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLER, + .createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count, + .immutableSamplers = immutableSamplers.data() + } + }; + + return m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout(bindings); + } + }()); + + if (!pipelineLayout) + { + m_creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR); + assert(false); + } struct { @@ -61,8 +129,8 @@ namespace nbl::ext::imgui constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; auto system = smart_refctd_ptr(m_creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently - auto archive = make_smart_refctd_ptr(smart_refctd_ptr(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive - auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeLoader = includeFinder->getDefaultFileSystemLoader(); includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); @@ -110,10 +178,10 @@ namespace nbl::ext::imgui std::stringstream stream; stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" - << "#define NBL_TEXTURES_BINDING " << m_creationParams.texturesInfo.bindingIx << "\n" - << "#define NBL_TEXTURES_SET " << m_creationParams.texturesInfo.setIx << "\n" - << "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.samplerStateInfo.bindingIx << "\n" - << "#define NBL_SAMPLER_STATES_SET " << m_creationParams.samplerStateInfo.setIx << "\n" + << "#define NBL_TEXTURES_BINDING " << m_creationParams.resources.texturesBindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.resources.samplersBindingIx << "\n" + << "#define NBL_RESOURCES_SET " << m_creationParams.resources.setIx << "\n" + << "#define NBL_RESOURCES_COUNT " << m_creationParams.resources.count << "\n" << "// <-\n\n"; const auto newCode = stream.str() + std::string(code); @@ -606,6 +674,81 @@ namespace nbl::ext::imgui UI::UI(S_CREATION_PARAMETERS&& params) : m_creationParams(std::move(params)) { + auto validateResourcesInfo = [&]() -> bool + { + if (m_creationParams.resources.descriptorSetLayout) // provided? we will validate your layout + { + auto validateResource = [&]() + { + constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", + ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; + + const auto& redirect = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.descriptorSetLayout->getDescriptorRedirect(descriptorType) : m_creationParams.resources.descriptorSetLayout->getImmutableSamplerRedirect(); + + const auto bindingCount = redirect.getBindingCount(); + + if (!bindingCount) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single one for default ImGUI Font Atlas texture!", system::ILogger::ELL_ERROR, typeLiteral.data()); + return false; + } + + bool ok = false; + for (uint32_t i = 0u; i < bindingCount; ++i) + { + const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); + const auto binding = redirect.getBinding(rangeStorageIndex); + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.texturesBindingIx : m_creationParams.resources.samplersBindingIx; + + if (binding.data == requestedBindingIx) + { + const auto count = redirect.getCount(binding); + + if (count != m_creationParams.resources.count) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but with different binding count!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + + const auto stage = redirect.getStageFlags(binding); + + if(!stage.hasFlags(m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS)) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + + const auto create = redirect.getCreateFlags(rangeStorageIndex); + + if (!create.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS : m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS)) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + + ok = true; + break; + } + } + + if (!ok) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + + return true; + }; + + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > () && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (); + + if (!ok) + return false; + } + + return true; + }; + const auto validation = std::to_array ({ std::make_pair(bool(m_creationParams.assetManager), "Invalid `m_creationParams.assetManager` is nullptr!"), @@ -614,10 +757,9 @@ namespace nbl::ext::imgui std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), (m_creationParams.assetManager && m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), - std::make_pair(bool(m_creationParams.texturesInfo.setIx <= 3u), "Invalid `m_creationParams.texturesInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(m_creationParams.samplerStateInfo.setIx <= 3u), "Invalid `m_creationParams.samplerStateInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(m_creationParams.texturesInfo.setIx == m_creationParams.samplerStateInfo.setIx), "Invalid `m_creationParams.texturesInfo.setIx` is not equal to `m_creationParams.samplerStateInfo.setIx`!"), - std::make_pair(bool(m_creationParams.texturesInfo.bindingIx != m_creationParams.samplerStateInfo.bindingIx), "Invalid `m_creationParams.texturesInfo.bindingIx` is equal to `m_creationParams.samplerStateInfo.bindingIx`!") + std::make_pair(bool(m_creationParams.resources.setIx <= 3u), "Invalid `m_creationParams.resources.setIx` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.resources.texturesBindingIx != m_creationParams.resources.samplersBindingIx), "Invalid `m_creationParams.resources.texturesBindingIx` is equal to `m_creationParams.resources.samplersBindingIx`!"), + std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources`!") }); for (const auto& [ok, error] : validation) diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index f1f571f496..045f0f9939 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -2,16 +2,16 @@ #error "NBL_TEXTURES_BINDING must be defined!" #endif -#ifndef NBL_TEXTURES_SET -#error "NBL_TEXTURES_SET must be defined!" -#endif - #ifndef NBL_SAMPLER_STATES_BINDING #error "NBL_SAMPLER_STATES_BINDING must be defined!" #endif -#ifndef NBL_SAMPLER_STATES_SET -#error "NBL_SAMPLER_STATES_SET must be defined!" +#ifndef NBL_RESOURCES_SET +#error "NBL_RESOURCES_SET must be defined!" +#endif + +#ifndef NBL_RESOURCES_COUNT +#error "NBL_RESOURCES_COUNT must be defined!" #endif #include "common.hlsl" @@ -19,8 +19,8 @@ [[vk::push_constant]] struct PushConstants pc; // separable image samplers to handle textures we do descriptor-index -[[vk::binding(NBL_TEXTURES_BINDING, NBL_TEXTURES_SET)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES]; -[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_SAMPLER_STATES_SET)]] SamplerState samplerStates[NBL_MAX_IMGUI_TEXTURES]; +[[vk::binding(NBL_TEXTURES_BINDING, NBL_RESOURCES_SET)]] Texture2D textures[NBL_RESOURCES_COUNT]; +[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_RESOURCES_SET)]] SamplerState samplerStates[NBL_RESOURCES_COUNT]; /* we use Indirect Indexed draw call to render whole GUI, note we do a cross From ebb16fbff24a1dc352186f7be83e78bac471ddb8 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 17 Sep 2024 14:23:52 +0200 Subject: [PATCH 094/148] reduce PerObjectData to 10 bytes --- src/nbl/ext/ImGui/shaders/common.hlsl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 427f1743d2..a7c9097597 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -55,6 +55,5 @@ struct PerObjectData { emulated_snorm16_t2 aabbMin; emulated_snorm16_t2 aabbMax; - uint32_t texId; - uint32_t padding; + uint16_t texId; }; \ No newline at end of file From 18949b17da5c4cab8386975afbf5a24a9c9ee36f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 17 Sep 2024 15:34:38 +0200 Subject: [PATCH 095/148] remove SIntendedSubmitInfo from UI::render, we cannot overflow while recording non-dynamic renderpass - bring back command buffer + add semaphore wait info for deferred memory deallocation, clean more code, add comments regarding linear allocator TODO, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 29 ++++++++++-------- src/nbl/ext/ImGui/ImGui.cpp | 56 ++++++++++++++++------------------- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/examples_tests b/examples_tests index 34e2baeab5..5d6f01371b 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 34e2baeab52bdabc5cf53d5dc890bc4fe135ca1d +Subproject commit 5d6f01371b5f82eb9bcf4ee5675d67ffc1654e1d diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 934efcad7e..e6445796fc 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -23,8 +23,10 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] - nbl::core::smart_refctd_ptr streamingTDBufferST; + nbl::core::smart_refctd_ptr streamingTDBufferST; //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + + static constexpr auto MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS = nbl::core::bitflag(nbl::video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); //! required flags + static constexpr auto MDI_BUFFER_REQUIRED_USAGE_FLAGS = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; //! required flags }; struct S_CREATION_PARAMETERS @@ -59,7 +61,7 @@ class UI final : public core::IReferenceCounted //! what we pass to ImGuiIO::AddMousePosEvent nbl::hlsl::float32_t2 mousePosition, - //! main display size in pixels (generally == GetMainViewport()->Size) + //! main display size in pixels displaySize; //! Nabla events you want to be handled with the backend @@ -75,14 +77,14 @@ class UI final : public core::IReferenceCounted UI(S_CREATION_PARAMETERS&& params); ~UI() override; - //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will cause runtime error [TODO: could have a setter & getter to control the default & currently hooked font texture ID and init 0u by default] - _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FONT_ATLAS_TEX_ID = 0u; + //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will result in undefined behaviour + static constexpr auto NBL_FONT_ATLAS_TEX_ID = 0u; - //! update ImGuiIO & record ImGUI *cpu* draw command lists, call it before .render + //! updates ImGuiIO & records ImGUI *cpu* draw command lists, you have to call it before .render bool update(const S_UPDATE_PARAMETERS& params); - //! updates mapped mdi buffer & records *gpu* draw commands, handles overflows for mdi allocation failure cases (pop & submit) - bool render(nbl::video::SIntendedSubmitInfo& info, const nbl::video::IGPUDescriptorSet* const descriptorSet, const std::span scissors = {}); + //! updates mapped mdi buffer & records *gpu* draw commands + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const nbl::video::IGPUDescriptorSet* const descriptorSet, const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded size_t registerListener(std::function const& listener); @@ -91,16 +93,19 @@ class UI final : public core::IReferenceCounted //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context void setContext(void* imguiContext); + //! creation parametrs + inline const S_CREATION_PARAMETERS& getCreationParameters() const { return m_creationParams; } + + //! ImGUI graphics pipeline + inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); } + //! image view default font texture inline nbl::video::IGPUImageView* getFontAtlasView() { return m_fontAtlasTexture.get(); } //! mdi streaming buffer inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.streamingTDBufferST.get(); } - //! ImGUI graphics pipeline - inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); } - - //! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); + //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: void createPipeline(); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 3036329787..1fe00d36c4 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -804,8 +804,6 @@ namespace nbl::ext::imgui void UI::createMDIBuffer() { constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; - constexpr static auto requiredAllocateFlags = core::bitflag(IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - constexpr static auto requiredUsageFlags = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; auto getRequiredAccessFlags = [&](const core::bitflag& properties) { @@ -824,7 +822,7 @@ namespace nbl::ext::imgui else { IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = requiredUsageFlags; + mdiCreationParams.usage = MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS; mdiCreationParams.size = mdiBufferDefaultSize; auto buffer = m_creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); @@ -832,7 +830,7 @@ namespace nbl::ext::imgui auto memoryReqs = buffer->getMemoryReqs(); memoryReqs.memoryTypeBits &= m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto allocation = m_creationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), requiredAllocateFlags); + auto allocation = m_creationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS); { const bool allocated = allocation.isValid(); assert(allocated); @@ -851,9 +849,9 @@ namespace nbl::ext::imgui const auto validation = std::to_array ({ - std::make_pair(buffer->getCreationParams().usage.hasFlags(requiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(buffer->getCreationParams().usage.hasFlags(MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), - std::make_pair(binding.memory->getAllocateFlags().hasFlags(requiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") }); @@ -866,17 +864,21 @@ namespace nbl::ext::imgui } } - bool UI::render(SIntendedSubmitInfo& info, const IGPUDescriptorSet* const descriptorSet, const std::span scissors) + bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const IGPUDescriptorSet* const descriptorSet, const std::span scissors) { - if (!info.valid()) + if (!commandBuffer) { - m_creationParams.utilities->getLogger()->log("Invalid SIntendedSubmitInfo!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Invalid command buffer!", system::ILogger::ELL_ERROR); return false; } - ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() + if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) + { + m_creationParams.utilities->getLogger()->log("Command buffer is not in recording state!", system::ILogger::ELL_ERROR); + return false; + } - auto* commandBuffer = info.getScratchCommandBuffer(); + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() ImGuiIO& io = ImGui::GetIO(); @@ -983,9 +985,13 @@ namespace nbl::ext::imgui // calculate upper bound byte size limit for mdi buffer const auto mdiBufferByteSize = std::reduce(std::begin(multiAllocParams.byteSizes), std::end(multiAllocParams.byteSizes)); + /* + TODO: use linear allocator instead, we could try to fill our tightly packed mdi buffer in a loop if full big request is impossible with single allocation call + */ + auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); { - auto timeout(std::chrono::steady_clock::now() + std::chrono::milliseconds(1u)); + const auto timeout (std::chrono::steady_clock::now() + std::chrono::milliseconds(1u)); size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); @@ -994,11 +1000,9 @@ namespace nbl::ext::imgui // retry, second attempt cull frees and execute deferred memory deallocation of offsets no longer in use unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); - // still no? then we will try suballocating smaller pieces of memory due to possible fragmentation issues & upstream in nice loop (if we get to a point when suballocation could not cover whole 1 sub draw call then we need to submit overflow & rerecord stuff + repeat) - // that's TODO but first a small test for not tightly packed index + vertex buffer - if (unallocatedSize != 0u) { + #ifdef NBL_IMPL_PRINT_DEBUG_ALLOC_RET_INFO m_creationParams.utilities->getLogger()->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string @@ -1014,13 +1018,16 @@ namespace nbl::ext::imgui m_creationParams.utilities->getLogger()->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); m_creationParams.utilities->getLogger()->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); m_creationParams.utilities->getLogger()->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); + #endif + + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data()); // fail, get rid of all requests instantly + m_mdi.streamingTDBufferST->cull_frees(); - exit(0x45); + return false; } } - auto waitInfo = info.getFutureScratchSemaphore(); - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); + m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // allocated, latch offsets deallocation } const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); @@ -1060,11 +1067,6 @@ namespace nbl::ext::imgui indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; - // TEST: I think this could be illegal in vulkan explaining why I get weird flickering but valid scene still I see (the geometry seems to be OK), - // could try BDA to get vertex + index instead and this is valid for sure - // indirect->firstIndex = indexObjectGlobalOffset + pcmd->IdxOffset + cmdListIndexOffset; - // indirect->vertexOffset = vertexObjectGlobalOffset + pcmd->VtxOffset + cmdListVertexOffset; - const auto scissor = clip.getScissor(clipRectangle); auto packSnorm16 = [](float ndc) -> int16_t @@ -1091,10 +1093,6 @@ namespace nbl::ext::imgui cmdListIndexOffset += cmd_list->IdxBuffer.Size; cmdListVertexOffset += cmd_list->VtxBuffer.Size; } - - // TEST: flush - // const ILogicalDevice::MappedMemoryRange memoryRange(binding.memory, 0ull, binding.memory->getAllocationSize()); - // m_creationParams.utilities->getLogicalDevice()->flushMappedMemoryRanges(1u, &memoryRange); } auto* rawPipeline = pipeline.get(); @@ -1106,8 +1104,6 @@ namespace nbl::ext::imgui const asset::SBufferBinding binding = { .offset = multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], - // TEST: start of MDI buffer - // .offset = offset, // 0u .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -1122,8 +1118,6 @@ namespace nbl::ext::imgui const asset::SBufferBinding bindings[] = {{ .offset = multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], - // TEST: start of MDI buffer - // .offset = offset, // 0u .buffer = smart_refctd_ptr(mdiBuffer) }}; From 551c7356fc0d5ef71028a530d1b88211b8e59bb2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 17 Sep 2024 16:14:50 +0200 Subject: [PATCH 096/148] ah forgot to uncomment some of GLSL extended math in include/nbl/builtin/hlsl/glsl_compat/core.hlsl --- include/nbl/builtin/hlsl/glsl_compat/core.hlsl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl index aafb312f31..081e80e1a7 100644 --- a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl +++ b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl @@ -124,10 +124,6 @@ enable_if_t, T> atomicCompSwap(Ptr_T ptr, T comparator, T * GLSL extended math */ - // COMMENTING OUT BECAUSE OF https://github.com/microsoft/DirectXShaderCompiler/issues/6751, bring back when fixed! - /* - - template // NBL_REQUIRES() extents are square SquareMatrix inverse(NBL_CONST_REF_ARG(SquareMatrix) mat) { @@ -139,8 +135,6 @@ float32_t2 unpackSnorm2x16(uint32_t p) return spirv::unpackSnorm2x16(p); } -*/ - /** * For Vertex Shaders */ From eaad1cd269d8d40d96936d5f11e7869546143d1e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 18 Sep 2024 11:15:56 +0200 Subject: [PATCH 097/148] allow for full UI resources setup freedom, exchange descriptor set layout with pipeline layout in the creation parameters, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 20 ++- src/nbl/ext/ImGui/ImGui.cpp | 172 ++++++++++++------------ src/nbl/ext/ImGui/shaders/common.hlsl | 3 - src/nbl/ext/ImGui/shaders/fragment.hlsl | 18 ++- 5 files changed, 110 insertions(+), 105 deletions(-) diff --git a/examples_tests b/examples_tests index 5d6f01371b..1e7ed33f6f 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 5d6f01371b5f82eb9bcf4ee5675d67ffc1654e1d +Subproject commit 1e7ed33f6fadd0666e1d74b1d7b8dcd9f4d5767f diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index e6445796fc..d18da04b41 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -33,11 +33,17 @@ class UI final : public core::IReferenceCounted { struct S_RESOURCE_PARAMETERS { - nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, if not provided then default layout will be created declaring: - const uint32_t setIx = 0u, // -> following set for ImGUI resources which consists of textures (ImGUI font atlas + optional user provided textures) & corresponding *immutable* samplers - count = 0x45u, // -> common amount of resources (since for a texture there is a sampler) - texturesBindingIx = 0u, // -> binding index for textures - samplersBindingIx = 1u; // -> binding index for samplers + nbl::video::IGPUPipelineLayout* const pipelineLayout = nullptr; //! optional, default layout used if not provided declaring required UI resources such as textures (required font atlas + optional user defined textures) & corresponding samplers + uint32_t count = 0x45u; //! amount of total UI textures (and corresponding samplers) + + struct S_BINDING_REQUEST_INFO //! for a given pipeline layout we need to know what is intended for UI resources + { + uint32_t setIx, //! descriptor set index for a resource + bindingIx; //! binding index for a given resource + }; + + const S_BINDING_REQUEST_INFO textures = { .setIx = 0u, .bindingIx = 0u }, //! optional, default texture binding request info used if not provided (set & binding index) + samplers = { .setIx = 0u, .bindingIx = 1u }; //! optional, default sampler binding request info used if not provided (set & binding index) using binding_flags_t = nbl::video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; static constexpr auto TEXTURES_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT; //! required flags @@ -83,8 +89,8 @@ class UI final : public core::IReferenceCounted //! updates ImGuiIO & records ImGUI *cpu* draw command lists, you have to call it before .render bool update(const S_UPDATE_PARAMETERS& params); - //! updates mapped mdi buffer & records *gpu* draw commands - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const nbl::video::IGPUDescriptorSet* const descriptorSet, const std::span scissors = {}); + //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded size_t registerListener(std::function const& listener); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1fe00d36c4..0b5da753b1 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -22,7 +22,6 @@ namespace nbl::ext::imgui { void UI::createPipeline() { - // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix SPushConstantRange pushConstantRanges[] = { { @@ -32,87 +31,77 @@ namespace nbl::ext::imgui } }; - auto createPipelineLayout = [&](const uint32_t setIx, core::smart_refctd_ptr descriptorSetLayout) -> core::smart_refctd_ptr + auto pipelineLayout = m_creationParams.resources.pipelineLayout ? smart_refctd_ptr(m_creationParams.resources.pipelineLayout) /* provided? good, at this point its validated and we just use it */ : + [&]() -> smart_refctd_ptr { - switch (setIx) + //! if default pipeline layout is not provided, we create it here given your request binding info with certain constraints about samplers - they are immutable separate and part of the default layout + smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; { - case 0u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, smart_refctd_ptr(descriptorSetLayout)); - case 1u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, smart_refctd_ptr(descriptorSetLayout)); - case 2u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout)); - case 3u: - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout)); - default: - assert(false); - return nullptr; + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ISampler::ETC_REPEAT; + params.TextureWrapV = ISampler::ETC_REPEAT; + params.TextureWrapW = ISampler::ETC_REPEAT; + + fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); } - }; - auto pipelineLayout = createPipelineLayout(m_creationParams.resources.setIx, - [&]() -> smart_refctd_ptr - { - if (m_creationParams.resources.descriptorSetLayout) - return smart_refctd_ptr(m_creationParams.resources.descriptorSetLayout); // provided? good we just use it, we are validated at this point - else { - //! if default descriptor set layout is not provided, we create it here - smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + IGPUSampler::SParams params; + params.MinLod = 0.f; + params.MaxLod = 0.f; + params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; + + userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler"); + } - using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - { - IGPUSampler::SParams params; - params.AnisotropicFilter = 1u; - params.TextureWrapU = ISampler::ETC_REPEAT; - params.TextureWrapV = ISampler::ETC_REPEAT; - params.TextureWrapW = ISampler::ETC_REPEAT; - - fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); - fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); - } + //! as mentioned we use immutable separate samplers and they are part of the descriptor set layout + std::vector> immutableSamplers(m_creationParams.resources.count); + for (auto& it : immutableSamplers) + it = smart_refctd_ptr(userTexturesSampler); - { - IGPUSampler::SParams params; - params.MinLod = 0.f; - params.MaxLod = 0.f; - params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; - - userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); - userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler"); - } + immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler); - //! note we use immutable separate samplers and they are part of the descriptor set layout - std::vector> immutableSamplers(m_creationParams.resources.count); - for (auto& it : immutableSamplers) - it = smart_refctd_ptr(userTexturesSampler); + auto textureBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = m_creationParams.resources.textures.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count + }; - immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler); + auto samplersBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = m_creationParams.resources.samplers.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLER, + .createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count, + .immutableSamplers = immutableSamplers.data() + }; - const IGPUDescriptorSetLayout::SBinding bindings[] = - { - { - .binding = m_creationParams.resources.texturesBindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, - .createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS, - .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, - .count = m_creationParams.resources.count - }, - { - .binding = m_creationParams.resources.samplersBindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLER, - .createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS, - .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, - .count = m_creationParams.resources.count, - .immutableSamplers = immutableSamplers.data() - } - }; + auto layouts = std::to_array>({nullptr, nullptr, nullptr, nullptr }); - return m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout(bindings); + if (m_creationParams.resources.textures.setIx == m_creationParams.resources.samplers.setIx) + layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding, samplersBinding}}); + else + { + layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding}}); + layouts[m_creationParams.resources.samplers.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{samplersBinding}}); } - }()); + + assert(layouts[m_creationParams.resources.textures.setIx]); + assert(layouts[m_creationParams.resources.samplers.setIx]); + + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); + }(); if (!pipelineLayout) { @@ -178,9 +167,10 @@ namespace nbl::ext::imgui std::stringstream stream; stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" - << "#define NBL_TEXTURES_BINDING " << m_creationParams.resources.texturesBindingIx << "\n" - << "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.resources.samplersBindingIx << "\n" - << "#define NBL_RESOURCES_SET " << m_creationParams.resources.setIx << "\n" + << "#define NBL_TEXTURES_BINDING_IX " << m_creationParams.resources.textures.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING_IX " << m_creationParams.resources.samplers.bindingIx << "\n" + << "#define NBL_TEXTURES_SET_IX " << m_creationParams.resources.textures.setIx << "\n" + << "#define NBL_SAMPLER_STATES_SET_IX " << m_creationParams.resources.samplers.setIx << "\n" << "#define NBL_RESOURCES_COUNT " << m_creationParams.resources.count << "\n" << "// <-\n\n"; @@ -676,14 +666,23 @@ namespace nbl::ext::imgui { auto validateResourcesInfo = [&]() -> bool { - if (m_creationParams.resources.descriptorSetLayout) // provided? we will validate your layout + auto* pipelineLayout = m_creationParams.resources.pipelineLayout; + + if (pipelineLayout) // provided? we will validate your pipeline layout to check if you declared required UI resources { - auto validateResource = [&]() + auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) { constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; - const auto& redirect = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.descriptorSetLayout->getDescriptorRedirect(descriptorType) : m_creationParams.resources.descriptorSetLayout->getImmutableSamplerRedirect(); + if(!descriptorSetLayout) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); + return false; + } + + const auto& redirect = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? descriptorSetLayout->getDescriptorRedirect(descriptorType) + : descriptorSetLayout->getImmutableSamplerRedirect(); // TODO: now I cannot assume it, need to check ordinary samplers as well const auto bindingCount = redirect.getBindingCount(); @@ -698,7 +697,7 @@ namespace nbl::ext::imgui { const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); const auto binding = redirect.getBinding(rangeStorageIndex); - const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.texturesBindingIx : m_creationParams.resources.samplersBindingIx; + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.textures.bindingIx : m_creationParams.resources.samplers.bindingIx; if (binding.data == requestedBindingIx) { @@ -739,8 +738,9 @@ namespace nbl::ext::imgui return true; }; - - const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > () && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (); + + const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[m_creationParams.resources.textures.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[m_creationParams.resources.samplers.setIx]); if (!ok) return false; @@ -757,9 +757,11 @@ namespace nbl::ext::imgui std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), (m_creationParams.assetManager && m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), - std::make_pair(bool(m_creationParams.resources.setIx <= 3u), "Invalid `m_creationParams.resources.setIx` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(m_creationParams.resources.texturesBindingIx != m_creationParams.resources.samplersBindingIx), "Invalid `m_creationParams.resources.texturesBindingIx` is equal to `m_creationParams.resources.samplersBindingIx`!"), - std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources`!") + std::make_pair(bool(m_creationParams.resources.count >= 1u), "Invalid `m_creationParams.resources.count` is equal to 0!"), + std::make_pair(bool(m_creationParams.resources.textures.setIx <= 3u), "Invalid `m_creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.resources.samplers.setIx <= 3u), "Invalid `m_creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.resources.textures.bindingIx != m_creationParams.resources.samplers.bindingIx), "Invalid `m_creationParams.resources.textures.bindingIx` is equal to `m_creationParams.resources.samplers.bindingIx`!"), + std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources` content!") }); for (const auto& [ok, error] : validation) @@ -864,7 +866,7 @@ namespace nbl::ext::imgui } } - bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const IGPUDescriptorSet* const descriptorSet, const std::span scissors) + bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) { if (!commandBuffer) { @@ -1095,10 +1097,6 @@ namespace nbl::ext::imgui } } - auto* rawPipeline = pipeline.get(); - commandBuffer->bindGraphicsPipeline(rawPipeline); - commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &descriptorSet); - const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index a7c9097597..7d3f525dbe 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -1,6 +1,3 @@ -// temporary, maybe we should ask user for it and the layout BUT only for sampler + texture binding IDs + set IDs they belong to and size of the texture array? -#define NBL_MAX_IMGUI_TEXTURES 69 - #ifdef __HLSL_VERSION struct VSInput { diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 045f0f9939..fdaf15613c 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -1,13 +1,17 @@ -#ifndef NBL_TEXTURES_BINDING -#error "NBL_TEXTURES_BINDING must be defined!" +#ifndef NBL_TEXTURES_BINDING_IX +#error "NBL_TEXTURES_BINDING_IX must be defined!" #endif -#ifndef NBL_SAMPLER_STATES_BINDING +#ifndef NBL_SAMPLER_STATES_BINDING_IX #error "NBL_SAMPLER_STATES_BINDING must be defined!" #endif -#ifndef NBL_RESOURCES_SET -#error "NBL_RESOURCES_SET must be defined!" +#ifndef NBL_TEXTURES_SET_IX +#error "NBL_TEXTURES_SET_IX must be defined!" +#endif + +#ifndef NBL_SAMPLER_STATES_SET_IX +#error "NBL_SAMPLER_STATES_SET_IX must be defined!" #endif #ifndef NBL_RESOURCES_COUNT @@ -19,8 +23,8 @@ [[vk::push_constant]] struct PushConstants pc; // separable image samplers to handle textures we do descriptor-index -[[vk::binding(NBL_TEXTURES_BINDING, NBL_RESOURCES_SET)]] Texture2D textures[NBL_RESOURCES_COUNT]; -[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_RESOURCES_SET)]] SamplerState samplerStates[NBL_RESOURCES_COUNT]; +[[vk::binding(NBL_TEXTURES_BINDING_IX, NBL_TEXTURES_SET_IX)]] Texture2D textures[NBL_RESOURCES_COUNT]; +[[vk::binding(NBL_SAMPLER_STATES_BINDING_IX, NBL_SAMPLER_STATES_SET_IX)]] SamplerState samplerStates[NBL_RESOURCES_COUNT]; /* we use Indirect Indexed draw call to render whole GUI, note we do a cross From 3c63d0177a3d2003918f9515a7f525c3046bf2f4 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 18 Sep 2024 15:41:31 +0200 Subject: [PATCH 098/148] update UI's validation to cover both immutable & non-immutable samplers --- src/nbl/ext/ImGui/ImGui.cpp | 40 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 0b5da753b1..b53874bb7a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -675,33 +675,51 @@ namespace nbl::ext::imgui constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; + auto anyBindingCount = [&m_creationParams = m_creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect) -> bool + { + if (redirect->getBindingCount()) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); + return false; + } + + return true; + }; + if(!descriptorSetLayout) { m_creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); return false; } - const auto& redirect = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? descriptorSetLayout->getDescriptorRedirect(descriptorType) - : descriptorSetLayout->getImmutableSamplerRedirect(); // TODO: now I cannot assume it, need to check ordinary samplers as well - - const auto bindingCount = redirect.getBindingCount(); + const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); - if (!bindingCount) + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + if (!anyBindingCount(redirect)) + return false; + else { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single one for default ImGUI Font Atlas texture!", system::ILogger::ELL_ERROR, typeLiteral.data()); - return false; + if (!anyBindingCount(redirect)) + { + redirect = &descriptorSetLayout->getImmutableSamplerRedirect(); // we must give it another try & request to look for immutable samplers + + if (!anyBindingCount(redirect)) + return false; + } } + const auto bindingCount = redirect->getBindingCount(); + bool ok = false; for (uint32_t i = 0u; i < bindingCount; ++i) { const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); - const auto binding = redirect.getBinding(rangeStorageIndex); + const auto binding = redirect->getBinding(rangeStorageIndex); const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.textures.bindingIx : m_creationParams.resources.samplers.bindingIx; if (binding.data == requestedBindingIx) { - const auto count = redirect.getCount(binding); + const auto count = redirect->getCount(binding); if (count != m_creationParams.resources.count) { @@ -709,7 +727,7 @@ namespace nbl::ext::imgui return false; } - const auto stage = redirect.getStageFlags(binding); + const auto stage = redirect->getStageFlags(binding); if(!stage.hasFlags(m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS)) { @@ -717,7 +735,7 @@ namespace nbl::ext::imgui return false; } - const auto create = redirect.getCreateFlags(rangeStorageIndex); + const auto create = redirect->getCreateFlags(rangeStorageIndex); if (!create.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS : m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS)) { From 3dd43b8d35c54cb781ba553395586649238d0aa7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 19 Sep 2024 10:52:35 +0200 Subject: [PATCH 099/148] fix ZERO_CHECK OpenEXR's deps timestamp issues, update libdeflate submodule --- 3rdparty/CMakeLists.txt | 33 ++++++++++++++++----------------- 3rdparty/libdeflate | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index daa86050c6..8f65e1b2e8 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -193,42 +193,41 @@ option(PNG_TESTS "Build libpng tests" OFF) add_subdirectory(libpng libpng EXCLUDE_FROM_ALL) add_dependencies(png_static zlibstatic) - # OpenEXR option(_NBL_COMPILE_WITH_OPEN_EXR_ "Build with OpenEXR library" ON) # Imath +set(IMATH_INSTALL OFF) add_subdirectory(imath EXCLUDE_FROM_ALL) if(_NBL_COMPILE_WITH_OPEN_EXR_) # Deflate - option(LIBDEFLATE_BUILD_SHARED_LIB "" OFF) - option(LIBDEFLATE_BUILD_STATIC_LIB "" ON) - option(LIBDEFLATE_GZIP_SUPPORT "" OFF) - option(LIBDEFLATE_BUILD_GZIP "" OFF) + set(LIBDEFLATE_BUILD_SHARED_LIB OFF) + set(LIBDEFLATE_BUILD_STATIC_LIB ON) + set(LIBDEFLATE_GZIP_SUPPORT OFF) + set(LIBDEFLATE_BUILD_GZIP OFF) add_subdirectory(libdeflate EXCLUDE_FROM_ALL) set(libdeflate_DIR "${CMAKE_CURRENT_BINARY_DIR}/libdeflate") # OpenEXR + set(OPENEXR_FORCE_INTERNAL_DEFLATE ON) # trick it into thinking its internal + set(EXR_DEFLATE_LIB libdeflate_static) # and pass deflate target directly from our build tree + set(OPENEXR_FORCE_INTERNAL_IMATH ON) # similar case, force it to look into target from build tree set(_OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) set(_OLD_BUILD_STATIC_LIBS ${BUILD_STATIC_LIBS}) set(_OLD_BUILD_TESTING ${BUILD_TESTING}) set(BUILD_SHARED_LIBS OFF) set(BUILD_STATIC_LIBS OFF) set(BUILD_TESTING OFF) - set(PYILMBASE_ENABLE OFF CACHE STRING "" FORCE) - set(OPENEXR_BUILD_UTILS OFF CACHE STRING "" FORCE) - set(OPENEXR_FORCE_INTERNAL_IMATH OFF CACHE STRING "" FORCE) - set(OPENEXR_FORCE_INTERNAL_DEFLATE OFF CACHE STRING "" FORCE) - set(OPENEXR_BUILD_TOOLS OFF CACHE STRING "" FORCE) - set(OPENEXR_INSTALL OFF CACHE STRING "" FORCE) - set(OPENEXR_INSTALL_DOCS OFF CACHE STRING "" FORCE) - set(OPENEXR_INSTALL_EXAMPLES OFF CACHE STRING "" FORCE) - set(OPENEXR_INSTALL_PKG_CONFIG OFF CACHE STRING "" FORCE) - set(OPENEXR_INSTALL_TOOLS OFF CACHE STRING "" FORCE) - + set(PYILMBASE_ENABLE OFF) + set(OPENEXR_BUILD_UTILS OFF) + set(OPENEXR_BUILD_TOOLS OFF) + set(OPENEXR_INSTALL OFF) + set(OPENEXR_INSTALL_DOCS OFF) + set(OPENEXR_INSTALL_EXAMPLES OFF) + set(OPENEXR_INSTALL_PKG_CONFIG OFF) + set(OPENEXR_INSTALL_TOOLS OFF) add_subdirectory(openexr EXCLUDE_FROM_ALL) - set(BUILD_SHARED_LIBS ${_OLD_BUILD_SHARED_LIBS}) set(BUILD_STATIC_LIBS ${_OLD_BUILD_STATIC_LIBS}) set(BUILD_TESTING ${_OLD_BUILD_TESTING}) diff --git a/3rdparty/libdeflate b/3rdparty/libdeflate index 0967ded69c..495a0062c3 160000 --- a/3rdparty/libdeflate +++ b/3rdparty/libdeflate @@ -1 +1 @@ -Subproject commit 0967ded69c622dfbdbae74544be2037eee21be65 +Subproject commit 495a0062c37768449715ec464f2e7b2cbc6f70d7 From 1fbbc06a7d022ed37ef68d99c9f68e03e16f45db Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 21 Sep 2024 21:36:40 +0200 Subject: [PATCH 100/148] replace StreamingTransientDataBufferST with GeneralpurposeAddressAllocator host allocator + LinearAddressAllocatorST sub-allocator combo & use allocator type traits to handle them, update examples_tests submodule, leave some TODO comments --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 14 +- src/nbl/ext/ImGui/ImGui.cpp | 282 +++++++++++++++++++++------------- 3 files changed, 182 insertions(+), 116 deletions(-) diff --git a/examples_tests b/examples_tests index 1e7ed33f6f..a83cbf6a47 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 1e7ed33f6fadd0666e1d74b1d7b8dcd9f4d5767f +Subproject commit a83cbf6a47232a8e5ea138aea2f43581c20bdf60 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d18da04b41..7c1e20376b 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -11,7 +11,9 @@ class UI final : public core::IReferenceCounted public: struct MDI { - using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; + using COMPOSE_T = nbl::video::IGPUBuffer; //! composes memory available for the allocator which allocates submemory ranges + using ALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer allocator - requests memory range from the compose memory + using SUBALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request enum E_BUFFER_CONTENT : uint8_t { @@ -23,7 +25,8 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - nbl::core::smart_refctd_ptr streamingTDBufferST; //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS] + typename ALLOCATOR_TRAITS_T::allocator_type allocator; //! mdi buffer allocator + nbl::core::smart_refctd_ptr buffer; //! streaming mdi buffer static constexpr auto MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS = nbl::core::bitflag(nbl::video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); //! required flags static constexpr auto MDI_BUFFER_REQUIRED_USAGE_FLAGS = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; //! required flags @@ -58,7 +61,7 @@ class UI final : public core::IReferenceCounted uint32_t subpassIx = 0u; //! optional, default value used if not provided S_RESOURCE_PARAMETERS resources; //! optional, default parameters used if not provided nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided - typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided + typename MDI::COMPOSE_T* const streamingBuffer = nullptr; //! optional, default MDI buffer allocated if not provided }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) @@ -109,7 +112,10 @@ class UI final : public core::IReferenceCounted inline nbl::video::IGPUImageView* getFontAtlasView() { return m_fontAtlasTexture.get(); } //! mdi streaming buffer - inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.streamingTDBufferST.get(); } + inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.buffer.get(); } + + //! mdi buffer allocator + inline typename MDI::ALLOCATOR_TRAITS_T::allocator_type* getStreamingAllocator() { return &m_mdi.allocator; } //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index b53874bb7a..a6cd513ba3 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -20,6 +20,10 @@ using namespace nbl::ui; namespace nbl::ext::imgui { + static constexpr auto MDI_COMPONENT_COUNT = UI::MDI::EBC_COUNT; + static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MDI_MAX_ALIGNMENT = *std::max_element(MDI_ALIGNMENTS.begin(), MDI_ALIGNMENTS.end()); + void UI::createPipeline() { SPushConstantRange pushConstantRanges[] = @@ -837,8 +841,8 @@ namespace nbl::ext::imgui return flags; }; - if (m_creationParams.streamingMDIBuffer) - m_mdi.streamingTDBufferST = core::smart_refctd_ptr(m_creationParams.streamingMDIBuffer); + if (m_creationParams.streamingBuffer) + m_mdi.buffer = core::smart_refctd_ptr(m_creationParams.streamingBuffer); else { IGPUBuffer::SCreationParams mdiCreationParams = {}; @@ -846,6 +850,7 @@ namespace nbl::ext::imgui mdiCreationParams.size = mdiBufferDefaultSize; auto buffer = m_creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + buffer->setObjectDebugName("MDI Upstream Buffer"); auto memoryReqs = buffer->getMemoryReqs(); memoryReqs.memoryTypeBits &= m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); @@ -860,13 +865,15 @@ namespace nbl::ext::imgui if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) m_creationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); - m_mdi.streamingTDBufferST = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); - m_mdi.streamingTDBufferST->getBuffer()->setObjectDebugName("MDI Upstream Buffer"); + m_mdi.buffer = std::move(buffer); } - auto buffer = m_mdi.streamingTDBufferST->getBuffer(); + auto buffer = m_mdi.buffer; auto binding = buffer->getBoundMemory(); + constexpr auto ALIGN_OFFSET = 0u, MIN_BLOCK_SIZE = 1024u; + m_mdi.allocator = MDI::ALLOCATOR_TRAITS_T::allocator_type(binding.memory->getMappedPointer(), binding.offset, ALIGN_OFFSET, MDI_MAX_ALIGNMENT, binding.memory->getAllocationSize(), MIN_BLOCK_SIZE); + const auto validation = std::to_array ({ std::make_pair(buffer->getCreationParams().usage.hasFlags(MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), @@ -884,6 +891,9 @@ namespace nbl::ext::imgui } } + template + concept ImDrawBufferType = std::same_as || std::same_as; + bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) { if (!commandBuffer) @@ -979,139 +989,189 @@ namespace nbl::ext::imgui return std::move(retV); }(); - static constexpr auto MDI_ALLOCATION_COUNT = MDI::EBC_COUNT; - static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); - - struct MULTI_ALLOC_PARAMS + struct MDI_PARAMS { - std::array byteSizes = {}; - std::array offsets = {}; + std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements + typename MDI::ALLOCATOR_TRAITS_T::size_type totalByteSizeRequest = {}, //! sum of bytesToFill + drawCount = {}; //! amount of indirect draw objects }; - MULTI_ALLOC_PARAMS multiAllocParams; - std::fill(multiAllocParams.offsets.data(), multiAllocParams.offsets.data() + MDI_ALLOCATION_COUNT, MDI::COMPOSE_T::invalid_value); - - // calculate upper bound byte size for each mdi's content - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + const MDI_PARAMS mdiParams = [&]() { - const ImDrawList* commandList = drawData->CmdLists[i]; + MDI_PARAMS params; - multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); - multiAllocParams.byteSizes[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); - multiAllocParams.byteSizes[MDI::EBC_INDEX_BUFFERS] += commandList->IdxBuffer.Size * sizeof(ImDrawIdx); - multiAllocParams.byteSizes[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); - } + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + { + const ImDrawList* commandList = drawData->CmdLists[i]; - // calculate upper bound byte size limit for mdi buffer - const auto mdiBufferByteSize = std::reduce(std::begin(multiAllocParams.byteSizes), std::end(multiAllocParams.byteSizes)); + // calculate upper bound byte size for each mdi's content + params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); + params.bytesToFill[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + params.bytesToFill[MDI::EBC_INDEX_BUFFERS] += commandList->IdxBuffer.Size * sizeof(ImDrawIdx); + params.bytesToFill[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); + } - /* - TODO: use linear allocator instead, we could try to fill our tightly packed mdi buffer in a loop if full big request is impossible with single allocation call - */ + // calculate upper bound byte size limit for mdi buffer + params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); + params.drawCount = params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); - auto mdiBuffer = smart_refctd_ptr(m_mdi.streamingTDBufferST->getBuffer()); - { - const auto timeout (std::chrono::steady_clock::now() + std::chrono::milliseconds(1u)); + return std::move(params); + }(); - size_t unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); - - if (unallocatedSize != 0u) - { - // retry, second attempt cull frees and execute deferred memory deallocation of offsets no longer in use - unallocatedSize = m_mdi.streamingTDBufferST->multi_allocate(timeout, MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), MDI_ALIGNMENTS.data()); + std::array mdiBytesFilled; + std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MDI_COMPONENT_COUNT, false); + std::array mdiOffsets; + std::fill(mdiOffsets.data(), mdiOffsets.data() + MDI_COMPONENT_COUNT, MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address); - if (unallocatedSize != 0u) - { - #ifdef NBL_IMPL_PRINT_DEBUG_ALLOC_RET_INFO - m_creationParams.utilities->getLogger()->log("Could not multi alloc mdi buffer!", system::ILogger::ELL_ERROR); + auto mdiBuffer = m_mdi.buffer; + { + auto binding = mdiBuffer->getBoundMemory(); + assert(binding.memory->isCurrentlyMapped()); - auto getOffsetStr = [&](const MDI::COMPOSE_T::value_type offset) -> std::string - { - return offset == MDI::COMPOSE_T::invalid_value ? "invalid_value" : std::to_string(offset); - }; + auto* const mdiData = reinterpret_cast(binding.memory->getMappedPointer()) + binding.offset; - m_creationParams.utilities->getLogger()->log("[mdi streaming buffer] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBuffer->getSize()).c_str()); - m_creationParams.utilities->getLogger()->log("[requested] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(mdiBufferByteSize).c_str()); - m_creationParams.utilities->getLogger()->log("[unallocated] = \"%s\" bytes", system::ILogger::ELL_ERROR, std::to_string(unallocatedSize).c_str()); + struct + { + typename MDI::ALLOCATOR_TRAITS_T::size_type offset = MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address, + alignment = MDI_MAX_ALIGNMENT, + multiAllocationSize = {}; + } requestState; - m_creationParams.utilities->getLogger()->log("[MDI::EBC_DRAW_INDIRECT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]).c_str()); - m_creationParams.utilities->getLogger()->log("[MDI::EBC_ELEMENT_STRUCTURES offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]).c_str()); - m_creationParams.utilities->getLogger()->log("[MDI::EBC_INDEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]).c_str()); - m_creationParams.utilities->getLogger()->log("[MDI::EBC_VERTEX_BUFFERS offset] = \"%s\" bytes", system::ILogger::ELL_ERROR, getOffsetStr(multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]).c_str()); - #endif + const auto start = std::chrono::steady_clock::now(); + float blockRequestFactor = 1.f; + requestState.multiAllocationSize = m_mdi.allocator.max_size(); // first we will try with single allocation request given max free block size from the allocator - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data()); // fail, get rid of all requests instantly - m_mdi.streamingTDBufferST->cull_frees(); + //! we must upload entire MDI buffer data to our streaming buffer, but we cannot guarantee allocation can be done in single request + for (typename MDI::ALLOCATOR_TRAITS_T::size_type uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) + { + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + if (elapsed.count() >= 1u) return false; - } - } - - m_mdi.streamingTDBufferST->multi_deallocate(MDI_ALLOCATION_COUNT, multiAllocParams.offsets.data(), multiAllocParams.byteSizes.data(), waitInfo); // allocated, latch offsets deallocation - } - const uint32_t drawCount = multiAllocParams.byteSizes[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); - { - auto binding = mdiBuffer->getBoundMemory(); - assert(binding.memory->isCurrentlyMapped()); + const auto leftSizeToUpload = mdiParams.totalByteSizeRequest - uploadedSize, + maxTotalFreeBlockSizeToAlloc = m_mdi.allocator.max_size(); + + MDI::ALLOCATOR_TRAITS_T::multi_alloc_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize, &requestState.alignment); - auto* const mdiData = reinterpret_cast(m_mdi.streamingTDBufferST->getBufferPointer()); + if (requestState.offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) + { + requestState.multiAllocationSize = (blockRequestFactor *= 0.5f); // failed? our following allocation reqest will be half of the previous + continue; + } + else + { + static constexpr auto ALIGN_OFFSET_NEEDED = 0u; + MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); + MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, mdiOffsets.size(), mdiOffsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); - auto* const indirectsMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES]); - auto* const elementsMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]); - auto* const indicesMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS]); - auto* const verticesMappedPointer = reinterpret_cast(mdiData + multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS]); + //! linear allocator is used to fill the mdi data within suballocation memory range, + //! there are a few restrictions regarding how MDI::E_BUFFER_CONTENT(s) can be packed, + //! and we have really 2 options how data can be allocated: + //! - tightly packed data in single request covering all MDI::E_BUFFER_CONTENT within the allocated memory range + //! - each of MDI::E_BUFFER_CONTENT allocated separately & each tightly packed - const auto indexObjectGlobalOffset = indicesMappedPointer - reinterpret_cast(mdiData); - const auto vertexObjectGlobalOffset = verticesMappedPointer - reinterpret_cast(mdiData); + auto fillDrawBuffers = [&]() + { + const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; - size_t cmdListIndexOffset = {}, cmdListVertexOffset = {}, drawID = {}; + if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + return 0u; - for (int n = 0; n < drawData->CmdListsCount; n++) - { - const auto* cmd_list = drawData->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; + auto* const data = mdiData + blockOffset; + size_t localOffset = {}; - const auto clipRectangle = clip.getClipRectangle(pcmd); + for (int n = 0; n < drawData->CmdListsCount; n++) + { + auto* cmd_list = drawData->CmdLists[n]; + + if constexpr (type == MDI::EBC_INDEX_BUFFERS) + { + ::memcpy(data + localOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + localOffset += cmd_list->IdxBuffer.Size; + } + else if (type == MDI::EBC_VERTEX_BUFFERS) + { + ::memcpy(data, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + localOffset += cmd_list->VtxBuffer.Size; + } + } - // update mdi's indirect & element structures - auto* indirect = indirectsMappedPointer + drawID; - auto* element = elementsMappedPointer + drawID; + mdiBytesFilled[type] = true; + return mdiParams.bytesToFill[type]; + }; - indirect->firstInstance = drawID; // use base instance as draw ID - indirect->indexCount = pcmd->ElemCount; - indirect->instanceCount = 1u; + auto fillIndirectStructures = [&]() + { + const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; - indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; - indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; + if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + return 0u; - const auto scissor = clip.getScissor(clipRectangle); + auto* const data = mdiData + blockOffset; + size_t localOffset = {}; - auto packSnorm16 = [](float ndc) -> int16_t - { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils - }; + size_t cmdListIndexOffset = {}, cmdListVertexOffset = {}, drawID = {}; - const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + for (int n = 0; n < drawData->CmdListsCount; n++) + { + const auto* cmd_list = drawData->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if constexpr (type == MDI::EBC_DRAW_INDIRECT_STRUCTURES) + { + auto* indirect = reinterpret_cast(data) + drawID; + + indirect->firstInstance = drawID; // use base instance as draw ID + indirect->indexCount = pcmd->ElemCount; + indirect->instanceCount = 1u; + indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; + indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; + } + else if (type == MDI::EBC_ELEMENT_STRUCTURES) + { + auto* element = reinterpret_cast(data) + drawID; + + const auto clipRectangle = clip.getClipRectangle(pcmd); + const auto scissor = clip.getScissor(clipRectangle); + + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; + + const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + + element->aabbMin.x = packSnorm16(vMin.x); + element->aabbMin.y = packSnorm16(vMin.y); + element->aabbMax.x = packSnorm16(vMax.x); + element->aabbMax.y = packSnorm16(vMax.y); + element->texId = pcmd->TextureId; + } + + ++drawID; + } + + cmdListIndexOffset += cmd_list->IdxBuffer.Size; + cmdListVertexOffset += cmd_list->VtxBuffer.Size; + } - element->aabbMin.x = packSnorm16(vMin.x); - element->aabbMin.y = packSnorm16(vMin.y); - element->aabbMax.x = packSnorm16(vMax.x); - element->aabbMax.y = packSnorm16(vMax.y); - element->texId = pcmd->TextureId; + mdiBytesFilled[type] = true; + return mdiParams.bytesToFill[type]; + }; - ++drawID; + //! from biggest requests to smallest + //! TODO: I should be able to check if I have valid offsets & fire those with MT + uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_VERTEX_BUFFERS > (); + uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_INDEX_BUFFERS > (); + uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_DRAW_INDIRECT_STRUCTURES > (); + uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_ELEMENT_STRUCTURES > (); } - // update mdi's vertex & index buffers - ::memcpy(indicesMappedPointer + cmdListIndexOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - ::memcpy(verticesMappedPointer + cmdListVertexOffset, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - - cmdListIndexOffset += cmd_list->IdxBuffer.Size; - cmdListVertexOffset += cmd_list->VtxBuffer.Size; + MDI::ALLOCATOR_TRAITS_T::multi_free_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize); } } @@ -1119,7 +1179,7 @@ namespace nbl::ext::imgui { const asset::SBufferBinding binding = { - .offset = multiAllocParams.offsets[MDI::EBC_INDEX_BUFFERS], + .offset = mdiOffsets[MDI::EBC_INDEX_BUFFERS], .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -1133,7 +1193,7 @@ namespace nbl::ext::imgui { const asset::SBufferBinding bindings[] = {{ - .offset = multiAllocParams.offsets[MDI::EBC_VERTEX_BUFFERS], + .offset = mdiOffsets[MDI::EBC_VERTEX_BUFFERS], .buffer = smart_refctd_ptr(mdiBuffer) }}; @@ -1173,8 +1233,8 @@ namespace nbl::ext::imgui { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + multiAllocParams.offsets[MDI::EBC_ELEMENT_STRUCTURES]}, - .elementCount = { drawCount }, + .elementBDA = { mdiBuffer->getDeviceAddress() + mdiOffsets[MDI::EBC_ELEMENT_STRUCTURES]}, + .elementCount = { mdiParams.drawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } @@ -1185,11 +1245,11 @@ namespace nbl::ext::imgui const asset::SBufferBinding binding = { - .offset = multiAllocParams.offsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES], + .offset = mdiOffsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES], .buffer = core::smart_refctd_ptr(mdiBuffer) }; - commandBuffer->drawIndexedIndirect(binding, drawCount, sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->drawIndexedIndirect(binding, mdiParams.drawCount, sizeof(VkDrawIndexedIndirectCommand)); } return true; From aa2cf2bb335d0430d026d8db3895e6385089a322 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Sep 2024 10:28:32 +0200 Subject: [PATCH 101/148] fix some block offset bugs, add debug checks, remove wait info from .render call, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 52 ++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/examples_tests b/examples_tests index a83cbf6a47..db3bc5e099 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit a83cbf6a47232a8e5ea138aea2f43581c20bdf60 +Subproject commit db3bc5e0991df7c0116f0fc10e24ba4362cace72 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 7c1e20376b..c6825f7a40 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -93,7 +93,7 @@ class UI final : public core::IReferenceCounted bool update(const S_UPDATE_PARAMETERS& params); //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded size_t registerListener(std::function const& listener); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index a6cd513ba3..df55aaf65c 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -894,7 +894,7 @@ namespace nbl::ext::imgui template concept ImDrawBufferType = std::same_as || std::same_as; - bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) + bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, const std::span scissors) { if (!commandBuffer) { @@ -1063,7 +1063,10 @@ namespace nbl::ext::imgui { static constexpr auto ALIGN_OFFSET_NEEDED = 0u; MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); - MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, mdiOffsets.size(), mdiOffsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); + + std::array offsets; + std::fill(offsets.data(), offsets.data() + MDI_COMPONENT_COUNT, MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address); + MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); //! linear allocator is used to fill the mdi data within suballocation memory range, //! there are a few restrictions regarding how MDI::E_BUFFER_CONTENT(s) can be packed, @@ -1073,13 +1076,12 @@ namespace nbl::ext::imgui auto fillDrawBuffers = [&]() { - const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; + const typename MDI::ALLOCATOR_TRAITS_T::size_type globalBlockOffset = offsets[type]; - if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + if (globalBlockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) return 0u; - auto* const data = mdiData + blockOffset; - size_t localOffset = {}; + auto* data = mdiData + globalBlockOffset; for (int n = 0; n < drawData->CmdListsCount; n++) { @@ -1087,31 +1089,33 @@ namespace nbl::ext::imgui if constexpr (type == MDI::EBC_INDEX_BUFFERS) { - ::memcpy(data + localOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - localOffset += cmd_list->IdxBuffer.Size; + const auto localInputBlockOffset = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); + ::memcpy(data, cmd_list->IdxBuffer.Data, localInputBlockOffset); + data += localInputBlockOffset; } else if (type == MDI::EBC_VERTEX_BUFFERS) { - ::memcpy(data, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - localOffset += cmd_list->VtxBuffer.Size; + const auto localInputBlockOffset = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); + ::memcpy(data, cmd_list->VtxBuffer.Data, localInputBlockOffset); + data += localInputBlockOffset; } } mdiBytesFilled[type] = true; + mdiOffsets[type] = globalBlockOffset; return mdiParams.bytesToFill[type]; }; auto fillIndirectStructures = [&]() { - const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; + const typename MDI::ALLOCATOR_TRAITS_T::size_type globalBlockOffset = offsets[type]; - if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + if (globalBlockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) return 0u; - auto* const data = mdiData + blockOffset; - size_t localOffset = {}; + auto* const data = mdiData + globalBlockOffset; - size_t cmdListIndexOffset = {}, cmdListVertexOffset = {}, drawID = {}; + size_t cmdListIndexObjectOffset = {}, cmdListVertexObjectOffset = {}, drawID = {}; for (int n = 0; n < drawData->CmdListsCount; n++) { @@ -1127,8 +1131,8 @@ namespace nbl::ext::imgui indirect->firstInstance = drawID; // use base instance as draw ID indirect->indexCount = pcmd->ElemCount; indirect->instanceCount = 1u; - indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; - indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; + indirect->firstIndex = pcmd->IdxOffset + cmdListIndexObjectOffset; + indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexObjectOffset; } else if (type == MDI::EBC_ELEMENT_STRUCTURES) { @@ -1155,11 +1159,12 @@ namespace nbl::ext::imgui ++drawID; } - cmdListIndexOffset += cmd_list->IdxBuffer.Size; - cmdListVertexOffset += cmd_list->VtxBuffer.Size; + cmdListIndexObjectOffset += cmd_list->IdxBuffer.Size; + cmdListVertexObjectOffset += cmd_list->VtxBuffer.Size; } mdiBytesFilled[type] = true; + mdiOffsets[type] = globalBlockOffset; return mdiParams.bytesToFill[type]; }; @@ -1175,6 +1180,15 @@ namespace nbl::ext::imgui } } + assert([&mdiOffsets]() -> bool + { + for (const auto& offset : mdiOffsets) + if (offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) + return false; // we should never hit this at this point + + return true; + }()); // debug check only + const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = From b08375187449223ff2296269342f13fc80a7c086 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Sep 2024 12:50:57 +0200 Subject: [PATCH 102/148] use draw data to get total idx & vtx count to calc total bytes to fill, clean code a bit --- src/nbl/ext/ImGui/ImGui.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index df55aaf65c..ba61eb36e8 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1003,13 +1003,11 @@ namespace nbl::ext::imgui for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { const ImDrawList* commandList = drawData->CmdLists[i]; - - // calculate upper bound byte size for each mdi's content params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); params.bytesToFill[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); - params.bytesToFill[MDI::EBC_INDEX_BUFFERS] += commandList->IdxBuffer.Size * sizeof(ImDrawIdx); - params.bytesToFill[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); } + params.bytesToFill[MDI::EBC_VERTEX_BUFFERS] = drawData->TotalVtxCount * sizeof(ImDrawVert); + params.bytesToFill[MDI::EBC_INDEX_BUFFERS] = drawData->TotalIdxCount * sizeof(ImDrawIdx); // calculate upper bound byte size limit for mdi buffer params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); @@ -1033,7 +1031,6 @@ namespace nbl::ext::imgui struct { typename MDI::ALLOCATOR_TRAITS_T::size_type offset = MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address, - alignment = MDI_MAX_ALIGNMENT, multiAllocationSize = {}; } requestState; @@ -1052,7 +1049,7 @@ namespace nbl::ext::imgui const auto leftSizeToUpload = mdiParams.totalByteSizeRequest - uploadedSize, maxTotalFreeBlockSizeToAlloc = m_mdi.allocator.max_size(); - MDI::ALLOCATOR_TRAITS_T::multi_alloc_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize, &requestState.alignment); + MDI::ALLOCATOR_TRAITS_T::multi_alloc_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize, &MDI_MAX_ALIGNMENT); if (requestState.offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) { @@ -1089,15 +1086,15 @@ namespace nbl::ext::imgui if constexpr (type == MDI::EBC_INDEX_BUFFERS) { - const auto localInputBlockOffset = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); - ::memcpy(data, cmd_list->IdxBuffer.Data, localInputBlockOffset); - data += localInputBlockOffset; + const auto blockStrideToFill = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); + ::memcpy(data, cmd_list->IdxBuffer.Data, blockStrideToFill); + data += blockStrideToFill; } else if (type == MDI::EBC_VERTEX_BUFFERS) { - const auto localInputBlockOffset = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); - ::memcpy(data, cmd_list->VtxBuffer.Data, localInputBlockOffset); - data += localInputBlockOffset; + const auto blockStrideToFill = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); + ::memcpy(data, cmd_list->VtxBuffer.Data, blockStrideToFill); + data += blockStrideToFill; } } From ba233362dd280116604755df21bef8dc39894a74 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Sep 2024 15:42:58 +0200 Subject: [PATCH 103/148] it had to be my previous streaming buffer *which uses general purpose under the hood* as it was but with linear suballocator & more clever block upstream loop; update the code, make it work --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 9 +--- src/nbl/ext/ImGui/ImGui.cpp | 77 ++++++++++++++++------------------- 3 files changed, 39 insertions(+), 49 deletions(-) diff --git a/examples_tests b/examples_tests index db3bc5e099..9d1aa18040 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit db3bc5e0991df7c0116f0fc10e24ba4362cace72 +Subproject commit 9d1aa1804044604273001e683346b0236a6c64ce diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index c6825f7a40..ca4a9adde0 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -11,8 +11,7 @@ class UI final : public core::IReferenceCounted public: struct MDI { - using COMPOSE_T = nbl::video::IGPUBuffer; //! composes memory available for the allocator which allocates submemory ranges - using ALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer allocator - requests memory range from the compose memory + using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; //! composes memory available for the general purpose allocator to suballocate memory ranges using SUBALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request enum E_BUFFER_CONTENT : uint8_t @@ -25,7 +24,6 @@ class UI final : public core::IReferenceCounted EBC_COUNT, }; - typename ALLOCATOR_TRAITS_T::allocator_type allocator; //! mdi buffer allocator nbl::core::smart_refctd_ptr buffer; //! streaming mdi buffer static constexpr auto MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS = nbl::core::bitflag(nbl::video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); //! required flags @@ -93,7 +91,7 @@ class UI final : public core::IReferenceCounted bool update(const S_UPDATE_PARAMETERS& params); //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources - bool render(nbl::video::IGPUCommandBuffer* commandBuffer, const std::span scissors = {}); + bool render(nbl::video::IGPUCommandBuffer* const commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded size_t registerListener(std::function const& listener); @@ -114,9 +112,6 @@ class UI final : public core::IReferenceCounted //! mdi streaming buffer inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.buffer.get(); } - //! mdi buffer allocator - inline typename MDI::ALLOCATOR_TRAITS_T::allocator_type* getStreamingAllocator() { return &m_mdi.allocator; } - //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index ba61eb36e8..1af32a2037 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -20,8 +20,10 @@ using namespace nbl::ui; namespace nbl::ext::imgui { + using MDI_SIZE_TYPE = typename UI::MDI::COMPOSE_T::size_type; + static constexpr auto INVALID_ADDRESS = UI::MDI::COMPOSE_T::invalid_value; static constexpr auto MDI_COMPONENT_COUNT = UI::MDI::EBC_COUNT; - static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); static constexpr auto MDI_MAX_ALIGNMENT = *std::max_element(MDI_ALIGNMENTS.begin(), MDI_ALIGNMENTS.end()); void UI::createPipeline() @@ -865,15 +867,12 @@ namespace nbl::ext::imgui if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) m_creationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); - m_mdi.buffer = std::move(buffer); + m_mdi.buffer = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); } - auto buffer = m_mdi.buffer; + auto buffer = m_mdi.buffer->getBuffer(); auto binding = buffer->getBoundMemory(); - constexpr auto ALIGN_OFFSET = 0u, MIN_BLOCK_SIZE = 1024u; - m_mdi.allocator = MDI::ALLOCATOR_TRAITS_T::allocator_type(binding.memory->getMappedPointer(), binding.offset, ALIGN_OFFSET, MDI_MAX_ALIGNMENT, binding.memory->getAllocationSize(), MIN_BLOCK_SIZE); - const auto validation = std::to_array ({ std::make_pair(buffer->getCreationParams().usage.hasFlags(MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), @@ -891,10 +890,7 @@ namespace nbl::ext::imgui } } - template - concept ImDrawBufferType = std::same_as || std::same_as; - - bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, const std::span scissors) + bool UI::render(nbl::video::IGPUCommandBuffer* const commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) { if (!commandBuffer) { @@ -991,8 +987,8 @@ namespace nbl::ext::imgui struct MDI_PARAMS { - std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements - typename MDI::ALLOCATOR_TRAITS_T::size_type totalByteSizeRequest = {}, //! sum of bytesToFill + std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements + MDI_SIZE_TYPE totalByteSizeRequest = {}, //! sum of bytesToFill drawCount = {}; //! amount of indirect draw objects }; @@ -1018,52 +1014,51 @@ namespace nbl::ext::imgui std::array mdiBytesFilled; std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MDI_COMPONENT_COUNT, false); - std::array mdiOffsets; - std::fill(mdiOffsets.data(), mdiOffsets.data() + MDI_COMPONENT_COUNT, MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address); + std::array mdiOffsets; + std::fill(mdiOffsets.data(), mdiOffsets.data() + MDI_COMPONENT_COUNT, INVALID_ADDRESS); - auto mdiBuffer = m_mdi.buffer; + auto streamingBuffer = m_mdi.buffer; { - auto binding = mdiBuffer->getBoundMemory(); + auto binding = streamingBuffer->getBuffer()->getBoundMemory(); assert(binding.memory->isCurrentlyMapped()); - auto* const mdiData = reinterpret_cast(binding.memory->getMappedPointer()) + binding.offset; + auto* const mdiData = reinterpret_cast(streamingBuffer->getBufferPointer()); struct { - typename MDI::ALLOCATOR_TRAITS_T::size_type offset = MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address, + MDI_SIZE_TYPE offset = INVALID_ADDRESS, multiAllocationSize = {}; } requestState; const auto start = std::chrono::steady_clock::now(); - float blockRequestFactor = 1.f; - requestState.multiAllocationSize = m_mdi.allocator.max_size(); // first we will try with single allocation request given max free block size from the allocator //! we must upload entire MDI buffer data to our streaming buffer, but we cannot guarantee allocation can be done in single request - for (typename MDI::ALLOCATOR_TRAITS_T::size_type uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) + for (MDI_SIZE_TYPE uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) { auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); if (elapsed.count() >= 1u) + { + streamingBuffer->cull_frees(); return false; + } - const auto leftSizeToUpload = mdiParams.totalByteSizeRequest - uploadedSize, - maxTotalFreeBlockSizeToAlloc = m_mdi.allocator.max_size(); - - MDI::ALLOCATOR_TRAITS_T::multi_alloc_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize, &MDI_MAX_ALIGNMENT); + requestState.offset = INVALID_ADDRESS; + requestState.multiAllocationSize = streamingBuffer->max_size(); // request available block memory size which we can try to allocate - if (requestState.offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) - { - requestState.multiAllocationSize = (blockRequestFactor *= 0.5f); // failed? our following allocation reqest will be half of the previous - continue; - } + static constexpr auto STREAMING_ALLOCATION_COUNT = 1u; + const size_t unallocatedSize = m_mdi.buffer->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, &MDI_MAX_ALIGNMENT); //! (*) note we request single tight chunk of memory with max alignment instead of MDI::E_BUFFER_CONTENT separate chunks + + if (requestState.offset == INVALID_ADDRESS) + continue; // failed? lets try again, TODO: should I here have my "blockMemoryFactor =* 0.5" and apply to requestState.multiAllocationSize? else { static constexpr auto ALIGN_OFFSET_NEEDED = 0u; - MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); + MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); //! (*) we create linear suballocator to fill the chunk memory (some of at least) with MDI::E_BUFFER_CONTENT data - std::array offsets; - std::fill(offsets.data(), offsets.data() + MDI_COMPONENT_COUNT, MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address); - MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); + std::array offsets; + std::fill(offsets.data(), offsets.data() + MDI_COMPONENT_COUNT, INVALID_ADDRESS); + MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignment per MDI::E_BUFFER_CONTENT block //! linear allocator is used to fill the mdi data within suballocation memory range, //! there are a few restrictions regarding how MDI::E_BUFFER_CONTENT(s) can be packed, @@ -1073,9 +1068,9 @@ namespace nbl::ext::imgui auto fillDrawBuffers = [&]() { - const typename MDI::ALLOCATOR_TRAITS_T::size_type globalBlockOffset = offsets[type]; + const MDI_SIZE_TYPE globalBlockOffset = offsets[type]; - if (globalBlockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + if (globalBlockOffset == INVALID_ADDRESS or mdiBytesFilled[type]) return 0u; auto* data = mdiData + globalBlockOffset; @@ -1105,9 +1100,9 @@ namespace nbl::ext::imgui auto fillIndirectStructures = [&]() { - const typename MDI::ALLOCATOR_TRAITS_T::size_type globalBlockOffset = offsets[type]; + const MDI_SIZE_TYPE globalBlockOffset = offsets[type]; - if (globalBlockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + if (globalBlockOffset == INVALID_ADDRESS or mdiBytesFilled[type]) return 0u; auto* const data = mdiData + globalBlockOffset; @@ -1172,20 +1167,20 @@ namespace nbl::ext::imgui uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_DRAW_INDIRECT_STRUCTURES > (); uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_ELEMENT_STRUCTURES > (); } - - MDI::ALLOCATOR_TRAITS_T::multi_free_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize); + streamingBuffer->multi_deallocate(STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required } } assert([&mdiOffsets]() -> bool { for (const auto& offset : mdiOffsets) - if (offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) + if (offset == INVALID_ADDRESS) return false; // we should never hit this at this point return true; }()); // debug check only + auto mdiBuffer = smart_refctd_ptr(m_mdi.buffer->getBuffer()); const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = From ca04c4a75d13130db659dbda9421446d6bd0bb92 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Sep 2024 17:27:46 +0200 Subject: [PATCH 104/148] update comments --- src/nbl/ext/ImGui/ImGui.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1af32a2037..dd9b0c0dea 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -989,7 +989,7 @@ namespace nbl::ext::imgui { std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements MDI_SIZE_TYPE totalByteSizeRequest = {}, //! sum of bytesToFill - drawCount = {}; //! amount of indirect draw objects + drawCount = {}; //! amount of objects to draw for indirect call request }; const MDI_PARAMS mdiParams = [&]() @@ -1161,7 +1161,6 @@ namespace nbl::ext::imgui }; //! from biggest requests to smallest - //! TODO: I should be able to check if I have valid offsets & fire those with MT uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_VERTEX_BUFFERS > (); uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_INDEX_BUFFERS > (); uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_DRAW_INDIRECT_STRUCTURES > (); From b3b1e32edfa782dbb6f165d582e627f5fae9c433 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Sep 2024 18:08:56 +0200 Subject: [PATCH 105/148] remove setting up 1.f alpha when UI is sampling non-font texture, update examples_tests submodule --- examples_tests | 2 +- src/nbl/ext/ImGui/shaders/fragment.hlsl | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples_tests b/examples_tests index 9d1aa18040..f3d8ded46c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 9d1aa1804044604273001e683346b0236a6c64ce +Subproject commit f3d8ded46cf2a1ce209a9cfa5dee1de93a31f621 diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index fdaf15613c..d8e48d3c63 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -37,10 +37,5 @@ float4 PSMain(PSInput input) : SV_Target0 // BDA for requesting object data const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* input.drawID); - float4 texel = textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.texId], input.uv) * input.color; - - if(self.texId != 0) // TMP! - texel.w = 1.f; - - return texel; + return textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.texId], input.uv) * input.color; } \ No newline at end of file From 34b96616c79d181373ea144d0624f359c16f5613 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Sep 2024 10:21:02 +0200 Subject: [PATCH 106/148] adjust Nabla ImGUI Test Engine to new UI API changes, make it work again --- tools/nite/CMakeLists.txt | 1 + tools/nite/main.cpp | 285 +++++++++++++++++++++----------------- 2 files changed, 162 insertions(+), 124 deletions(-) diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt index 91ee8e3a47..946453a553 100644 --- a/tools/nite/CMakeLists.txt +++ b/tools/nite/CMakeLists.txt @@ -16,6 +16,7 @@ nbl_create_executable_project("" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" add_dependencies(${EXECUTABLE_NAME} argparse) target_include_directories(${EXECUTABLE_NAME} PUBLIC $ + "${NBL_ROOT_PATH}/examples_tests/common/include" ) nbl_adjust_flags(MAP_RELEASE Release MAP_RELWITHDEBINFO RelWithDebInfo MAP_DEBUG Debug) diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp index 6c69f83e6e..af1db69278 100644 --- a/tools/nite/main.cpp +++ b/tools/nite/main.cpp @@ -6,8 +6,8 @@ #include #include "nbl/video/utilities/CSimpleResizeSurface.h" -#include "../common/SimpleWindowedApplication.hpp" -#include "../common/InputSystem.hpp" +#include "SimpleWindowedApplication.hpp" +#include "CEventCallback.hpp" #include "nbl/ext/ImGui/ImGui.h" #include "nbl/ui/ICursorControl.h" @@ -30,48 +30,6 @@ using namespace asset; using namespace ui; using namespace video; -class CEventCallback : public ISimpleManagedSurface::ICallback -{ -public: - CEventCallback(nbl::core::smart_refctd_ptr&& m_inputSystem, nbl::system::logger_opt_smart_ptr&& logger) : m_inputSystem(std::move(m_inputSystem)), m_logger(std::move(logger)) {} - CEventCallback() {} - - void setLogger(nbl::system::logger_opt_smart_ptr& logger) - { - m_logger = logger; - } - void setInputSystem(nbl::core::smart_refctd_ptr&& m_inputSystem) - { - m_inputSystem = std::move(m_inputSystem); - } -private: - - void onMouseConnected_impl(nbl::core::smart_refctd_ptr&& mch) override - { - m_logger.log("A mouse %p has been connected", nbl::system::ILogger::ELL_INFO, mch.get()); - m_inputSystem.get()->add(m_inputSystem.get()->m_mouse, std::move(mch)); - } - void onMouseDisconnected_impl(nbl::ui::IMouseEventChannel* mch) override - { - m_logger.log("A mouse %p has been disconnected", nbl::system::ILogger::ELL_INFO, mch); - m_inputSystem.get()->remove(m_inputSystem.get()->m_mouse, mch); - } - void onKeyboardConnected_impl(nbl::core::smart_refctd_ptr&& kbch) override - { - m_logger.log("A keyboard %p has been connected", nbl::system::ILogger::ELL_INFO, kbch.get()); - m_inputSystem.get()->add(m_inputSystem.get()->m_keyboard, std::move(kbch)); - } - void onKeyboardDisconnected_impl(nbl::ui::IKeyboardEventChannel* kbch) override - { - m_logger.log("A keyboard %p has been disconnected", nbl::system::ILogger::ELL_INFO, kbch); - m_inputSystem.get()->remove(m_inputSystem.get()->m_keyboard, kbch); - } - -private: - nbl::core::smart_refctd_ptr m_inputSystem = nullptr; - nbl::system::logger_opt_smart_ptr m_logger = nullptr; -}; - class NITETool final : public examples::SimpleWindowedApplication { using device_base_t = examples::SimpleWindowedApplication; @@ -151,9 +109,11 @@ class NITETool final : public examples::SimpleWindowedApplication m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); - if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + if (!device_base_t::onAppInitialized(smart_refctd_ptr(m_system))) return false; + m_assetManager = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + m_semaphore = m_device->createSemaphore(m_realFrameIx); if (!m_semaphore) return logFail("Failed to Create a Semaphore!"); @@ -214,9 +174,54 @@ class NITETool final : public examples::SimpleWindowedApplication return logFail("Couldn't create Command Buffer!"); } - ui = core::make_smart_refctd_ptr(smart_refctd_ptr(m_device), (int)m_maxFramesInFlight, renderpass, nullptr, smart_refctd_ptr(m_window)); - auto* ctx = reinterpret_cast(ui->getContext()); - ImGui::SetCurrentContext(ctx); + ui.manager = core::make_smart_refctd_ptr + ( + nbl::ext::imgui::UI::S_CREATION_PARAMETERS + { + .assetManager = m_assetManager.get(), + .utilities = m_utils.get(), + .transfer = getTransferUpQueue(), + .renderpass = renderpass, + .subpassIx = 0u + } + ); + + { + // note that we use default layout provided by our extension (textures & samplers -> single set at 0u ix) + const auto* descriptorSetLayout = ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + const auto& params = ui.manager->getCreationParameters(); + + IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = params.resources.count; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = params.resources.count; + descriptorPoolInfo.maxSets = 1u; + descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; + + auto pool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); + assert(pool); + + pool->createDescriptorSets(1u, &descriptorSetLayout, &ui.descriptorSet); + assert(ui.descriptorSet); + + // texture atlas + our scene texture, note we don't create info & write pair for the font sampler because UI extension's is immutable and baked into DS layout + IGPUDescriptorSet::SDescriptorInfo descriptorInfo; + IGPUDescriptorSet::SWriteDescriptorSet writes; + + descriptorInfo.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + descriptorInfo.desc = core::smart_refctd_ptr(ui.manager->getFontAtlasView()); + + writes.dstSet = ui.descriptorSet.get(); + writes.binding = 0u; + writes.arrayElement = 0u; + writes.count = 1u; + writes.info = &descriptorInfo; + + if (!m_device->updateDescriptorSets({ {writes} }, {})) + { + m_logger->log("Failed to update Descriptor Set!", ILogger::ELL_ERROR); + return false; + } + } // Initialize Test Engine engine = ImGuiTestEngine_CreateContext(); @@ -229,6 +234,7 @@ class NITETool final : public examples::SimpleWindowedApplication RegisterTests_All(engine); // Start engine + auto* ctx = reinterpret_cast(ui.manager->getContext()); ImGuiTestEngine_Start(engine, ctx); ImGuiTestEngine_InstallDefaultCrashHandler(); @@ -251,7 +257,7 @@ class NITETool final : public examples::SimpleWindowedApplication ImGuiTestEngine_QueueTests(engine, group, nullptr, flags); } - ui->registerListener([this]() -> void + ui.manager->registerListener([this]() -> void { ImGuiTestEngine_ShowTestEngineWindows(engine, nullptr); } @@ -266,54 +272,6 @@ class NITETool final : public examples::SimpleWindowedApplication inline void workLoopBody() override { - static std::chrono::microseconds previousEventTimestamp{}; - // TODO: Use real deltaTime instead - float deltaTimeInSec = 0.1f; - - struct - { - std::vector mouse {}; - std::vector keyboard {}; - } capturedEvents; - - m_inputSystem->getDefaultMouse(&mouse); - m_inputSystem->getDefaultKeyboard(&keyboard); - - mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void - { - for (const auto& e : events) - { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; - capturedEvents.mouse.emplace_back(e); - } - }, m_logger.get()); - - keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void - { - for (const auto& e : events) - { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; - capturedEvents.keyboard.emplace_back(e); - } - }, m_logger.get()); - - const auto mousePosition = m_window->getCursorControl()->getPosition(); - - // C++ no instance of constructor matches the argument list argument types are: (std::_Vector_const_iterator>>, std::_Vector_const_iterator>>) - // const nbl::ui::IMouseEventChannel::range_t mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); - // const nbl::ui::IKeyboardEventChannel::range_t keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); - - core::SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); - core::SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); - - ui->update(deltaTimeInSec, { mousePosition.x , mousePosition.y}, mouseEvents, keyboardEvents); - const auto resourceIx = m_realFrameIx % m_maxFramesInFlight; if (m_realFrameIx >= m_maxFramesInFlight) @@ -329,14 +287,13 @@ class NITETool final : public examples::SimpleWindowedApplication return; } - m_currentImageAcquire = m_surface->acquireNextImage(); - if (!m_currentImageAcquire) - return; + // acquire new image + cpu events + update(); auto* const cb = m_cmdBufs.data()[resourceIx].get(); cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - cb->beginDebugMarker("NITE Tool Frame"); + cb->beginDebugMarker("Nabla ImGUI Test Engine Frame"); auto* queue = getGraphicsQueue(); @@ -350,27 +307,36 @@ class NITETool final : public examples::SimpleWindowedApplication viewport.height = WIN_H; } cb->setViewport(0u, 1u, &viewport); + + const VkRect2D currentRenderArea = { - const VkRect2D currentRenderArea = - { - .offset = {0,0}, - .extent = {m_window->getWidth(),m_window->getHeight()} - }; + .offset = {0,0}, + .extent = {m_window->getWidth(),m_window->getHeight()} + }; - const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + IQueue::SSubmitInfo::SCommandBufferInfo commandBuffersInfo[] = { {.cmdbuf = cb } }; + + // UI render pass + { + static constexpr nbl::video::IGPUCommandBuffer::SClearColorValue color = { .float32 = {0.f,0.f,0.f,1.f} }; auto scRes = static_cast(m_surface->getSwapchainResources()); - const IGPUCommandBuffer::SRenderpassBeginInfo info = + const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = { .framebuffer = scRes->getFramebuffer(m_currentImageAcquire.imageIndex), - .colorClearValues = &clearValue, + .colorClearValues = &color, .depthStencilClearValues = nullptr, .renderArea = currentRenderArea }; - cb->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + nbl::video::ISemaphore::SWaitInfo waitInfo = { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1u }; + + cb->beginRenderPass(renderpassInfo, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + const auto uiParams = ui.manager->getCreationParameters(); + auto* pipeline = ui.manager->getPipeline(); + cb->bindGraphicsPipeline(pipeline); + cb->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), uiParams.resources.textures.setIx, 1u, &ui.descriptorSet.get()); // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx + ui.manager->render(cb, waitInfo); + cb->endRenderPass(); } - - ui->render(cb, resourceIx); - cb->endRenderPass(); cb->end(); { const IQueue::SSubmitInfo::SSemaphoreInfo rendered[] = @@ -381,13 +347,10 @@ class NITETool final : public examples::SimpleWindowedApplication .stageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT } }; + + bool ok = false; { { - const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[] = - { - {.cmdbuf = cb } - }; - const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = { { @@ -396,26 +359,39 @@ class NITETool final : public examples::SimpleWindowedApplication .stageMask = PIPELINE_STAGE_FLAGS::NONE } }; + const IQueue::SSubmitInfo infos[] = { { .waitSemaphores = acquired, - .commandBuffers = commandBuffers, + .commandBuffers = commandBuffersInfo, .signalSemaphores = rendered } }; - if (queue->submit(infos) != IQueue::RESULT::SUCCESS) + ok = queue->submit(infos) == IQueue::RESULT::SUCCESS; + if (!ok) m_realFrameIx--; } } - m_window->setCaption("[Nabla IMGUI Test Engine]"); + m_window->setCaption("Nabla ImGUI Test Engine"); m_surface->present(m_currentImageAcquire.imageIndex, rendered); - // Post swap Test Engine - ImGuiTestEngine_PostSwap(engine); + if (ok) + { + const nbl::video::ISemaphore::SWaitInfo waitInfos[] = + { { + .semaphore = m_semaphore.get(), + .value = m_realFrameIx + } }; + + m_device->blockForSemaphores(waitInfos); + } } + + // Post swap Test Engine + ImGuiTestEngine_PostSwap(engine); } inline bool keepRunning() override @@ -453,6 +429,61 @@ class NITETool final : public examples::SimpleWindowedApplication return device_base_t::onAppTerminated() && good; } + inline void update() + { + static std::chrono::microseconds previousEventTimestamp{}; + + m_inputSystem->getDefaultMouse(&mouse); + m_inputSystem->getDefaultKeyboard(&keyboard); + + m_currentImageAcquire = m_surface->acquireNextImage(); + + struct + { + std::vector mouse{}; + std::vector keyboard{}; + } capturedEvents; + + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + for (const auto& e : events) + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); + } + }, m_logger.get()); + + keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + for (const auto& e : events) + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get()); + + const auto cursorPosition = m_window->getCursorControl()->getPosition(); + + nbl::ext::imgui::UI::S_UPDATE_PARAMETERS params = + { + .mousePosition = nbl::hlsl::float32_t2(cursorPosition.x, cursorPosition.y) - nbl::hlsl::float32_t2(m_window->getX(), m_window->getY()), + .displaySize = { m_window->getWidth(), m_window->getHeight() }, + .events = + { + .mouse = core::SRange(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()), + .keyboard = core::SRange(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()) + } + }; + + ui.manager->update(params); + } + private: smart_refctd_ptr m_window; smart_refctd_ptr> m_surface; @@ -464,7 +495,13 @@ class NITETool final : public examples::SimpleWindowedApplication std::array, ISwapchain::MaxImages> m_cmdBufs; ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; - nbl::core::smart_refctd_ptr ui; + struct C_UI + { + nbl::core::smart_refctd_ptr manager; + core::smart_refctd_ptr descriptorSet; + } ui; + + smart_refctd_ptr m_assetManager; core::smart_refctd_ptr m_inputSystem; InputSystem::ChannelReader mouse; InputSystem::ChannelReader keyboard; From db8248e71a40d6394e01521f31dc31a646c76c57 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Sep 2024 10:28:05 +0200 Subject: [PATCH 107/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index f3d8ded46c..64071aefa5 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit f3d8ded46cf2a1ce209a9cfa5dee1de93a31f621 +Subproject commit 64071aefa515e3aab05061b40824ef8596c89c5e From ddee91c11e99fbb419dadeaeb56f804f20e5fd2c Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 25 Sep 2024 07:45:24 +0200 Subject: [PATCH 108/148] save work, resolved 19 comments --- 3rdparty/CMakeLists.txt | 43 +++- include/nbl/ext/ImGui/ImGui.h | 179 +++++++++----- src/nbl/ext/ImGui/ImGui.cpp | 428 ++++++++++++++++++---------------- 3 files changed, 383 insertions(+), 267 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 8f65e1b2e8..a316eb5e17 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -389,7 +389,48 @@ string(APPEND NBL_IMPL_IMCONFIG_CONTENT // note we override it with default imguis (void*) type #include -#define ImTextureID uint32_t + +//! Custom "ImTextureID" info struct for Nabla UI backend purposes about resource sampler & texture descriptor binding's array indicies +struct SImResourceInfo +{ + //! texture descriptor binding's array index + uint16_t textureID : 14, + + //! sampler descriptor binding's array index, note its only meta for the texture for which we define operators to compare to (so you can specify which sampler ID should be used from the binding array) + samplerID : 2; + + bool operator==(const SImResourceInfo& other) const + { + return textureID == other.textureID; + } + + bool operator!=(const SImResourceInfo& other) const + { + return textureID != other.textureID; + } + + bool operator<(const SImResourceInfo& other) const + { + return textureID < other.textureID; + } + + bool operator>(const SImResourceInfo& other) const + { + return textureID > other.textureID; + } + + bool operator<=(const SImResourceInfo& other) const + { + return textureID <= other.textureID; + } + + bool operator>=(const SImResourceInfo& other) const + { + return textureID >= other.textureID; + } +}; + +#define ImTextureID SImResourceInfo #define IMGUI_ENABLE_FREETYPE #define IMGUI_DISABLE_OBSOLETE_KEYIO #undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index ca4a9adde0..729890bd09 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -9,124 +9,175 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - struct MDI + //! Reserved font atlas indicies for backend textures & samplers descriptor binding's array, any attempt to hook user defined texture ID == FontAtlasTexId will result in undefined behaviour + static constexpr auto FontAtlasTexId = 0u, FontAtlasSamplerId = 0u; + + struct SMdiBuffer { - using COMPOSE_T = nbl::video::StreamingTransientDataBufferST>; //! composes memory available for the general purpose allocator to suballocate memory ranges - using SUBALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request + //! composes memory available for the general purpose allocator to suballocate memory ranges + using compose_t = video::StreamingTransientDataBufferST>; + + //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request + using suballocator_traits_t = core::address_allocator_traits>; - enum E_BUFFER_CONTENT : uint8_t + enum class Content : uint16_t { - EBC_DRAW_INDIRECT_STRUCTURES, - EBC_ELEMENT_STRUCTURES, - EBC_INDEX_BUFFERS, - EBC_VERTEX_BUFFERS, + INDIRECT_STRUCTURES, + ELEMENT_STRUCTURES, + INDEX_BUFFERS, + VERTEX_BUFFERS, - EBC_COUNT, + COUNT, }; - nbl::core::smart_refctd_ptr buffer; //! streaming mdi buffer + //! streaming mdi buffer + core::smart_refctd_ptr compose; - static constexpr auto MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS = nbl::core::bitflag(nbl::video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); //! required flags - static constexpr auto MDI_BUFFER_REQUIRED_USAGE_FLAGS = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; //! required flags + //! required buffer allocate flags + static constexpr auto RequiredAllocateFlags = core::bitflag(video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + + //! required buffer usage flags + static constexpr auto RequiredUsageFlags = core::bitflag(asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | asset::IBuffer::EUF_INDEX_BUFFER_BIT | asset::IBuffer::EUF_VERTEX_BUFFER_BIT | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; }; - struct S_CREATION_PARAMETERS + struct SResourceParameters { - struct S_RESOURCE_PARAMETERS + //! for a given pipeline layout we need to know what is intended for UI resources + struct SBindingInfo + { + //! descriptor set index for a resource + uint32_t setIx, + + //! binding index for a given resource + bindingIx; + }; + + //! Reserved indexes for default backend samplers descriptor binding's array - use only if you created your pipeline layout with createDefaultPipelineLayout. If you need more or custom samplers then create the pipeline layout yourself + enum class DefaultSamplerIx : uint16_t { - nbl::video::IGPUPipelineLayout* const pipelineLayout = nullptr; //! optional, default layout used if not provided declaring required UI resources such as textures (required font atlas + optional user defined textures) & corresponding samplers - uint32_t count = 0x45u; //! amount of total UI textures (and corresponding samplers) - - struct S_BINDING_REQUEST_INFO //! for a given pipeline layout we need to know what is intended for UI resources - { - uint32_t setIx, //! descriptor set index for a resource - bindingIx; //! binding index for a given resource - }; - - const S_BINDING_REQUEST_INFO textures = { .setIx = 0u, .bindingIx = 0u }, //! optional, default texture binding request info used if not provided (set & binding index) - samplers = { .setIx = 0u, .bindingIx = 1u }; //! optional, default sampler binding request info used if not provided (set & binding index) - - using binding_flags_t = nbl::video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - static constexpr auto TEXTURES_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT; //! required flags - static constexpr auto SAMPLERS_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_NONE); //! required flags - static constexpr auto RESOURCES_REQUIRED_STAGE_FLAGS = nbl::asset::IShader::E_SHADER_STAGE::ESS_FRAGMENT; //! required stage + FONT_ATLAS = FontAtlasSamplerId, + USER, + + COUNT, }; - nbl::asset::IAssetManager* const assetManager; //! required - nbl::video::IUtilities* const utilities; //! required - nbl::video::IQueue* const transfer; //! required - nbl::video::IGPURenderpass* const renderpass; //! required - uint32_t subpassIx = 0u; //! optional, default value used if not provided - S_RESOURCE_PARAMETERS resources; //! optional, default parameters used if not provided - nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided - typename MDI::COMPOSE_T* const streamingBuffer = nullptr; //! optional, default MDI buffer allocated if not provided + using binding_flags_t = video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + + //! required textures binding creation flags + static constexpr auto TexturesRequiredCreateFlags = core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT; + + //! required samplers binding creation flags + static constexpr auto SamplersRequiredCreateFlags = core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT); + + //! required shader stage flags + static constexpr auto RequiredShaderStageFlags = asset::IShader::E_SHADER_STAGE::ESS_FRAGMENT; + + //! required, fill the info to instruct the backend about the required UI resources + SBindingInfo texturesInfo, samplersInfo; + + private: + uint32_t texturesCount, samplersCount; + + friend class UI; + }; + + struct SCachedCreationParams + { + //! required, you provide us information about your required UI binding resources which we validate at creation time + SResourceParameters resources; + + //! required + core::smart_refctd_ptr utilities; + + //! optional, default MDI buffer allocated if not provided + core::smart_refctd_ptr const streamingBuffer = nullptr; + }; + + struct SCreationParameters : public SCachedCreationParams + { + //! required + video::IQueue* const transfer = nullptr; + + //! required, must declare required UI resources such as textures (required font atlas + optional user defined textures) & samplers + core::smart_refctd_ptr pipelineLayout; + + //! required + core::smart_refctd_ptr assetManager = nullptr; + + //! required + core::smart_refctd_ptr renderpass = nullptr; + + //! optional, default value used if not provided + uint32_t subpassIx = 0u; + + //! optional, no cache used if not provided + core::smart_refctd_ptr const pipelineCache = nullptr; }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) - struct S_UPDATE_PARAMETERS + struct SUpdateParameters { //! what we pass to ImGuiIO::AddMousePosEvent - nbl::hlsl::float32_t2 mousePosition, + hlsl::float32_t2 mousePosition, //! main display size in pixels displaySize; - //! Nabla events you want to be handled with the backend - struct S_EVENTS - { - core::SRange mouse; - core::SRange keyboard; - }; + //! Nabla mouse events you want to be handled with the backend + std::span mouseEvents = {}; - S_EVENTS events; + //! Nabla keyboard events you want to be handled with the backend + std::span keyboardEvents = {}; }; - UI(S_CREATION_PARAMETERS&& params); + UI(SCreationParameters&& params); ~UI() override; - //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will result in undefined behaviour - static constexpr auto NBL_FONT_ATLAS_TEX_ID = 0u; - //! updates ImGuiIO & records ImGUI *cpu* draw command lists, you have to call it before .render - bool update(const S_UPDATE_PARAMETERS& params); + bool update(const SUpdateParameters& params); //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources - bool render(nbl::video::IGPUCommandBuffer* const commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); + bool render(video::IGPUCommandBuffer* const commandBuffer, video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); - //! registers lambda listener in which ImGUI calls should be recorded + //! registers lambda listener in which ImGUI calls should be recorded, use the returned id to unregister the listener size_t registerListener(std::function const& listener); + + //! unregisters listener with the given id std::optional unregisterListener(size_t id); //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context void setContext(void* imguiContext); - //! creation parametrs - inline const S_CREATION_PARAMETERS& getCreationParameters() const { return m_creationParams; } + //! creates default pipeline layout for the UI resources, "texturesCount" argument is textures descriptor binding's array size. Samplers are immutable and part of the created layout, SResourceParameters::DefaultSamplerIx::COUNT is the size of the samplers descriptor binding's array + static core::smart_refctd_ptr createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo = { .setIx = 0u, .bindingIx = 0u }, const SResourceParameters::SBindingInfo samplersInfo = { .setIx = 0u, .bindingIx = 1u }, uint32_t texturesCount = 0x45); + + //! creation cached parametrs + inline const SCachedCreationParams& getCreationParameters() const { return m_cachedCreationParams; } //! ImGUI graphics pipeline - inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); } + inline const video::IGPUGraphicsPipeline* getPipeline() const { return m_pipeline.get(); } //! image view default font texture - inline nbl::video::IGPUImageView* getFontAtlasView() { return m_fontAtlasTexture.get(); } + inline const video::IGPUImageView* getFontAtlasView() const { return m_fontAtlasTexture.get(); } //! mdi streaming buffer - inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.buffer.get(); } + inline const typename SMdiBuffer::compose_t* getStreamingBuffer() const { return m_mdi.compose.get(); } //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: void createPipeline(); void createMDIBuffer(); - void handleMouseEvents(const S_UPDATE_PARAMETERS& params) const; - void handleKeyEvents(const S_UPDATE_PARAMETERS& params) const; + void handleMouseEvents(const SUpdateParameters& params) const; + void handleKeyEvents(const SUpdateParameters& params) const; video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer); - S_CREATION_PARAMETERS m_creationParams; + SCachedCreationParams m_cachedCreationParams; - core::smart_refctd_ptr pipeline; + core::smart_refctd_ptr m_pipeline; core::smart_refctd_ptr m_fontAtlasTexture; - MDI m_mdi; + SMdiBuffer m_mdi; std::vector> m_subscribers {}; }; } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index dd9b0c0dea..271273a86a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -4,7 +4,6 @@ #include #include -#include "nbl/system/IApplicationFramework.h" #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" @@ -20,98 +19,110 @@ using namespace nbl::ui; namespace nbl::ext::imgui { - using MDI_SIZE_TYPE = typename UI::MDI::COMPOSE_T::size_type; - static constexpr auto INVALID_ADDRESS = UI::MDI::COMPOSE_T::invalid_value; - static constexpr auto MDI_COMPONENT_COUNT = UI::MDI::EBC_COUNT; - static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); - static constexpr auto MDI_MAX_ALIGNMENT = *std::max_element(MDI_ALIGNMENTS.begin(), MDI_ALIGNMENTS.end()); + using mdi_buffer_t = UI::SMdiBuffer; + using compose_t = typename mdi_buffer_t::compose_t; + using mdi_size_t = compose_t::size_type; - void UI::createPipeline() + static constexpr auto InvalidAddress = compose_t::invalid_value; + static constexpr auto MdiComponentCount = (uint32_t)UI::SMdiBuffer::Content::COUNT; + static constexpr auto MdiAlignments = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MdiMaxAlignment = *std::max_element(MdiAlignments.begin(), MdiAlignments.end()); + + static constexpr SPushConstantRange PushConstantRanges[] = { - SPushConstantRange pushConstantRanges[] = { - { - .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, - .offset = 0, - .size = sizeof(PushConstants) - } - }; + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, + .offset = 0, + .size = sizeof(PushConstants) + } + }; + + core::smart_refctd_ptr UI::createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) + { + if (!utilities) + return nullptr; + + if (texturesInfo.bindingIx == samplersInfo.bindingIx) + return false; - auto pipelineLayout = m_creationParams.resources.pipelineLayout ? smart_refctd_ptr(m_creationParams.resources.pipelineLayout) /* provided? good, at this point its validated and we just use it */ : - [&]() -> smart_refctd_ptr + if (!texturesCount) + return nullptr; + + smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; { - //! if default pipeline layout is not provided, we create it here given your request binding info with certain constraints about samplers - they are immutable separate and part of the default layout - smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ISampler::ETC_REPEAT; + params.TextureWrapV = ISampler::ETC_REPEAT; + params.TextureWrapW = ISampler::ETC_REPEAT; + + fontAtlasUISampler = utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); + } - using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - { - IGPUSampler::SParams params; - params.AnisotropicFilter = 1u; - params.TextureWrapU = ISampler::ETC_REPEAT; - params.TextureWrapV = ISampler::ETC_REPEAT; - params.TextureWrapW = ISampler::ETC_REPEAT; - - fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); - fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); - } + { + IGPUSampler::SParams params; + params.MinLod = 0.f; + params.MaxLod = 0.f; + params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; + + userTexturesSampler = utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler->setObjectDebugName("Nabla default ImGUI user texture sampler"); + } - { - IGPUSampler::SParams params; - params.MinLod = 0.f; - params.MaxLod = 0.f; - params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; - - userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); - userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler"); - } + //! note we use immutable separate samplers and they are part of the descriptor set layout + std::array, (uint32_t)SResourceParameters::DefaultSamplerIx::COUNT> immutableSamplers; + immutableSamplers[(uint32_t)SResourceParameters::DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); + immutableSamplers[(uint32_t)SResourceParameters::DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); - //! as mentioned we use immutable separate samplers and they are part of the descriptor set layout - std::vector> immutableSamplers(m_creationParams.resources.count); - for (auto& it : immutableSamplers) - it = smart_refctd_ptr(userTexturesSampler); + auto textureBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = texturesInfo.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = SResourceParameters::TexturesRequiredCreateFlags, + .stageFlags = SResourceParameters::RequiredShaderStageFlags, + .count = texturesCount + }; - immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler); + auto samplersBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = samplersInfo.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLER, + .createFlags = SResourceParameters::SamplersRequiredCreateFlags, + .stageFlags = SResourceParameters::RequiredShaderStageFlags, + .count = immutableSamplers.size(), + .immutableSamplers = immutableSamplers.data() + }; - auto textureBinding = IGPUDescriptorSetLayout::SBinding - { - .binding = m_creationParams.resources.textures.bindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, - .createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS, - .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, - .count = m_creationParams.resources.count - }; + auto layouts = std::to_array>({ nullptr, nullptr, nullptr, nullptr }); - auto samplersBinding = IGPUDescriptorSetLayout::SBinding - { - .binding = m_creationParams.resources.samplers.bindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLER, - .createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS, - .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, - .count = m_creationParams.resources.count, - .immutableSamplers = immutableSamplers.data() - }; + if (texturesInfo.setIx == samplersInfo.setIx) + layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding, samplersBinding} }); + else + { + layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding} }); + layouts[samplersInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {samplersBinding} }); + } - auto layouts = std::to_array>({nullptr, nullptr, nullptr, nullptr }); + assert(layouts[texturesInfo.setIx]); + assert(layouts[samplersInfo.setIx]); - if (m_creationParams.resources.textures.setIx == m_creationParams.resources.samplers.setIx) - layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding, samplersBinding}}); - else - { - layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding}}); - layouts[m_creationParams.resources.samplers.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{samplersBinding}}); - } + return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); + } - assert(layouts[m_creationParams.resources.textures.setIx]); - assert(layouts[m_creationParams.resources.samplers.setIx]); + void UI::createPipeline() + { + auto& creationParams = reinterpret_cast(m_cachedCreationParams); - return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); - }(); + auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); if (!pipelineLayout) { - m_creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR); assert(false); } @@ -123,25 +134,25 @@ namespace nbl::ext::imgui { constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; - auto system = smart_refctd_ptr(m_creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently - auto archive = make_smart_refctd_ptr(smart_refctd_ptr(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive - auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeLoader = includeFinder->getDefaultFileSystemLoader(); includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); auto createShader = [&]() -> core::smart_refctd_ptr { - asset::IAssetLoader::SAssetLoadParams params = {}; - params.logger = m_creationParams.utilities->getLogger(); + asset::IAssetLoader::SAssetLoadParams params = {}; + params.logger = creationParams.utilities->getLogger(); params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); - auto bundle = m_creationParams.assetManager->getAsset(key.value, params); + auto bundle = creationParams.assetManager->getAsset(key.value, params); const auto assets = bundle.getContents(); if (assets.empty()) { - m_creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); return nullptr; } @@ -150,7 +161,7 @@ namespace nbl::ext::imgui CHLSLCompiler::SOptions options = {}; options.stage = stage; options.preprocessorOptions.sourceIdentifier = key.value; - options.preprocessorOptions.logger = m_creationParams.utilities->getLogger(); + options.preprocessorOptions.logger = creationParams.utilities->getLogger(); options.preprocessorOptions.includeFinder = includeFinder.get(); auto compileToSPIRV = [&]() -> core::smart_refctd_ptr @@ -173,11 +184,11 @@ namespace nbl::ext::imgui std::stringstream stream; stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" - << "#define NBL_TEXTURES_BINDING_IX " << m_creationParams.resources.textures.bindingIx << "\n" - << "#define NBL_SAMPLER_STATES_BINDING_IX " << m_creationParams.resources.samplers.bindingIx << "\n" - << "#define NBL_TEXTURES_SET_IX " << m_creationParams.resources.textures.setIx << "\n" - << "#define NBL_SAMPLER_STATES_SET_IX " << m_creationParams.resources.samplers.setIx << "\n" - << "#define NBL_RESOURCES_COUNT " << m_creationParams.resources.count << "\n" + << "#define NBL_TEXTURES_BINDING_IX " << creationParams.resources.textures.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING_IX " << creationParams.resources.samplers.bindingIx << "\n" + << "#define NBL_TEXTURES_SET_IX " << creationParams.resources.textures.setIx << "\n" + << "#define NBL_SAMPLER_STATES_SET_IX " << creationParams.resources.samplers.setIx << "\n" + << "#define NBL_RESOURCES_COUNT " << creationParams.resources.count << "\n" << "// <-\n\n"; const auto newCode = stream.str() + std::string(code); @@ -194,14 +205,14 @@ namespace nbl::ext::imgui if (!spirv) { - m_creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); return nullptr; } - auto gpu = m_creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); + auto gpu = creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); if (!gpu) - m_creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", system::ILogger::ELL_ERROR, key.value); return gpu; }; @@ -283,13 +294,13 @@ namespace nbl::ext::imgui auto& param = params[0u]; param.layout = pipelineLayout.get(); param.shaders = specs; - param.renderpass = m_creationParams.renderpass; - param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = m_creationParams.subpassIx }; + param.renderpass = creationParams.renderpass.get(); + param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = creationParams.subpassIx }; }; - if (!m_creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(m_creationParams.pipelineCache, params, &pipeline)) + if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &m_pipeline)) { - m_creationParams.utilities->getLogger()->log("Could not create pipeline!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create pipeline!", system::ILogger::ELL_ERROR); assert(false); } } @@ -297,6 +308,8 @@ namespace nbl::ext::imgui ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer) { + auto& creationParams = reinterpret_cast(m_cachedCreationParams); + // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. @@ -316,7 +329,7 @@ namespace nbl::ext::imgui uint8_t* pixels = nullptr; int32_t width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - io.Fonts->SetTexID(NBL_FONT_ATLAS_TEX_ID); + io.Fonts->SetTexID({ .textureID = FontAtlasTexId, .samplerID = FontAtlasSamplerId }); if (!pixels || width<=0 || height<=0) return IQueue::RESULT::OTHER_ERROR; @@ -361,18 +374,18 @@ namespace nbl::ext::imgui region->imageExtent = { params.extent.width, params.extent.height, 1u }; } - auto image = m_creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); + auto image = m_cachedCreationParams.utilities->getLogicalDevice()->createImage(std::move(params)); if (!image) { - m_creationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla IMGUI extension Font Image"); - if (!m_creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) + if (!m_cachedCreationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { - m_creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -382,15 +395,15 @@ namespace nbl::ext::imgui { IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; - auto scratchSemaphore = m_creationParams.utilities->getLogicalDevice()->createSemaphore(0); + auto scratchSemaphore = m_cachedCreationParams.utilities->getLogicalDevice()->createSemaphore(0); if (!scratchSemaphore) { - m_creationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - sInfo.queue = m_creationParams.transfer; + sInfo.queue = creationParams.transfer; sInfo.waitSemaphores = {}; sInfo.commandBuffers = { &cmdInfo, 1 }; sInfo.scratchSemaphore = @@ -422,9 +435,9 @@ namespace nbl::ext::imgui cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!m_creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!m_cachedCreationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - m_creationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -437,9 +450,9 @@ namespace nbl::ext::imgui cmdBuffer->end(); const auto submit = sInfo.popSubmit({}); - if (m_creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) + if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - m_creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -451,7 +464,7 @@ namespace nbl::ext::imgui params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontAtlasTexture = m_creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); + m_fontAtlasTexture = m_cachedCreationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -459,13 +472,13 @@ namespace nbl::ext::imgui return retval; } - void UI::handleMouseEvents(const S_UPDATE_PARAMETERS& params) const + void UI::handleMouseEvents(const SUpdateParameters& params) const { auto& io = ImGui::GetIO(); io.AddMousePosEvent(params.mousePosition.x, params.mousePosition.y); - for (const auto& e : params.events.mouse) + for (const auto& e : params.mouseEvents) { switch (e.type) { @@ -634,7 +647,7 @@ namespace nbl::ext::imgui return map; } - void UI::handleKeyEvents(const S_UPDATE_PARAMETERS& params) const + void UI::handleKeyEvents(const SUpdateParameters& params) const { auto& io = ImGui::GetIO(); @@ -642,20 +655,20 @@ namespace nbl::ext::imgui const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc { - for (const auto& e : params.events.keyboard) + for (const auto& e : params.keyboardEvents) if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) return true; return false; }(); - for (const auto& e : params.events.keyboard) + for (const auto& e : params.keyboardEvents) { const auto& bind = keyMap[e.keyCode]; const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; if(bind.target == ImGuiKey_None) - m_creationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); else if (e.action == SKeyboardEvent::ECA_PRESSED) { @@ -667,12 +680,14 @@ namespace nbl::ext::imgui } } - UI::UI(S_CREATION_PARAMETERS&& params) - : m_creationParams(std::move(params)) + UI::UI(SCreationParameters&& params) + : m_cachedCreationParams(std::move(params)) { + auto& creationParams = reinterpret_cast(m_cachedCreationParams); + auto validateResourcesInfo = [&]() -> bool { - auto* pipelineLayout = m_creationParams.resources.pipelineLayout; + auto* pipelineLayout = creationParams.pipelineLayout.get(); if (pipelineLayout) // provided? we will validate your pipeline layout to check if you declared required UI resources { @@ -681,11 +696,12 @@ namespace nbl::ext::imgui constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; - auto anyBindingCount = [&m_creationParams = m_creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect) -> bool + // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further + auto anyBindingCount = [&m_cachedCreationParams = m_cachedCreationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect) -> bool { if (redirect->getBindingCount()) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); return false; } @@ -694,7 +710,7 @@ namespace nbl::ext::imgui if(!descriptorSetLayout) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); return false; } @@ -721,31 +737,36 @@ namespace nbl::ext::imgui { const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); const auto binding = redirect->getBinding(rangeStorageIndex); - const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.textures.bindingIx : m_creationParams.resources.samplers.bindingIx; + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_cachedCreationParams.resources.texturesInfo.bindingIx : m_cachedCreationParams.resources.samplersInfo.bindingIx; if (binding.data == requestedBindingIx) { const auto count = redirect->getCount(binding); - if (count != m_creationParams.resources.count) + if(!count) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but with different binding count!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but the binding resource count == 0u!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + m_cachedCreationParams.resources.texturesCount = count; + else + m_cachedCreationParams.resources.samplersCount = count; + const auto stage = redirect->getStageFlags(binding); - if(!stage.hasFlags(m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS)) + if(!stage.hasFlags(m_cachedCreationParams.resources.RequiredShaderStageFlags)) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } - const auto create = redirect->getCreateFlags(rangeStorageIndex); + const auto creation = redirect->getCreateFlags(rangeStorageIndex); - if (!create.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS : m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS)) + if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_cachedCreationParams.resources.TexturesRequiredCreateFlags : m_cachedCreationParams.resources.SamplersRequiredCreateFlags)) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -756,7 +777,7 @@ namespace nbl::ext::imgui if (!ok) { - m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -764,7 +785,7 @@ namespace nbl::ext::imgui }; const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); - const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[m_creationParams.resources.textures.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[m_creationParams.resources.samplers.setIx]); + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[m_cachedCreationParams.resources.texturesInfo.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[m_cachedCreationParams.resources.samplersInfo.setIx]); if (!ok) return false; @@ -775,23 +796,22 @@ namespace nbl::ext::imgui const auto validation = std::to_array ({ - std::make_pair(bool(m_creationParams.assetManager), "Invalid `m_creationParams.assetManager` is nullptr!"), - std::make_pair(bool(m_creationParams.assetManager->getSystem()), "Invalid `m_creationParams.assetManager->getSystem()` is nullptr!"), - std::make_pair(bool(m_creationParams.utilities), "Invalid `m_creationParams.utilities` is nullptr!"), - std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), - std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), - (m_creationParams.assetManager && m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), - std::make_pair(bool(m_creationParams.resources.count >= 1u), "Invalid `m_creationParams.resources.count` is equal to 0!"), - std::make_pair(bool(m_creationParams.resources.textures.setIx <= 3u), "Invalid `m_creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(m_creationParams.resources.samplers.setIx <= 3u), "Invalid `m_creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(m_creationParams.resources.textures.bindingIx != m_creationParams.resources.samplers.bindingIx), "Invalid `m_creationParams.resources.textures.bindingIx` is equal to `m_creationParams.resources.samplers.bindingIx`!"), - std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources` content!") + std::make_pair(bool(creationParams.assetManager), "Invalid `creationParams.assetManager` is nullptr!"), + std::make_pair(bool(creationParams.assetManager->getSystem()), "Invalid `creationParams.assetManager->getSystem()` is nullptr!"), + std::make_pair(bool(creationParams.utilities), "Invalid `creationParams.utilities` is nullptr!"), + std::make_pair(bool(creationParams.transfer), "Invalid `creationParams.transfer` is nullptr!"), + std::make_pair(bool(creationParams.renderpass), "Invalid `creationParams.renderpass` is nullptr!"), + (creationParams.assetManager && creationParams.utilities && creationParams.transfer && creationParams.renderpass) ? std::make_pair(bool(creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), + std::make_pair(bool(creationParams.resources.texturesInfo.setIx <= 3u), "Invalid `creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(creationParams.resources.samplersInfo.setIx <= 3u), "Invalid `creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(creationParams.resources.texturesInfo.bindingIx != creationParams.resources.samplersInfo.bindingIx), "Invalid `creationParams.resources.textures.bindingIx` is equal to `creationParams.resources.samplers.bindingIx`!"), + std::make_pair(bool(validateResourcesInfo()), "Invalid `creationParams.resources` content!") }); for (const auto& [ok, error] : validation) if (!ok) { - m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); assert(false); } @@ -799,16 +819,16 @@ namespace nbl::ext::imgui { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - smart_refctd_ptr pool = m_creationParams.utilities->getLogicalDevice()->createCommandPool(m_creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { - m_creationParams.utilities->getLogger()->log("Could not create command pool!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create command pool!", system::ILogger::ELL_ERROR); assert(false); } if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) { - m_creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); assert(false); } } @@ -843,21 +863,21 @@ namespace nbl::ext::imgui return flags; }; - if (m_creationParams.streamingBuffer) - m_mdi.buffer = core::smart_refctd_ptr(m_creationParams.streamingBuffer); + if (m_cachedCreationParams.streamingBuffer) + m_mdi.compose = core::smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); else { IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS; + mdiCreationParams.usage = SMdiBuffer::RequiredUsageFlags; mdiCreationParams.size = mdiBufferDefaultSize; - auto buffer = m_creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + auto buffer = m_cachedCreationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); buffer->setObjectDebugName("MDI Upstream Buffer"); auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + memoryReqs.memoryTypeBits &= m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto allocation = m_creationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS); + auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), SMdiBuffer::RequiredAllocateFlags); { const bool allocated = allocation.isValid(); assert(allocated); @@ -865,19 +885,19 @@ namespace nbl::ext::imgui auto memory = allocation.memory; if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) - m_creationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); - m_mdi.buffer = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.compose = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); } - auto buffer = m_mdi.buffer->getBuffer(); + auto buffer = m_mdi.compose->getBuffer(); auto binding = buffer->getBoundMemory(); const auto validation = std::to_array ({ - std::make_pair(buffer->getCreationParams().usage.hasFlags(MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), - std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), - std::make_pair(binding.memory->getAllocateFlags().hasFlags(MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(buffer->getCreationParams().usage.hasFlags(SMdiBuffer::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(SMdiBuffer::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") }); @@ -885,7 +905,7 @@ namespace nbl::ext::imgui for (const auto& [ok, error] : validation) if (!ok) { - m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); assert(false); } } @@ -894,13 +914,13 @@ namespace nbl::ext::imgui { if (!commandBuffer) { - m_creationParams.utilities->getLogger()->log("Invalid command buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", system::ILogger::ELL_ERROR); return false; } if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) { - m_creationParams.utilities->getLogger()->log("Command buffer is not in recording state!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", system::ILogger::ELL_ERROR); return false; } @@ -910,7 +930,7 @@ namespace nbl::ext::imgui if (!io.Fonts->IsBuilt()) { - m_creationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); return false; } @@ -987,11 +1007,15 @@ namespace nbl::ext::imgui struct MDI_PARAMS { - std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements - MDI_SIZE_TYPE totalByteSizeRequest = {}, //! sum of bytesToFill + std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements + mdi_size_t totalByteSizeRequest = {}, //! sum of bytesToFill drawCount = {}; //! amount of objects to draw for indirect call request }; + /* + TODO: first try to alloc params.drawCount, then on fail divide requests by 2, if we cannot allocate minimum 1 draw list we must ret false + */ + const MDI_PARAMS mdiParams = [&]() { MDI_PARAMS params; @@ -999,25 +1023,25 @@ namespace nbl::ext::imgui for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { const ImDrawList* commandList = drawData->CmdLists[i]; - params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); - params.bytesToFill[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + params.bytesToFill[SMdiBuffer::Content::INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); + params.bytesToFill[SMdiBuffer::Content::ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); } - params.bytesToFill[MDI::EBC_VERTEX_BUFFERS] = drawData->TotalVtxCount * sizeof(ImDrawVert); - params.bytesToFill[MDI::EBC_INDEX_BUFFERS] = drawData->TotalIdxCount * sizeof(ImDrawIdx); + params.bytesToFill[SMdiBuffer::Content::VERTEX_BUFFERS] = drawData->TotalVtxCount * sizeof(ImDrawVert); + params.bytesToFill[SMdiBuffer::Content::INDEX_BUFFERS] = drawData->TotalIdxCount * sizeof(ImDrawIdx); // calculate upper bound byte size limit for mdi buffer params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); - params.drawCount = params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); + params.drawCount = params.bytesToFill[SMdiBuffer::Content::INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); return std::move(params); }(); - std::array mdiBytesFilled; - std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MDI_COMPONENT_COUNT, false); - std::array mdiOffsets; - std::fill(mdiOffsets.data(), mdiOffsets.data() + MDI_COMPONENT_COUNT, INVALID_ADDRESS); + std::array mdiBytesFilled; + std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MdiComponentCount, false); + std::array mdiOffsets; + std::fill(mdiOffsets.data(), mdiOffsets.data() + MdiComponentCount, InvalidAddress); - auto streamingBuffer = m_mdi.buffer; + auto streamingBuffer = m_mdi.compose; { auto binding = streamingBuffer->getBuffer()->getBoundMemory(); assert(binding.memory->isCurrentlyMapped()); @@ -1026,14 +1050,14 @@ namespace nbl::ext::imgui struct { - MDI_SIZE_TYPE offset = INVALID_ADDRESS, + mdi_size_t offset = InvalidAddress, multiAllocationSize = {}; } requestState; const auto start = std::chrono::steady_clock::now(); //! we must upload entire MDI buffer data to our streaming buffer, but we cannot guarantee allocation can be done in single request - for (MDI_SIZE_TYPE uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) + for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) { auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); @@ -1043,34 +1067,34 @@ namespace nbl::ext::imgui return false; } - requestState.offset = INVALID_ADDRESS; + requestState.offset = InvalidAddress; requestState.multiAllocationSize = streamingBuffer->max_size(); // request available block memory size which we can try to allocate static constexpr auto STREAMING_ALLOCATION_COUNT = 1u; - const size_t unallocatedSize = m_mdi.buffer->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, &MDI_MAX_ALIGNMENT); //! (*) note we request single tight chunk of memory with max alignment instead of MDI::E_BUFFER_CONTENT separate chunks + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with max alignment instead of MDI::E_BUFFER_CONTENT separate chunks - if (requestState.offset == INVALID_ADDRESS) + if (requestState.offset == InvalidAddress) continue; // failed? lets try again, TODO: should I here have my "blockMemoryFactor =* 0.5" and apply to requestState.multiAllocationSize? else { static constexpr auto ALIGN_OFFSET_NEEDED = 0u; - MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); //! (*) we create linear suballocator to fill the chunk memory (some of at least) with MDI::E_BUFFER_CONTENT data + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MdiMaxAlignment, requestState.multiAllocationSize); //! (*) we create linear suballocator to fill the chunk memory (some of at least) with MDI::E_BUFFER_CONTENT data - std::array offsets; - std::fill(offsets.data(), offsets.data() + MDI_COMPONENT_COUNT, INVALID_ADDRESS); - MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignment per MDI::E_BUFFER_CONTENT block + std::array offsets; + std::fill(offsets.data(), offsets.data() + MdiComponentCount, InvalidAddress); + SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MdiAlignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignment per MDI::E_BUFFER_CONTENT block //! linear allocator is used to fill the mdi data within suballocation memory range, - //! there are a few restrictions regarding how MDI::E_BUFFER_CONTENT(s) can be packed, + //! there are a few restrictions regarding how SMdiBuffer::Content(s) can be packed, //! and we have really 2 options how data can be allocated: - //! - tightly packed data in single request covering all MDI::E_BUFFER_CONTENT within the allocated memory range - //! - each of MDI::E_BUFFER_CONTENT allocated separately & each tightly packed + //! - tightly packed data in single request covering all SMdiBuffer::Content within the allocated memory range + //! - each of SMdiBuffer::Content allocated separately & each tightly packed - auto fillDrawBuffers = [&]() + auto fillDrawBuffers = [&]() { - const MDI_SIZE_TYPE globalBlockOffset = offsets[type]; + const mdi_size_t globalBlockOffset = offsets[type]; - if (globalBlockOffset == INVALID_ADDRESS or mdiBytesFilled[type]) + if (globalBlockOffset == InvalidAddress or mdiBytesFilled[type]) return 0u; auto* data = mdiData + globalBlockOffset; @@ -1079,13 +1103,13 @@ namespace nbl::ext::imgui { auto* cmd_list = drawData->CmdLists[n]; - if constexpr (type == MDI::EBC_INDEX_BUFFERS) + if constexpr (type == SMdiBuffer::Content::INDEX_BUFFERS) { const auto blockStrideToFill = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); ::memcpy(data, cmd_list->IdxBuffer.Data, blockStrideToFill); data += blockStrideToFill; } - else if (type == MDI::EBC_VERTEX_BUFFERS) + else if (type == SMdiBuffer::Content::VERTEX_BUFFERS) { const auto blockStrideToFill = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); ::memcpy(data, cmd_list->VtxBuffer.Data, blockStrideToFill); @@ -1098,11 +1122,11 @@ namespace nbl::ext::imgui return mdiParams.bytesToFill[type]; }; - auto fillIndirectStructures = [&]() + auto fillIndirectStructures = [&]() { - const MDI_SIZE_TYPE globalBlockOffset = offsets[type]; + const mdi_size_t globalBlockOffset = offsets[type]; - if (globalBlockOffset == INVALID_ADDRESS or mdiBytesFilled[type]) + if (globalBlockOffset == InvalidAddress or mdiBytesFilled[type]) return 0u; auto* const data = mdiData + globalBlockOffset; @@ -1116,7 +1140,7 @@ namespace nbl::ext::imgui { const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if constexpr (type == MDI::EBC_DRAW_INDIRECT_STRUCTURES) + if constexpr (type == SMdiBuffer::Content::INDIRECT_STRUCTURES) { auto* indirect = reinterpret_cast(data) + drawID; @@ -1126,7 +1150,7 @@ namespace nbl::ext::imgui indirect->firstIndex = pcmd->IdxOffset + cmdListIndexObjectOffset; indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexObjectOffset; } - else if (type == MDI::EBC_ELEMENT_STRUCTURES) + else if (type == SMdiBuffer::Content::ELEMENT_STRUCTURES) { auto* element = reinterpret_cast(data) + drawID; @@ -1161,10 +1185,10 @@ namespace nbl::ext::imgui }; //! from biggest requests to smallest - uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_VERTEX_BUFFERS > (); - uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_INDEX_BUFFERS > (); - uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_DRAW_INDIRECT_STRUCTURES > (); - uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_ELEMENT_STRUCTURES > (); + uploadedSize += fillDrawBuffers.template operator() < SMdiBuffer::Content::VERTEX_BUFFERS > (); + uploadedSize += fillDrawBuffers.template operator() < SMdiBuffer::Content::INDEX_BUFFERS > (); + uploadedSize += fillIndirectStructures.template operator() < SMdiBuffer::Content::INDIRECT_STRUCTURES > (); + uploadedSize += fillIndirectStructures.template operator() < SMdiBuffer::Content::ELEMENT_STRUCTURES > (); } streamingBuffer->multi_deallocate(STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required } @@ -1173,7 +1197,7 @@ namespace nbl::ext::imgui assert([&mdiOffsets]() -> bool { for (const auto& offset : mdiOffsets) - if (offset == INVALID_ADDRESS) + if (offset == InvalidAddress) return false; // we should never hit this at this point return true; @@ -1190,7 +1214,7 @@ namespace nbl::ext::imgui if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) { - m_creationParams.utilities->getLogger()->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); assert(false); } } @@ -1204,7 +1228,7 @@ namespace nbl::ext::imgui if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) { - m_creationParams.utilities->getLogger()->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); assert(false); } } From 114c079fb99cf45ac5b8064581fed918a2bc0dac Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 25 Sep 2024 11:50:29 +0200 Subject: [PATCH 109/148] make it compile after saving work, update examples_tests - back to resolving following review comments --- 3rdparty/CMakeLists.txt | 24 ++++++-- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 33 ++++++----- src/nbl/ext/ImGui/ImGui.cpp | 78 +++++++++++++------------ src/nbl/ext/ImGui/shaders/common.hlsl | 9 ++- src/nbl/ext/ImGui/shaders/fragment.hlsl | 14 +++-- 6 files changed, 93 insertions(+), 67 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index a316eb5e17..884660bdcd 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -391,15 +391,29 @@ string(APPEND NBL_IMPL_IMCONFIG_CONTENT #include //! Custom "ImTextureID" info struct for Nabla UI backend purposes about resource sampler & texture descriptor binding's array indicies +//! must be 4 bytes size & alignment to pass imgui static asserts (it checks for contiguous blocks in memory to make sure it can do some memcpies) struct SImResourceInfo { //! texture descriptor binding's array index - uint16_t textureID : 14, - - //! sampler descriptor binding's array index, note its only meta for the texture for which we define operators to compare to (so you can specify which sampler ID should be used from the binding array) - samplerID : 2; + uint32_t textureID : 26; + + //! sampler descriptor binding's array index + uint32_t samplerIx : 6; + + SImResourceInfo() : textureID(0u), samplerIx(0u) {} + + SImResourceInfo(uint32_t texID) + { + textureID = texID; + samplerIx = 0u; + } + + explicit operator intptr_t() const + { + return static_cast(textureID); + } - bool operator==(const SImResourceInfo& other) const + bool operator==(const SImResourceInfo& other) const { return textureID == other.textureID; } diff --git a/examples_tests b/examples_tests index 64071aefa5..c4ec0367ea 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 64071aefa515e3aab05061b40824ef8596c89c5e +Subproject commit c4ec0367eaff8f25cbd0ec2ee6c5bcc3541f9a25 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 729890bd09..d6ef364a8a 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -9,9 +9,18 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - //! Reserved font atlas indicies for backend textures & samplers descriptor binding's array, any attempt to hook user defined texture ID == FontAtlasTexId will result in undefined behaviour + //! Reserved font atlas indicies for default backend textures & samplers descriptor binding's array static constexpr auto FontAtlasTexId = 0u, FontAtlasSamplerId = 0u; + //! Reserved indexes for default backend samplers descriptor binding's array - use only if you created your pipeline layout with createDefaultPipelineLayout. If you need more or custom samplers then create the pipeline layout yourself + enum class DefaultSamplerIx : uint16_t + { + FONT_ATLAS = FontAtlasSamplerId, + USER, + + COUNT, + }; + struct SMdiBuffer { //! composes memory available for the general purpose allocator to suballocate memory ranges @@ -52,15 +61,6 @@ class UI final : public core::IReferenceCounted bindingIx; }; - //! Reserved indexes for default backend samplers descriptor binding's array - use only if you created your pipeline layout with createDefaultPipelineLayout. If you need more or custom samplers then create the pipeline layout yourself - enum class DefaultSamplerIx : uint16_t - { - FONT_ATLAS = FontAtlasSamplerId, - USER, - - COUNT, - }; - using binding_flags_t = video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; //! required textures binding creation flags @@ -90,13 +90,13 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr utilities; //! optional, default MDI buffer allocated if not provided - core::smart_refctd_ptr const streamingBuffer = nullptr; + core::smart_refctd_ptr streamingBuffer = nullptr; }; struct SCreationParameters : public SCachedCreationParams { //! required - video::IQueue* const transfer = nullptr; + video::IQueue* transfer = nullptr; //! required, must declare required UI resources such as textures (required font atlas + optional user defined textures) & samplers core::smart_refctd_ptr pipelineLayout; @@ -111,7 +111,7 @@ class UI final : public core::IReferenceCounted uint32_t subpassIx = 0u; //! optional, no cache used if not provided - core::smart_refctd_ptr const pipelineCache = nullptr; + core::smart_refctd_ptr pipelineCache = nullptr; }; //! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update) @@ -149,7 +149,7 @@ class UI final : public core::IReferenceCounted void setContext(void* imguiContext); //! creates default pipeline layout for the UI resources, "texturesCount" argument is textures descriptor binding's array size. Samplers are immutable and part of the created layout, SResourceParameters::DefaultSamplerIx::COUNT is the size of the samplers descriptor binding's array - static core::smart_refctd_ptr createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo = { .setIx = 0u, .bindingIx = 0u }, const SResourceParameters::SBindingInfo samplersInfo = { .setIx = 0u, .bindingIx = 1u }, uint32_t texturesCount = 0x45); + static core::smart_refctd_ptr createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount = 0x45); //! creation cached parametrs inline const SCachedCreationParams& getCreationParameters() const { return m_cachedCreationParams; } @@ -158,7 +158,10 @@ class UI final : public core::IReferenceCounted inline const video::IGPUGraphicsPipeline* getPipeline() const { return m_pipeline.get(); } //! image view default font texture - inline const video::IGPUImageView* getFontAtlasView() const { return m_fontAtlasTexture.get(); } + //! TODO: we cannot expose immutable view of our default font texture because user MUST be able to write a descriptor, + //! we can have mutable getter or we can decide to not create any default font texture at all and force users + //! to do it externally (have a static helper to do it which gives you mutable view) + inline video::IGPUImageView* getFontAtlasView() const { return m_fontAtlasTexture.get(); } //! mdi streaming buffer inline const typename SMdiBuffer::compose_t* getStreamingBuffer() const { return m_mdi.compose.get(); } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 271273a86a..6493c28bf2 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -43,7 +43,7 @@ namespace nbl::ext::imgui return nullptr; if (texturesInfo.bindingIx == samplersInfo.bindingIx) - return false; + return nullptr; if (!texturesCount) return nullptr; @@ -75,9 +75,9 @@ namespace nbl::ext::imgui } //! note we use immutable separate samplers and they are part of the descriptor set layout - std::array, (uint32_t)SResourceParameters::DefaultSamplerIx::COUNT> immutableSamplers; - immutableSamplers[(uint32_t)SResourceParameters::DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); - immutableSamplers[(uint32_t)SResourceParameters::DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); + std::array, (uint32_t)DefaultSamplerIx::COUNT> immutableSamplers; + immutableSamplers[(uint32_t)DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); + immutableSamplers[(uint32_t)DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); auto textureBinding = IGPUDescriptorSetLayout::SBinding { @@ -184,11 +184,12 @@ namespace nbl::ext::imgui std::stringstream stream; stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" - << "#define NBL_TEXTURES_BINDING_IX " << creationParams.resources.textures.bindingIx << "\n" - << "#define NBL_SAMPLER_STATES_BINDING_IX " << creationParams.resources.samplers.bindingIx << "\n" - << "#define NBL_TEXTURES_SET_IX " << creationParams.resources.textures.setIx << "\n" - << "#define NBL_SAMPLER_STATES_SET_IX " << creationParams.resources.samplers.setIx << "\n" - << "#define NBL_RESOURCES_COUNT " << creationParams.resources.count << "\n" + << "#define NBL_TEXTURES_BINDING_IX " << creationParams.resources.texturesInfo.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING_IX " << creationParams.resources.samplersInfo.bindingIx << "\n" + << "#define NBL_TEXTURES_SET_IX " << creationParams.resources.texturesInfo.setIx << "\n" + << "#define NBL_SAMPLER_STATES_SET_IX " << creationParams.resources.samplersInfo.setIx << "\n" + << "#define NBL_TEXTURES_COUNT " << creationParams.resources.texturesCount << "\n" + << "#define NBL_SAMPLERS_COUNT " << creationParams.resources.samplersCount << "\n" << "// <-\n\n"; const auto newCode = stream.str() + std::string(code); @@ -329,7 +330,11 @@ namespace nbl::ext::imgui uint8_t* pixels = nullptr; int32_t width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - io.Fonts->SetTexID({ .textureID = FontAtlasTexId, .samplerID = FontAtlasSamplerId }); + SImResourceInfo info; + info.textureID = FontAtlasTexId; + info.samplerIx = FontAtlasSamplerId; + + io.Fonts->SetTexID(info); if (!pixels || width<=0 || height<=0) return IQueue::RESULT::OTHER_ERROR; @@ -381,15 +386,13 @@ namespace nbl::ext::imgui m_cachedCreationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } - image->setObjectDebugName("Nabla IMGUI extension Font Image"); + image->setObjectDebugName("Nabla ImGUI default font"); if (!m_cachedCreationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { m_cachedCreationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } - - image->setObjectDebugName("Nabla IMGUI extension Font Atlas"); SIntendedSubmitInfo sInfo; { @@ -1023,15 +1026,15 @@ namespace nbl::ext::imgui for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { const ImDrawList* commandList = drawData->CmdLists[i]; - params.bytesToFill[SMdiBuffer::Content::INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); - params.bytesToFill[SMdiBuffer::Content::ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + params.bytesToFill[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); + params.bytesToFill[static_cast>(SMdiBuffer::Content::ELEMENT_STRUCTURES)] += commandList->CmdBuffer.Size * sizeof(PerObjectData); } - params.bytesToFill[SMdiBuffer::Content::VERTEX_BUFFERS] = drawData->TotalVtxCount * sizeof(ImDrawVert); - params.bytesToFill[SMdiBuffer::Content::INDEX_BUFFERS] = drawData->TotalIdxCount * sizeof(ImDrawIdx); + params.bytesToFill[static_cast>(SMdiBuffer::Content::VERTEX_BUFFERS)] = drawData->TotalVtxCount * sizeof(ImDrawVert); + params.bytesToFill[static_cast>(SMdiBuffer::Content::INDEX_BUFFERS)] = drawData->TotalIdxCount * sizeof(ImDrawIdx); // calculate upper bound byte size limit for mdi buffer params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); - params.drawCount = params.bytesToFill[SMdiBuffer::Content::INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); + params.drawCount = params.bytesToFill[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)] / sizeof(VkDrawIndexedIndirectCommand); return std::move(params); }(); @@ -1092,9 +1095,10 @@ namespace nbl::ext::imgui auto fillDrawBuffers = [&]() { - const mdi_size_t globalBlockOffset = offsets[type]; + const auto underlying_type = static_cast>(type); + const mdi_size_t globalBlockOffset = offsets[underlying_type]; - if (globalBlockOffset == InvalidAddress or mdiBytesFilled[type]) + if (globalBlockOffset == InvalidAddress or mdiBytesFilled[underlying_type]) return 0u; auto* data = mdiData + globalBlockOffset; @@ -1117,16 +1121,17 @@ namespace nbl::ext::imgui } } - mdiBytesFilled[type] = true; - mdiOffsets[type] = globalBlockOffset; - return mdiParams.bytesToFill[type]; + mdiBytesFilled[underlying_type] = true; + mdiOffsets[underlying_type] = globalBlockOffset; + return mdiParams.bytesToFill[underlying_type]; }; auto fillIndirectStructures = [&]() { - const mdi_size_t globalBlockOffset = offsets[type]; + const auto underlying_type = static_cast>(type); + const mdi_size_t globalBlockOffset = offsets[underlying_type]; - if (globalBlockOffset == InvalidAddress or mdiBytesFilled[type]) + if (globalBlockOffset == InvalidAddress or mdiBytesFilled[underlying_type]) return 0u; auto* const data = mdiData + globalBlockOffset; @@ -1169,7 +1174,8 @@ namespace nbl::ext::imgui element->aabbMin.y = packSnorm16(vMin.y); element->aabbMax.x = packSnorm16(vMax.x); element->aabbMax.y = packSnorm16(vMax.y); - element->texId = pcmd->TextureId; + element->texId = pcmd->TextureId.textureID; + element->samplerIx = pcmd->TextureId.samplerIx; } ++drawID; @@ -1179,9 +1185,9 @@ namespace nbl::ext::imgui cmdListVertexObjectOffset += cmd_list->VtxBuffer.Size; } - mdiBytesFilled[type] = true; - mdiOffsets[type] = globalBlockOffset; - return mdiParams.bytesToFill[type]; + mdiBytesFilled[underlying_type] = true; + mdiOffsets[underlying_type] = globalBlockOffset; + return mdiParams.bytesToFill[underlying_type]; }; //! from biggest requests to smallest @@ -1203,12 +1209,12 @@ namespace nbl::ext::imgui return true; }()); // debug check only - auto mdiBuffer = smart_refctd_ptr(m_mdi.buffer->getBuffer()); + auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = { - .offset = mdiOffsets[MDI::EBC_INDEX_BUFFERS], + .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDEX_BUFFERS)], .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -1222,7 +1228,7 @@ namespace nbl::ext::imgui { const asset::SBufferBinding bindings[] = {{ - .offset = mdiOffsets[MDI::EBC_VERTEX_BUFFERS], + .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::VERTEX_BUFFERS)], .buffer = smart_refctd_ptr(mdiBuffer) }}; @@ -1262,19 +1268,19 @@ namespace nbl::ext::imgui { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + mdiOffsets[MDI::EBC_ELEMENT_STRUCTURES]}, + .elementBDA = { mdiBuffer->getDeviceAddress() + mdiOffsets[static_cast>( SMdiBuffer::Content::ELEMENT_STRUCTURES )]}, .elementCount = { mdiParams.drawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } }; - commandBuffer->pushConstants(pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); } const asset::SBufferBinding binding = { - .offset = mdiOffsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES], + .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)], .buffer = core::smart_refctd_ptr(mdiBuffer) }; @@ -1284,7 +1290,7 @@ namespace nbl::ext::imgui return true; } - bool UI::update(const S_UPDATE_PARAMETERS& params) + bool UI::update(const SUpdateParameters& params) { auto & io = ImGui::GetIO(); diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 7d3f525dbe..d4472aa7f9 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -44,13 +44,12 @@ struct emulated_snorm16_t2 } #endif - int16_t x; - int16_t y; + int16_t x, y; }; struct PerObjectData { - emulated_snorm16_t2 aabbMin; - emulated_snorm16_t2 aabbMax; - uint16_t texId; + emulated_snorm16_t2 aabbMin, aabbMax; + uint16_t texId : 14; + uint16_t samplerIx : 2; }; \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index d8e48d3c63..3073bea4e1 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -14,8 +14,12 @@ #error "NBL_SAMPLER_STATES_SET_IX must be defined!" #endif -#ifndef NBL_RESOURCES_COUNT -#error "NBL_RESOURCES_COUNT must be defined!" +#ifndef NBL_TEXTURES_COUNT +#error "NBL_TEXTURES_COUNT must be defined!" +#endif + +#ifndef NBL_SAMPLERS_COUNT +#error "NBL_SAMPLERS_COUNT must be defined!" #endif #include "common.hlsl" @@ -23,8 +27,8 @@ [[vk::push_constant]] struct PushConstants pc; // separable image samplers to handle textures we do descriptor-index -[[vk::binding(NBL_TEXTURES_BINDING_IX, NBL_TEXTURES_SET_IX)]] Texture2D textures[NBL_RESOURCES_COUNT]; -[[vk::binding(NBL_SAMPLER_STATES_BINDING_IX, NBL_SAMPLER_STATES_SET_IX)]] SamplerState samplerStates[NBL_RESOURCES_COUNT]; +[[vk::binding(NBL_TEXTURES_BINDING_IX, NBL_TEXTURES_SET_IX)]] Texture2D textures[NBL_TEXTURES_COUNT]; +[[vk::binding(NBL_SAMPLER_STATES_BINDING_IX, NBL_SAMPLER_STATES_SET_IX)]] SamplerState samplerStates[NBL_SAMPLERS_COUNT]; /* we use Indirect Indexed draw call to render whole GUI, note we do a cross @@ -37,5 +41,5 @@ float4 PSMain(PSInput input) : SV_Target0 // BDA for requesting object data const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* input.drawID); - return textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.texId], input.uv) * input.color; + return textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.samplerIx], input.uv) * input.color; } \ No newline at end of file From ac4e041d7cf484dd6bdbbc838b859655ddbec6e8 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 25 Sep 2024 12:15:25 +0200 Subject: [PATCH 110/148] fix bugs with cached creation params & bindings validation, perform tests & make it work with current content, update examples_tests submodule - back to resolving the review comments --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 8 ++-- src/nbl/ext/ImGui/ImGui.cpp | 79 +++++++++++++++++------------------ 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/examples_tests b/examples_tests index c4ec0367ea..478ed1878d 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit c4ec0367eaff8f25cbd0ec2ee6c5bcc3541f9a25 +Subproject commit 478ed1878d31cda7670a760f03999b5ed52759d1 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d6ef364a8a..aff306cee9 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -76,7 +76,7 @@ class UI final : public core::IReferenceCounted SBindingInfo texturesInfo, samplersInfo; private: - uint32_t texturesCount, samplersCount; + uint32_t texturesCount = {}, samplersCount = {}; friend class UI; }; @@ -169,11 +169,11 @@ class UI final : public core::IReferenceCounted //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); private: - void createPipeline(); - void createMDIBuffer(); + void createPipeline(SCreationParameters& creationParams); + void createMDIBuffer(SCreationParameters& creationParams); void handleMouseEvents(const SUpdateParameters& params) const; void handleKeyEvents(const SUpdateParameters& params) const; - video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer); + video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams); SCachedCreationParams m_cachedCreationParams; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 6493c28bf2..4525387fd9 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -114,10 +114,8 @@ namespace nbl::ext::imgui return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); } - void UI::createPipeline() + void UI::createPipeline(SCreationParameters& creationParams) { - auto& creationParams = reinterpret_cast(m_cachedCreationParams); - auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); if (!pipelineLayout) @@ -307,10 +305,8 @@ namespace nbl::ext::imgui } } - ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer) + ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) { - auto& creationParams = reinterpret_cast(m_cachedCreationParams); - // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. @@ -379,18 +375,18 @@ namespace nbl::ext::imgui region->imageExtent = { params.extent.width, params.extent.height, 1u }; } - auto image = m_cachedCreationParams.utilities->getLogicalDevice()->createImage(std::move(params)); + auto image = creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); if (!image) { - m_cachedCreationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla ImGUI default font"); - if (!m_cachedCreationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) + if (!creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { - m_cachedCreationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -398,10 +394,10 @@ namespace nbl::ext::imgui { IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; - auto scratchSemaphore = m_cachedCreationParams.utilities->getLogicalDevice()->createSemaphore(0); + auto scratchSemaphore = creationParams.utilities->getLogicalDevice()->createSemaphore(0); if (!scratchSemaphore) { - m_cachedCreationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); @@ -438,9 +434,9 @@ namespace nbl::ext::imgui cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!m_cachedCreationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - m_cachedCreationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -455,7 +451,7 @@ namespace nbl::ext::imgui const auto submit = sInfo.popSubmit({}); if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - m_cachedCreationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -467,7 +463,7 @@ namespace nbl::ext::imgui params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontAtlasTexture = m_cachedCreationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); + m_fontAtlasTexture = creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -683,11 +679,8 @@ namespace nbl::ext::imgui } } - UI::UI(SCreationParameters&& params) - : m_cachedCreationParams(std::move(params)) + UI::UI(SCreationParameters&& creationParams) { - auto& creationParams = reinterpret_cast(m_cachedCreationParams); - auto validateResourcesInfo = [&]() -> bool { auto* pipelineLayout = creationParams.pipelineLayout.get(); @@ -700,31 +693,35 @@ namespace nbl::ext::imgui ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further - auto anyBindingCount = [&m_cachedCreationParams = m_cachedCreationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect) -> bool + auto anyBindingCount = [&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool { - if (redirect->getBindingCount()) + bool ok = redirect->getBindingCount(); + + if (!ok && logError) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); return false; } - return true; + return ok; }; if(!descriptorSetLayout) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); return false; } const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + { if (!anyBindingCount(redirect)) return false; + } else { - if (!anyBindingCount(redirect)) + if (!anyBindingCount(redirect, false)) { redirect = &descriptorSetLayout->getImmutableSamplerRedirect(); // we must give it another try & request to look for immutable samplers @@ -740,7 +737,7 @@ namespace nbl::ext::imgui { const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); const auto binding = redirect->getBinding(rangeStorageIndex); - const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_cachedCreationParams.resources.texturesInfo.bindingIx : m_cachedCreationParams.resources.samplersInfo.bindingIx; + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx : creationParams.resources.samplersInfo.bindingIx; if (binding.data == requestedBindingIx) { @@ -748,28 +745,28 @@ namespace nbl::ext::imgui if(!count) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but the binding resource count == 0u!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) - m_cachedCreationParams.resources.texturesCount = count; + creationParams.resources.texturesCount = count; else - m_cachedCreationParams.resources.samplersCount = count; + creationParams.resources.samplersCount = count; const auto stage = redirect->getStageFlags(binding); - if(!stage.hasFlags(m_cachedCreationParams.resources.RequiredShaderStageFlags)) + if(!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } const auto creation = redirect->getCreateFlags(rangeStorageIndex); - if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_cachedCreationParams.resources.TexturesRequiredCreateFlags : m_cachedCreationParams.resources.SamplersRequiredCreateFlags)) + if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -780,7 +777,7 @@ namespace nbl::ext::imgui if (!ok) { - m_cachedCreationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `m_cachedCreationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -788,7 +785,7 @@ namespace nbl::ext::imgui }; const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); - const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[m_cachedCreationParams.resources.texturesInfo.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[m_cachedCreationParams.resources.samplersInfo.setIx]); + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[creationParams.resources.texturesInfo.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[creationParams.resources.samplersInfo.setIx]); if (!ok) return false; @@ -840,17 +837,19 @@ namespace nbl::ext::imgui IMGUI_CHECKVERSION(); ImGui::CreateContext(); - createPipeline(); - createMDIBuffer(); - createFontAtlasTexture(transistentCMD.get()); + createPipeline(creationParams); + createMDIBuffer(creationParams); + createFontAtlasTexture(transistentCMD.get(), creationParams); auto & io = ImGui::GetIO(); io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports + + m_cachedCreationParams = std::move(creationParams); } UI::~UI() = default; - void UI::createMDIBuffer() + void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) { constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; From 7307a699c72d7fffd78bbccac3cbb88199b8dbda Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 26 Sep 2024 09:07:00 +0200 Subject: [PATCH 111/148] save work - UI shaders update --- include/nbl/system/ISystem.h | 1 + src/nbl/ext/ImGui/CMakeLists.txt | 1 + src/nbl/ext/ImGui/ImGui.cpp | 2 + src/nbl/ext/ImGui/shaders/common.hlsl | 62 +++++++------------------ src/nbl/ext/ImGui/shaders/fragment.hlsl | 3 ++ src/nbl/ext/ImGui/shaders/psinput.hlsl | 14 ++++++ src/nbl/ext/ImGui/shaders/vertex.hlsl | 13 +++++- src/nbl/system/ISystem.cpp | 17 +++++-- 8 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 src/nbl/ext/ImGui/shaders/psinput.hlsl diff --git a/include/nbl/system/ISystem.h b/include/nbl/system/ISystem.h index 553ae05c0e..8f73e9e1fb 100644 --- a/include/nbl/system/ISystem.h +++ b/include/nbl/system/ISystem.h @@ -146,6 +146,7 @@ class NBL_API2 ISystem : public core::IReferenceCounted } void unmountBuiltins(); + bool areBuiltinsMounted() const; // struct SystemInfo diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index 6f905f2ced..d88c2c5a32 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -32,6 +32,7 @@ get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/sr get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "psinput.hlsl") LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide external descriptor set layout + ImGUI textures & sampler state set/binding Ixs (for pipeline layout) at runtime diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 4525387fd9..4b78d0f340 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -133,6 +133,8 @@ namespace nbl::ext::imgui constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently + assert(system->areBuiltinsMounted()); //! we assume user has all Nabla builtins mounted, but we won't check it at release + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index d4472aa7f9..e0e7b39ae6 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -1,55 +1,27 @@ -#ifdef __HLSL_VERSION - struct VSInput - { - [[vk::location(0)]] float2 position : POSITION; - [[vk::location(1)]] float2 uv : TEXCOORD0; - [[vk::location(2)]] float4 color : COLOR0; - }; - - struct PSInput + +namespace nbl::hlsl +{ + struct emulated_snorm16_t2 { - float4 position : SV_Position; - float2 uv : TEXCOORD0; - float4 color : COLOR0; - uint drawID : SV_InstanceID; - float clip[4] : SV_ClipDistance; + uint32_t packed; }; +} +namespace nbl::ext::imgui +{ struct PushConstants { uint64_t elementBDA; uint64_t elementCount; - float2 scale; - float2 translate; - float4 viewport; - }; - -#else - struct PushConstants - { - uint64_t elementBDA; - uint64_t elementCount; - float scale[2]; - float translate[2]; - float viewport[4]; + nbl::hlsl::float32_t2 scale; + nbl::hlsl::float32_t2 translate; + nbl::hlsl::float32_t4 viewport; }; -#endif // __HLSL_VERSION -struct emulated_snorm16_t2 -{ - #ifdef __HLSL_VERSION - float32_t2 unpack() // returns in NDC [-1, 1] range + struct PerObjectData { - return clamp(float32_t2(x, y) / 32767.0f, -1.f, +1.f); - } - #endif - - int16_t x, y; -}; - -struct PerObjectData -{ - emulated_snorm16_t2 aabbMin, aabbMax; - uint16_t texId : 14; - uint16_t samplerIx : 2; -}; \ No newline at end of file + nbl::hlsl::emulated_snorm16_t2 aabbMin, aabbMax; + uint32_t texId : 26; + uint32_t samplerIx : 6; + }; +} diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 3073bea4e1..26e2b461a3 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -23,6 +23,9 @@ #endif #include "common.hlsl" +#include "psinput.hlsl" + +using namespace nbl::ext::imgui; [[vk::push_constant]] struct PushConstants pc; diff --git a/src/nbl/ext/ImGui/shaders/psinput.hlsl b/src/nbl/ext/ImGui/shaders/psinput.hlsl new file mode 100644 index 0000000000..0b1a5ebf0d --- /dev/null +++ b/src/nbl/ext/ImGui/shaders/psinput.hlsl @@ -0,0 +1,14 @@ + +#ifdef __HLSL_VERSION +namespace nbl::ext::imgui +{ + struct PSInput + { + float32_t4 position : SV_Position; + float32_t2 uv : TEXCOORD0; + float32_t4 color : COLOR0; + float32_t4 clip : SV_ClipDistance; + uint drawID : SV_InstanceID; + }; +} +#endif // __HLSL_VERSION \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index 18479a02db..6aa363bc39 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -1,7 +1,16 @@ +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" #include "common.hlsl" +#include "psinput.hlsl" [[vk::push_constant]] struct PushConstants pc; +struct VSInput +{ + [[vk::location(0)]] float2 position : POSITION; + [[vk::location(1)]] float2 uv : TEXCOORD0; + [[vk::location(2)]] float4 color : COLOR0; +}; + /* we use Indirect Indexed draw call to render whole GUI, note we do a cross platform trick and use base instance index as replacement for gl_DrawID @@ -20,8 +29,8 @@ PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) // NDC [-1, 1] range output.position = float4(input.position * pc.scale + pc.translate, 0, 1); - const float2 vMin = self.aabbMin.unpack(); - const float2 vMax = self.aabbMax.unpack(); + const float32_t2 vMin = nbl::hlsl::unpackSnorm2x16(self.aabbMin.packed); + const float32_t2 vMax = nbl::hlsl::unpackSnorm2x16(self.aabbMax.packed); // clip planes calculations, axis aligned output.clip[0] = output.position.x - vMin.x; diff --git a/src/nbl/system/ISystem.cpp b/src/nbl/system/ISystem.cpp index 7e86d15cb0..53cbfb6e0f 100644 --- a/src/nbl/system/ISystem.cpp +++ b/src/nbl/system/ISystem.cpp @@ -312,10 +312,11 @@ bool ISystem::ICaller::flushMapping(IFile* file, size_t offset, size_t size) return retval; } -void ISystem::unmountBuiltins() { +void ISystem::unmountBuiltins() { auto removeByKey = [&, this](const char* s) { auto range = m_cachedArchiveFiles.findRange(s); + std::vector> items_to_remove = {}; //is it always just 1 item? for (auto it = range.begin(); it != range.end(); ++it) { @@ -326,8 +327,18 @@ void ISystem::unmountBuiltins() { m_cachedArchiveFiles.removeObject(items_to_remove[i], s); } }; - removeByKey("nbl/builtin"); + + removeByKey("nbl"); removeByKey("spirv"); removeByKey("boost"); - +} + +bool ISystem::areBuiltinsMounted() const +{ + // TODO: we need to span our keys and reuse accross this cpp to not DRY + for (const auto& it : { "nbl", "spirv", "boost" }) + if (m_cachedArchiveFiles.findRange(it).empty()) + return false; + + return true; } \ No newline at end of file From c4f4470e698cd5f1573f1dbd182e817931c893aa Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 1 Oct 2024 12:48:07 +0200 Subject: [PATCH 112/148] update shaders --- src/nbl/ext/ImGui/ImGui.cpp | 14 +++++++++----- src/nbl/ext/ImGui/shaders/common.hlsl | 19 +++++++++---------- src/nbl/ext/ImGui/shaders/psinput.hlsl | 11 ++++++++--- src/nbl/ext/ImGui/shaders/vertex.hlsl | 14 +++++++------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 4b78d0f340..8fe2cac348 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -16,6 +16,7 @@ using namespace nbl::video; using namespace nbl::core; using namespace nbl::asset; using namespace nbl::ui; +using namespace nbl::hlsl; namespace nbl::ext::imgui { @@ -1165,16 +1166,19 @@ namespace nbl::ext::imgui auto packSnorm16 = [](float ndc) -> int16_t { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); + }; + + auto packToUint32 = [](uint16_t x, uint16_t y) -> uint32_t + { + return (static_cast(x) << 16) | static_cast(y); }; const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - element->aabbMin.x = packSnorm16(vMin.x); - element->aabbMin.y = packSnorm16(vMin.y); - element->aabbMax.x = packSnorm16(vMax.x); - element->aabbMax.y = packSnorm16(vMax.y); + element->aabbMin = packToUint32(packSnorm16(vMin.x), packSnorm16(vMin.y)); + element->aabbMax = packToUint32(packSnorm16(vMax.x), packSnorm16(vMax.y)); element->texId = pcmd->TextureId.textureID; element->samplerIx = pcmd->TextureId.samplerIx; } diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index e0e7b39ae6..8c2cd2159e 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -1,13 +1,10 @@ - -namespace nbl::hlsl -{ - struct emulated_snorm16_t2 - { - uint32_t packed; - }; -} +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" -namespace nbl::ext::imgui +namespace nbl +{ +namespace ext +{ +namespace imgui { struct PushConstants { @@ -20,8 +17,10 @@ namespace nbl::ext::imgui struct PerObjectData { - nbl::hlsl::emulated_snorm16_t2 aabbMin, aabbMax; + uint32_t aabbMin, aabbMax; //! snorm16_t2 packed as [uint16_t, uint16_t] uint32_t texId : 26; uint32_t samplerIx : 6; }; } +} +} diff --git a/src/nbl/ext/ImGui/shaders/psinput.hlsl b/src/nbl/ext/ImGui/shaders/psinput.hlsl index 0b1a5ebf0d..cb0fdf11e7 100644 --- a/src/nbl/ext/ImGui/shaders/psinput.hlsl +++ b/src/nbl/ext/ImGui/shaders/psinput.hlsl @@ -1,6 +1,9 @@ - #ifdef __HLSL_VERSION -namespace nbl::ext::imgui +namespace nbl +{ +namespace ext +{ +namespace imgui { struct PSInput { @@ -11,4 +14,6 @@ namespace nbl::ext::imgui uint drawID : SV_InstanceID; }; } -#endif // __HLSL_VERSION \ No newline at end of file +} +} +#endif // __HLSL_VERSION diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index 6aa363bc39..6a47cb5b5a 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -1,8 +1,7 @@ -#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" #include "common.hlsl" #include "psinput.hlsl" -[[vk::push_constant]] struct PushConstants pc; +[[vk::push_constant]] struct nbl::ext::imgui::PushConstants pc; struct VSInput { @@ -17,20 +16,21 @@ struct VSInput to request per object data with BDA */ -PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) +nbl::ext::imgui::PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) { - PSInput output; + nbl::ext::imgui::PSInput output; output.color = input.color; output.uv = input.uv; output.drawID = drawID; // BDA for requesting object data - const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); + const nbl::ext::imgui::PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(nbl::ext::imgui::PerObjectData)* drawID); // NDC [-1, 1] range output.position = float4(input.position * pc.scale + pc.translate, 0, 1); - const float32_t2 vMin = nbl::hlsl::unpackSnorm2x16(self.aabbMin.packed); - const float32_t2 vMax = nbl::hlsl::unpackSnorm2x16(self.aabbMax.packed); + + const float32_t2 vMin = nbl::hlsl::glsl::unpackSnorm2x16(self.aabbMin); + const float32_t2 vMax = nbl::hlsl::glsl::unpackSnorm2x16(self.aabbMax); // clip planes calculations, axis aligned output.clip[0] = output.position.x - vMin.x; From 791ef1ceab7814def8aef214bd80201fb4fac480 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 1 Oct 2024 13:02:12 +0200 Subject: [PATCH 113/148] fix runtime issues after shaders update --- src/nbl/ext/ImGui/ImGui.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 8fe2cac348..1747f4717e 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1166,19 +1166,20 @@ namespace nbl::ext::imgui auto packSnorm16 = [](float ndc) -> int16_t { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils }; - auto packToUint32 = [](uint16_t x, uint16_t y) -> uint32_t + const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + + struct snorm16_t2_packed { - return (static_cast(x) << 16) | static_cast(y); + int16_t x, y; }; - const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; + reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - element->aabbMin = packToUint32(packSnorm16(vMin.x), packSnorm16(vMin.y)); - element->aabbMax = packToUint32(packSnorm16(vMax.x), packSnorm16(vMax.y)); element->texId = pcmd->TextureId.textureID; element->samplerIx = pcmd->TextureId.samplerIx; } From a73014c4d039e939309e7f3ab73b3396b789c139 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 1 Oct 2024 15:02:58 +0200 Subject: [PATCH 114/148] add static mount method to the UI's extension --- include/nbl/ext/ImGui/ImGui.h | 3 +++ src/nbl/ext/ImGui/ImGui.cpp | 27 ++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index aff306cee9..12c1c99ca6 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -151,6 +151,9 @@ class UI final : public core::IReferenceCounted //! creates default pipeline layout for the UI resources, "texturesCount" argument is textures descriptor binding's array size. Samplers are immutable and part of the created layout, SResourceParameters::DefaultSamplerIx::COUNT is the size of the samplers descriptor binding's array static core::smart_refctd_ptr createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount = 0x45); + //! mounts the extension's archive to given system - useful if you want to create your own shaders with common header included + static const core::smart_refctd_ptr mount(core::smart_refctd_ptr logger, system::ISystem* system, const std::string_view archiveAlias = ""); + //! creation cached parametrs inline const SCachedCreationParams& getCreationParameters() const { return m_cachedCreationParams; } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1747f4717e..f5a1909319 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -115,6 +115,19 @@ namespace nbl::ext::imgui return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); } + const core::smart_refctd_ptr UI::mount(core::smart_refctd_ptr logger, system::ISystem* system, const std::string_view archiveAlias) + { + assert(system); + + if(!system) + return nullptr; + + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); + system->mount(smart_refctd_ptr(archive), archiveAlias.data()); + + return smart_refctd_ptr(archive); + } + void UI::createPipeline(SCreationParameters& creationParams) { auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); @@ -133,11 +146,11 @@ namespace nbl::ext::imgui { constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; - auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently - assert(system->areBuiltinsMounted()); //! we assume user has all Nabla builtins mounted, but we won't check it at release + //! proxy the system, we will touch it gently + auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); - auto archive = make_smart_refctd_ptr(smart_refctd_ptr(creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive - auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeLoader = includeFinder->getDefaultFileSystemLoader(); includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); @@ -219,7 +232,11 @@ namespace nbl::ext::imgui return gpu; }; - system->mount(smart_refctd_ptr(archive), NBL_ARCHIVE_ALIAS.data()); + //! we assume user has all Nabla builtins mounted - we don't check it at release + assert(system->areBuiltinsMounted()); + + //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it + auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); From 977aac00ec4b593538afeff06812d28cad75516b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 1 Oct 2024 15:16:56 +0200 Subject: [PATCH 115/148] add viewportCount to creation parameters + no more nbl:: in the UI's cpp - using namespace! --- include/nbl/ext/ImGui/ImGui.h | 3 + src/nbl/ext/ImGui/ImGui.cpp | 128 +++++++++++++++++----------------- 2 files changed, 68 insertions(+), 63 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 12c1c99ca6..3f8eb35b39 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -91,6 +91,9 @@ class UI final : public core::IReferenceCounted //! optional, default MDI buffer allocated if not provided core::smart_refctd_ptr streamingBuffer = nullptr; + + //! optional, default single one + uint32_t viewportCount = 1u; }; struct SCreationParameters : public SCachedCreationParams diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index f5a1909319..88c31e7a7f 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -15,6 +15,7 @@ using namespace nbl::video; using namespace nbl::core; using namespace nbl::asset; +using namespace nbl::system; using namespace nbl::ui; using namespace nbl::hlsl; @@ -38,7 +39,7 @@ namespace nbl::ext::imgui } }; - core::smart_refctd_ptr UI::createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) + smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) { if (!utilities) return nullptr; @@ -76,7 +77,7 @@ namespace nbl::ext::imgui } //! note we use immutable separate samplers and they are part of the descriptor set layout - std::array, (uint32_t)DefaultSamplerIx::COUNT> immutableSamplers; + std::array, (uint32_t)DefaultSamplerIx::COUNT> immutableSamplers; immutableSamplers[(uint32_t)DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); immutableSamplers[(uint32_t)DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); @@ -115,14 +116,14 @@ namespace nbl::ext::imgui return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); } - const core::smart_refctd_ptr UI::mount(core::smart_refctd_ptr logger, system::ISystem* system, const std::string_view archiveAlias) + const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) { assert(system); if(!system) return nullptr; - auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); system->mount(smart_refctd_ptr(archive), archiveAlias.data()); return smart_refctd_ptr(archive); @@ -134,20 +135,20 @@ namespace nbl::ext::imgui if (!pipelineLayout) { - creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create pipeline layout!", ILogger::ELL_ERROR); assert(false); } struct { - core::smart_refctd_ptr vertex, fragment; + smart_refctd_ptr vertex, fragment; } shaders; { constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; //! proxy the system, we will touch it gently - auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); + auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); @@ -155,9 +156,9 @@ namespace nbl::ext::imgui auto includeLoader = includeFinder->getDefaultFileSystemLoader(); includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); - auto createShader = [&]() -> core::smart_refctd_ptr + auto createShader = [&]() -> smart_refctd_ptr { - asset::IAssetLoader::SAssetLoadParams params = {}; + IAssetLoader::SAssetLoadParams params = {}; params.logger = creationParams.utilities->getLogger(); params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); @@ -166,11 +167,11 @@ namespace nbl::ext::imgui if (assets.empty()) { - creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", ILogger::ELL_ERROR, key.value); return nullptr; } - const auto shader = IAsset::castDown(assets[0]); + const auto shader = IAsset::castDown(assets[0]); CHLSLCompiler::SOptions options = {}; options.stage = stage; @@ -178,7 +179,7 @@ namespace nbl::ext::imgui options.preprocessorOptions.logger = creationParams.utilities->getLogger(); options.preprocessorOptions.includeFinder = includeFinder.get(); - auto compileToSPIRV = [&]() -> core::smart_refctd_ptr + auto compileToSPIRV = [&]() -> smart_refctd_ptr { #define NBL_DEFAULT_OPTIONS "-spirv", "-Zpr", "-enable-16bit-types", "-fvk-use-scalar-layout", "-Wno-c++11-extensions", "-Wno-c++1z-extensions", "-Wno-c++14-extensions", "-Wno-gnu-static-float-init", "-fspv-target-env=vulkan1.3", "-HV", "202x" /* default required params, just to not throw warnings */ const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); @@ -220,14 +221,14 @@ namespace nbl::ext::imgui if (!spirv) { - creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", ILogger::ELL_ERROR, key.value); return nullptr; } auto gpu = creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); if (!gpu) - creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", system::ILogger::ELL_ERROR, key.value); + creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", ILogger::ELL_ERROR, key.value); return gpu; }; @@ -236,7 +237,7 @@ namespace nbl::ext::imgui assert(system->areBuiltinsMounted()); //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it - auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); + auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); @@ -250,7 +251,7 @@ namespace nbl::ext::imgui vertexInputParams.enabledBindingFlags = 0b1u; vertexInputParams.enabledAttribFlags = 0b111u; - vertexInputParams.bindings[0].inputRate = asset::SVertexInputBindingParams::EVIR_PER_VERTEX; + vertexInputParams.bindings[0].inputRate = SVertexInputBindingParams::EVIR_PER_VERTEX; vertexInputParams.bindings[0].stride = sizeof(ImDrawVert); auto& position = vertexInputParams.attributes[0]; @@ -294,6 +295,7 @@ namespace nbl::ext::imgui rasterizationParams.faceCullingMode = EFCM_NONE; rasterizationParams.depthWriteEnable = false; rasterizationParams.depthBoundsTestEnable = false; + rasterizationParams.viewportCount = creationParams.viewportCount; } SPrimitiveAssemblyParams primitiveAssemblyParams{}; @@ -319,13 +321,13 @@ namespace nbl::ext::imgui if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &m_pipeline)) { - creationParams.utilities->getLogger()->log("Could not create pipeline!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create pipeline!", ILogger::ELL_ERROR); assert(false); } } } - ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) + ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -358,7 +360,7 @@ namespace nbl::ext::imgui const size_t componentsCount = 4, image_size = width * height * componentsCount * sizeof(uint8_t); _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; - const auto buffer = core::make_smart_refctd_ptr< asset::CCustomAllocatorCPUBuffer, true> >(image_size, pixels, core::adopt_memory); + const auto buffer = make_smart_refctd_ptr< CCustomAllocatorCPUBuffer, true> >(image_size, pixels, adopt_memory); IGPUImage::SCreationParams params; params.flags = static_cast(0u); @@ -399,14 +401,14 @@ namespace nbl::ext::imgui if (!image) { - creationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create font image!", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla ImGUI default font"); if (!creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { - creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -417,7 +419,7 @@ namespace nbl::ext::imgui auto scratchSemaphore = creationParams.utilities->getLogicalDevice()->createSemaphore(0); if (!scratchSemaphore) { - creationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create scratch semaphore", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); @@ -433,7 +435,7 @@ namespace nbl::ext::imgui }; // we have no explicit source stage and access to sync against, brand new clean image. - const asset::SMemoryBarrier toTransferDep = { + const SMemoryBarrier toTransferDep = { .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, }; @@ -456,7 +458,7 @@ namespace nbl::ext::imgui // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - creationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not upload font image contents", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -471,7 +473,7 @@ namespace nbl::ext::imgui const auto submit = sInfo.popSubmit({}); if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -481,7 +483,7 @@ namespace nbl::ext::imgui params.format = image->getCreationParameters().format; params.viewType = IImageView::ET_2D; params.subresourceRange = regions.subresource; - params.image = core::smart_refctd_ptr(image); + params.image = smart_refctd_ptr(image); m_fontAtlasTexture = creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } @@ -523,7 +525,7 @@ namespace nbl::ext::imgui case SMouseEvent::EET_SCROLL: { _NBL_STATIC_INLINE_CONSTEXPR auto scalar = 0.02f; - const auto wheel = nbl::hlsl::float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; + const auto wheel = float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; io.AddMouseWheelEvent(wheel.x, wheel.y); } break; @@ -687,7 +689,7 @@ namespace nbl::ext::imgui const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; if(bind.target == ImGuiKey_None) - m_cachedCreationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), ILogger::ELL_ERROR); else if (e.action == SKeyboardEvent::ECA_PRESSED) { @@ -719,7 +721,7 @@ namespace nbl::ext::imgui if (!ok && logError) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); return false; } @@ -728,7 +730,7 @@ namespace nbl::ext::imgui if(!descriptorSetLayout) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); return false; } @@ -765,7 +767,7 @@ namespace nbl::ext::imgui if(!count) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -778,7 +780,7 @@ namespace nbl::ext::imgui if(!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -786,7 +788,7 @@ namespace nbl::ext::imgui if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -797,7 +799,7 @@ namespace nbl::ext::imgui if (!ok) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } @@ -831,24 +833,24 @@ namespace nbl::ext::imgui for (const auto& [ok, error] : validation) if (!ok) { - creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); assert(false); } - smart_refctd_ptr transistentCMD; + smart_refctd_ptr transistentCMD; { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { - creationParams.utilities->getLogger()->log("Could not create command pool!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create command pool!", ILogger::ELL_ERROR); assert(false); } if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) { - creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", ILogger::ELL_ERROR); assert(false); } } @@ -873,9 +875,9 @@ namespace nbl::ext::imgui { constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; - auto getRequiredAccessFlags = [&](const core::bitflag& properties) + auto getRequiredAccessFlags = [&](const bitflag& properties) { - core::bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); + bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) flags |= IDeviceMemoryAllocation::EMCAF_READ; @@ -886,7 +888,7 @@ namespace nbl::ext::imgui }; if (m_cachedCreationParams.streamingBuffer) - m_mdi.compose = core::smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); + m_mdi.compose = smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); else { IGPUBuffer::SCreationParams mdiCreationParams = {}; @@ -907,9 +909,9 @@ namespace nbl::ext::imgui auto memory = allocation.memory; if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) - m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); - m_mdi.compose = core::make_smart_refctd_ptr(asset::SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_mdi.compose = make_smart_refctd_ptr(SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); } auto buffer = m_mdi.compose->getBuffer(); @@ -927,22 +929,22 @@ namespace nbl::ext::imgui for (const auto& [ok, error] : validation) if (!ok) { - m_cachedCreationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); assert(false); } } - bool UI::render(nbl::video::IGPUCommandBuffer* const commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) + bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::span scissors) { if (!commandBuffer) { - m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", ILogger::ELL_ERROR); return false; } if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) { - m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", ILogger::ELL_ERROR); return false; } @@ -952,7 +954,7 @@ namespace nbl::ext::imgui if (!io.Fonts->IsBuilt()) { - m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", ILogger::ELL_ERROR); return false; } @@ -1008,10 +1010,10 @@ namespace nbl::ext::imgui struct TRS { - core::vector2df_SIMD scale; - core::vector2df_SIMD translate; + vector2df_SIMD scale; + vector2df_SIMD translate; - core::vector2df_SIMD toNDC(core::vector2df_SIMD in) const + vector2df_SIMD toNDC(vector2df_SIMD in) const { return in * scale + translate; } @@ -1021,8 +1023,8 @@ namespace nbl::ext::imgui { TRS retV; - retV.scale = core::vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; - retV.translate = core::vector2df_SIMD { -1.0f, -1.0f } - core::vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; + retV.scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; + retV.translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; return std::move(retV); }(); @@ -1183,11 +1185,11 @@ namespace nbl::ext::imgui auto packSnorm16 = [](float ndc) -> int16_t { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils }; - const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); struct snorm16_t2_packed { @@ -1235,7 +1237,7 @@ namespace nbl::ext::imgui auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); const auto offset = mdiBuffer->getBoundMemory().offset; { - const asset::SBufferBinding binding = + const SBufferBinding binding = { .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDEX_BUFFERS)], .buffer = smart_refctd_ptr(mdiBuffer) @@ -1243,13 +1245,13 @@ namespace nbl::ext::imgui if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", ILogger::ELL_ERROR); assert(false); } } { - const asset::SBufferBinding bindings[] = + const SBufferBinding bindings[] = {{ .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::VERTEX_BUFFERS)], .buffer = smart_refctd_ptr(mdiBuffer) @@ -1257,7 +1259,7 @@ namespace nbl::ext::imgui if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", ILogger::ELL_ERROR); assert(false); } } @@ -1301,10 +1303,10 @@ namespace nbl::ext::imgui commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); } - const asset::SBufferBinding binding = + const SBufferBinding binding = { .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)], - .buffer = core::smart_refctd_ptr(mdiBuffer) + .buffer = smart_refctd_ptr(mdiBuffer) }; commandBuffer->drawIndexedIndirect(binding, mdiParams.drawCount, sizeof(VkDrawIndexedIndirectCommand)); From 8604014536e38f79eb8a091c489c1353e7029a27 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 1 Oct 2024 15:34:40 +0200 Subject: [PATCH 116/148] update default MDI buffer creation constants, add validation for UI's render call - check if renderpass instance is open by requesting cmd's cached inheritance subpass info --- src/nbl/ext/ImGui/ImGui.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 88c31e7a7f..c41b9c9226 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -873,7 +873,7 @@ namespace nbl::ext::imgui void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) { - constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; + constexpr static uint32_t minStreamingBufferAllocationSize = 128u, maxStreamingBufferAllocationAlignment = 4096u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; auto getRequiredAccessFlags = [&](const bitflag& properties) { @@ -948,6 +948,17 @@ namespace nbl::ext::imgui return false; } + { + const auto info = commandBuffer->getCachedInheritanceInfo(); + const bool recordingSubpass = info.subpass != IGPURenderpass::SCreationParams::SSubpassDependency::External; + + if(!recordingSubpass) + { + m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not recording a subpass!", ILogger::ELL_ERROR); + return false; + } + } + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() ImGuiIO& io = ImGui::GetIO(); From 3b61167415730f549a84d8419e5b743ee350e142 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 5 Oct 2024 12:24:04 +0200 Subject: [PATCH 117/148] fix CHLSLCompiler - move RequiredArguments into private section and expose public getter. Update sources accordingly, explain in the comment why you cannot keep such data symbols in public section. Add missing L"-HV", L"202x" arguments to RequiredArguments --- include/nbl/asset/utils/CHLSLCompiler.h | 34 ++++++++++++++++--------- src/nbl/asset/utils/CHLSLCompiler.cpp | 17 ++++++++----- src/nbl/ext/ImGui/ImGui.cpp | 23 ++++++++++++++--- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/include/nbl/asset/utils/CHLSLCompiler.h b/include/nbl/asset/utils/CHLSLCompiler.h index 395c528ff6..75c3e87c8c 100644 --- a/include/nbl/asset/utils/CHLSLCompiler.h +++ b/include/nbl/asset/utils/CHLSLCompiler.h @@ -54,18 +54,11 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler std::string preprocessShader(std::string&& code, IShader::E_SHADER_STAGE& stage, const SPreprocessorOptions& preprocessOptions, std::vector& dxc_compile_flags_override, std::vector* dependencies = nullptr) const; void insertIntoStart(std::string& code, std::ostringstream&& ins) const override; - constexpr static inline const wchar_t* RequiredArguments[] = { // TODO: and if dxcOptions is span of std::string then why w_chars there? https://en.cppreference.com/w/cpp/string/basic_string - L"-spirv", - L"-Zpr", - L"-enable-16bit-types", - L"-fvk-use-scalar-layout", - L"-Wno-c++11-extensions", - L"-Wno-c++1z-extensions", - L"-Wno-c++14-extensions", - L"-Wno-gnu-static-float-init", - L"-fspv-target-env=vulkan1.3" - }; - constexpr static inline uint32_t RequiredArgumentCount = sizeof(RequiredArguments) / sizeof(RequiredArguments[0]); + + static constexpr auto getRequiredArguments() //! returns required arguments for the compiler's backend + { + return std::span(RequiredArguments); + } protected: // This can't be a unique_ptr due to it being an undefined type @@ -81,6 +74,23 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler ret.setCommonData(options); return ret; } + + private: + // we cannot have PUBLIC data symbol in header we do export - endpoint application will fail on linker with delayed DLL loading mechanism (thats why we trick it with private member hidden from the export + provide exported getter) + // https://learn.microsoft.com/en-us/previous-versions/w59k653y(v=vs.100)?redirectedfrom=MSDN + constexpr static inline auto RequiredArguments = std::to_array // TODO: and if dxcOptions is span of std::string then why w_chars there? https://en.cppreference.com/w/cpp/string/basic_string + ({ + L"-spirv", + L"-Zpr", + L"-enable-16bit-types", + L"-fvk-use-scalar-layout", + L"-Wno-c++11-extensions", + L"-Wno-c++1z-extensions", + L"-Wno-c++14-extensions", + L"-Wno-gnu-static-float-init", + L"-fspv-target-env=vulkan1.3", + L"-HV", L"202x" + }); }; } diff --git a/src/nbl/asset/utils/CHLSLCompiler.cpp b/src/nbl/asset/utils/CHLSLCompiler.cpp index ca0af41baf..594441bd8c 100644 --- a/src/nbl/asset/utils/CHLSLCompiler.cpp +++ b/src/nbl/asset/utils/CHLSLCompiler.cpp @@ -211,12 +211,15 @@ static void add_required_arguments_if_not_present(std::vector& arg auto set = std::unordered_set(); for (int i = 0; i < arguments.size(); i++) set.insert(arguments[i]); - for (int j = 0; j < CHLSLCompiler::RequiredArgumentCount; j++) + + const auto required = CHLSLCompiler::getRequiredArguments(); + + for (int j = 0; j < required.size(); j++) { - bool missing = set.find(CHLSLCompiler::RequiredArguments[j]) == set.end(); + bool missing = set.find(required[j]) == set.end(); if (missing) { - logger.log("Compile flag error: Required compile flag not found %ls. This flag will be force enabled, as it is required by Nabla.", system::ILogger::ELL_WARNING, CHLSLCompiler::RequiredArguments[j]); - arguments.push_back(CHLSLCompiler::RequiredArguments[j]); + logger.log("Compile flag error: Required compile flag not found %ls. This flag will be force enabled, as it is required by Nabla.", system::ILogger::ELL_WARNING, required[j]); + arguments.push_back(required[j]); } } } @@ -397,9 +400,10 @@ core::smart_refctd_ptr CHLSLCompiler::compileToSPIRV_impl(const std: populate_arguments_with_type_conversion(arguments, hlslOptions.dxcOptions, logger); } else { // lastly default arguments + const auto required = CHLSLCompiler::getRequiredArguments(); arguments = {}; - for (size_t i = 0; i < RequiredArgumentCount; i++) - arguments.push_back(RequiredArguments[i]); + for (size_t i = 0; i < required.size(); i++) + arguments.push_back(required[i]); arguments.push_back(L"-HV"); arguments.push_back(L"202x"); // TODO: add this to `CHLSLCompiler::SOptions` and handle it properly in `dxc_compile_flags.empty()` @@ -479,5 +483,4 @@ void CHLSLCompiler::insertIntoStart(std::string& code, std::ostringstream&& ins) code.insert(0u, ins.str()); } - #endif \ No newline at end of file diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index c41b9c9226..58d6832424 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" @@ -181,19 +183,34 @@ namespace nbl::ext::imgui auto compileToSPIRV = [&]() -> smart_refctd_ptr { - #define NBL_DEFAULT_OPTIONS "-spirv", "-Zpr", "-enable-16bit-types", "-fvk-use-scalar-layout", "-Wno-c++11-extensions", "-Wno-c++1z-extensions", "-Wno-c++14-extensions", "-Wno-gnu-static-float-init", "-fspv-target-env=vulkan1.3", "-HV", "202x" /* default required params, just to not throw warnings */ + auto toOptions = [](const std::array& in) // options must be alive till compileToSPIRV ends + { + const auto required = CHLSLCompiler::getRequiredArguments(); + std::array options; + + std::wstring_convert> converter; + for (uint32_t i = 0; i < required.size(); ++i) + options[i] = converter.to_bytes(required[i]); // meh + + uint32_t offset = required.size(); + for (const auto& opt : in) + options[offset++] = std::string(opt); + + return options; + }; + const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); if constexpr (stage == IShader::E_SHADER_STAGE::ESS_VERTEX) { - const auto VERTEX_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "vs_6_7", "-E", "VSMain", "-O3"}); + const auto VERTEX_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "vs_6_7", "-E", "VSMain", "-O3" })); options.dxcOptions = VERTEX_COMPILE_OPTIONS; return compiler->compileToSPIRV(code.data(), options); // we good here - no code patching } else if (stage == IShader::E_SHADER_STAGE::ESS_FRAGMENT) { - const auto FRAGMENT_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "ps_6_7", "-E", "PSMain", "-O3"}); + const auto FRAGMENT_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "ps_6_7", "-E", "PSMain", "-O3" })); options.dxcOptions = FRAGMENT_COMPILE_OPTIONS; std::stringstream stream; From 86d9f20bd9da6398b70e9b5e20882ab54aeccdb1 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 5 Oct 2024 23:31:37 +0200 Subject: [PATCH 118/148] play with offsets & allow for null indirect invocation in case of missing content (allocation/suballocation failed) - bind mdi buffer with 0u offset and operate on indirect structure offsets (second attempt) to access vertex & index buffers, update suballocation strategy. TODO: lower big chunk request allocation size on fail + eliminate runtime artifacts --- include/nbl/ext/ImGui/ImGui.h | 12 +- src/nbl/ext/ImGui/ImGui.cpp | 306 ++++++++++++++++++---------------- 2 files changed, 164 insertions(+), 154 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 3f8eb35b39..d01ec69c87 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -29,16 +29,6 @@ class UI final : public core::IReferenceCounted //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request using suballocator_traits_t = core::address_allocator_traits>; - enum class Content : uint16_t - { - INDIRECT_STRUCTURES, - ELEMENT_STRUCTURES, - INDEX_BUFFERS, - VERTEX_BUFFERS, - - COUNT, - }; - //! streaming mdi buffer core::smart_refctd_ptr compose; @@ -140,7 +130,7 @@ class UI final : public core::IReferenceCounted bool update(const SUpdateParameters& params); //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources - bool render(video::IGPUCommandBuffer* const commandBuffer, video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); + bool render(video::IGPUCommandBuffer* const commandBuffer, video::ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint = std::chrono::steady_clock::now() + std::chrono::milliseconds(1u), const std::span scissors = {}); //! registers lambda listener in which ImGUI calls should be recorded, use the returned id to unregister the listener size_t registerListener(std::function const& listener); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 58d6832424..2744323b9c 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -28,10 +28,18 @@ namespace nbl::ext::imgui using mdi_size_t = compose_t::size_type; static constexpr auto InvalidAddress = compose_t::invalid_value; - static constexpr auto MdiComponentCount = (uint32_t)UI::SMdiBuffer::Content::COUNT; static constexpr auto MdiAlignments = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); static constexpr auto MdiMaxAlignment = *std::max_element(MdiAlignments.begin(), MdiAlignments.end()); + // those must be allocated within single block for content - even though its possible to tell vkCmdDrawIndexedIndirect about stride we cannot guarantee each one will be the same size with our allocation strategy + enum class TightContent : uint16_t + { + INDIRECT_STRUCTURES, + ELEMENT_STRUCTURES, + + COUNT, + }; + static constexpr SPushConstantRange PushConstantRanges[] = { { @@ -951,7 +959,7 @@ namespace nbl::ext::imgui } } - bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::span scissors) + bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint, const std::span scissors) { if (!commandBuffer) { @@ -1057,42 +1065,74 @@ namespace nbl::ext::imgui return std::move(retV); }(); - struct MDI_PARAMS + const auto totalIndirectDrawCount = [&]() { - std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements - mdi_size_t totalByteSizeRequest = {}, //! sum of bytesToFill - drawCount = {}; //! amount of objects to draw for indirect call request - }; + uint32_t count = {}; - /* - TODO: first try to alloc params.drawCount, then on fail divide requests by 2, if we cannot allocate minimum 1 draw list we must ret false - */ + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + { + const ImDrawList* commandList = drawData->CmdLists[i]; + count += commandList->CmdBuffer.Size; + } - const MDI_PARAMS mdiParams = [&]() + return count; + }(); + + //! we compose a struct of offsets for our mdi streaming iteration + struct SAllocationParams + { + //! for a mdi streaming buffer iteration we compose offsets for which + //! first slot is reserved for TightContent::INDIRECT_STRUCTURES, + //! second for TightContent::ELEMENT_STRUCTURES + //! and left slots are for indirect object buffers ([vertex, index] .. [vertex, index]) + std::vector offsets; + std::vector filled; + } allocation; + + struct SMdiLimits { - MDI_PARAMS params; + //! sum of allocation.sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects + mdi_size_t totalByteSizeRequest = {}, + + //! amount of total objects to draw + totalIndirectDrawCount = {}; + + //! corresponding allocation sizes & alignments for allocation.offsets - index shader between offset & size (we don't std::pair (and similar) those on purpose - need them separate in continous memory each to align nicely with our allocator api) + std::vector sizes, alignments; + }; + + const SMdiLimits mdiLimits = [&]() + { + SMdiLimits limits; + + limits.sizes.emplace_back() = {}; allocation.offsets.emplace_back() = InvalidAddress; limits.alignments.emplace_back() = alignof(VkDrawIndexedIndirectCommand); allocation.filled.emplace_back() = false; + limits.sizes.emplace_back() = {}; allocation.offsets.emplace_back() = InvalidAddress; limits.alignments.emplace_back() = alignof(PerObjectData); allocation.filled.emplace_back() = false; for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { const ImDrawList* commandList = drawData->CmdLists[i]; - params.bytesToFill[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); - params.bytesToFill[static_cast>(SMdiBuffer::Content::ELEMENT_STRUCTURES)] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; + + allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); limits.alignments.emplace_back() = alignof(ImDrawVert); allocation.filled.emplace_back() = false; + allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); limits.alignments.emplace_back() = alignof(ImDrawIdx); allocation.filled.emplace_back() = false; } - params.bytesToFill[static_cast>(SMdiBuffer::Content::VERTEX_BUFFERS)] = drawData->TotalVtxCount * sizeof(ImDrawVert); - params.bytesToFill[static_cast>(SMdiBuffer::Content::INDEX_BUFFERS)] = drawData->TotalIdxCount * sizeof(ImDrawIdx); - // calculate upper bound byte size limit for mdi buffer - params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); - params.drawCount = params.bytesToFill[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)] / sizeof(VkDrawIndexedIndirectCommand); + limits.totalByteSizeRequest += limits.sizes[(uint32_t)TightContent::INDIRECT_STRUCTURES] = (limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand)); + limits.totalByteSizeRequest += limits.sizes[(uint32_t)TightContent::ELEMENT_STRUCTURES] = (limits.totalIndirectDrawCount * sizeof(PerObjectData)); + limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); + limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); + + assert([&]() -> bool // we should never hit it + { + return (allocation.offsets.size() == limits.sizes.size()) + && (allocation.offsets.size() == allocation.filled.size()) + && (std::reduce(std::begin(limits.sizes), std::end(limits.sizes)) == limits.totalByteSizeRequest) + && (std::all_of(std::cbegin(allocation.offsets), std::cend(allocation.offsets), [](const auto& offset) { return offset == InvalidAddress; })); + }()); // debug check only - return std::move(params); + return limits; }(); - std::array mdiBytesFilled; - std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MdiComponentCount, false); - std::array mdiOffsets; - std::fill(mdiOffsets.data(), mdiOffsets.data() + MdiComponentCount, InvalidAddress); - auto streamingBuffer = m_mdi.compose; { auto binding = streamingBuffer->getBuffer()->getBoundMemory(); @@ -1102,113 +1142,99 @@ namespace nbl::ext::imgui struct { - mdi_size_t offset = InvalidAddress, - multiAllocationSize = {}; - } requestState; - - const auto start = std::chrono::steady_clock::now(); + mdi_size_t offset, size; + } bigChunkRequestState; - //! we must upload entire MDI buffer data to our streaming buffer, but we cannot guarantee allocation can be done in single request - for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) + //! we will try to upload entrie MDI buffer with all available indirect data to our streaming buffer, but we cannot guarantee the allocation can be done in single request nor we allocate all totalIndirectDrawCount at all - we can hit timeout and it may appear not all of totalIndirectDrawCount will be uploaded then + for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiLimits.totalByteSizeRequest;) { - auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + bigChunkRequestState.offset = InvalidAddress; + bigChunkRequestState.size = streamingBuffer->max_size(); // request available block memory size which we can try to allocate - if (elapsed.count() >= 1u) - { - streamingBuffer->cull_frees(); - return false; - } - - requestState.offset = InvalidAddress; - requestState.multiAllocationSize = streamingBuffer->max_size(); // request available block memory size which we can try to allocate - - static constexpr auto STREAMING_ALLOCATION_COUNT = 1u; - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with max alignment instead of MDI::E_BUFFER_CONTENT separate chunks + constexpr auto StreamingAllocationCount = 1u; + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data - if (requestState.offset == InvalidAddress) - continue; // failed? lets try again, TODO: should I here have my "blockMemoryFactor =* 0.5" and apply to requestState.multiAllocationSize? + if (bigChunkRequestState.offset == InvalidAddress) + continue; // failed? lets try again, TODO: should I here have my "blockMemoryFactor =* 0.5" and apply to bigChunkRequestState.size? else { - static constexpr auto ALIGN_OFFSET_NEEDED = 0u; - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MdiMaxAlignment, requestState.multiAllocationSize); //! (*) we create linear suballocator to fill the chunk memory (some of at least) with MDI::E_BUFFER_CONTENT data + constexpr auto AlignOffsetNeeded = 0u; + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, MdiMaxAlignment, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) + SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call - std::array offsets; - std::fill(offsets.data(), offsets.data() + MdiComponentCount, InvalidAddress); - SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, offsets.size(), offsets.data(), mdiParams.bytesToFill.data(), MdiAlignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignment per MDI::E_BUFFER_CONTENT block - - //! linear allocator is used to fill the mdi data within suballocation memory range, - //! there are a few restrictions regarding how SMdiBuffer::Content(s) can be packed, - //! and we have really 2 options how data can be allocated: - //! - tightly packed data in single request covering all SMdiBuffer::Content within the allocated memory range - //! - each of SMdiBuffer::Content allocated separately & each tightly packed - - auto fillDrawBuffers = [&]() + auto upload = [&]() -> size_t { - const auto underlying_type = static_cast>(type); - const mdi_size_t globalBlockOffset = offsets[underlying_type]; + size_t uploaded = {}; - if (globalBlockOffset == InvalidAddress or mdiBytesFilled[underlying_type]) - return 0u; + // they are very small & negligible in size compared to buffers, but this small pool which we will conditionally fill on successfull object buffer suballocations is required to not complicate things (if we cannot allocate all mdiLimits.totalIndirectDrawCount object buffers then simply those coresponding structures wont be filled, we treat both components as arrays) + const bool structuresSuballocated = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES] != InvalidAddress && allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES] != InvalidAddress; - auto* data = mdiData + globalBlockOffset; - - for (int n = 0; n < drawData->CmdListsCount; n++) + if (structuresSuballocated) // note that suballocated only means we have valid address(es) we can work on, it doesn't mean we filled anything { - auto* cmd_list = drawData->CmdLists[n]; - - if constexpr (type == SMdiBuffer::Content::INDEX_BUFFERS) + auto* const indirectStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES]); + auto* const elementStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]); { - const auto blockStrideToFill = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); - ::memcpy(data, cmd_list->IdxBuffer.Data, blockStrideToFill); - data += blockStrideToFill; - } - else if (type == SMdiBuffer::Content::VERTEX_BUFFERS) - { - const auto blockStrideToFill = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); - ::memcpy(data, cmd_list->VtxBuffer.Data, blockStrideToFill); - data += blockStrideToFill; - } - } - - mdiBytesFilled[underlying_type] = true; - mdiOffsets[underlying_type] = globalBlockOffset; - return mdiParams.bytesToFill[underlying_type]; - }; - - auto fillIndirectStructures = [&]() - { - const auto underlying_type = static_cast>(type); - const mdi_size_t globalBlockOffset = offsets[underlying_type]; - - if (globalBlockOffset == InvalidAddress or mdiBytesFilled[underlying_type]) - return 0u; - - auto* const data = mdiData + globalBlockOffset; + if (!allocation.filled[(uint32_t)TightContent::INDIRECT_STRUCTURES]) + { + uploaded += mdiLimits.sizes[(uint32_t)TightContent::INDIRECT_STRUCTURES]; + allocation.filled[(uint32_t)TightContent::INDIRECT_STRUCTURES] = true; // I make a assumption here since I can access them later - but I don't guarantee all of them will be present since we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally + } - size_t cmdListIndexObjectOffset = {}, cmdListVertexObjectOffset = {}, drawID = {}; + if (!allocation.filled[(uint32_t)TightContent::ELEMENT_STRUCTURES]) + { + uploaded += mdiLimits.sizes[(uint32_t)TightContent::ELEMENT_STRUCTURES]; + allocation.filled[(uint32_t)TightContent::ELEMENT_STRUCTURES] = true; // I make a assumption here since I can access them later - but I don't guarantee all of them will be present since we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally + } + } - for (int n = 0; n < drawData->CmdListsCount; n++) - { - const auto* cmd_list = drawData->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + uint32_t drawID = {}; + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { - const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; + const auto* commandList = drawData->CmdLists[i]; + const auto& [vertexBuffer, indexBuffer] = std::make_tuple(commandList->VtxBuffer, commandList->IdxBuffer); + const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple((uint32_t)TightContent::COUNT + 2u * i + 0u, (uint32_t)TightContent::COUNT + 2u * i + 1u); // look at SAllocationParams::offsets if you dont remember why like this - if constexpr (type == SMdiBuffer::Content::INDIRECT_STRUCTURES) + auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) + { + auto& offset = allocation.offsets[allocationIx]; const auto& bytesToFill = mdiLimits.sizes[allocationIx]; + + if (offset == InvalidAddress) + return false; + else + if (!allocation.filled[allocationIx]) + { + ::memcpy(mdiData + offset, in, bytesToFill); + uploaded += bytesToFill; + allocation.filled[allocationIx] = true; + } + + return true; + }; + + // we consider buffers valid if we suballocated them (under the hood filled) + const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); + const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(allocation.offsets[vtxAllocationIx] / sizeof(ImDrawVert), allocation.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); + + for (uint32_t j = 0; j < commandList->CmdBuffer.Size; j++) { - auto* indirect = reinterpret_cast(data) + drawID; + const auto* cmd = &commandList->CmdBuffer[j]; + auto* indirect = indirectStructures + drawID; + auto* element = elementStructures + drawID; - indirect->firstInstance = drawID; // use base instance as draw ID - indirect->indexCount = pcmd->ElemCount; + // we will make a trick to keep indirect & element structs in the mdi iteration but explicitly execute dummy null invocation if we don't have vertex or index buffer for the struct (suballocation failed for any of those 2 buffers). + // TODO: we could make the current structs pool "dynamic" in size and treat as simple stack instead (trying it first to make things easier) + indirect->indexCount = buffersSuballocated ? cmd->ElemCount /* valid invocation */ : 0u /* null invocation */; + + indirect->firstInstance = drawID; // we use base instance as draw ID indirect->instanceCount = 1u; - indirect->firstIndex = pcmd->IdxOffset + cmdListIndexObjectOffset; - indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexObjectOffset; - } - else if (type == SMdiBuffer::Content::ELEMENT_STRUCTURES) - { - auto* element = reinterpret_cast(data) + drawID; - const auto clipRectangle = clip.getClipRectangle(pcmd); + // starting to wonder, for some reason imgui decided to keep single vertex & index shared between cmds within cmd list + // but maybe we should cut current [vertexBuffer, indexBuffer] with respect to cmd->IdxOffset & cmd->VtxOffset (therefore we could have even smaller alloc requests, now a few structs can point to the same buffer but with different offsets [indirect]) + // though not sure if I don't double some data then + indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated + indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated + + const auto clipRectangle = clip.getClipRectangle(cmd); const auto scissor = clip.getScissor(clipRectangle); auto packSnorm16 = [](float ndc) -> int16_t @@ -1227,47 +1253,41 @@ namespace nbl::ext::imgui reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - element->texId = pcmd->TextureId.textureID; - element->samplerIx = pcmd->TextureId.samplerIx; - } + element->texId = cmd->TextureId.textureID; + element->samplerIx = cmd->TextureId.samplerIx; - ++drawID; + ++drawID; + } } - - cmdListIndexObjectOffset += cmd_list->IdxBuffer.Size; - cmdListVertexObjectOffset += cmd_list->VtxBuffer.Size; } - mdiBytesFilled[underlying_type] = true; - mdiOffsets[underlying_type] = globalBlockOffset; - return mdiParams.bytesToFill[underlying_type]; + return uploaded; }; - //! from biggest requests to smallest - uploadedSize += fillDrawBuffers.template operator() < SMdiBuffer::Content::VERTEX_BUFFERS > (); - uploadedSize += fillDrawBuffers.template operator() < SMdiBuffer::Content::INDEX_BUFFERS > (); - uploadedSize += fillIndirectStructures.template operator() < SMdiBuffer::Content::INDIRECT_STRUCTURES > (); - uploadedSize += fillIndirectStructures.template operator() < SMdiBuffer::Content::ELEMENT_STRUCTURES > (); + uploadedSize += upload(); } - streamingBuffer->multi_deallocate(STREAMING_ALLOCATION_COUNT, &requestState.offset, &requestState.multiAllocationSize, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required - } - } + streamingBuffer->multi_deallocate(StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required + + // we let to run it at least once + const bool timeout = std::chrono::steady_clock::now() >= waitPoint; - assert([&mdiOffsets]() -> bool - { - for (const auto& offset : mdiOffsets) - if (offset == InvalidAddress) - return false; // we should never hit this at this point + if (timeout) + { + if (uploadedSize >= mdiLimits.totalByteSizeRequest) + break; // must be lucky to hit it or on debug - return true; - }()); // debug check only + streamingBuffer->cull_frees(); + return false; + } + } + } auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); const auto offset = mdiBuffer->getBoundMemory().offset; { const SBufferBinding binding = { - .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDEX_BUFFERS)], + .offset = 0u, .buffer = smart_refctd_ptr(mdiBuffer) }; @@ -1281,7 +1301,7 @@ namespace nbl::ext::imgui { const SBufferBinding bindings[] = {{ - .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::VERTEX_BUFFERS)], + .offset = 0u, .buffer = smart_refctd_ptr(mdiBuffer) }}; @@ -1321,8 +1341,8 @@ namespace nbl::ext::imgui { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + mdiOffsets[static_cast>( SMdiBuffer::Content::ELEMENT_STRUCTURES )]}, - .elementCount = { mdiParams.drawCount }, + .elementBDA = { mdiBuffer->getDeviceAddress() + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]}, + .elementCount = { mdiLimits.totalIndirectDrawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } @@ -1333,11 +1353,11 @@ namespace nbl::ext::imgui const SBufferBinding binding = { - .offset = mdiOffsets[static_cast>(SMdiBuffer::Content::INDIRECT_STRUCTURES)], + .offset = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES], .buffer = smart_refctd_ptr(mdiBuffer) }; - commandBuffer->drawIndexedIndirect(binding, mdiParams.drawCount, sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->drawIndexedIndirect(binding, mdiLimits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); } return true; From 6d45f26332930fad44689a34586e91fdacf9065d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 6 Oct 2024 10:41:11 +0200 Subject: [PATCH 119/148] spot runtime issues - indirect vertex offsets is calculated incorrectly (modulo with the vertex struct size != 0u). Another problem rises, both streaming & linear allocator won't accept alignment which is not PoT - maybe I could hack the imgui vertex struct and just make it PoT in size with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT --- src/nbl/ext/ImGui/ImGui.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 2744323b9c..b08fbde0bd 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -29,7 +29,9 @@ namespace nbl::ext::imgui static constexpr auto InvalidAddress = compose_t::invalid_value; static constexpr auto MdiAlignments = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); static constexpr auto MdiMaxAlignment = *std::max_element(MdiAlignments.begin(), MdiAlignments.end()); + static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); // those must be allocated within single block for content - even though its possible to tell vkCmdDrawIndexedIndirect about stride we cannot guarantee each one will be the same size with our allocation strategy enum class TightContent : uint16_t @@ -1113,8 +1115,8 @@ namespace nbl::ext::imgui const ImDrawList* commandList = drawData->CmdLists[i]; limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; - allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); limits.alignments.emplace_back() = alignof(ImDrawVert); allocation.filled.emplace_back() = false; - allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); limits.alignments.emplace_back() = alignof(ImDrawIdx); allocation.filled.emplace_back() = false; + allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); limits.alignments.emplace_back() = sizeof(ImDrawVert); allocation.filled.emplace_back() = false; + allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); limits.alignments.emplace_back() = sizeof(ImDrawIdx); allocation.filled.emplace_back() = false; } limits.totalByteSizeRequest += limits.sizes[(uint32_t)TightContent::INDIRECT_STRUCTURES] = (limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand)); @@ -1159,7 +1161,7 @@ namespace nbl::ext::imgui else { constexpr auto AlignOffsetNeeded = 0u; - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, MdiMaxAlignment, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, MdiMaxSize /* we care about vertex struct which is MdiMaxSize (20) bytes */, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call auto upload = [&]() -> size_t @@ -1211,6 +1213,16 @@ namespace nbl::ext::imgui return true; }; + auto validateObjectOffsets = [&]() -> bool + { + const auto vtxModulo = allocation.offsets[vtxAllocationIx] % sizeof(ImDrawVert); + const auto ixModulo = allocation.offsets[idxAllocationIx] % sizeof(ImDrawIdx); + + return (vtxModulo == 0u && ixModulo == 0u); // we must be aligned! + }; + + assert(validateObjectOffsets()); // debug only + // we consider buffers valid if we suballocated them (under the hood filled) const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(allocation.offsets[vtxAllocationIx] / sizeof(ImDrawVert), allocation.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); From 7eb21ac5d9fcd11f638c5d3bbdad5712b7e558e7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 6 Oct 2024 10:51:54 +0200 Subject: [PATCH 120/148] ahh I see now, the PoT is required ONLY for max alignment, multi alloc request will accept non PoT alignment requests as well - it works now! --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index b08fbde0bd..5c2b842845 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1161,7 +1161,7 @@ namespace nbl::ext::imgui else { constexpr auto AlignOffsetNeeded = 0u; - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, MdiMaxSize /* we care about vertex struct which is MdiMaxSize (20) bytes */, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, 32u /* we care about vertex struct which is MdiMaxSize (20) bytes */, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call auto upload = [&]() -> size_t From 83991acebba9925d925e98b46b6752dab8926555 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 6 Oct 2024 14:19:38 +0200 Subject: [PATCH 121/148] clean code, add more debug validation, update align offset required for suballocator --- include/nbl/core/math/intutil.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 94 +++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/include/nbl/core/math/intutil.h b/include/nbl/core/math/intutil.h index fd8d27fc10..d365835163 100644 --- a/include/nbl/core/math/intutil.h +++ b/include/nbl/core/math/intutil.h @@ -42,7 +42,7 @@ NBL_FORCE_INLINE constexpr bool isPoT(INT_TYPE value) template NBL_FORCE_INLINE constexpr INT_TYPE roundUpToPoT(INT_TYPE value) { - return INT_TYPE(0x1u)<(value-INT_TYPE(1))); + return INT_TYPE(0x1u)<(value-INT_TYPE(1))); // this wont result in constexpr because findMSB is not one } template diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5c2b842845..5c57e8c9b2 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -28,10 +28,9 @@ namespace nbl::ext::imgui using mdi_size_t = compose_t::size_type; static constexpr auto InvalidAddress = compose_t::invalid_value; - static constexpr auto MdiAlignments = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); static constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); - static constexpr auto MdiMaxAlignment = *std::max_element(MdiAlignments.begin(), MdiAlignments.end()); static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); + static const auto MdiMaxAlignment = roundUpToPoT(MdiMaxSize); // those must be allocated within single block for content - even though its possible to tell vkCmdDrawIndexedIndirect about stride we cannot guarantee each one will be the same size with our allocation strategy enum class TightContent : uint16_t @@ -1151,24 +1150,38 @@ namespace nbl::ext::imgui for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiLimits.totalByteSizeRequest;) { bigChunkRequestState.offset = InvalidAddress; - bigChunkRequestState.size = streamingBuffer->max_size(); // request available block memory size which we can try to allocate + bigChunkRequestState.size = streamingBuffer->max_size(); // TODO: divide by 2 request strategy on fail constexpr auto StreamingAllocationCount = 1u; const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data if (bigChunkRequestState.offset == InvalidAddress) - continue; // failed? lets try again, TODO: should I here have my "blockMemoryFactor =* 0.5" and apply to bigChunkRequestState.size? + continue; else { - constexpr auto AlignOffsetNeeded = 0u; - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, AlignOffsetNeeded, 32u /* we care about vertex struct which is MdiMaxSize (20) bytes */, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) + const auto alignOffsetNeeded = MdiMaxSize - (bigChunkRequestState.offset % MdiMaxSize); + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, alignOffsetNeeded, MdiMaxAlignment, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call auto upload = [&]() -> size_t { size_t uploaded = {}; - // they are very small & negligible in size compared to buffers, but this small pool which we will conditionally fill on successfull object buffer suballocations is required to not complicate things (if we cannot allocate all mdiLimits.totalIndirectDrawCount object buffers then simply those coresponding structures wont be filled, we treat both components as arrays) + auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t + { + const bool isFilled = allocation.filled[allocationIx]; + + if (!isFilled) + { + const auto bytesToFill = mdiLimits.sizes[allocationIx]; + uploaded += bytesToFill; + allocation.filled[allocationIx] = true; + return bytesToFill; + } + return 0u; + }; + + // they are very small & negligible in size compared to buffers, but this small pool which we will conditionally fill on successfull object buffer suballocations is required to not complicate things (if we cannot allocate all mdiLimits.totalIndirectDrawCount object buffers then simply those coresponding structures will be filled with dummy params making it an invocation with 0u indices, we treat both components as arrays) const bool structuresSuballocated = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES] != InvalidAddress && allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES] != InvalidAddress; if (structuresSuballocated) // note that suballocated only means we have valid address(es) we can work on, it doesn't mean we filled anything @@ -1176,21 +1189,14 @@ namespace nbl::ext::imgui auto* const indirectStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES]); auto* const elementStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]); { - if (!allocation.filled[(uint32_t)TightContent::INDIRECT_STRUCTURES]) - { - uploaded += mdiLimits.sizes[(uint32_t)TightContent::INDIRECT_STRUCTURES]; - allocation.filled[(uint32_t)TightContent::INDIRECT_STRUCTURES] = true; // I make a assumption here since I can access them later - but I don't guarantee all of them will be present since we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally - } - - if (!allocation.filled[(uint32_t)TightContent::ELEMENT_STRUCTURES]) - { - uploaded += mdiLimits.sizes[(uint32_t)TightContent::ELEMENT_STRUCTURES]; - allocation.filled[(uint32_t)TightContent::ELEMENT_STRUCTURES] = true; // I make a assumption here since I can access them later - but I don't guarantee all of them will be present since we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally - } + // I make a assumption here since I can access them later but I don't guarantee all of them will be present, + // we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally + updateSuballocation((uint32_t)TightContent::INDIRECT_STRUCTURES); + updateSuballocation((uint32_t)TightContent::ELEMENT_STRUCTURES); } uint32_t drawID = {}; - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + for (uint32_t i = 0u; i < drawData->CmdListsCount; i++) { const auto* commandList = drawData->CmdLists[i]; const auto& [vertexBuffer, indexBuffer] = std::make_tuple(commandList->VtxBuffer, commandList->IdxBuffer); @@ -1203,37 +1209,45 @@ namespace nbl::ext::imgui if (offset == InvalidAddress) return false; else - if (!allocation.filled[allocationIx]) - { + { + const auto bytesToFill = updateSuballocation(allocationIx); + + if (bytesToFill != 0u) ::memcpy(mdiData + offset, in, bytesToFill); - uploaded += bytesToFill; - allocation.filled[allocationIx] = true; - } + } return true; }; auto validateObjectOffsets = [&]() -> bool { - const auto vtxModulo = allocation.offsets[vtxAllocationIx] % sizeof(ImDrawVert); - const auto ixModulo = allocation.offsets[idxAllocationIx] % sizeof(ImDrawIdx); + const auto [vtxOffset, idxOffset] = std::make_tuple(allocation.offsets[vtxAllocationIx], allocation.offsets[idxAllocationIx]); + bool ok = true; + + if (vtxOffset != InvalidAddress) + ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); + + if (idxOffset != InvalidAddress) + ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); + + _NBL_BREAK_IF(!ok); - return (vtxModulo == 0u && ixModulo == 0u); // we must be aligned! + return ok; // if offsets are valid then must be aligned properly! }; - assert(validateObjectOffsets()); // debug only - - // we consider buffers valid if we suballocated them (under the hood filled) + assert(validateObjectOffsets()); // debug check only + + // we consider buffers valid if we suballocated them (under the hood filled) - if buffers are valid then subindirect call referencing them is too const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(allocation.offsets[vtxAllocationIx] / sizeof(ImDrawVert), allocation.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); - - for (uint32_t j = 0; j < commandList->CmdBuffer.Size; j++) + + for (uint32_t j = 0u; j < commandList->CmdBuffer.Size; j++) { const auto* cmd = &commandList->CmdBuffer[j]; auto* indirect = indirectStructures + drawID; auto* element = elementStructures + drawID; - // we will make a trick to keep indirect & element structs in the mdi iteration but explicitly execute dummy null invocation if we don't have vertex or index buffer for the struct (suballocation failed for any of those 2 buffers). + // we make a trick to keep indirect & element structs in the mdi iteration but explicitly execute dummy null invocation if we don't have vertex or index buffer for the struct (suballocation failed for any of those 2 buffers). // TODO: we could make the current structs pool "dynamic" in size and treat as simple stack instead (trying it first to make things easier) indirect->indexCount = buffersSuballocated ? cmd->ElemCount /* valid invocation */ : 0u /* null invocation */; @@ -1242,7 +1256,7 @@ namespace nbl::ext::imgui // starting to wonder, for some reason imgui decided to keep single vertex & index shared between cmds within cmd list // but maybe we should cut current [vertexBuffer, indexBuffer] with respect to cmd->IdxOffset & cmd->VtxOffset (therefore we could have even smaller alloc requests, now a few structs can point to the same buffer but with different offsets [indirect]) - // though not sure if I don't double some data then + // though not sure if I don't double some data then <- EDIT: YES, turns out we may double some data indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated @@ -1272,15 +1286,14 @@ namespace nbl::ext::imgui } } } - return uploaded; }; uploadedSize += upload(); } streamingBuffer->multi_deallocate(StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required - - // we let to run it at least once + + // we let it run at least once const bool timeout = std::chrono::steady_clock::now() >= waitPoint; if (timeout) @@ -1303,7 +1316,8 @@ namespace nbl::ext::imgui .buffer = smart_refctd_ptr(mdiBuffer) }; - if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) + constexpr auto IndexType = sizeof(ImDrawIdx) == 2u ? EIT_16BIT : EIT_32BIT; + if (!commandBuffer->bindIndexBuffer(binding, IndexType)) { m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", ILogger::ELL_ERROR); assert(false); @@ -1317,7 +1331,7 @@ namespace nbl::ext::imgui .buffer = smart_refctd_ptr(mdiBuffer) }}; - if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) + if(!commandBuffer->bindVertexBuffers(0u, 1u, bindings)) { m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", ILogger::ELL_ERROR); assert(false); @@ -1334,7 +1348,7 @@ namespace nbl::ext::imgui .maxDepth = 1.0f, }; - commandBuffer->setViewport(0, 1, &viewport); + commandBuffer->setViewport(0u, 1u, &viewport); { if (scissors.empty()) { From ee5ce4be3ad52b1f78d575fec7db9bdef18e542e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 6 Oct 2024 16:52:38 +0200 Subject: [PATCH 122/148] add a few allocation strategies for tests, I think the one with division is nice though I wrote my thoughts in comments --- src/nbl/ext/ImGui/ImGui.cpp | 128 +++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5c57e8c9b2..fccd16d5f5 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1143,24 +1143,60 @@ namespace nbl::ext::imgui struct { - mdi_size_t offset, size; + std::vector offsets, sizes; + float memoryBlockFactor = 1.f; } bigChunkRequestState; //! we will try to upload entrie MDI buffer with all available indirect data to our streaming buffer, but we cannot guarantee the allocation can be done in single request nor we allocate all totalIndirectDrawCount at all - we can hit timeout and it may appear not all of totalIndirectDrawCount will be uploaded then for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiLimits.totalByteSizeRequest;) { - bigChunkRequestState.offset = InvalidAddress; - bigChunkRequestState.size = streamingBuffer->max_size(); // TODO: divide by 2 request strategy on fail + // ok we cannot make it just + // min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest - uploadedSize)) + // with bigChunkRequestState.memoryBlockFactor being divided by 2 because we will always have at least one offset which cannot be suballocated by linear allocator, this will be too tight for the suballocator to respect alignments - to make it work this delta would need a little factor which would add something to this difference I guess + // tests: + + #define ALLOC_STRATEGY_1 + //#define ALLOC_STRATEGY_2 + //#define ALLOC_STRATEGY_3 + + #ifdef ALLOC_STRATEGY_1 + mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest * bigChunkRequestState.memoryBlockFactor)); // we divide requests, delta has space for suballocator's padding - we trying to add another block with the fixed size, but if not posible we divide the block by 2 + + constexpr auto StreamingAllocationCount = 1u; + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + + if (chunkOffset == InvalidAddress) + { + bigChunkRequestState.memoryBlockFactor *= 0.5f; + continue; + } + #endif + #ifdef ALLOC_STRATEGY_2 + mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest - uploadedSize) * 2u /* we request twice the delta with respect to max_size UB */); constexpr auto StreamingAllocationCount = 1u; - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data - if (bigChunkRequestState.offset == InvalidAddress) + if (chunkOffset == InvalidAddress) continue; + #endif + #ifdef ALLOC_STRATEGY_3 + mdi_size_t chunkOffset = InvalidAddress, chunkSize = streamingBuffer->max_size(); // take all whats available <- dumbie I guess + + constexpr auto StreamingAllocationCount = 1u; + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + + if (chunkOffset == InvalidAddress) + continue; + #endif else { - const auto alignOffsetNeeded = MdiMaxSize - (bigChunkRequestState.offset % MdiMaxSize); - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, bigChunkRequestState.offset, alignOffsetNeeded, MdiMaxAlignment, bigChunkRequestState.size); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) + // chunk allocated? put state onto stack & keep alive for suballocator to fill it as required + bigChunkRequestState.offsets.emplace_back() = chunkOffset; + bigChunkRequestState.sizes.emplace_back() = chunkSize; + + const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call auto upload = [&]() -> size_t @@ -1181,16 +1217,18 @@ namespace nbl::ext::imgui return 0u; }; - // they are very small & negligible in size compared to buffers, but this small pool which we will conditionally fill on successfull object buffer suballocations is required to not complicate things (if we cannot allocate all mdiLimits.totalIndirectDrawCount object buffers then simply those coresponding structures will be filled with dummy params making it an invocation with 0u indices, we treat both components as arrays) + // they are *very* small (<1% of the total request size) & negligible in size compared to buffers - at the end we must have them all anyway (explained in following comment) const bool structuresSuballocated = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES] != InvalidAddress && allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES] != InvalidAddress; - if (structuresSuballocated) // note that suballocated only means we have valid address(es) we can work on, it doesn't mean we filled anything + if (structuresSuballocated) // note that suballocated only means we have valid address(es) we can work on, it doesn't mean we filled anything (suballocated -> *can* fill) { auto* const indirectStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES]); auto* const elementStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]); { - // I make a assumption here since I can access them later but I don't guarantee all of them will be present, - // we can fail other suballocations which are required for the struct, note that in reality we fill them below & conditionally + // I make a assumption here since I can access them later but I don't guarantee all of them will be present at the first run, we can fail buffer + // subalocations from the current memory block chunk which makes a command list invalid for the iteration! Because of that we fill them conditionally + // once buffers are correctly suballocated for handled command list - at the end we must have them all filled regardless what chunk their data come from due + // to the fact we cannot submit an overflow, we don't have dynamic rendering allowing us to stop recording the subpass, submit work to queue & start recording again updateSuballocation((uint32_t)TightContent::INDIRECT_STRUCTURES); updateSuballocation((uint32_t)TightContent::ELEMENT_STRUCTURES); } @@ -1237,52 +1275,50 @@ namespace nbl::ext::imgui assert(validateObjectOffsets()); // debug check only - // we consider buffers valid if we suballocated them (under the hood filled) - if buffers are valid then subindirect call referencing them is too + // we consider buffers valid for command list if we suballocated them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list with indirects is as well const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(allocation.offsets[vtxAllocationIx] / sizeof(ImDrawVert), allocation.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); - for (uint32_t j = 0u; j < commandList->CmdBuffer.Size; j++) + if (buffersSuballocated) { - const auto* cmd = &commandList->CmdBuffer[j]; - auto* indirect = indirectStructures + drawID; - auto* element = elementStructures + drawID; + for (uint32_t j = 0u; j < commandList->CmdBuffer.Size; j++) + { + const auto* cmd = &commandList->CmdBuffer[j]; + auto* indirect = indirectStructures + drawID; + auto* element = elementStructures + drawID; - // we make a trick to keep indirect & element structs in the mdi iteration but explicitly execute dummy null invocation if we don't have vertex or index buffer for the struct (suballocation failed for any of those 2 buffers). - // TODO: we could make the current structs pool "dynamic" in size and treat as simple stack instead (trying it first to make things easier) - indirect->indexCount = buffersSuballocated ? cmd->ElemCount /* valid invocation */ : 0u /* null invocation */; + indirect->indexCount = cmd->ElemCount; - indirect->firstInstance = drawID; // we use base instance as draw ID - indirect->instanceCount = 1u; + // we use base instance as draw ID + indirect->firstInstance = drawID; + indirect->instanceCount = 1u; + indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; + indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; - // starting to wonder, for some reason imgui decided to keep single vertex & index shared between cmds within cmd list - // but maybe we should cut current [vertexBuffer, indexBuffer] with respect to cmd->IdxOffset & cmd->VtxOffset (therefore we could have even smaller alloc requests, now a few structs can point to the same buffer but with different offsets [indirect]) - // though not sure if I don't double some data then <- EDIT: YES, turns out we may double some data - indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated - indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; // safe to assume due to indirect->indexCount depending on buffersSuballocated + const auto clipRectangle = clip.getClipRectangle(cmd); + const auto scissor = clip.getScissor(clipRectangle); - const auto clipRectangle = clip.getClipRectangle(cmd); - const auto scissor = clip.getScissor(clipRectangle); + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; - auto packSnorm16 = [](float ndc) -> int16_t - { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils - }; + const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + struct snorm16_t2_packed + { + int16_t x, y; + }; - struct snorm16_t2_packed - { - int16_t x, y; - }; - - reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; - reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; + reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; + reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - element->texId = cmd->TextureId.textureID; - element->samplerIx = cmd->TextureId.samplerIx; + element->texId = cmd->TextureId.textureID; + element->samplerIx = cmd->TextureId.samplerIx; - ++drawID; + ++drawID; + } } } } @@ -1291,8 +1327,7 @@ namespace nbl::ext::imgui uploadedSize += upload(); } - streamingBuffer->multi_deallocate(StreamingAllocationCount, &bigChunkRequestState.offset, &bigChunkRequestState.size, waitInfo); //! (*) block allocated, we just latch offsets deallocation to keep it alive as long as required - + // we let it run at least once const bool timeout = std::chrono::steady_clock::now() >= waitPoint; @@ -1305,6 +1340,7 @@ namespace nbl::ext::imgui return false; } } + streamingBuffer->multi_deallocate(bigChunkRequestState.offsets.size(), bigChunkRequestState.offsets.data(), bigChunkRequestState.sizes.data(), waitInfo); //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required } auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); From 9b47b314ca41d2da11e5a5fe346cd1f6c05a94f8 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 6 Oct 2024 16:55:00 +0200 Subject: [PATCH 123/148] typo --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index fccd16d5f5..4677eb6685 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1147,7 +1147,7 @@ namespace nbl::ext::imgui float memoryBlockFactor = 1.f; } bigChunkRequestState; - //! we will try to upload entrie MDI buffer with all available indirect data to our streaming buffer, but we cannot guarantee the allocation can be done in single request nor we allocate all totalIndirectDrawCount at all - we can hit timeout and it may appear not all of totalIndirectDrawCount will be uploaded then + //! we will try to upload entrie MDI buffer with all available indirect data to our streaming buffer, but we cannot guarantee the allocation can be done in single request nor data will come from single continous memory block (chunk) for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiLimits.totalByteSizeRequest;) { // ok we cannot make it just From d914d6f1d76804145fd4fdece7e7da03e12b7946 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 7 Oct 2024 16:33:59 +0200 Subject: [PATCH 124/148] change strategy, chunk per command list with linear suballocator & command list custom iterator - meh I have a leak somewhere --- src/nbl/ext/ImGui/ImGui.cpp | 443 +++++++++++++++++++++--------------- 1 file changed, 260 insertions(+), 183 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 4677eb6685..24efa82a9b 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -32,13 +32,178 @@ namespace nbl::ext::imgui static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); static const auto MdiMaxAlignment = roundUpToPoT(MdiMaxSize); - // those must be allocated within single block for content - even though its possible to tell vkCmdDrawIndexedIndirect about stride we cannot guarantee each one will be the same size with our allocation strategy - enum class TightContent : uint16_t + struct DrawItemMeta { - INDIRECT_STRUCTURES, - ELEMENT_STRUCTURES, + //! total left bytes to upload for X-th command list + size_t totalLeftBytesToUpload; - COUNT, + //! we suballocate total of (command buffers count * 2u /*vertex & index buffers*/) buffers, we assume we have [ ... [vertex buffer, index buffer] ... ] + std::vector offsets, sizes, alignments; + std::vector filled = { false, false }; + + //! those buffers will be suballocated & filled from a block of memory, each block memory request is multiplied with this factor - if a block fails to be allocated the factor decreases (divided by 2 on fail) + float memoryBlockFactor = 1.f; + }; + + struct DrawItem + { + ImDrawList* cmdList; + DrawItemMeta& meta; + uint32_t cmdListIndex, drawIdOffset; + }; + + class ImGuiCommandListIterator + { + public: + using value_type = DrawItem; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + ImGuiCommandListIterator(const ImDrawData* drawData, std::vector& metaData, uint32_t index = 0u) + : drawData(drawData), metaList(metaData), index(index) {} + + ImGuiCommandListIterator& operator++() + { + auto* currentList = drawData->CmdLists[index]; + drawIdOffset += currentList->CmdBuffer.Size; + + ++index; + return *this; + } + + bool operator!=(const ImGuiCommandListIterator& other) const + { + return index != other.index; + } + + value_type operator*() const + { + return { .cmdList = drawData->CmdLists[index], .meta = metaList[index], .cmdListIndex = index, .drawIdOffset = drawIdOffset }; + } + + private: + const ImDrawData* drawData; + std::vector& metaList; + uint32_t index = {}, drawIdOffset = {}; + }; + + class ImGuiCommandListRange + { + public: + //! those structs we allocate within single block, in general we assume we fail entire render call if we cannot upload all indirect objects (no dynamic rendering) - its possible to have those structs in separate blocks & to tell vkCmdDrawIndexedIndirect about block strides but we cannot guarantee each one will be the same size + //! with our allocation strategy unless we split indirect call into smaller pieces (however it doesnt make any sense if we assume all objects must be uploaded anyway imo - if all then why to bother?), also there is a very low chance this memory block will ever exceed 1KB even if you have a lot of GUI windows (< 45 draw commands, 22 bytes * limits.totalIndirectDrawCount) since its very small. + struct STightStructs + { + // we have total COUNT of blocks to allocate first + enum StructureIx + { + INDIRECT_STRUCTURES = 0u, + ELEMENT_STRUCTURES = 1u, + COUNT + }; + + std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData) }; + bool allocated = false; + }; + + ImGuiCommandListRange(const ImDrawData* drawData) + : drawData(drawData), metaList(drawData->CmdListsCount) + { + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + { + auto& meta = metaList[i]; + const ImDrawList* commandList = drawData->CmdLists[i]; + + limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; + + meta.offsets.emplace_back() = InvalidAddress; meta.totalLeftBytesToUpload += meta.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); meta.alignments.emplace_back() = sizeof(ImDrawVert); + meta.offsets.emplace_back() = InvalidAddress; meta.totalLeftBytesToUpload += meta.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); meta.alignments.emplace_back() = sizeof(ImDrawIdx); + + assert([&]() -> bool // we should never hit it + { + return (meta.offsets.size() == meta.sizes.size()) + && (meta.filled.size() == meta.filled.size()) + && (std::reduce(std::begin(meta.sizes), std::end(meta.sizes)) == meta.totalLeftBytesToUpload) + && (std::all_of(std::cbegin(meta.offsets), std::cend(meta.offsets), [](const auto& offset) { return offset == InvalidAddress; })); + }()); // debug check only + } + + limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); + limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); + + requiredStructsBlockInfo.sizes[STightStructs::INDIRECT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand); + requiredStructsBlockInfo.sizes[STightStructs::ELEMENT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(PerObjectData); + } + + inline ImGuiCommandListIterator begin() { return ImGuiCommandListIterator(drawData, metaList, 0u); } + inline ImGuiCommandListIterator end() { return ImGuiCommandListIterator(drawData, metaList, drawData->CmdListsCount); } + + //! allocates a chunk from which STightStructs::COUNT blocks will be suballocated, required structs are indirects & elements + bool allocateRequiredBlock(mdi_buffer_t* mdi) + { + requiredStructsBlockInfo.allocated = true; + + auto blockOffset = InvalidAddress; + const auto blockSize = std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes))); + + mdi->compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &blockOffset, &blockSize, &MdiMaxAlignment); + + if (blockOffset == InvalidAddress) + return (requiredStructsBlockInfo.allocated = false); + + auto* const mdiData = reinterpret_cast(mdi->compose->getBufferPointer()); + mdi_buffer_t::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, blockOffset, 0u, MdiMaxAlignment, blockSize); + mdi_buffer_t::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), requiredStructsBlockInfo.alignments.data()); + + for (const auto& offset : requiredStructsBlockInfo.offsets) + if (offset == InvalidAddress) + requiredStructsBlockInfo.allocated = false; + + return requiredStructsBlockInfo.allocated; + } + + void latchDeallocations(mdi_buffer_t* mdi, ISemaphore::SWaitInfo waitInfo) + { + deallocateRequiredBlock(mdi, waitInfo); // indirect & element structs + deallocateLeftChunks(mdi, waitInfo); // vertex & index buffers + } + + inline const auto& getLimits() { return limits; } + inline const auto& getRequiredStructsBlockInfo() { return requiredStructsBlockInfo; } + + struct + { + std::vector offsets, sizes; + } bigChunkRequestInfo; + + private: + + void deallocateRequiredBlock(mdi_buffer_t* mdi, ISemaphore::SWaitInfo& waitInfo) + { + mdi->compose->multi_deallocate(requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), waitInfo); + } + + void deallocateLeftChunks(mdi_buffer_t* mdi, ISemaphore::SWaitInfo& waitInfo) + { + mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); + } + + struct SLimits + { + //! sum of metaList[x].sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects, note we don't count element & indirect structers there + size_t totalByteSizeRequest = {}, + + //! amount of total objects to draw with indirect indexed call + totalIndirectDrawCount = {}; + }; + + const ImDrawData* drawData; + std::vector metaList; + + SLimits limits; + STightStructs requiredStructsBlockInfo; }; static constexpr SPushConstantRange PushConstantRanges[] = @@ -1066,183 +1231,81 @@ namespace nbl::ext::imgui return std::move(retV); }(); - const auto totalIndirectDrawCount = [&]() - { - uint32_t count = {}; - - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) - { - const ImDrawList* commandList = drawData->CmdLists[i]; - count += commandList->CmdBuffer.Size; - } - - return count; - }(); - - //! we compose a struct of offsets for our mdi streaming iteration - struct SAllocationParams - { - //! for a mdi streaming buffer iteration we compose offsets for which - //! first slot is reserved for TightContent::INDIRECT_STRUCTURES, - //! second for TightContent::ELEMENT_STRUCTURES - //! and left slots are for indirect object buffers ([vertex, index] .. [vertex, index]) - std::vector offsets; - std::vector filled; - } allocation; - - struct SMdiLimits - { - //! sum of allocation.sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects - mdi_size_t totalByteSizeRequest = {}, - - //! amount of total objects to draw - totalIndirectDrawCount = {}; - - //! corresponding allocation sizes & alignments for allocation.offsets - index shader between offset & size (we don't std::pair (and similar) those on purpose - need them separate in continous memory each to align nicely with our allocator api) - std::vector sizes, alignments; - }; - - const SMdiLimits mdiLimits = [&]() - { - SMdiLimits limits; - - limits.sizes.emplace_back() = {}; allocation.offsets.emplace_back() = InvalidAddress; limits.alignments.emplace_back() = alignof(VkDrawIndexedIndirectCommand); allocation.filled.emplace_back() = false; - limits.sizes.emplace_back() = {}; allocation.offsets.emplace_back() = InvalidAddress; limits.alignments.emplace_back() = alignof(PerObjectData); allocation.filled.emplace_back() = false; - - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) - { - const ImDrawList* commandList = drawData->CmdLists[i]; - limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; - - allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); limits.alignments.emplace_back() = sizeof(ImDrawVert); allocation.filled.emplace_back() = false; - allocation.offsets.emplace_back() = InvalidAddress; limits.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); limits.alignments.emplace_back() = sizeof(ImDrawIdx); allocation.filled.emplace_back() = false; - } - - limits.totalByteSizeRequest += limits.sizes[(uint32_t)TightContent::INDIRECT_STRUCTURES] = (limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand)); - limits.totalByteSizeRequest += limits.sizes[(uint32_t)TightContent::ELEMENT_STRUCTURES] = (limits.totalIndirectDrawCount * sizeof(PerObjectData)); - limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); - limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); - - assert([&]() -> bool // we should never hit it - { - return (allocation.offsets.size() == limits.sizes.size()) - && (allocation.offsets.size() == allocation.filled.size()) - && (std::reduce(std::begin(limits.sizes), std::end(limits.sizes)) == limits.totalByteSizeRequest) - && (std::all_of(std::cbegin(allocation.offsets), std::cend(allocation.offsets), [](const auto& offset) { return offset == InvalidAddress; })); - }()); // debug check only - - return limits; - }(); - - auto streamingBuffer = m_mdi.compose; + ImGuiCommandListRange imCmdRange(drawData); { + auto streamingBuffer = m_mdi.compose; auto binding = streamingBuffer->getBuffer()->getBoundMemory(); assert(binding.memory->isCurrentlyMapped()); auto* const mdiData = reinterpret_cast(streamingBuffer->getBufferPointer()); - - struct - { - std::vector offsets, sizes; - float memoryBlockFactor = 1.f; - } bigChunkRequestState; - - //! we will try to upload entrie MDI buffer with all available indirect data to our streaming buffer, but we cannot guarantee the allocation can be done in single request nor data will come from single continous memory block (chunk) - for (mdi_size_t uploadedSize = 0ull; uploadedSize < mdiLimits.totalByteSizeRequest;) + const auto& requiredStructsBlockInfo = imCmdRange.getRequiredStructsBlockInfo(); + auto& chunksInfo = imCmdRange.bigChunkRequestInfo; + const auto& limits = imCmdRange.getLimits(); + + //! we will try to upload all imgui data to mdi streaming buffer but we cannot guarantee an allocation can be done in single request nor buffers data will come from continous memory block (from single chunk) + for (mdi_size_t totalUploadedSize = 0ull; totalUploadedSize < limits.totalByteSizeRequest;) { - // ok we cannot make it just - // min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest - uploadedSize)) - // with bigChunkRequestState.memoryBlockFactor being divided by 2 because we will always have at least one offset which cannot be suballocated by linear allocator, this will be too tight for the suballocator to respect alignments - to make it work this delta would need a little factor which would add something to this difference I guess - // tests: - - #define ALLOC_STRATEGY_1 - //#define ALLOC_STRATEGY_2 - //#define ALLOC_STRATEGY_3 + auto uploadCommandListData = [&](DrawItem drawItem) + { + //! (*) note we make an assumption here, at this point they are allocated & ready to fill, read the ImGuiCommandListRange::STightStructs description for more info + auto* const indirectStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES]); + auto* const elementStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES]); - #ifdef ALLOC_STRATEGY_1 - mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest * bigChunkRequestState.memoryBlockFactor)); // we divide requests, delta has space for suballocator's padding - we trying to add another block with the fixed size, but if not posible we divide the block by 2 + if (drawItem.meta.totalLeftBytesToUpload >= 0u) + { + constexpr auto StreamingAllocationCount = 1u; + + // not only memoryBlockFactor divided by 2 will fail us, but even without it we will always fail suballocator with one offset becauase delta is too tight + // mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (drawItem.meta.totalLeftBytesToUpload /* * drawItem.meta.memoryBlockFactor*/)); - constexpr auto StreamingAllocationCount = 1u; - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), limits.totalByteSizeRequest /* temporary giving MORE then required for suballocator because of padding.. */); - if (chunkOffset == InvalidAddress) - { - bigChunkRequestState.memoryBlockFactor *= 0.5f; - continue; - } - #endif - #ifdef ALLOC_STRATEGY_2 - mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (mdiLimits.totalByteSizeRequest - uploadedSize) * 2u /* we request twice the delta with respect to max_size UB */); + //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill buffers + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); - constexpr auto StreamingAllocationCount = 1u; - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + if (chunkOffset == InvalidAddress) + { + drawItem.meta.memoryBlockFactor *= 0.5f; // the problem is if another chunk failed because of SUBALLOCATOR then its because of padding probably.. decreasing the chunk request size wont help in that case and we should rather INCREASE the delta then with left bytes to upload for the suballocator to success with alignment restrictions + return; + } + else + { + // chunk allocated? update the state & let suballocator do the job + chunksInfo.offsets.emplace_back() = chunkOffset; + chunksInfo.sizes.emplace_back() = chunkSize; + const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); - if (chunkOffset == InvalidAddress) - continue; - #endif - #ifdef ALLOC_STRATEGY_3 - mdi_size_t chunkOffset = InvalidAddress, chunkSize = streamingBuffer->max_size(); // take all whats available <- dumbie I guess + //! (*) we create linear suballocator to fill the allocated chunk of memory + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); - constexpr auto StreamingAllocationCount = 1u; - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill data + //! (*) we suballocate from the allocated chunk with required alignments - multi request all with single traits call per imgui command list + SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, drawItem.meta.offsets.size(), drawItem.meta.offsets.data(), drawItem.meta.sizes.data(), drawItem.meta.alignments.data()); - if (chunkOffset == InvalidAddress) - continue; - #endif - else - { - // chunk allocated? put state onto stack & keep alive for suballocator to fill it as required - bigChunkRequestState.offsets.emplace_back() = chunkOffset; - bigChunkRequestState.sizes.emplace_back() = chunkSize; + auto upload = [&]() -> size_t + { + size_t uploaded = {}; - const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); //! (*) we create linear suballocator to fill the allocated chunk of memory (some of at least) - SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, allocation.offsets.size(), allocation.offsets.data(), mdiLimits.sizes.data(), mdiLimits.alignments.data()); //! (*) we suballocate memory regions from the allocated chunk with required alignments - multi request all with single traits call + auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t + { + const bool isFilled = drawItem.meta.filled[allocationIx]; - auto upload = [&]() -> size_t - { - size_t uploaded = {}; + if (!isFilled) + { + const auto bytesToFill = drawItem.meta.sizes[allocationIx]; + uploaded += bytesToFill; + drawItem.meta.filled[allocationIx] = true; + return bytesToFill; + } - auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t - { - const bool isFilled = allocation.filled[allocationIx]; + return 0u; + }; - if (!isFilled) - { - const auto bytesToFill = mdiLimits.sizes[allocationIx]; - uploaded += bytesToFill; - allocation.filled[allocationIx] = true; - return bytesToFill; - } - return 0u; - }; - - // they are *very* small (<1% of the total request size) & negligible in size compared to buffers - at the end we must have them all anyway (explained in following comment) - const bool structuresSuballocated = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES] != InvalidAddress && allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES] != InvalidAddress; - - if (structuresSuballocated) // note that suballocated only means we have valid address(es) we can work on, it doesn't mean we filled anything (suballocated -> *can* fill) - { - auto* const indirectStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES]); - auto* const elementStructures = reinterpret_cast(mdiData + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]); - { - // I make a assumption here since I can access them later but I don't guarantee all of them will be present at the first run, we can fail buffer - // subalocations from the current memory block chunk which makes a command list invalid for the iteration! Because of that we fill them conditionally - // once buffers are correctly suballocated for handled command list - at the end we must have them all filled regardless what chunk their data come from due - // to the fact we cannot submit an overflow, we don't have dynamic rendering allowing us to stop recording the subpass, submit work to queue & start recording again - updateSuballocation((uint32_t)TightContent::INDIRECT_STRUCTURES); - updateSuballocation((uint32_t)TightContent::ELEMENT_STRUCTURES); - } - - uint32_t drawID = {}; - for (uint32_t i = 0u; i < drawData->CmdListsCount; i++) - { - const auto* commandList = drawData->CmdLists[i]; - const auto& [vertexBuffer, indexBuffer] = std::make_tuple(commandList->VtxBuffer, commandList->IdxBuffer); - const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple((uint32_t)TightContent::COUNT + 2u * i + 0u, (uint32_t)TightContent::COUNT + 2u * i + 1u); // look at SAllocationParams::offsets if you dont remember why like this + const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); + const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(0u, 1u); // check DrawItemMeta to see why auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) { - auto& offset = allocation.offsets[allocationIx]; const auto& bytesToFill = mdiLimits.sizes[allocationIx]; + auto& offset = drawItem.meta.offsets[allocationIx]; if (offset == InvalidAddress) return false; @@ -1259,7 +1322,7 @@ namespace nbl::ext::imgui auto validateObjectOffsets = [&]() -> bool { - const auto [vtxOffset, idxOffset] = std::make_tuple(allocation.offsets[vtxAllocationIx], allocation.offsets[idxAllocationIx]); + const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); bool ok = true; if (vtxOffset != InvalidAddress) @@ -1275,22 +1338,23 @@ namespace nbl::ext::imgui assert(validateObjectOffsets()); // debug check only - // we consider buffers valid for command list if we suballocated them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list with indirects is as well + // we consider buffers valid for command list if we suballocated them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); - const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(allocation.offsets[vtxAllocationIx] / sizeof(ImDrawVert), allocation.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); + const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); if (buffersSuballocated) { - for (uint32_t j = 0u; j < commandList->CmdBuffer.Size; j++) + for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) { - const auto* cmd = &commandList->CmdBuffer[j]; + const uint32_t drawID = drawItem.drawIdOffset + j; + + const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; auto* indirect = indirectStructures + drawID; auto* element = elementStructures + drawID; - indirect->indexCount = cmd->ElemCount; - // we use base instance as draw ID indirect->firstInstance = drawID; + indirect->indexCount = cmd->ElemCount; indirect->instanceCount = 1u; indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; @@ -1316,31 +1380,43 @@ namespace nbl::ext::imgui element->texId = cmd->TextureId.textureID; element->samplerIx = cmd->TextureId.samplerIx; - - ++drawID; } } - } + + return uploaded; + }; + + const size_t uploaded = upload(); + const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; + + totalUploadedSize += uploaded; + drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); } - return uploaded; - }; + } + }; - uploadedSize += upload(); - } - + if(!requiredStructsBlockInfo.allocated) + imCmdRange.allocateRequiredBlock(&m_mdi); + + // attempt to upload data only if we could allocate minimum of required indirect & element structs + if(requiredStructsBlockInfo.allocated) + std::for_each(imCmdRange.begin(), imCmdRange.end(), uploadCommandListData); + // we let it run at least once const bool timeout = std::chrono::steady_clock::now() >= waitPoint; if (timeout) { - if (uploadedSize >= mdiLimits.totalByteSizeRequest) + if (totalUploadedSize >= limits.totalByteSizeRequest) break; // must be lucky to hit it or on debug + imCmdRange.latchDeallocations(&m_mdi, waitInfo); streamingBuffer->cull_frees(); return false; } } - streamingBuffer->multi_deallocate(bigChunkRequestState.offsets.size(), bigChunkRequestState.offsets.data(), bigChunkRequestState.sizes.data(), waitInfo); //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required + //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required + imCmdRange.latchDeallocations(&m_mdi, waitInfo); } auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); @@ -1400,11 +1476,12 @@ namespace nbl::ext::imgui draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. */ + const auto& [structureOffsets, limits] = std::make_tuple(imCmdRange.getRequiredStructsBlockInfo().offsets, imCmdRange.getLimits()); { PushConstants constants { - .elementBDA = { mdiBuffer->getDeviceAddress() + allocation.offsets[(uint32_t)TightContent::ELEMENT_STRUCTURES]}, - .elementCount = { mdiLimits.totalIndirectDrawCount }, + .elementBDA = { mdiBuffer->getDeviceAddress() + structureOffsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES] }, + .elementCount = { limits.totalIndirectDrawCount }, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } @@ -1415,11 +1492,11 @@ namespace nbl::ext::imgui const SBufferBinding binding = { - .offset = allocation.offsets[(uint32_t)TightContent::INDIRECT_STRUCTURES], + .offset = structureOffsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES], .buffer = smart_refctd_ptr(mdiBuffer) }; - commandBuffer->drawIndexedIndirect(binding, mdiLimits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->drawIndexedIndirect(binding, limits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); } return true; From 5482fc4a21b902080d412eed33ff3e643cdf4354 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 7 Oct 2024 16:43:55 +0200 Subject: [PATCH 125/148] typo! --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 24efa82a9b..e2ca821d5e 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -124,7 +124,7 @@ namespace nbl::ext::imgui assert([&]() -> bool // we should never hit it { return (meta.offsets.size() == meta.sizes.size()) - && (meta.filled.size() == meta.filled.size()) + && (meta.filled.size() == meta.offsets.size()) && (std::reduce(std::begin(meta.sizes), std::end(meta.sizes)) == meta.totalLeftBytesToUpload) && (std::all_of(std::cbegin(meta.offsets), std::cend(meta.offsets), [](const auto& offset) { return offset == InvalidAddress; })); }()); // debug check only From ec05b2e856e0f14a35cebfad7ce7b7d867ff0392 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 7 Oct 2024 16:47:14 +0200 Subject: [PATCH 126/148] typo 2 --- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index e2ca821d5e..1a50ae8428 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1258,7 +1258,7 @@ namespace nbl::ext::imgui // not only memoryBlockFactor divided by 2 will fail us, but even without it we will always fail suballocator with one offset becauase delta is too tight // mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (drawItem.meta.totalLeftBytesToUpload /* * drawItem.meta.memoryBlockFactor*/)); - mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), limits.totalByteSizeRequest /* temporary giving MORE then required for suballocator because of padding.. */); + mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), limits.totalByteSizeRequest * drawItem.meta.memoryBlockFactor /* temporary giving MORE then required for suballocator because of padding.. */); //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill buffers const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); From 3c6babd9818166a658467caac9d3f0bafbf3ad16 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 7 Oct 2024 21:51:21 +0200 Subject: [PATCH 127/148] improve allocate strategy, serve tight chunks without wasting memory from which I can suballocate buffers with padding & alignments taken into account - but I still hit asserts when requesting streaming buffer's max_size after finishing render call --- src/nbl/ext/ImGui/ImGui.cpp | 196 ++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 1a50ae8428..b9a119831a 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -34,11 +34,19 @@ namespace nbl::ext::imgui struct DrawItemMeta { + enum SBufferIx + { + VERTEX, + INDEX, + + COUNT + }; + //! total left bytes to upload for X-th command list size_t totalLeftBytesToUpload; - //! we suballocate total of (command buffers count * 2u /*vertex & index buffers*/) buffers, we assume we have [ ... [vertex buffer, index buffer] ... ] - std::vector offsets, sizes, alignments; + //! we allocate SBufferIx::COUNT chunks per command list from which we suballocate to operate on + std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { sizeof(ImDrawVert), sizeof(ImDrawIdx) }; std::vector filled = { false, false }; //! those buffers will be suballocated & filled from a block of memory, each block memory request is multiplied with this factor - if a block fails to be allocated the factor decreases (divided by 2 on fail) @@ -96,7 +104,7 @@ namespace nbl::ext::imgui //! with our allocation strategy unless we split indirect call into smaller pieces (however it doesnt make any sense if we assume all objects must be uploaded anyway imo - if all then why to bother?), also there is a very low chance this memory block will ever exceed 1KB even if you have a lot of GUI windows (< 45 draw commands, 22 bytes * limits.totalIndirectDrawCount) since its very small. struct STightStructs { - // we have total COUNT of blocks to allocate first + // we have total StructureIx::COUNT of blocks to allocate first before uploading command lists data enum StructureIx { INDIRECT_STRUCTURES = 0u, @@ -117,9 +125,8 @@ namespace nbl::ext::imgui const ImDrawList* commandList = drawData->CmdLists[i]; limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; - - meta.offsets.emplace_back() = InvalidAddress; meta.totalLeftBytesToUpload += meta.sizes.emplace_back() = commandList->VtxBuffer.Size * sizeof(ImDrawVert); meta.alignments.emplace_back() = sizeof(ImDrawVert); - meta.offsets.emplace_back() = InvalidAddress; meta.totalLeftBytesToUpload += meta.sizes.emplace_back() = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); meta.alignments.emplace_back() = sizeof(ImDrawIdx); + meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::VERTEX] = commandList->VtxBuffer.Size * sizeof(ImDrawVert); + meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::INDEX] = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); assert([&]() -> bool // we should never hit it { @@ -145,29 +152,30 @@ namespace nbl::ext::imgui { requiredStructsBlockInfo.allocated = true; - auto blockOffset = InvalidAddress; - const auto blockSize = std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes))); + auto [blockOffset, blockSize] = std::make_tuple(InvalidAddress, std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes)))); mdi->compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &blockOffset, &blockSize, &MdiMaxAlignment); if (blockOffset == InvalidAddress) return (requiredStructsBlockInfo.allocated = false); + bigChunkRequestInfo.offsets.emplace_back() = blockOffset; + bigChunkRequestInfo.sizes.emplace_back() = blockSize; + auto* const mdiData = reinterpret_cast(mdi->compose->getBufferPointer()); mdi_buffer_t::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, blockOffset, 0u, MdiMaxAlignment, blockSize); mdi_buffer_t::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), requiredStructsBlockInfo.alignments.data()); for (const auto& offset : requiredStructsBlockInfo.offsets) if (offset == InvalidAddress) - requiredStructsBlockInfo.allocated = false; + return (requiredStructsBlockInfo.allocated) = false; return requiredStructsBlockInfo.allocated; } void latchDeallocations(mdi_buffer_t* mdi, ISemaphore::SWaitInfo waitInfo) { - deallocateRequiredBlock(mdi, waitInfo); // indirect & element structs - deallocateLeftChunks(mdi, waitInfo); // vertex & index buffers + mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); } inline const auto& getLimits() { return limits; } @@ -180,16 +188,6 @@ namespace nbl::ext::imgui private: - void deallocateRequiredBlock(mdi_buffer_t* mdi, ISemaphore::SWaitInfo& waitInfo) - { - mdi->compose->multi_deallocate(requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), waitInfo); - } - - void deallocateLeftChunks(mdi_buffer_t* mdi, ISemaphore::SWaitInfo& waitInfo) - { - mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); - } - struct SLimits { //! sum of metaList[x].sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects, note we don't count element & indirect structers there @@ -1251,102 +1249,114 @@ namespace nbl::ext::imgui auto* const indirectStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES]); auto* const elementStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES]); - if (drawItem.meta.totalLeftBytesToUpload >= 0u) - { - constexpr auto StreamingAllocationCount = 1u; - - // not only memoryBlockFactor divided by 2 will fail us, but even without it we will always fail suballocator with one offset becauase delta is too tight - // mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), (drawItem.meta.totalLeftBytesToUpload /* * drawItem.meta.memoryBlockFactor*/)); - - mdi_size_t chunkOffset = InvalidAddress, chunkSize = min(streamingBuffer->max_size(), limits.totalByteSizeRequest * drawItem.meta.memoryBlockFactor /* temporary giving MORE then required for suballocator because of padding.. */); - - //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill buffers - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), StreamingAllocationCount, &chunkOffset, &chunkSize, &MdiMaxAlignment); + const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); + const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(DrawItemMeta::VERTEX, DrawItemMeta::INDEX); - if (chunkOffset == InvalidAddress) - { - drawItem.meta.memoryBlockFactor *= 0.5f; // the problem is if another chunk failed because of SUBALLOCATOR then its because of padding probably.. decreasing the chunk request size wont help in that case and we should rather INCREASE the delta then with left bytes to upload for the suballocator to success with alignment restrictions - return; - } - else + if (drawItem.meta.totalLeftBytesToUpload >= 0u) + { + // we have 2 buffers to fill per command list, we will try with as tight streaming memory chunks as possible to not waste block memory (too big chunks allocated but only certain % used in reality) & make it a way our suballocator likes them (we respect required alignments) + for (uint16_t bufferIx = 0u; bufferIx < DrawItemMeta::COUNT; ++bufferIx) { - // chunk allocated? update the state & let suballocator do the job - chunksInfo.offsets.emplace_back() = chunkOffset; - chunksInfo.sizes.emplace_back() = chunkSize; - const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); + if (drawItem.meta.filled[bufferIx]) + continue; - //! (*) we create linear suballocator to fill the allocated chunk of memory - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); + const auto& bufferSizeTotalUploadRequest = drawItem.meta.sizes[bufferIx]; + auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + MdiMaxAlignment /* (**) add extra padding to let suballocator start at nice offset */)); - //! (*) we suballocate from the allocated chunk with required alignments - multi request all with single traits call per imgui command list - SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, drawItem.meta.offsets.size(), drawItem.meta.offsets.data(), drawItem.meta.sizes.data(), drawItem.meta.alignments.data()); + //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill buffers + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &chunkOffset, &chunkSize, &MdiMaxAlignment); - auto upload = [&]() -> size_t + if (chunkOffset == InvalidAddress) + return; + else { - size_t uploaded = {}; + // chunk allocated for a buffer? update the state & let suballocator do the job (we tried to waste minimum required memory and not leave empty space in the chunk) + chunksInfo.offsets.emplace_back() = chunkOffset; + chunksInfo.sizes.emplace_back() = chunkSize; + const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); // read (**) + + //! (*) we create linear suballocator to fill the allocated chunk of memory + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); - auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t + //! (*) we suballocate from the allocated chunk with required alignments + SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, 1u, drawItem.meta.offsets.data() + bufferIx, drawItem.meta.sizes.data() + bufferIx, drawItem.meta.alignments.data() + bufferIx); + + auto upload = [&]() -> size_t { - const bool isFilled = drawItem.meta.filled[allocationIx]; + size_t uploaded = {}; - if (!isFilled) + auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t { - const auto bytesToFill = drawItem.meta.sizes[allocationIx]; - uploaded += bytesToFill; - drawItem.meta.filled[allocationIx] = true; - return bytesToFill; - } + const bool isFilled = drawItem.meta.filled[allocationIx]; - return 0u; - }; + if (!isFilled) + { + const auto bytesToFill = drawItem.meta.sizes[allocationIx]; + uploaded += bytesToFill; + drawItem.meta.filled[allocationIx] = true; + return bytesToFill; + } - const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); - const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(0u, 1u); // check DrawItemMeta to see why + return 0u; + }; - auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) - { - auto& offset = drawItem.meta.offsets[allocationIx]; + auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) + { + auto& offset = drawItem.meta.offsets[allocationIx]; - if (offset == InvalidAddress) - return false; - else + if (offset == InvalidAddress) + return false; + else + { + const auto bytesToFill = updateSuballocation(allocationIx); + + if (bytesToFill != 0u) + ::memcpy(mdiData + offset, in, bytesToFill); + } + + return true; + }; + + auto validateObjectOffsets = [&]() -> bool { - const auto bytesToFill = updateSuballocation(allocationIx); + const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); + bool ok = true; - if (bytesToFill != 0u) - ::memcpy(mdiData + offset, in, bytesToFill); - } + if (vtxOffset != InvalidAddress) + ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); - return true; - }; + if (idxOffset != InvalidAddress) + ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); - auto validateObjectOffsets = [&]() -> bool - { - const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); - bool ok = true; + _NBL_BREAK_IF(!ok); - if (vtxOffset != InvalidAddress) - ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); + return ok; // if offsets are valid then must be aligned properly! + }; - if (idxOffset != InvalidAddress) - ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); + assert(validateObjectOffsets()); // debug check only - _NBL_BREAK_IF(!ok); + fillBuffer(vertexBuffer.Data, vtxAllocationIx); + fillBuffer(indexBuffer.Data, idxAllocationIx); - return ok; // if offsets are valid then must be aligned properly! + return uploaded; }; - assert(validateObjectOffsets()); // debug check only + const size_t uploaded = upload(); + const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; - // we consider buffers valid for command list if we suballocated them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well - const auto buffersSuballocated = fillBuffer(vertexBuffer.Data, vtxAllocationIx) && fillBuffer(indexBuffer.Data, idxAllocationIx); - const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = buffersSuballocated ? std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)) : std::make_tuple((size_t)0u, (size_t)0u); + totalUploadedSize += uploaded; + drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); - if (buffersSuballocated) + // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well + const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; + + if (buffersFilled) { + const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); + for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) { - const uint32_t drawID = drawItem.drawIdOffset + j; + const uint32_t drawID = drawItem.drawIdOffset + j; const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; auto* indirect = indirectStructures + drawID; @@ -1382,15 +1392,7 @@ namespace nbl::ext::imgui element->samplerIx = cmd->TextureId.samplerIx; } } - - return uploaded; - }; - - const size_t uploaded = upload(); - const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; - - totalUploadedSize += uploaded; - drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); + } } } }; From bed64ec7a47d1972facdff6c5f250db550c568a1 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 8 Oct 2024 08:39:34 +0200 Subject: [PATCH 128/148] lower the chunk padding to required minimum --- src/nbl/ext/ImGui/ImGui.cpp | 86 +++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index b9a119831a..50bbd399d2 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1251,6 +1251,7 @@ namespace nbl::ext::imgui const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(DrawItemMeta::VERTEX, DrawItemMeta::INDEX); + constexpr auto ChunkPaddings = std::to_array({ sizeof(ImDrawVert), sizeof(ImDrawIdx) }); if (drawItem.meta.totalLeftBytesToUpload >= 0u) { @@ -1261,22 +1262,25 @@ namespace nbl::ext::imgui continue; const auto& bufferSizeTotalUploadRequest = drawItem.meta.sizes[bufferIx]; - auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + MdiMaxAlignment /* (**) add extra padding to let suballocator start at nice offset */)); + const auto& requiredChunkPadding = ChunkPaddings[bufferIx]; - //! (*) note we request single tight chunk of memory with fixed max alignment - big address space from which we fill try to suballocate to fill buffers + /* (**) note we add extra requiredChunkPadding to let suballocator start at required multiple of size of packed object, this way suballocator always success if the block can be allocated */ + auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + requiredChunkPadding)); + + //! (*) the request therefore is tight & contains small padding for the suballocator to find the proper offset start const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &chunkOffset, &chunkSize, &MdiMaxAlignment); if (chunkOffset == InvalidAddress) return; else { - // chunk allocated for a buffer? update the state & let suballocator do the job (we tried to waste minimum required memory and not leave empty space in the chunk) + // chunk allocated for a buffer? update the state's offset table stack & let suballocator do the job (we made sure the only memory we "waste" is the padding part, at this point suballocator *should* always success) chunksInfo.offsets.emplace_back() = chunkOffset; chunksInfo.sizes.emplace_back() = chunkSize; - const auto alignOffsetNeeded = MdiMaxSize - (chunkOffset % MdiMaxSize); // read (**) + const auto alignOffsetRequired = requiredChunkPadding - (chunkOffset % requiredChunkPadding); // read (**), this is the key part //! (*) we create linear suballocator to fill the allocated chunk of memory - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetNeeded, MdiMaxAlignment, chunkSize); + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetRequired, MdiMaxAlignment, chunkSize); //! (*) we suballocate from the allocated chunk with required alignments SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, 1u, drawItem.meta.offsets.data() + bufferIx, drawItem.meta.sizes.data() + bufferIx, drawItem.meta.alignments.data() + bufferIx); @@ -1346,52 +1350,52 @@ namespace nbl::ext::imgui totalUploadedSize += uploaded; drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); + } + } - // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well - const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; - - if (buffersFilled) - { - const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); + // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well + const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; - for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) - { - const uint32_t drawID = drawItem.drawIdOffset + j; + if (buffersFilled) + { + const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); - const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; - auto* indirect = indirectStructures + drawID; - auto* element = elementStructures + drawID; + for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) + { + const uint32_t drawID = drawItem.drawIdOffset + j; - // we use base instance as draw ID - indirect->firstInstance = drawID; - indirect->indexCount = cmd->ElemCount; - indirect->instanceCount = 1u; - indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; - indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; + const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; + auto* indirect = indirectStructures + drawID; + auto* element = elementStructures + drawID; - const auto clipRectangle = clip.getClipRectangle(cmd); - const auto scissor = clip.getScissor(clipRectangle); + // we use base instance as draw ID + indirect->firstInstance = drawID; + indirect->indexCount = cmd->ElemCount; + indirect->instanceCount = 1u; + indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; + indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; - auto packSnorm16 = [](float ndc) -> int16_t - { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils - }; + const auto clipRectangle = clip.getClipRectangle(cmd); + const auto scissor = clip.getScissor(clipRectangle); - const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; - struct snorm16_t2_packed - { - int16_t x, y; - }; + const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + + struct snorm16_t2_packed + { + int16_t x, y; + }; - reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; - reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; + reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; + reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - element->texId = cmd->TextureId.textureID; - element->samplerIx = cmd->TextureId.samplerIx; - } - } + element->texId = cmd->TextureId.textureID; + element->samplerIx = cmd->TextureId.samplerIx; } } } From f09fb93af847771e0cca5a5f7eb7683b03ab3531 Mon Sep 17 00:00:00 2001 From: devsh Date: Wed, 9 Oct 2024 09:27:24 +0200 Subject: [PATCH 129/148] fix indentation --- src/nbl/ext/ImGui/ImGui.cpp | 2444 +++++++++++++++++------------------ 1 file changed, 1222 insertions(+), 1222 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 50bbd399d2..987974329d 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -23,1533 +23,1533 @@ using namespace nbl::hlsl; namespace nbl::ext::imgui { - using mdi_buffer_t = UI::SMdiBuffer; - using compose_t = typename mdi_buffer_t::compose_t; - using mdi_size_t = compose_t::size_type; +using mdi_buffer_t = UI::SMdiBuffer; +using compose_t = typename mdi_buffer_t::compose_t; +using mdi_size_t = compose_t::size_type; - static constexpr auto InvalidAddress = compose_t::invalid_value; - static constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); - static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); - static const auto MdiMaxAlignment = roundUpToPoT(MdiMaxSize); +static constexpr auto InvalidAddress = compose_t::invalid_value; +static constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); +static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); +static const auto MdiMaxAlignment = roundUpToPoT(MdiMaxSize); - struct DrawItemMeta +struct DrawItemMeta +{ + enum SBufferIx { - enum SBufferIx - { - VERTEX, - INDEX, + VERTEX, + INDEX, - COUNT - }; + COUNT + }; - //! total left bytes to upload for X-th command list - size_t totalLeftBytesToUpload; + //! total left bytes to upload for X-th command list + size_t totalLeftBytesToUpload; - //! we allocate SBufferIx::COUNT chunks per command list from which we suballocate to operate on - std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { sizeof(ImDrawVert), sizeof(ImDrawIdx) }; - std::vector filled = { false, false }; + //! we allocate SBufferIx::COUNT chunks per command list from which we suballocate to operate on + std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { sizeof(ImDrawVert), sizeof(ImDrawIdx) }; + std::vector filled = { false, false }; - //! those buffers will be suballocated & filled from a block of memory, each block memory request is multiplied with this factor - if a block fails to be allocated the factor decreases (divided by 2 on fail) - float memoryBlockFactor = 1.f; - }; + //! those buffers will be suballocated & filled from a block of memory, each block memory request is multiplied with this factor - if a block fails to be allocated the factor decreases (divided by 2 on fail) + float memoryBlockFactor = 1.f; +}; - struct DrawItem - { - ImDrawList* cmdList; - DrawItemMeta& meta; - uint32_t cmdListIndex, drawIdOffset; - }; +struct DrawItem +{ + ImDrawList* cmdList; + DrawItemMeta& meta; + uint32_t cmdListIndex, drawIdOffset; +}; + +class ImGuiCommandListIterator +{ +public: + using value_type = DrawItem; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + ImGuiCommandListIterator(const ImDrawData* drawData, std::vector& metaData, uint32_t index = 0u) + : drawData(drawData), metaList(metaData), index(index) {} - class ImGuiCommandListIterator + ImGuiCommandListIterator& operator++() { - public: - using value_type = DrawItem; - using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::forward_iterator_tag; + auto* currentList = drawData->CmdLists[index]; + drawIdOffset += currentList->CmdBuffer.Size; - ImGuiCommandListIterator(const ImDrawData* drawData, std::vector& metaData, uint32_t index = 0u) - : drawData(drawData), metaList(metaData), index(index) {} + ++index; + return *this; + } - ImGuiCommandListIterator& operator++() - { - auto* currentList = drawData->CmdLists[index]; - drawIdOffset += currentList->CmdBuffer.Size; + bool operator!=(const ImGuiCommandListIterator& other) const + { + return index != other.index; + } - ++index; - return *this; - } + value_type operator*() const + { + return { .cmdList = drawData->CmdLists[index], .meta = metaList[index], .cmdListIndex = index, .drawIdOffset = drawIdOffset }; + } - bool operator!=(const ImGuiCommandListIterator& other) const - { - return index != other.index; - } +private: + const ImDrawData* drawData; + std::vector& metaList; + uint32_t index = {}, drawIdOffset = {}; +}; - value_type operator*() const +class ImGuiCommandListRange +{ +public: + //! those structs we allocate within single block, in general we assume we fail entire render call if we cannot upload all indirect objects (no dynamic rendering) - its possible to have those structs in separate blocks & to tell vkCmdDrawIndexedIndirect about block strides but we cannot guarantee each one will be the same size + //! with our allocation strategy unless we split indirect call into smaller pieces (however it doesnt make any sense if we assume all objects must be uploaded anyway imo - if all then why to bother?), also there is a very low chance this memory block will ever exceed 1KB even if you have a lot of GUI windows (< 45 draw commands, 22 bytes * limits.totalIndirectDrawCount) since its very small. + struct STightStructs + { + // we have total StructureIx::COUNT of blocks to allocate first before uploading command lists data + enum StructureIx { - return { .cmdList = drawData->CmdLists[index], .meta = metaList[index], .cmdListIndex = index, .drawIdOffset = drawIdOffset }; - } + INDIRECT_STRUCTURES = 0u, + ELEMENT_STRUCTURES = 1u, + COUNT + }; - private: - const ImDrawData* drawData; - std::vector& metaList; - uint32_t index = {}, drawIdOffset = {}; + std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData) }; + bool allocated = false; }; - class ImGuiCommandListRange + ImGuiCommandListRange(const ImDrawData* drawData) + : drawData(drawData), metaList(drawData->CmdListsCount) { - public: - //! those structs we allocate within single block, in general we assume we fail entire render call if we cannot upload all indirect objects (no dynamic rendering) - its possible to have those structs in separate blocks & to tell vkCmdDrawIndexedIndirect about block strides but we cannot guarantee each one will be the same size - //! with our allocation strategy unless we split indirect call into smaller pieces (however it doesnt make any sense if we assume all objects must be uploaded anyway imo - if all then why to bother?), also there is a very low chance this memory block will ever exceed 1KB even if you have a lot of GUI windows (< 45 draw commands, 22 bytes * limits.totalIndirectDrawCount) since its very small. - struct STightStructs + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { - // we have total StructureIx::COUNT of blocks to allocate first before uploading command lists data - enum StructureIx - { - INDIRECT_STRUCTURES = 0u, - ELEMENT_STRUCTURES = 1u, - COUNT - }; + auto& meta = metaList[i]; + const ImDrawList* commandList = drawData->CmdLists[i]; - std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData) }; - bool allocated = false; - }; + limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; + meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::VERTEX] = commandList->VtxBuffer.Size * sizeof(ImDrawVert); + meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::INDEX] = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); - ImGuiCommandListRange(const ImDrawData* drawData) - : drawData(drawData), metaList(drawData->CmdListsCount) - { - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) + assert([&]() -> bool // we should never hit it { - auto& meta = metaList[i]; - const ImDrawList* commandList = drawData->CmdLists[i]; - - limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; - meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::VERTEX] = commandList->VtxBuffer.Size * sizeof(ImDrawVert); - meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::INDEX] = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); - - assert([&]() -> bool // we should never hit it - { - return (meta.offsets.size() == meta.sizes.size()) - && (meta.filled.size() == meta.offsets.size()) - && (std::reduce(std::begin(meta.sizes), std::end(meta.sizes)) == meta.totalLeftBytesToUpload) - && (std::all_of(std::cbegin(meta.offsets), std::cend(meta.offsets), [](const auto& offset) { return offset == InvalidAddress; })); - }()); // debug check only - } - - limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); - limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); - - requiredStructsBlockInfo.sizes[STightStructs::INDIRECT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand); - requiredStructsBlockInfo.sizes[STightStructs::ELEMENT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(PerObjectData); + return (meta.offsets.size() == meta.sizes.size()) + && (meta.filled.size() == meta.offsets.size()) + && (std::reduce(std::begin(meta.sizes), std::end(meta.sizes)) == meta.totalLeftBytesToUpload) + && (std::all_of(std::cbegin(meta.offsets), std::cend(meta.offsets), [](const auto& offset) { return offset == InvalidAddress; })); + }()); // debug check only } - inline ImGuiCommandListIterator begin() { return ImGuiCommandListIterator(drawData, metaList, 0u); } - inline ImGuiCommandListIterator end() { return ImGuiCommandListIterator(drawData, metaList, drawData->CmdListsCount); } + limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); + limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); - //! allocates a chunk from which STightStructs::COUNT blocks will be suballocated, required structs are indirects & elements - bool allocateRequiredBlock(mdi_buffer_t* mdi) - { - requiredStructsBlockInfo.allocated = true; + requiredStructsBlockInfo.sizes[STightStructs::INDIRECT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand); + requiredStructsBlockInfo.sizes[STightStructs::ELEMENT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(PerObjectData); + } - auto [blockOffset, blockSize] = std::make_tuple(InvalidAddress, std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes)))); + inline ImGuiCommandListIterator begin() { return ImGuiCommandListIterator(drawData, metaList, 0u); } + inline ImGuiCommandListIterator end() { return ImGuiCommandListIterator(drawData, metaList, drawData->CmdListsCount); } - mdi->compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &blockOffset, &blockSize, &MdiMaxAlignment); + //! allocates a chunk from which STightStructs::COUNT blocks will be suballocated, required structs are indirects & elements + bool allocateRequiredBlock(mdi_buffer_t* mdi) + { + requiredStructsBlockInfo.allocated = true; - if (blockOffset == InvalidAddress) - return (requiredStructsBlockInfo.allocated = false); + auto [blockOffset, blockSize] = std::make_tuple(InvalidAddress, std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes)))); - bigChunkRequestInfo.offsets.emplace_back() = blockOffset; - bigChunkRequestInfo.sizes.emplace_back() = blockSize; + mdi->compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &blockOffset, &blockSize, &MdiMaxAlignment); - auto* const mdiData = reinterpret_cast(mdi->compose->getBufferPointer()); - mdi_buffer_t::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, blockOffset, 0u, MdiMaxAlignment, blockSize); - mdi_buffer_t::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), requiredStructsBlockInfo.alignments.data()); + if (blockOffset == InvalidAddress) + return (requiredStructsBlockInfo.allocated = false); - for (const auto& offset : requiredStructsBlockInfo.offsets) - if (offset == InvalidAddress) - return (requiredStructsBlockInfo.allocated) = false; + bigChunkRequestInfo.offsets.emplace_back() = blockOffset; + bigChunkRequestInfo.sizes.emplace_back() = blockSize; - return requiredStructsBlockInfo.allocated; - } + auto* const mdiData = reinterpret_cast(mdi->compose->getBufferPointer()); + mdi_buffer_t::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, blockOffset, 0u, MdiMaxAlignment, blockSize); + mdi_buffer_t::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), requiredStructsBlockInfo.alignments.data()); - void latchDeallocations(mdi_buffer_t* mdi, ISemaphore::SWaitInfo waitInfo) - { - mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); - } + for (const auto& offset : requiredStructsBlockInfo.offsets) + if (offset == InvalidAddress) + return (requiredStructsBlockInfo.allocated) = false; - inline const auto& getLimits() { return limits; } - inline const auto& getRequiredStructsBlockInfo() { return requiredStructsBlockInfo; } + return requiredStructsBlockInfo.allocated; + } - struct - { - std::vector offsets, sizes; - } bigChunkRequestInfo; + void latchDeallocations(mdi_buffer_t* mdi, ISemaphore::SWaitInfo waitInfo) + { + mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); + } - private: + inline const auto& getLimits() { return limits; } + inline const auto& getRequiredStructsBlockInfo() { return requiredStructsBlockInfo; } - struct SLimits - { - //! sum of metaList[x].sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects, note we don't count element & indirect structers there - size_t totalByteSizeRequest = {}, + struct + { + std::vector offsets, sizes; + } bigChunkRequestInfo; - //! amount of total objects to draw with indirect indexed call - totalIndirectDrawCount = {}; - }; +private: - const ImDrawData* drawData; - std::vector metaList; + struct SLimits + { + //! sum of metaList[x].sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects, note we don't count element & indirect structers there + size_t totalByteSizeRequest = {}, - SLimits limits; - STightStructs requiredStructsBlockInfo; + //! amount of total objects to draw with indirect indexed call + totalIndirectDrawCount = {}; }; - static constexpr SPushConstantRange PushConstantRanges[] = - { - { - .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, - .offset = 0, - .size = sizeof(PushConstants) - } - }; + const ImDrawData* drawData; + std::vector metaList; - smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) + SLimits limits; + STightStructs requiredStructsBlockInfo; +}; + +static constexpr SPushConstantRange PushConstantRanges[] = +{ { - if (!utilities) - return nullptr; + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, + .offset = 0, + .size = sizeof(PushConstants) + } +}; - if (texturesInfo.bindingIx == samplersInfo.bindingIx) - return nullptr; +smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) +{ + if (!utilities) + return nullptr; - if (!texturesCount) - return nullptr; + if (texturesInfo.bindingIx == samplersInfo.bindingIx) + return nullptr; - smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + if (!texturesCount) + return nullptr; - using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - { - IGPUSampler::SParams params; - params.AnisotropicFilter = 1u; - params.TextureWrapU = ISampler::ETC_REPEAT; - params.TextureWrapV = ISampler::ETC_REPEAT; - params.TextureWrapW = ISampler::ETC_REPEAT; - - fontAtlasUISampler = utilities->getLogicalDevice()->createSampler(params); - fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); - } + smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; - { - IGPUSampler::SParams params; - params.MinLod = 0.f; - params.MaxLod = 0.f; - params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; - params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; - - userTexturesSampler = utilities->getLogicalDevice()->createSampler(params); - userTexturesSampler->setObjectDebugName("Nabla default ImGUI user texture sampler"); - } + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + { + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ISampler::ETC_REPEAT; + params.TextureWrapV = ISampler::ETC_REPEAT; + params.TextureWrapW = ISampler::ETC_REPEAT; + + fontAtlasUISampler = utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); + } - //! note we use immutable separate samplers and they are part of the descriptor set layout - std::array, (uint32_t)DefaultSamplerIx::COUNT> immutableSamplers; - immutableSamplers[(uint32_t)DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); - immutableSamplers[(uint32_t)DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); + { + IGPUSampler::SParams params; + params.MinLod = 0.f; + params.MaxLod = 0.f; + params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; + + userTexturesSampler = utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler->setObjectDebugName("Nabla default ImGUI user texture sampler"); + } - auto textureBinding = IGPUDescriptorSetLayout::SBinding - { - .binding = texturesInfo.bindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, - .createFlags = SResourceParameters::TexturesRequiredCreateFlags, - .stageFlags = SResourceParameters::RequiredShaderStageFlags, - .count = texturesCount - }; + //! note we use immutable separate samplers and they are part of the descriptor set layout + std::array, (uint32_t)DefaultSamplerIx::COUNT> immutableSamplers; + immutableSamplers[(uint32_t)DefaultSamplerIx::FONT_ATLAS] = smart_refctd_ptr(fontAtlasUISampler); + immutableSamplers[(uint32_t)DefaultSamplerIx::USER] = smart_refctd_ptr(userTexturesSampler); - auto samplersBinding = IGPUDescriptorSetLayout::SBinding - { - .binding = samplersInfo.bindingIx, - .type = IDescriptor::E_TYPE::ET_SAMPLER, - .createFlags = SResourceParameters::SamplersRequiredCreateFlags, - .stageFlags = SResourceParameters::RequiredShaderStageFlags, - .count = immutableSamplers.size(), - .immutableSamplers = immutableSamplers.data() - }; - - auto layouts = std::to_array>({ nullptr, nullptr, nullptr, nullptr }); + auto textureBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = texturesInfo.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = SResourceParameters::TexturesRequiredCreateFlags, + .stageFlags = SResourceParameters::RequiredShaderStageFlags, + .count = texturesCount + }; - if (texturesInfo.setIx == samplersInfo.setIx) - layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding, samplersBinding} }); - else - { - layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding} }); - layouts[samplersInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {samplersBinding} }); - } + auto samplersBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = samplersInfo.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLER, + .createFlags = SResourceParameters::SamplersRequiredCreateFlags, + .stageFlags = SResourceParameters::RequiredShaderStageFlags, + .count = immutableSamplers.size(), + .immutableSamplers = immutableSamplers.data() + }; - assert(layouts[texturesInfo.setIx]); - assert(layouts[samplersInfo.setIx]); + auto layouts = std::to_array>({ nullptr, nullptr, nullptr, nullptr }); - return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); + if (texturesInfo.setIx == samplersInfo.setIx) + layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding, samplersBinding} }); + else + { + layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding} }); + layouts[samplersInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {samplersBinding} }); } - const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) - { - assert(system); + assert(layouts[texturesInfo.setIx]); + assert(layouts[samplersInfo.setIx]); + + return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); +} + +const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) +{ + assert(system); - if(!system) - return nullptr; + if(!system) + return nullptr; + + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); + system->mount(smart_refctd_ptr(archive), archiveAlias.data()); - auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); - system->mount(smart_refctd_ptr(archive), archiveAlias.data()); + return smart_refctd_ptr(archive); +} - return smart_refctd_ptr(archive); +void UI::createPipeline(SCreationParameters& creationParams) +{ + auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); + + if (!pipelineLayout) + { + creationParams.utilities->getLogger()->log("Could not create pipeline layout!", ILogger::ELL_ERROR); + assert(false); } - void UI::createPipeline(SCreationParameters& creationParams) + struct { - auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); + smart_refctd_ptr vertex, fragment; + } shaders; - if (!pipelineLayout) - { - creationParams.utilities->getLogger()->log("Could not create pipeline layout!", ILogger::ELL_ERROR); - assert(false); - } + { + constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; + + //! proxy the system, we will touch it gently + auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); - struct - { - smart_refctd_ptr vertex, fragment; - } shaders; + //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); + auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); + auto includeLoader = includeFinder->getDefaultFileSystemLoader(); + includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); + auto createShader = [&]() -> smart_refctd_ptr { - constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; - - //! proxy the system, we will touch it gently - auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); + IAssetLoader::SAssetLoadParams params = {}; + params.logger = creationParams.utilities->getLogger(); + params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); - //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV - auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); - auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); - auto includeLoader = includeFinder->getDefaultFileSystemLoader(); - includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); + auto bundle = creationParams.assetManager->getAsset(key.value, params); + const auto assets = bundle.getContents(); - auto createShader = [&]() -> smart_refctd_ptr + if (assets.empty()) { - IAssetLoader::SAssetLoadParams params = {}; - params.logger = creationParams.utilities->getLogger(); - params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); - - auto bundle = creationParams.assetManager->getAsset(key.value, params); - const auto assets = bundle.getContents(); - - if (assets.empty()) - { - creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", ILogger::ELL_ERROR, key.value); - return nullptr; - } + creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", ILogger::ELL_ERROR, key.value); + return nullptr; + } - const auto shader = IAsset::castDown(assets[0]); + const auto shader = IAsset::castDown(assets[0]); - CHLSLCompiler::SOptions options = {}; - options.stage = stage; - options.preprocessorOptions.sourceIdentifier = key.value; - options.preprocessorOptions.logger = creationParams.utilities->getLogger(); - options.preprocessorOptions.includeFinder = includeFinder.get(); + CHLSLCompiler::SOptions options = {}; + options.stage = stage; + options.preprocessorOptions.sourceIdentifier = key.value; + options.preprocessorOptions.logger = creationParams.utilities->getLogger(); + options.preprocessorOptions.includeFinder = includeFinder.get(); - auto compileToSPIRV = [&]() -> smart_refctd_ptr + auto compileToSPIRV = [&]() -> smart_refctd_ptr + { + auto toOptions = [](const std::array& in) // options must be alive till compileToSPIRV ends { - auto toOptions = [](const std::array& in) // options must be alive till compileToSPIRV ends - { - const auto required = CHLSLCompiler::getRequiredArguments(); - std::array options; - - std::wstring_convert> converter; - for (uint32_t i = 0; i < required.size(); ++i) - options[i] = converter.to_bytes(required[i]); // meh + const auto required = CHLSLCompiler::getRequiredArguments(); + std::array options; - uint32_t offset = required.size(); - for (const auto& opt : in) - options[offset++] = std::string(opt); + std::wstring_convert> converter; + for (uint32_t i = 0; i < required.size(); ++i) + options[i] = converter.to_bytes(required[i]); // meh - return options; - }; - - const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); - - if constexpr (stage == IShader::E_SHADER_STAGE::ESS_VERTEX) - { - const auto VERTEX_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "vs_6_7", "-E", "VSMain", "-O3" })); - options.dxcOptions = VERTEX_COMPILE_OPTIONS; + uint32_t offset = required.size(); + for (const auto& opt : in) + options[offset++] = std::string(opt); - return compiler->compileToSPIRV(code.data(), options); // we good here - no code patching - } - else if (stage == IShader::E_SHADER_STAGE::ESS_FRAGMENT) - { - const auto FRAGMENT_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "ps_6_7", "-E", "PSMain", "-O3" })); - options.dxcOptions = FRAGMENT_COMPILE_OPTIONS; - - std::stringstream stream; - - stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" - << "#define NBL_TEXTURES_BINDING_IX " << creationParams.resources.texturesInfo.bindingIx << "\n" - << "#define NBL_SAMPLER_STATES_BINDING_IX " << creationParams.resources.samplersInfo.bindingIx << "\n" - << "#define NBL_TEXTURES_SET_IX " << creationParams.resources.texturesInfo.setIx << "\n" - << "#define NBL_SAMPLER_STATES_SET_IX " << creationParams.resources.samplersInfo.setIx << "\n" - << "#define NBL_TEXTURES_COUNT " << creationParams.resources.texturesCount << "\n" - << "#define NBL_SAMPLERS_COUNT " << creationParams.resources.samplersCount << "\n" - << "// <-\n\n"; - - const auto newCode = stream.str() + std::string(code); - return compiler->compileToSPIRV(newCode.c_str(), options); // but here we do patch the code with additional define directives for which values are taken from the creation parameters - } - else - { - static_assert(stage != IShader::E_SHADER_STAGE::ESS_UNKNOWN, "Unknown shader stage!"); - return nullptr; - } + return options; }; - auto spirv = compileToSPIRV(); + const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); + + if constexpr (stage == IShader::E_SHADER_STAGE::ESS_VERTEX) + { + const auto VERTEX_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "vs_6_7", "-E", "VSMain", "-O3" })); + options.dxcOptions = VERTEX_COMPILE_OPTIONS; - if (!spirv) + return compiler->compileToSPIRV(code.data(), options); // we good here - no code patching + } + else if (stage == IShader::E_SHADER_STAGE::ESS_FRAGMENT) { - creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", ILogger::ELL_ERROR, key.value); + const auto FRAGMENT_COMPILE_OPTIONS = toOptions(std::to_array({ "-T", "ps_6_7", "-E", "PSMain", "-O3" })); + options.dxcOptions = FRAGMENT_COMPILE_OPTIONS; + + std::stringstream stream; + + stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" + << "#define NBL_TEXTURES_BINDING_IX " << creationParams.resources.texturesInfo.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING_IX " << creationParams.resources.samplersInfo.bindingIx << "\n" + << "#define NBL_TEXTURES_SET_IX " << creationParams.resources.texturesInfo.setIx << "\n" + << "#define NBL_SAMPLER_STATES_SET_IX " << creationParams.resources.samplersInfo.setIx << "\n" + << "#define NBL_TEXTURES_COUNT " << creationParams.resources.texturesCount << "\n" + << "#define NBL_SAMPLERS_COUNT " << creationParams.resources.samplersCount << "\n" + << "// <-\n\n"; + + const auto newCode = stream.str() + std::string(code); + return compiler->compileToSPIRV(newCode.c_str(), options); // but here we do patch the code with additional define directives for which values are taken from the creation parameters + } + else + { + static_assert(stage != IShader::E_SHADER_STAGE::ESS_UNKNOWN, "Unknown shader stage!"); return nullptr; } + }; - auto gpu = creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); + auto spirv = compileToSPIRV(); - if (!gpu) - creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", ILogger::ELL_ERROR, key.value); + if (!spirv) + { + creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", ILogger::ELL_ERROR, key.value); + return nullptr; + } - return gpu; - }; + auto gpu = creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); - //! we assume user has all Nabla builtins mounted - we don't check it at release - assert(system->areBuiltinsMounted()); + if (!gpu) + creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", ILogger::ELL_ERROR, key.value); - //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it - auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); - shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); - shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); - system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); + return gpu; + }; - assert(shaders.vertex); - assert(shaders.fragment); - } + //! we assume user has all Nabla builtins mounted - we don't check it at release + assert(system->areBuiltinsMounted()); + + //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it + auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); + shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); + shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); + system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); + + assert(shaders.vertex); + assert(shaders.fragment); + } - SVertexInputParams vertexInputParams{}; - { - vertexInputParams.enabledBindingFlags = 0b1u; - vertexInputParams.enabledAttribFlags = 0b111u; - - vertexInputParams.bindings[0].inputRate = SVertexInputBindingParams::EVIR_PER_VERTEX; - vertexInputParams.bindings[0].stride = sizeof(ImDrawVert); - - auto& position = vertexInputParams.attributes[0]; - position.format = EF_R32G32_SFLOAT; - position.relativeOffset = offsetof(ImDrawVert, pos); - position.binding = 0u; - - auto& uv = vertexInputParams.attributes[1]; - uv.format = EF_R32G32_SFLOAT; - uv.relativeOffset = offsetof(ImDrawVert, uv); - uv.binding = 0u; - - auto& color = vertexInputParams.attributes[2]; - color.format = EF_R8G8B8A8_UNORM; - color.relativeOffset = offsetof(ImDrawVert, col); - color.binding = 0u; - } + SVertexInputParams vertexInputParams{}; + { + vertexInputParams.enabledBindingFlags = 0b1u; + vertexInputParams.enabledAttribFlags = 0b111u; + + vertexInputParams.bindings[0].inputRate = SVertexInputBindingParams::EVIR_PER_VERTEX; + vertexInputParams.bindings[0].stride = sizeof(ImDrawVert); + + auto& position = vertexInputParams.attributes[0]; + position.format = EF_R32G32_SFLOAT; + position.relativeOffset = offsetof(ImDrawVert, pos); + position.binding = 0u; + + auto& uv = vertexInputParams.attributes[1]; + uv.format = EF_R32G32_SFLOAT; + uv.relativeOffset = offsetof(ImDrawVert, uv); + uv.binding = 0u; + + auto& color = vertexInputParams.attributes[2]; + color.format = EF_R8G8B8A8_UNORM; + color.relativeOffset = offsetof(ImDrawVert, col); + color.binding = 0u; + } - SBlendParams blendParams{}; - { - blendParams.logicOp = ELO_NO_OP; + SBlendParams blendParams{}; + { + blendParams.logicOp = ELO_NO_OP; - auto& param = blendParams.blendParams[0]; + auto& param = blendParams.blendParams[0]; - // color blending factors (for RGB) - param.srcColorFactor = EBF_SRC_ALPHA; - param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA; - param.colorBlendOp = EBO_ADD; + // color blending factors (for RGB) + param.srcColorFactor = EBF_SRC_ALPHA; + param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.colorBlendOp = EBO_ADD; - // alpha blending factors (for A) - param.srcAlphaFactor = EBF_ONE; - param.dstAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA; - param.alphaBlendOp = EBO_ADD; + // alpha blending factors (for A) + param.srcAlphaFactor = EBF_ONE; + param.dstAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.alphaBlendOp = EBO_ADD; - // Write all components (R, G, B, A) - param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u); - } + // Write all components (R, G, B, A) + param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u); + } - SRasterizationParams rasterizationParams{}; - { - rasterizationParams.faceCullingMode = EFCM_NONE; - rasterizationParams.depthWriteEnable = false; - rasterizationParams.depthBoundsTestEnable = false; - rasterizationParams.viewportCount = creationParams.viewportCount; - } + SRasterizationParams rasterizationParams{}; + { + rasterizationParams.faceCullingMode = EFCM_NONE; + rasterizationParams.depthWriteEnable = false; + rasterizationParams.depthBoundsTestEnable = false; + rasterizationParams.viewportCount = creationParams.viewportCount; + } - SPrimitiveAssemblyParams primitiveAssemblyParams{}; - { - primitiveAssemblyParams.primitiveType = EPT_TRIANGLE_LIST; - } + SPrimitiveAssemblyParams primitiveAssemblyParams{}; + { + primitiveAssemblyParams.primitiveType = EPT_TRIANGLE_LIST; + } + { + const IGPUShader::SSpecInfo specs[] = { - const IGPUShader::SSpecInfo specs[] = - { - { .entryPoint = "VSMain", .shader = shaders.vertex.get() }, - { .entryPoint = "PSMain", .shader = shaders.fragment.get() } - }; + { .entryPoint = "VSMain", .shader = shaders.vertex.get() }, + { .entryPoint = "PSMain", .shader = shaders.fragment.get() } + }; - IGPUGraphicsPipeline::SCreationParams params[1]; - { - auto& param = params[0u]; - param.layout = pipelineLayout.get(); - param.shaders = specs; - param.renderpass = creationParams.renderpass.get(); - param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = creationParams.subpassIx }; - }; + IGPUGraphicsPipeline::SCreationParams params[1]; + { + auto& param = params[0u]; + param.layout = pipelineLayout.get(); + param.shaders = specs; + param.renderpass = creationParams.renderpass.get(); + param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = creationParams.subpassIx }; + }; - if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &m_pipeline)) - { - creationParams.utilities->getLogger()->log("Could not create pipeline!", ILogger::ELL_ERROR); - assert(false); - } + if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &m_pipeline)) + { + creationParams.utilities->getLogger()->log("Could not create pipeline!", ILogger::ELL_ERROR); + assert(false); } } +} - ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) - { - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. - // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. - // - Read 'docs/FONTS.md' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); - ImGuiIO& io = ImGui::GetIO(); - - // TODO: don't `pixels` need to be freed somehow!? (Use a uniqueptr with custom deleter lambda) - uint8_t* pixels = nullptr; - int32_t width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - SImResourceInfo info; - info.textureID = FontAtlasTexId; - info.samplerIx = FontAtlasSamplerId; - - io.Fonts->SetTexID(info); - - if (!pixels || width<=0 || height<=0) - return IQueue::RESULT::OTHER_ERROR; - - const size_t componentsCount = 4, image_size = width * height * componentsCount * sizeof(uint8_t); +ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) +{ + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //io.Fonts->AddFontDefault(); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); + ImGuiIO& io = ImGui::GetIO(); + + // TODO: don't `pixels` need to be freed somehow!? (Use a uniqueptr with custom deleter lambda) + uint8_t* pixels = nullptr; + int32_t width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + SImResourceInfo info; + info.textureID = FontAtlasTexId; + info.samplerIx = FontAtlasSamplerId; + + io.Fonts->SetTexID(info); + + if (!pixels || width<=0 || height<=0) + return IQueue::RESULT::OTHER_ERROR; + + const size_t componentsCount = 4, image_size = width * height * componentsCount * sizeof(uint8_t); - _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; - const auto buffer = make_smart_refctd_ptr< CCustomAllocatorCPUBuffer, true> >(image_size, pixels, adopt_memory); + _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; + const auto buffer = make_smart_refctd_ptr< CCustomAllocatorCPUBuffer, true> >(image_size, pixels, adopt_memory); - IGPUImage::SCreationParams params; - params.flags = static_cast(0u); - params.type = IImage::ET_2D; - params.format = NBL_FORMAT_FONT; - params.extent = { static_cast(width), static_cast(height), 1u }; - params.mipLevels = 1; - params.arrayLayers = 1u; - params.samples = IImage::ESCF_1_BIT; - params.usage |= IGPUImage::EUF_TRANSFER_DST_BIT | IGPUImage::EUF_SAMPLED_BIT | IGPUImage::E_USAGE_FLAGS::EUF_TRANSFER_SRC_BIT; // do you really need the SRC bit? - - struct - { - smart_refctd_dynamic_array data = make_refctd_dynamic_array>(1ull); - SRange range = { data->begin(), data->end() }; - IImage::SSubresourceRange subresource = - { - .aspectMask = IImage::EAF_COLOR_BIT, - .baseMipLevel = 0u, - .levelCount = 1u, - .baseArrayLayer = 0u, - .layerCount = 1u - }; - } regions; + IGPUImage::SCreationParams params; + params.flags = static_cast(0u); + params.type = IImage::ET_2D; + params.format = NBL_FORMAT_FONT; + params.extent = { static_cast(width), static_cast(height), 1u }; + params.mipLevels = 1; + params.arrayLayers = 1u; + params.samples = IImage::ESCF_1_BIT; + params.usage |= IGPUImage::EUF_TRANSFER_DST_BIT | IGPUImage::EUF_SAMPLED_BIT | IGPUImage::E_USAGE_FLAGS::EUF_TRANSFER_SRC_BIT; // do you really need the SRC bit? + + struct + { + smart_refctd_dynamic_array data = make_refctd_dynamic_array>(1ull); + SRange range = { data->begin(), data->end() }; + IImage::SSubresourceRange subresource = { - auto* region = regions.data->begin(); - region->bufferOffset = 0ull; - region->bufferRowLength = params.extent.width; - region->bufferImageHeight = 0u; - region->imageSubresource = {}; - region->imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; - region->imageSubresource.layerCount = 1u; - region->imageOffset = { 0u, 0u, 0u }; - region->imageExtent = { params.extent.width, params.extent.height, 1u }; - } + .aspectMask = IImage::EAF_COLOR_BIT, + .baseMipLevel = 0u, + .levelCount = 1u, + .baseArrayLayer = 0u, + .layerCount = 1u + }; + } regions; + { + auto* region = regions.data->begin(); + region->bufferOffset = 0ull; + region->bufferRowLength = params.extent.width; + region->bufferImageHeight = 0u; + region->imageSubresource = {}; + region->imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; + region->imageSubresource.layerCount = 1u; + region->imageOffset = { 0u, 0u, 0u }; + region->imageExtent = { params.extent.width, params.extent.height, 1u }; + } - auto image = creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); + auto image = creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); - if (!image) - { - creationParams.utilities->getLogger()->log("Could not create font image!", ILogger::ELL_ERROR); - return IQueue::RESULT::OTHER_ERROR; - } - image->setObjectDebugName("Nabla ImGUI default font"); + if (!image) + { + creationParams.utilities->getLogger()->log("Could not create font image!", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; + } + image->setObjectDebugName("Nabla ImGUI default font"); + + if (!creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) + { + creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; + } - if (!creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) + SIntendedSubmitInfo sInfo; + { + IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; + + auto scratchSemaphore = creationParams.utilities->getLogicalDevice()->createSemaphore(0); + if (!scratchSemaphore) { - creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not create scratch semaphore", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } + scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - SIntendedSubmitInfo sInfo; + sInfo.queue = creationParams.transfer; + sInfo.waitSemaphores = {}; + sInfo.commandBuffers = { &cmdInfo, 1 }; + sInfo.scratchSemaphore = { - IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; - - auto scratchSemaphore = creationParams.utilities->getLogicalDevice()->createSemaphore(0); - if (!scratchSemaphore) - { - creationParams.utilities->getLogger()->log("Could not create scratch semaphore", ILogger::ELL_ERROR); - return IQueue::RESULT::OTHER_ERROR; - } - scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - - sInfo.queue = creationParams.transfer; - sInfo.waitSemaphores = {}; - sInfo.commandBuffers = { &cmdInfo, 1 }; - sInfo.scratchSemaphore = - { - .semaphore = scratchSemaphore.get(), - .value = 0u, - .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS - }; + .semaphore = scratchSemaphore.get(), + .value = 0u, + .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS + }; - // we have no explicit source stage and access to sync against, brand new clean image. - const SMemoryBarrier toTransferDep = { - .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, - .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, - }; - const auto transferLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL; - // transition to TRANSFER_DST - IGPUCommandBuffer::SImageMemoryBarrier barriers[] = - { - { - .barrier = {.dep = toTransferDep}, - .image = image.get(), - .subresourceRange = regions.subresource, - .oldLayout = IGPUImage::LAYOUT::UNDEFINED, // wiping transition - .newLayout = transferLayout - } - }; - - cmdBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); - // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. - // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + // we have no explicit source stage and access to sync against, brand new clean image. + const SMemoryBarrier toTransferDep = { + .dstStageMask = PIPELINE_STAGE_FLAGS::COPY_BIT, + .dstAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, + }; + const auto transferLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL; + // transition to TRANSFER_DST + IGPUCommandBuffer::SImageMemoryBarrier barriers[] = + { { - creationParams.utilities->getLogger()->log("Could not upload font image contents", ILogger::ELL_ERROR); - return IQueue::RESULT::OTHER_ERROR; - } + .barrier = {.dep = toTransferDep}, + .image = image.get(), + .subresourceRange = regions.subresource, + .oldLayout = IGPUImage::LAYOUT::UNDEFINED, // wiping transition + .newLayout = transferLayout + } + }; - // we only need to sync with semaphore signal - barriers[0].barrier.dep = toTransferDep.nextBarrier(sInfo.scratchSemaphore.stageMask,ACCESS_FLAGS::NONE); - // transition to READ_ONLY_OPTIMAL ready for rendering with sampling - barriers[0].oldLayout = barriers[0].newLayout; - barriers[0].newLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; - cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); - cmdBuffer->end(); + cmdBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); + // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. + // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away + if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + { + creationParams.utilities->getLogger()->log("Could not upload font image contents", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; + } - const auto submit = sInfo.popSubmit({}); - if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) - { - creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", ILogger::ELL_ERROR); - return IQueue::RESULT::OTHER_ERROR; - } + // we only need to sync with semaphore signal + barriers[0].barrier.dep = toTransferDep.nextBarrier(sInfo.scratchSemaphore.stageMask,ACCESS_FLAGS::NONE); + // transition to READ_ONLY_OPTIMAL ready for rendering with sampling + barriers[0].oldLayout = barriers[0].newLayout; + barriers[0].newLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; + cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); + cmdBuffer->end(); + + const auto submit = sInfo.popSubmit({}); + if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) + { + creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; } + } - { - IGPUImageView::SCreationParams params; - params.format = image->getCreationParameters().format; - params.viewType = IImageView::ET_2D; - params.subresourceRange = regions.subresource; - params.image = smart_refctd_ptr(image); + { + IGPUImageView::SCreationParams params; + params.format = image->getCreationParameters().format; + params.viewType = IImageView::ET_2D; + params.subresourceRange = regions.subresource; + params.image = smart_refctd_ptr(image); - m_fontAtlasTexture = creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); - } - - ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); - retval.set({sInfo.scratchSemaphore.semaphore,sInfo.scratchSemaphore.value}); - return retval; + m_fontAtlasTexture = creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } + + ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); + retval.set({sInfo.scratchSemaphore.semaphore,sInfo.scratchSemaphore.value}); + return retval; +} - void UI::handleMouseEvents(const SUpdateParameters& params) const - { - auto& io = ImGui::GetIO(); +void UI::handleMouseEvents(const SUpdateParameters& params) const +{ + auto& io = ImGui::GetIO(); - io.AddMousePosEvent(params.mousePosition.x, params.mousePosition.y); + io.AddMousePosEvent(params.mousePosition.x, params.mousePosition.y); - for (const auto& e : params.mouseEvents) + for (const auto& e : params.mouseEvents) + { + switch (e.type) { - switch (e.type) - { - case SMouseEvent::EET_CLICK: - { - ImGuiMouseButton_ button = ImGuiMouseButton_COUNT; - if (e.clickEvent.mouseButton == EMB_LEFT_BUTTON) - button = ImGuiMouseButton_Left; - else if (e.clickEvent.mouseButton == EMB_RIGHT_BUTTON) - button = ImGuiMouseButton_Right; - else if (e.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) - button = ImGuiMouseButton_Middle; - - if (button == ImGuiMouseButton_COUNT) - continue; - - if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) - io.AddMouseButtonEvent(button, true); - else if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) - io.AddMouseButtonEvent(button, false); - } break; - - case SMouseEvent::EET_SCROLL: - { - _NBL_STATIC_INLINE_CONSTEXPR auto scalar = 0.02f; - const auto wheel = float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; + case SMouseEvent::EET_CLICK: + { + ImGuiMouseButton_ button = ImGuiMouseButton_COUNT; + if (e.clickEvent.mouseButton == EMB_LEFT_BUTTON) + button = ImGuiMouseButton_Left; + else if (e.clickEvent.mouseButton == EMB_RIGHT_BUTTON) + button = ImGuiMouseButton_Right; + else if (e.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) + button = ImGuiMouseButton_Middle; + + if (button == ImGuiMouseButton_COUNT) + continue; + + if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) + io.AddMouseButtonEvent(button, true); + else if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) + io.AddMouseButtonEvent(button, false); + } break; + + case SMouseEvent::EET_SCROLL: + { + _NBL_STATIC_INLINE_CONSTEXPR auto scalar = 0.02f; + const auto wheel = float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; - io.AddMouseWheelEvent(wheel.x, wheel.y); - } break; + io.AddMouseWheelEvent(wheel.x, wheel.y); + } break; - case SMouseEvent::EET_MOVEMENT: + case SMouseEvent::EET_MOVEMENT: - default: - break; - } + default: + break; } } +} - struct NBL_TO_IMGUI_KEY_BIND - { - ImGuiKey target; - char physicalSmall; - char physicalBig; - }; - - // maps Nabla keys to IMGUIs - _NBL_STATIC_INLINE_CONSTEXPR std::array createKeyMap() - { - std::array map = { { NBL_TO_IMGUI_KEY_BIND{ImGuiKey_None, '0', '0'} } }; - - #define NBL_REGISTER_KEY(__NBL_KEY__, __IMGUI_KEY__) \ - map[__NBL_KEY__] = NBL_TO_IMGUI_KEY_BIND{__IMGUI_KEY__, keyCodeToChar(__NBL_KEY__, false), keyCodeToChar(__NBL_KEY__, true)}; - - NBL_REGISTER_KEY(EKC_BACKSPACE, ImGuiKey_Backspace); - NBL_REGISTER_KEY(EKC_TAB, ImGuiKey_Tab); - NBL_REGISTER_KEY(EKC_ENTER, ImGuiKey_Enter); - NBL_REGISTER_KEY(EKC_LEFT_SHIFT, ImGuiKey_LeftShift); - NBL_REGISTER_KEY(EKC_RIGHT_SHIFT, ImGuiKey_RightShift); - NBL_REGISTER_KEY(EKC_LEFT_CONTROL, ImGuiKey_LeftCtrl); - NBL_REGISTER_KEY(EKC_RIGHT_CONTROL, ImGuiKey_RightCtrl); - NBL_REGISTER_KEY(EKC_LEFT_ALT, ImGuiKey_LeftAlt); - NBL_REGISTER_KEY(EKC_RIGHT_ALT, ImGuiKey_RightAlt); - NBL_REGISTER_KEY(EKC_PAUSE, ImGuiKey_Pause); - NBL_REGISTER_KEY(EKC_CAPS_LOCK, ImGuiKey_CapsLock); - NBL_REGISTER_KEY(EKC_ESCAPE, ImGuiKey_Escape); - NBL_REGISTER_KEY(EKC_SPACE, ImGuiKey_Space); - NBL_REGISTER_KEY(EKC_PAGE_UP, ImGuiKey_PageUp); - NBL_REGISTER_KEY(EKC_PAGE_DOWN, ImGuiKey_PageDown); - NBL_REGISTER_KEY(EKC_END, ImGuiKey_End); - NBL_REGISTER_KEY(EKC_HOME, ImGuiKey_Home); - NBL_REGISTER_KEY(EKC_LEFT_ARROW, ImGuiKey_LeftArrow); - NBL_REGISTER_KEY(EKC_RIGHT_ARROW, ImGuiKey_RightArrow); - NBL_REGISTER_KEY(EKC_DOWN_ARROW, ImGuiKey_DownArrow); - NBL_REGISTER_KEY(EKC_UP_ARROW, ImGuiKey_UpArrow); - NBL_REGISTER_KEY(EKC_PRINT_SCREEN, ImGuiKey_PrintScreen); - NBL_REGISTER_KEY(EKC_INSERT, ImGuiKey_Insert); - NBL_REGISTER_KEY(EKC_DELETE, ImGuiKey_Delete); - NBL_REGISTER_KEY(EKC_APPS, ImGuiKey_Menu); - NBL_REGISTER_KEY(EKC_COMMA, ImGuiKey_Comma); - NBL_REGISTER_KEY(EKC_PERIOD, ImGuiKey_Period); - NBL_REGISTER_KEY(EKC_SEMICOLON, ImGuiKey_Semicolon); - NBL_REGISTER_KEY(EKC_OPEN_BRACKET, ImGuiKey_LeftBracket); - NBL_REGISTER_KEY(EKC_CLOSE_BRACKET, ImGuiKey_RightBracket); - NBL_REGISTER_KEY(EKC_BACKSLASH, ImGuiKey_Backslash); - NBL_REGISTER_KEY(EKC_APOSTROPHE, ImGuiKey_Apostrophe); - NBL_REGISTER_KEY(EKC_ADD, ImGuiKey_KeypadAdd); - NBL_REGISTER_KEY(EKC_SUBTRACT, ImGuiKey_KeypadSubtract); - NBL_REGISTER_KEY(EKC_MULTIPLY, ImGuiKey_KeypadMultiply); - NBL_REGISTER_KEY(EKC_DIVIDE, ImGuiKey_KeypadDivide); - NBL_REGISTER_KEY(EKC_0, ImGuiKey_0); - NBL_REGISTER_KEY(EKC_1, ImGuiKey_1); - NBL_REGISTER_KEY(EKC_2, ImGuiKey_2); - NBL_REGISTER_KEY(EKC_3, ImGuiKey_3); - NBL_REGISTER_KEY(EKC_4, ImGuiKey_4); - NBL_REGISTER_KEY(EKC_5, ImGuiKey_5); - NBL_REGISTER_KEY(EKC_6, ImGuiKey_6); - NBL_REGISTER_KEY(EKC_7, ImGuiKey_7); - NBL_REGISTER_KEY(EKC_8, ImGuiKey_8); - NBL_REGISTER_KEY(EKC_9, ImGuiKey_9); - NBL_REGISTER_KEY(EKC_A, ImGuiKey_A); - NBL_REGISTER_KEY(EKC_B, ImGuiKey_B); - NBL_REGISTER_KEY(EKC_C, ImGuiKey_C); - NBL_REGISTER_KEY(EKC_D, ImGuiKey_D); - NBL_REGISTER_KEY(EKC_E, ImGuiKey_E); - NBL_REGISTER_KEY(EKC_F, ImGuiKey_F); - NBL_REGISTER_KEY(EKC_G, ImGuiKey_G); - NBL_REGISTER_KEY(EKC_H, ImGuiKey_H); - NBL_REGISTER_KEY(EKC_I, ImGuiKey_I); - NBL_REGISTER_KEY(EKC_J, ImGuiKey_J); - NBL_REGISTER_KEY(EKC_K, ImGuiKey_K); - NBL_REGISTER_KEY(EKC_L, ImGuiKey_L); - NBL_REGISTER_KEY(EKC_M, ImGuiKey_M); - NBL_REGISTER_KEY(EKC_N, ImGuiKey_N); - NBL_REGISTER_KEY(EKC_O, ImGuiKey_O); - NBL_REGISTER_KEY(EKC_P, ImGuiKey_P); - NBL_REGISTER_KEY(EKC_Q, ImGuiKey_Q); - NBL_REGISTER_KEY(EKC_R, ImGuiKey_R); - NBL_REGISTER_KEY(EKC_S, ImGuiKey_S); - NBL_REGISTER_KEY(EKC_T, ImGuiKey_T); - NBL_REGISTER_KEY(EKC_U, ImGuiKey_U); - NBL_REGISTER_KEY(EKC_V, ImGuiKey_V); - NBL_REGISTER_KEY(EKC_W, ImGuiKey_W); - NBL_REGISTER_KEY(EKC_X, ImGuiKey_X); - NBL_REGISTER_KEY(EKC_Y, ImGuiKey_Y); - NBL_REGISTER_KEY(EKC_Z, ImGuiKey_Z); - NBL_REGISTER_KEY(EKC_NUMPAD_0, ImGuiKey_Keypad0); - NBL_REGISTER_KEY(EKC_NUMPAD_1, ImGuiKey_Keypad1); - NBL_REGISTER_KEY(EKC_NUMPAD_2, ImGuiKey_Keypad2); - NBL_REGISTER_KEY(EKC_NUMPAD_3, ImGuiKey_Keypad3); - NBL_REGISTER_KEY(EKC_NUMPAD_4, ImGuiKey_Keypad4); - NBL_REGISTER_KEY(EKC_NUMPAD_5, ImGuiKey_Keypad5); - NBL_REGISTER_KEY(EKC_NUMPAD_6, ImGuiKey_Keypad6); - NBL_REGISTER_KEY(EKC_NUMPAD_7, ImGuiKey_Keypad7); - NBL_REGISTER_KEY(EKC_NUMPAD_8, ImGuiKey_Keypad8); - NBL_REGISTER_KEY(EKC_NUMPAD_9, ImGuiKey_Keypad9); - NBL_REGISTER_KEY(EKC_F1, ImGuiKey_F1); - NBL_REGISTER_KEY(EKC_F2, ImGuiKey_F2); - NBL_REGISTER_KEY(EKC_F3, ImGuiKey_F3); - NBL_REGISTER_KEY(EKC_F4, ImGuiKey_F4); - NBL_REGISTER_KEY(EKC_F5, ImGuiKey_F5); - NBL_REGISTER_KEY(EKC_F6, ImGuiKey_F6); - NBL_REGISTER_KEY(EKC_F7, ImGuiKey_F7); - NBL_REGISTER_KEY(EKC_F8, ImGuiKey_F8); - NBL_REGISTER_KEY(EKC_F9, ImGuiKey_F9); - NBL_REGISTER_KEY(EKC_F10, ImGuiKey_F10); - NBL_REGISTER_KEY(EKC_F11, ImGuiKey_F11); - NBL_REGISTER_KEY(EKC_F12, ImGuiKey_F12); - NBL_REGISTER_KEY(EKC_F13, ImGuiKey_F13); - NBL_REGISTER_KEY(EKC_F14, ImGuiKey_F14); - NBL_REGISTER_KEY(EKC_F15, ImGuiKey_F15); - NBL_REGISTER_KEY(EKC_F16, ImGuiKey_F16); - NBL_REGISTER_KEY(EKC_F17, ImGuiKey_F17); - NBL_REGISTER_KEY(EKC_F18, ImGuiKey_F18); - NBL_REGISTER_KEY(EKC_F19, ImGuiKey_F19); - NBL_REGISTER_KEY(EKC_F20, ImGuiKey_F20); - NBL_REGISTER_KEY(EKC_F21, ImGuiKey_F21); - NBL_REGISTER_KEY(EKC_F22, ImGuiKey_F22); - NBL_REGISTER_KEY(EKC_F23, ImGuiKey_F23); - NBL_REGISTER_KEY(EKC_F24, ImGuiKey_F24); - NBL_REGISTER_KEY(EKC_NUM_LOCK, ImGuiKey_NumLock); - NBL_REGISTER_KEY(EKC_SCROLL_LOCK, ImGuiKey_ScrollLock); - NBL_REGISTER_KEY(EKC_VOLUME_MUTE, ImGuiKey_None); - NBL_REGISTER_KEY(EKC_VOLUME_UP, ImGuiKey_None); - NBL_REGISTER_KEY(EKC_VOLUME_DOWN, ImGuiKey_None); - - return map; - } - - void UI::handleKeyEvents(const SUpdateParameters& params) const - { - auto& io = ImGui::GetIO(); - - _NBL_STATIC_INLINE_CONSTEXPR auto keyMap = createKeyMap(); +struct NBL_TO_IMGUI_KEY_BIND +{ + ImGuiKey target; + char physicalSmall; + char physicalBig; +}; - const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc - { - for (const auto& e : params.keyboardEvents) - if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) - return true; +// maps Nabla keys to IMGUIs +constexpr std::array createKeyMap() +{ + std::array map = { { NBL_TO_IMGUI_KEY_BIND{ImGuiKey_None, '0', '0'} } }; + + #define NBL_REGISTER_KEY(__NBL_KEY__, __IMGUI_KEY__) \ + map[__NBL_KEY__] = NBL_TO_IMGUI_KEY_BIND{__IMGUI_KEY__, keyCodeToChar(__NBL_KEY__, false), keyCodeToChar(__NBL_KEY__, true)}; + + NBL_REGISTER_KEY(EKC_BACKSPACE, ImGuiKey_Backspace); + NBL_REGISTER_KEY(EKC_TAB, ImGuiKey_Tab); + NBL_REGISTER_KEY(EKC_ENTER, ImGuiKey_Enter); + NBL_REGISTER_KEY(EKC_LEFT_SHIFT, ImGuiKey_LeftShift); + NBL_REGISTER_KEY(EKC_RIGHT_SHIFT, ImGuiKey_RightShift); + NBL_REGISTER_KEY(EKC_LEFT_CONTROL, ImGuiKey_LeftCtrl); + NBL_REGISTER_KEY(EKC_RIGHT_CONTROL, ImGuiKey_RightCtrl); + NBL_REGISTER_KEY(EKC_LEFT_ALT, ImGuiKey_LeftAlt); + NBL_REGISTER_KEY(EKC_RIGHT_ALT, ImGuiKey_RightAlt); + NBL_REGISTER_KEY(EKC_PAUSE, ImGuiKey_Pause); + NBL_REGISTER_KEY(EKC_CAPS_LOCK, ImGuiKey_CapsLock); + NBL_REGISTER_KEY(EKC_ESCAPE, ImGuiKey_Escape); + NBL_REGISTER_KEY(EKC_SPACE, ImGuiKey_Space); + NBL_REGISTER_KEY(EKC_PAGE_UP, ImGuiKey_PageUp); + NBL_REGISTER_KEY(EKC_PAGE_DOWN, ImGuiKey_PageDown); + NBL_REGISTER_KEY(EKC_END, ImGuiKey_End); + NBL_REGISTER_KEY(EKC_HOME, ImGuiKey_Home); + NBL_REGISTER_KEY(EKC_LEFT_ARROW, ImGuiKey_LeftArrow); + NBL_REGISTER_KEY(EKC_RIGHT_ARROW, ImGuiKey_RightArrow); + NBL_REGISTER_KEY(EKC_DOWN_ARROW, ImGuiKey_DownArrow); + NBL_REGISTER_KEY(EKC_UP_ARROW, ImGuiKey_UpArrow); + NBL_REGISTER_KEY(EKC_PRINT_SCREEN, ImGuiKey_PrintScreen); + NBL_REGISTER_KEY(EKC_INSERT, ImGuiKey_Insert); + NBL_REGISTER_KEY(EKC_DELETE, ImGuiKey_Delete); + NBL_REGISTER_KEY(EKC_APPS, ImGuiKey_Menu); + NBL_REGISTER_KEY(EKC_COMMA, ImGuiKey_Comma); + NBL_REGISTER_KEY(EKC_PERIOD, ImGuiKey_Period); + NBL_REGISTER_KEY(EKC_SEMICOLON, ImGuiKey_Semicolon); + NBL_REGISTER_KEY(EKC_OPEN_BRACKET, ImGuiKey_LeftBracket); + NBL_REGISTER_KEY(EKC_CLOSE_BRACKET, ImGuiKey_RightBracket); + NBL_REGISTER_KEY(EKC_BACKSLASH, ImGuiKey_Backslash); + NBL_REGISTER_KEY(EKC_APOSTROPHE, ImGuiKey_Apostrophe); + NBL_REGISTER_KEY(EKC_ADD, ImGuiKey_KeypadAdd); + NBL_REGISTER_KEY(EKC_SUBTRACT, ImGuiKey_KeypadSubtract); + NBL_REGISTER_KEY(EKC_MULTIPLY, ImGuiKey_KeypadMultiply); + NBL_REGISTER_KEY(EKC_DIVIDE, ImGuiKey_KeypadDivide); + NBL_REGISTER_KEY(EKC_0, ImGuiKey_0); + NBL_REGISTER_KEY(EKC_1, ImGuiKey_1); + NBL_REGISTER_KEY(EKC_2, ImGuiKey_2); + NBL_REGISTER_KEY(EKC_3, ImGuiKey_3); + NBL_REGISTER_KEY(EKC_4, ImGuiKey_4); + NBL_REGISTER_KEY(EKC_5, ImGuiKey_5); + NBL_REGISTER_KEY(EKC_6, ImGuiKey_6); + NBL_REGISTER_KEY(EKC_7, ImGuiKey_7); + NBL_REGISTER_KEY(EKC_8, ImGuiKey_8); + NBL_REGISTER_KEY(EKC_9, ImGuiKey_9); + NBL_REGISTER_KEY(EKC_A, ImGuiKey_A); + NBL_REGISTER_KEY(EKC_B, ImGuiKey_B); + NBL_REGISTER_KEY(EKC_C, ImGuiKey_C); + NBL_REGISTER_KEY(EKC_D, ImGuiKey_D); + NBL_REGISTER_KEY(EKC_E, ImGuiKey_E); + NBL_REGISTER_KEY(EKC_F, ImGuiKey_F); + NBL_REGISTER_KEY(EKC_G, ImGuiKey_G); + NBL_REGISTER_KEY(EKC_H, ImGuiKey_H); + NBL_REGISTER_KEY(EKC_I, ImGuiKey_I); + NBL_REGISTER_KEY(EKC_J, ImGuiKey_J); + NBL_REGISTER_KEY(EKC_K, ImGuiKey_K); + NBL_REGISTER_KEY(EKC_L, ImGuiKey_L); + NBL_REGISTER_KEY(EKC_M, ImGuiKey_M); + NBL_REGISTER_KEY(EKC_N, ImGuiKey_N); + NBL_REGISTER_KEY(EKC_O, ImGuiKey_O); + NBL_REGISTER_KEY(EKC_P, ImGuiKey_P); + NBL_REGISTER_KEY(EKC_Q, ImGuiKey_Q); + NBL_REGISTER_KEY(EKC_R, ImGuiKey_R); + NBL_REGISTER_KEY(EKC_S, ImGuiKey_S); + NBL_REGISTER_KEY(EKC_T, ImGuiKey_T); + NBL_REGISTER_KEY(EKC_U, ImGuiKey_U); + NBL_REGISTER_KEY(EKC_V, ImGuiKey_V); + NBL_REGISTER_KEY(EKC_W, ImGuiKey_W); + NBL_REGISTER_KEY(EKC_X, ImGuiKey_X); + NBL_REGISTER_KEY(EKC_Y, ImGuiKey_Y); + NBL_REGISTER_KEY(EKC_Z, ImGuiKey_Z); + NBL_REGISTER_KEY(EKC_NUMPAD_0, ImGuiKey_Keypad0); + NBL_REGISTER_KEY(EKC_NUMPAD_1, ImGuiKey_Keypad1); + NBL_REGISTER_KEY(EKC_NUMPAD_2, ImGuiKey_Keypad2); + NBL_REGISTER_KEY(EKC_NUMPAD_3, ImGuiKey_Keypad3); + NBL_REGISTER_KEY(EKC_NUMPAD_4, ImGuiKey_Keypad4); + NBL_REGISTER_KEY(EKC_NUMPAD_5, ImGuiKey_Keypad5); + NBL_REGISTER_KEY(EKC_NUMPAD_6, ImGuiKey_Keypad6); + NBL_REGISTER_KEY(EKC_NUMPAD_7, ImGuiKey_Keypad7); + NBL_REGISTER_KEY(EKC_NUMPAD_8, ImGuiKey_Keypad8); + NBL_REGISTER_KEY(EKC_NUMPAD_9, ImGuiKey_Keypad9); + NBL_REGISTER_KEY(EKC_F1, ImGuiKey_F1); + NBL_REGISTER_KEY(EKC_F2, ImGuiKey_F2); + NBL_REGISTER_KEY(EKC_F3, ImGuiKey_F3); + NBL_REGISTER_KEY(EKC_F4, ImGuiKey_F4); + NBL_REGISTER_KEY(EKC_F5, ImGuiKey_F5); + NBL_REGISTER_KEY(EKC_F6, ImGuiKey_F6); + NBL_REGISTER_KEY(EKC_F7, ImGuiKey_F7); + NBL_REGISTER_KEY(EKC_F8, ImGuiKey_F8); + NBL_REGISTER_KEY(EKC_F9, ImGuiKey_F9); + NBL_REGISTER_KEY(EKC_F10, ImGuiKey_F10); + NBL_REGISTER_KEY(EKC_F11, ImGuiKey_F11); + NBL_REGISTER_KEY(EKC_F12, ImGuiKey_F12); + NBL_REGISTER_KEY(EKC_F13, ImGuiKey_F13); + NBL_REGISTER_KEY(EKC_F14, ImGuiKey_F14); + NBL_REGISTER_KEY(EKC_F15, ImGuiKey_F15); + NBL_REGISTER_KEY(EKC_F16, ImGuiKey_F16); + NBL_REGISTER_KEY(EKC_F17, ImGuiKey_F17); + NBL_REGISTER_KEY(EKC_F18, ImGuiKey_F18); + NBL_REGISTER_KEY(EKC_F19, ImGuiKey_F19); + NBL_REGISTER_KEY(EKC_F20, ImGuiKey_F20); + NBL_REGISTER_KEY(EKC_F21, ImGuiKey_F21); + NBL_REGISTER_KEY(EKC_F22, ImGuiKey_F22); + NBL_REGISTER_KEY(EKC_F23, ImGuiKey_F23); + NBL_REGISTER_KEY(EKC_F24, ImGuiKey_F24); + NBL_REGISTER_KEY(EKC_NUM_LOCK, ImGuiKey_NumLock); + NBL_REGISTER_KEY(EKC_SCROLL_LOCK, ImGuiKey_ScrollLock); + NBL_REGISTER_KEY(EKC_VOLUME_MUTE, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_UP, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_DOWN, ImGuiKey_None); + + return map; +} + +void UI::handleKeyEvents(const SUpdateParameters& params) const +{ + auto& io = ImGui::GetIO(); - return false; - }(); + _NBL_STATIC_INLINE_CONSTEXPR auto keyMap = createKeyMap(); + const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc + { for (const auto& e : params.keyboardEvents) - { - const auto& bind = keyMap[e.keyCode]; - const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; + if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) + return true; - if(bind.target == ImGuiKey_None) - m_cachedCreationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), ILogger::ELL_ERROR); - else - if (e.action == SKeyboardEvent::ECA_PRESSED) - { - io.AddKeyEvent(bind.target, true); - io.AddInputCharacter(iCharacter); - } - else if (e.action == SKeyboardEvent::ECA_RELEASED) - io.AddKeyEvent(bind.target, false); - } - } + return false; + }(); - UI::UI(SCreationParameters&& creationParams) + for (const auto& e : params.keyboardEvents) { - auto validateResourcesInfo = [&]() -> bool - { - auto* pipelineLayout = creationParams.pipelineLayout.get(); + const auto& bind = keyMap[e.keyCode]; + const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; - if (pipelineLayout) // provided? we will validate your pipeline layout to check if you declared required UI resources + if(bind.target == ImGuiKey_None) + m_cachedCreationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), ILogger::ELL_ERROR); + else + if (e.action == SKeyboardEvent::ECA_PRESSED) { - auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) - { - constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", - ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; + io.AddKeyEvent(bind.target, true); + io.AddInputCharacter(iCharacter); + } + else if (e.action == SKeyboardEvent::ECA_RELEASED) + io.AddKeyEvent(bind.target, false); + } +} - // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further - auto anyBindingCount = [&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool - { - bool ok = redirect->getBindingCount(); +UI::UI(SCreationParameters&& creationParams) +{ + auto validateResourcesInfo = [&]() -> bool + { + auto* pipelineLayout = creationParams.pipelineLayout.get(); - if (!ok && logError) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); - return false; - } + if (pipelineLayout) // provided? we will validate your pipeline layout to check if you declared required UI resources + { + auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) + { + constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", + ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; - return ok; - }; + // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further + auto anyBindingCount = [&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool + { + bool ok = redirect->getBindingCount(); - if(!descriptorSetLayout) + if (!ok && logError) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); return false; } - const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); + return ok; + }; + + if(!descriptorSetLayout) + { + creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); + return false; + } + + const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); - if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + { + if (!anyBindingCount(redirect)) + return false; + } + else + { + if (!anyBindingCount(redirect, false)) { + redirect = &descriptorSetLayout->getImmutableSamplerRedirect(); // we must give it another try & request to look for immutable samplers + if (!anyBindingCount(redirect)) return false; } - else - { - if (!anyBindingCount(redirect, false)) - { - redirect = &descriptorSetLayout->getImmutableSamplerRedirect(); // we must give it another try & request to look for immutable samplers + } - if (!anyBindingCount(redirect)) - return false; - } - } + const auto bindingCount = redirect->getBindingCount(); - const auto bindingCount = redirect->getBindingCount(); + bool ok = false; + for (uint32_t i = 0u; i < bindingCount; ++i) + { + const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); + const auto binding = redirect->getBinding(rangeStorageIndex); + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx : creationParams.resources.samplersInfo.bindingIx; - bool ok = false; - for (uint32_t i = 0u; i < bindingCount; ++i) + if (binding.data == requestedBindingIx) { - const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); - const auto binding = redirect->getBinding(rangeStorageIndex); - const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx : creationParams.resources.samplersInfo.bindingIx; + const auto count = redirect->getCount(binding); - if (binding.data == requestedBindingIx) + if(!count) { - const auto count = redirect->getCount(binding); - - if(!count) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } - - if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) - creationParams.resources.texturesCount = count; - else - creationParams.resources.samplersCount = count; + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } - const auto stage = redirect->getStageFlags(binding); + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + creationParams.resources.texturesCount = count; + else + creationParams.resources.samplersCount = count; - if(!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } + const auto stage = redirect->getStageFlags(binding); - const auto creation = redirect->getCreateFlags(rangeStorageIndex); + if(!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) + { + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } - if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } + const auto creation = redirect->getCreateFlags(rangeStorageIndex); - ok = true; - break; + if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) + { + creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; } - } - if (!ok) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; + ok = true; + break; } - - return true; - }; - - const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); - const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[creationParams.resources.texturesInfo.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[creationParams.resources.samplersInfo.setIx]); + } if (!ok) + { + creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; - } + } - return true; - }; + return true; + }; + + const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[creationParams.resources.texturesInfo.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[creationParams.resources.samplersInfo.setIx]); - const auto validation = std::to_array - ({ - std::make_pair(bool(creationParams.assetManager), "Invalid `creationParams.assetManager` is nullptr!"), - std::make_pair(bool(creationParams.assetManager->getSystem()), "Invalid `creationParams.assetManager->getSystem()` is nullptr!"), - std::make_pair(bool(creationParams.utilities), "Invalid `creationParams.utilities` is nullptr!"), - std::make_pair(bool(creationParams.transfer), "Invalid `creationParams.transfer` is nullptr!"), - std::make_pair(bool(creationParams.renderpass), "Invalid `creationParams.renderpass` is nullptr!"), - (creationParams.assetManager && creationParams.utilities && creationParams.transfer && creationParams.renderpass) ? std::make_pair(bool(creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), - std::make_pair(bool(creationParams.resources.texturesInfo.setIx <= 3u), "Invalid `creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(creationParams.resources.samplersInfo.setIx <= 3u), "Invalid `creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), - std::make_pair(bool(creationParams.resources.texturesInfo.bindingIx != creationParams.resources.samplersInfo.bindingIx), "Invalid `creationParams.resources.textures.bindingIx` is equal to `creationParams.resources.samplers.bindingIx`!"), - std::make_pair(bool(validateResourcesInfo()), "Invalid `creationParams.resources` content!") - }); - - for (const auto& [ok, error] : validation) if (!ok) - { - creationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); - assert(false); - } + return false; + } - smart_refctd_ptr transistentCMD; + return true; + }; + + const auto validation = std::to_array + ({ + std::make_pair(bool(creationParams.assetManager), "Invalid `creationParams.assetManager` is nullptr!"), + std::make_pair(bool(creationParams.assetManager->getSystem()), "Invalid `creationParams.assetManager->getSystem()` is nullptr!"), + std::make_pair(bool(creationParams.utilities), "Invalid `creationParams.utilities` is nullptr!"), + std::make_pair(bool(creationParams.transfer), "Invalid `creationParams.transfer` is nullptr!"), + std::make_pair(bool(creationParams.renderpass), "Invalid `creationParams.renderpass` is nullptr!"), + (creationParams.assetManager && creationParams.utilities && creationParams.transfer && creationParams.renderpass) ? std::make_pair(bool(creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"), + std::make_pair(bool(creationParams.resources.texturesInfo.setIx <= 3u), "Invalid `creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(creationParams.resources.samplersInfo.setIx <= 3u), "Invalid `creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(creationParams.resources.texturesInfo.bindingIx != creationParams.resources.samplersInfo.bindingIx), "Invalid `creationParams.resources.textures.bindingIx` is equal to `creationParams.resources.samplers.bindingIx`!"), + std::make_pair(bool(validateResourcesInfo()), "Invalid `creationParams.resources` content!") + }); + + for (const auto& [ok, error] : validation) + if (!ok) { - using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; + creationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); + assert(false); + } - smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); - if (!pool) - { - creationParams.utilities->getLogger()->log("Could not create command pool!", ILogger::ELL_ERROR); - assert(false); - } + smart_refctd_ptr transistentCMD; + { + using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; + + smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + if (!pool) + { + creationParams.utilities->getLogger()->log("Could not create command pool!", ILogger::ELL_ERROR); + assert(false); + } - if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) - { - creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", ILogger::ELL_ERROR); - assert(false); - } + if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) + { + creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", ILogger::ELL_ERROR); + assert(false); } + } - // Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); + // Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); - createPipeline(creationParams); - createMDIBuffer(creationParams); - createFontAtlasTexture(transistentCMD.get(), creationParams); + createPipeline(creationParams); + createMDIBuffer(creationParams); + createFontAtlasTexture(transistentCMD.get(), creationParams); - auto & io = ImGui::GetIO(); - io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports + auto & io = ImGui::GetIO(); + io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports - m_cachedCreationParams = std::move(creationParams); - } + m_cachedCreationParams = std::move(creationParams); +} + +UI::~UI() = default; - UI::~UI() = default; +void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) +{ + constexpr static uint32_t minStreamingBufferAllocationSize = 128u, maxStreamingBufferAllocationAlignment = 4096u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; - void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) + auto getRequiredAccessFlags = [&](const bitflag& properties) { - constexpr static uint32_t minStreamingBufferAllocationSize = 128u, maxStreamingBufferAllocationAlignment = 4096u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; + bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); - auto getRequiredAccessFlags = [&](const bitflag& properties) - { - bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_READ; + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_WRITE; - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) - flags |= IDeviceMemoryAllocation::EMCAF_READ; - if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) - flags |= IDeviceMemoryAllocation::EMCAF_WRITE; + return flags; + }; - return flags; - }; + if (m_cachedCreationParams.streamingBuffer) + m_mdi.compose = smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); + else + { + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = SMdiBuffer::RequiredUsageFlags; + mdiCreationParams.size = mdiBufferDefaultSize; - if (m_cachedCreationParams.streamingBuffer) - m_mdi.compose = smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); - else + auto buffer = m_cachedCreationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + buffer->setObjectDebugName("MDI Upstream Buffer"); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), SMdiBuffer::RequiredAllocateFlags); { - IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = SMdiBuffer::RequiredUsageFlags; - mdiCreationParams.size = mdiBufferDefaultSize; + const bool allocated = allocation.isValid(); + assert(allocated); + } + auto memory = allocation.memory; - auto buffer = m_cachedCreationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); - buffer->setObjectDebugName("MDI Upstream Buffer"); + if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) + m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); - auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + m_mdi.compose = make_smart_refctd_ptr(SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + } - auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), SMdiBuffer::RequiredAllocateFlags); - { - const bool allocated = allocation.isValid(); - assert(allocated); - } - auto memory = allocation.memory; + auto buffer = m_mdi.compose->getBuffer(); + auto binding = buffer->getBoundMemory(); - if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) - m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); + const auto validation = std::to_array + ({ + std::make_pair(buffer->getCreationParams().usage.hasFlags(SMdiBuffer::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(SMdiBuffer::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) + std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") + }); - m_mdi.compose = make_smart_refctd_ptr(SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + for (const auto& [ok, error] : validation) + if (!ok) + { + m_cachedCreationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); + assert(false); } +} - auto buffer = m_mdi.compose->getBuffer(); - auto binding = buffer->getBoundMemory(); - - const auto validation = std::to_array - ({ - std::make_pair(buffer->getCreationParams().usage.hasFlags(SMdiBuffer::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), - std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), - std::make_pair(binding.memory->getAllocateFlags().hasFlags(SMdiBuffer::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), - std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) - std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") - }); +bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint, const std::span scissors) +{ + if (!commandBuffer) + { + m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", ILogger::ELL_ERROR); + return false; + } - for (const auto& [ok, error] : validation) - if (!ok) - { - m_cachedCreationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); - assert(false); - } + if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) + { + m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", ILogger::ELL_ERROR); + return false; } - bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint, const std::span scissors) { - if (!commandBuffer) - { - m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", ILogger::ELL_ERROR); - return false; - } + const auto info = commandBuffer->getCachedInheritanceInfo(); + const bool recordingSubpass = info.subpass != IGPURenderpass::SCreationParams::SSubpassDependency::External; - if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) + if(!recordingSubpass) { - m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", ILogger::ELL_ERROR); + m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not recording a subpass!", ILogger::ELL_ERROR); return false; } + } - { - const auto info = commandBuffer->getCachedInheritanceInfo(); - const bool recordingSubpass = info.subpass != IGPURenderpass::SCreationParams::SSubpassDependency::External; - - if(!recordingSubpass) - { - m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not recording a subpass!", ILogger::ELL_ERROR); - return false; - } - } - - ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() - ImGuiIO& io = ImGui::GetIO(); + ImGuiIO& io = ImGui::GetIO(); - if (!io.Fonts->IsBuilt()) - { - m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", ILogger::ELL_ERROR); - return false; - } + if (!io.Fonts->IsBuilt()) + { + m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", ILogger::ELL_ERROR); + return false; + } - auto const* drawData = ImGui::GetDrawData(); + auto const* drawData = ImGui::GetDrawData(); - if (!drawData) - return false; + if (!drawData) + return false; - // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - float const frameBufferWidth = drawData->DisplaySize.x * drawData->FramebufferScale.x; - float const frameBufferHeight = drawData->DisplaySize.y * drawData->FramebufferScale.y; - if (frameBufferWidth > 0 && frameBufferHeight > 0 && drawData->TotalVtxCount > 0) + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + float const frameBufferWidth = drawData->DisplaySize.x * drawData->FramebufferScale.x; + float const frameBufferHeight = drawData->DisplaySize.y * drawData->FramebufferScale.y; + if (frameBufferWidth > 0 && frameBufferHeight > 0 && drawData->TotalVtxCount > 0) + { + const struct { - const struct - { - ImVec2 off; // (0,0) unless using multi-viewports - ImVec2 scale; // (1,1) unless using retina display which are often (2,2) - ImVec2 framebuffer; // width, height + ImVec2 off; // (0,0) unless using multi-viewports + ImVec2 scale; // (1,1) unless using retina display which are often (2,2) + ImVec2 framebuffer; // width, height - // Project scissor/clipping rectangles into frame-buffer space - ImVec4 getClipRectangle(const ImDrawCmd* cmd) const - { - assert(cmd); + // Project scissor/clipping rectangles into frame-buffer space + ImVec4 getClipRectangle(const ImDrawCmd* cmd) const + { + assert(cmd); - ImVec4 rectangle; - rectangle.x = (cmd->ClipRect.x - off.x) * scale.x; - rectangle.y = (cmd->ClipRect.y - off.y) * scale.y; - rectangle.z = (cmd->ClipRect.z - off.x) * scale.x; - rectangle.w = (cmd->ClipRect.w - off.y) * scale.y; + ImVec4 rectangle; + rectangle.x = (cmd->ClipRect.x - off.x) * scale.x; + rectangle.y = (cmd->ClipRect.y - off.y) * scale.y; + rectangle.z = (cmd->ClipRect.z - off.x) * scale.x; + rectangle.w = (cmd->ClipRect.w - off.y) * scale.y; - return rectangle; - } + return rectangle; + } - VkRect2D getScissor(ImVec4 clipRectangle) const - { - // Negative offsets are illegal for vkCmdSetScissor - if (clipRectangle.x < 0.0f) - clipRectangle.x = 0.0f; - if (clipRectangle.y < 0.0f) - clipRectangle.y = 0.0f; - - {// Apply scissor/clipping rectangle - VkRect2D scissor {}; - scissor.offset.x = static_cast(clipRectangle.x); - scissor.offset.y = static_cast(clipRectangle.y); - scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); - scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); + VkRect2D getScissor(ImVec4 clipRectangle) const + { + // Negative offsets are illegal for vkCmdSetScissor + if (clipRectangle.x < 0.0f) + clipRectangle.x = 0.0f; + if (clipRectangle.y < 0.0f) + clipRectangle.y = 0.0f; + + {// Apply scissor/clipping rectangle + VkRect2D scissor {}; + scissor.offset.x = static_cast(clipRectangle.x); + scissor.offset.y = static_cast(clipRectangle.y); + scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); + scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); - return scissor; - } + return scissor; } - } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; + } + } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; - struct TRS - { - vector2df_SIMD scale; - vector2df_SIMD translate; - - vector2df_SIMD toNDC(vector2df_SIMD in) const - { - return in * scale + translate; - } - }; + struct TRS + { + vector2df_SIMD scale; + vector2df_SIMD translate; - const TRS trs = [&]() + vector2df_SIMD toNDC(vector2df_SIMD in) const { - TRS retV; + return in * scale + translate; + } + }; - retV.scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; - retV.translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; + const TRS trs = [&]() + { + TRS retV; - return std::move(retV); - }(); + retV.scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; + retV.translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; - ImGuiCommandListRange imCmdRange(drawData); - { - auto streamingBuffer = m_mdi.compose; - auto binding = streamingBuffer->getBuffer()->getBoundMemory(); - assert(binding.memory->isCurrentlyMapped()); - - auto* const mdiData = reinterpret_cast(streamingBuffer->getBufferPointer()); - const auto& requiredStructsBlockInfo = imCmdRange.getRequiredStructsBlockInfo(); - auto& chunksInfo = imCmdRange.bigChunkRequestInfo; - const auto& limits = imCmdRange.getLimits(); + return std::move(retV); + }(); + + ImGuiCommandListRange imCmdRange(drawData); + { + auto streamingBuffer = m_mdi.compose; + auto binding = streamingBuffer->getBuffer()->getBoundMemory(); + assert(binding.memory->isCurrentlyMapped()); + + auto* const mdiData = reinterpret_cast(streamingBuffer->getBufferPointer()); + const auto& requiredStructsBlockInfo = imCmdRange.getRequiredStructsBlockInfo(); + auto& chunksInfo = imCmdRange.bigChunkRequestInfo; + const auto& limits = imCmdRange.getLimits(); - //! we will try to upload all imgui data to mdi streaming buffer but we cannot guarantee an allocation can be done in single request nor buffers data will come from continous memory block (from single chunk) - for (mdi_size_t totalUploadedSize = 0ull; totalUploadedSize < limits.totalByteSizeRequest;) + //! we will try to upload all imgui data to mdi streaming buffer but we cannot guarantee an allocation can be done in single request nor buffers data will come from continous memory block (from single chunk) + for (mdi_size_t totalUploadedSize = 0ull; totalUploadedSize < limits.totalByteSizeRequest;) + { + auto uploadCommandListData = [&](DrawItem drawItem) { - auto uploadCommandListData = [&](DrawItem drawItem) - { - //! (*) note we make an assumption here, at this point they are allocated & ready to fill, read the ImGuiCommandListRange::STightStructs description for more info - auto* const indirectStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES]); - auto* const elementStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES]); - - const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); - const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(DrawItemMeta::VERTEX, DrawItemMeta::INDEX); - constexpr auto ChunkPaddings = std::to_array({ sizeof(ImDrawVert), sizeof(ImDrawIdx) }); - - if (drawItem.meta.totalLeftBytesToUpload >= 0u) - { - // we have 2 buffers to fill per command list, we will try with as tight streaming memory chunks as possible to not waste block memory (too big chunks allocated but only certain % used in reality) & make it a way our suballocator likes them (we respect required alignments) - for (uint16_t bufferIx = 0u; bufferIx < DrawItemMeta::COUNT; ++bufferIx) - { - if (drawItem.meta.filled[bufferIx]) - continue; + //! (*) note we make an assumption here, at this point they are allocated & ready to fill, read the ImGuiCommandListRange::STightStructs description for more info + auto* const indirectStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES]); + auto* const elementStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES]); + + const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); + const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(DrawItemMeta::VERTEX, DrawItemMeta::INDEX); + constexpr auto ChunkPaddings = std::to_array({ sizeof(ImDrawVert), sizeof(ImDrawIdx) }); + + if (drawItem.meta.totalLeftBytesToUpload >= 0u) + { + // we have 2 buffers to fill per command list, we will try with as tight streaming memory chunks as possible to not waste block memory (too big chunks allocated but only certain % used in reality) & make it a way our suballocator likes them (we respect required alignments) + for (uint16_t bufferIx = 0u; bufferIx < DrawItemMeta::COUNT; ++bufferIx) + { + if (drawItem.meta.filled[bufferIx]) + continue; - const auto& bufferSizeTotalUploadRequest = drawItem.meta.sizes[bufferIx]; - const auto& requiredChunkPadding = ChunkPaddings[bufferIx]; + const auto& bufferSizeTotalUploadRequest = drawItem.meta.sizes[bufferIx]; + const auto& requiredChunkPadding = ChunkPaddings[bufferIx]; - /* (**) note we add extra requiredChunkPadding to let suballocator start at required multiple of size of packed object, this way suballocator always success if the block can be allocated */ - auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + requiredChunkPadding)); + /* (**) note we add extra requiredChunkPadding to let suballocator start at required multiple of size of packed object, this way suballocator always success if the block can be allocated */ + auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + requiredChunkPadding)); - //! (*) the request therefore is tight & contains small padding for the suballocator to find the proper offset start - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &chunkOffset, &chunkSize, &MdiMaxAlignment); + //! (*) the request therefore is tight & contains small padding for the suballocator to find the proper offset start + const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &chunkOffset, &chunkSize, &MdiMaxAlignment); - if (chunkOffset == InvalidAddress) - return; - else - { - // chunk allocated for a buffer? update the state's offset table stack & let suballocator do the job (we made sure the only memory we "waste" is the padding part, at this point suballocator *should* always success) - chunksInfo.offsets.emplace_back() = chunkOffset; - chunksInfo.sizes.emplace_back() = chunkSize; - const auto alignOffsetRequired = requiredChunkPadding - (chunkOffset % requiredChunkPadding); // read (**), this is the key part + if (chunkOffset == InvalidAddress) + return; + else + { + // chunk allocated for a buffer? update the state's offset table stack & let suballocator do the job (we made sure the only memory we "waste" is the padding part, at this point suballocator *should* always success) + chunksInfo.offsets.emplace_back() = chunkOffset; + chunksInfo.sizes.emplace_back() = chunkSize; + const auto alignOffsetRequired = requiredChunkPadding - (chunkOffset % requiredChunkPadding); // read (**), this is the key part + + //! (*) we create linear suballocator to fill the allocated chunk of memory + SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetRequired, MdiMaxAlignment, chunkSize); - //! (*) we create linear suballocator to fill the allocated chunk of memory - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetRequired, MdiMaxAlignment, chunkSize); + //! (*) we suballocate from the allocated chunk with required alignments + SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, 1u, drawItem.meta.offsets.data() + bufferIx, drawItem.meta.sizes.data() + bufferIx, drawItem.meta.alignments.data() + bufferIx); - //! (*) we suballocate from the allocated chunk with required alignments - SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, 1u, drawItem.meta.offsets.data() + bufferIx, drawItem.meta.sizes.data() + bufferIx, drawItem.meta.alignments.data() + bufferIx); + auto upload = [&]() -> size_t + { + size_t uploaded = {}; - auto upload = [&]() -> size_t + auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t { - size_t uploaded = {}; + const bool isFilled = drawItem.meta.filled[allocationIx]; - auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t + if (!isFilled) { - const bool isFilled = drawItem.meta.filled[allocationIx]; + const auto bytesToFill = drawItem.meta.sizes[allocationIx]; + uploaded += bytesToFill; + drawItem.meta.filled[allocationIx] = true; + return bytesToFill; + } - if (!isFilled) - { - const auto bytesToFill = drawItem.meta.sizes[allocationIx]; - uploaded += bytesToFill; - drawItem.meta.filled[allocationIx] = true; - return bytesToFill; - } + return 0u; + }; - return 0u; - }; + auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) + { + auto& offset = drawItem.meta.offsets[allocationIx]; - auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) + if (offset == InvalidAddress) + return false; + else { - auto& offset = drawItem.meta.offsets[allocationIx]; + const auto bytesToFill = updateSuballocation(allocationIx); - if (offset == InvalidAddress) - return false; - else - { - const auto bytesToFill = updateSuballocation(allocationIx); + if (bytesToFill != 0u) + ::memcpy(mdiData + offset, in, bytesToFill); + } - if (bytesToFill != 0u) - ::memcpy(mdiData + offset, in, bytesToFill); - } - - return true; - }; + return true; + }; - auto validateObjectOffsets = [&]() -> bool - { - const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); - bool ok = true; + auto validateObjectOffsets = [&]() -> bool + { + const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); + bool ok = true; - if (vtxOffset != InvalidAddress) - ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); + if (vtxOffset != InvalidAddress) + ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); - if (idxOffset != InvalidAddress) - ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); + if (idxOffset != InvalidAddress) + ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); - _NBL_BREAK_IF(!ok); + _NBL_BREAK_IF(!ok); - return ok; // if offsets are valid then must be aligned properly! - }; + return ok; // if offsets are valid then must be aligned properly! + }; - assert(validateObjectOffsets()); // debug check only + assert(validateObjectOffsets()); // debug check only - fillBuffer(vertexBuffer.Data, vtxAllocationIx); - fillBuffer(indexBuffer.Data, idxAllocationIx); + fillBuffer(vertexBuffer.Data, vtxAllocationIx); + fillBuffer(indexBuffer.Data, idxAllocationIx); - return uploaded; - }; + return uploaded; + }; - const size_t uploaded = upload(); - const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; + const size_t uploaded = upload(); + const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; - totalUploadedSize += uploaded; - drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); - } + totalUploadedSize += uploaded; + drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); } + } - // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well - const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; + // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well + const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; - if (buffersFilled) - { - const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); + if (buffersFilled) + { + const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); - for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) - { - const uint32_t drawID = drawItem.drawIdOffset + j; + for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) + { + const uint32_t drawID = drawItem.drawIdOffset + j; - const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; - auto* indirect = indirectStructures + drawID; - auto* element = elementStructures + drawID; + const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; + auto* indirect = indirectStructures + drawID; + auto* element = elementStructures + drawID; - // we use base instance as draw ID - indirect->firstInstance = drawID; - indirect->indexCount = cmd->ElemCount; - indirect->instanceCount = 1u; - indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; - indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; + // we use base instance as draw ID + indirect->firstInstance = drawID; + indirect->indexCount = cmd->ElemCount; + indirect->instanceCount = 1u; + indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; + indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; - const auto clipRectangle = clip.getClipRectangle(cmd); - const auto scissor = clip.getScissor(clipRectangle); + const auto clipRectangle = clip.getClipRectangle(cmd); + const auto scissor = clip.getScissor(clipRectangle); - auto packSnorm16 = [](float ndc) -> int16_t - { - return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils - }; + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; - const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - struct snorm16_t2_packed - { - int16_t x, y; - }; + struct snorm16_t2_packed + { + int16_t x, y; + }; - reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; - reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; + reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; + reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - element->texId = cmd->TextureId.textureID; - element->samplerIx = cmd->TextureId.samplerIx; - } + element->texId = cmd->TextureId.textureID; + element->samplerIx = cmd->TextureId.samplerIx; } } - }; + } + }; - if(!requiredStructsBlockInfo.allocated) - imCmdRange.allocateRequiredBlock(&m_mdi); + if(!requiredStructsBlockInfo.allocated) + imCmdRange.allocateRequiredBlock(&m_mdi); - // attempt to upload data only if we could allocate minimum of required indirect & element structs - if(requiredStructsBlockInfo.allocated) - std::for_each(imCmdRange.begin(), imCmdRange.end(), uploadCommandListData); + // attempt to upload data only if we could allocate minimum of required indirect & element structs + if(requiredStructsBlockInfo.allocated) + std::for_each(imCmdRange.begin(), imCmdRange.end(), uploadCommandListData); - // we let it run at least once - const bool timeout = std::chrono::steady_clock::now() >= waitPoint; - - if (timeout) - { - if (totalUploadedSize >= limits.totalByteSizeRequest) - break; // must be lucky to hit it or on debug + // we let it run at least once + const bool timeout = std::chrono::steady_clock::now() >= waitPoint; - imCmdRange.latchDeallocations(&m_mdi, waitInfo); - streamingBuffer->cull_frees(); - return false; - } - } - //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required - imCmdRange.latchDeallocations(&m_mdi, waitInfo); - } - - auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); - const auto offset = mdiBuffer->getBoundMemory().offset; - { - const SBufferBinding binding = + if (timeout) { - .offset = 0u, - .buffer = smart_refctd_ptr(mdiBuffer) - }; + if (totalUploadedSize >= limits.totalByteSizeRequest) + break; // must be lucky to hit it or on debug - constexpr auto IndexType = sizeof(ImDrawIdx) == 2u ? EIT_16BIT : EIT_32BIT; - if (!commandBuffer->bindIndexBuffer(binding, IndexType)) - { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", ILogger::ELL_ERROR); - assert(false); + imCmdRange.latchDeallocations(&m_mdi, waitInfo); + streamingBuffer->cull_frees(); + return false; } } + //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required + imCmdRange.latchDeallocations(&m_mdi, waitInfo); + } + auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); + const auto offset = mdiBuffer->getBoundMemory().offset; + { + const SBufferBinding binding = { - const SBufferBinding bindings[] = - {{ - .offset = 0u, - .buffer = smart_refctd_ptr(mdiBuffer) - }}; + .offset = 0u, + .buffer = smart_refctd_ptr(mdiBuffer) + }; - if(!commandBuffer->bindVertexBuffers(0u, 1u, bindings)) - { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", ILogger::ELL_ERROR); - assert(false); - } + constexpr auto IndexType = sizeof(ImDrawIdx) == 2u ? EIT_16BIT : EIT_32BIT; + if (!commandBuffer->bindIndexBuffer(binding, IndexType)) + { + m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", ILogger::ELL_ERROR); + assert(false); } + } - SViewport const viewport - { - .x = 0, - .y = 0, - .width = frameBufferWidth, - .height = frameBufferHeight, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; + { + const SBufferBinding bindings[] = + {{ + .offset = 0u, + .buffer = smart_refctd_ptr(mdiBuffer) + }}; - commandBuffer->setViewport(0u, 1u, &viewport); + if(!commandBuffer->bindVertexBuffers(0u, 1u, bindings)) { - if (scissors.empty()) - { - VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; - commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) - } - else - commandBuffer->setScissor(scissors); + m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", ILogger::ELL_ERROR); + assert(false); } - - /* - Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to - draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - */ + } - const auto& [structureOffsets, limits] = std::make_tuple(imCmdRange.getRequiredStructsBlockInfo().offsets, imCmdRange.getLimits()); - { - PushConstants constants - { - .elementBDA = { mdiBuffer->getDeviceAddress() + structureOffsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES] }, - .elementCount = { limits.totalIndirectDrawCount }, - .scale = { trs.scale[0u], trs.scale[1u] }, - .translate = { trs.translate[0u], trs.translate[1u] }, - .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } - }; + SViewport const viewport + { + .x = 0, + .y = 0, + .width = frameBufferWidth, + .height = frameBufferHeight, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; - commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + commandBuffer->setViewport(0u, 1u, &viewport); + { + if (scissors.empty()) + { + VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; + commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) } + else + commandBuffer->setScissor(scissors); + } + + /* + Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to + draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + */ - const SBufferBinding binding = + const auto& [structureOffsets, limits] = std::make_tuple(imCmdRange.getRequiredStructsBlockInfo().offsets, imCmdRange.getLimits()); + { + PushConstants constants { - .offset = structureOffsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES], - .buffer = smart_refctd_ptr(mdiBuffer) + .elementBDA = { mdiBuffer->getDeviceAddress() + structureOffsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES] }, + .elementCount = { limits.totalIndirectDrawCount }, + .scale = { trs.scale[0u], trs.scale[1u] }, + .translate = { trs.translate[0u], trs.translate[1u] }, + .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } }; - commandBuffer->drawIndexedIndirect(binding, limits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); } - - return true; + + const SBufferBinding binding = + { + .offset = structureOffsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES], + .buffer = smart_refctd_ptr(mdiBuffer) + }; + + commandBuffer->drawIndexedIndirect(binding, limits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); } + + return true; +} - bool UI::update(const SUpdateParameters& params) - { - auto & io = ImGui::GetIO(); +bool UI::update(const SUpdateParameters& params) +{ + auto & io = ImGui::GetIO(); - io.DisplaySize = ImVec2(params.displaySize.x, params.displaySize.y); + io.DisplaySize = ImVec2(params.displaySize.x, params.displaySize.y); - handleMouseEvents(params); - handleKeyEvents(params); + handleMouseEvents(params); + handleKeyEvents(params); - ImGui::NewFrame(); + ImGui::NewFrame(); - for (auto const& subscriber : m_subscribers) - subscriber(); + for (auto const& subscriber : m_subscribers) + subscriber(); - return true; - } + return true; +} - size_t UI::registerListener(const std::function& listener) - { - assert(listener != nullptr); - m_subscribers.emplace_back(listener); - return m_subscribers.size() - 1u; - } +size_t UI::registerListener(const std::function& listener) +{ + assert(listener != nullptr); + m_subscribers.emplace_back(listener); + return m_subscribers.size() - 1u; +} - std::optional UI::unregisterListener(size_t id) +std::optional UI::unregisterListener(size_t id) +{ + if (id < m_subscribers.size()) { - if (id < m_subscribers.size()) - { - m_subscribers.erase(m_subscribers.begin() + id); - return id; - } - - return std::nullopt; + m_subscribers.erase(m_subscribers.begin() + id); + return id; } - void* UI::getContext() - { - return reinterpret_cast(ImGui::GetCurrentContext()); - } + return std::nullopt; +} - void UI::setContext(void* imguiContext) - { - ImGui::SetCurrentContext(reinterpret_cast(imguiContext)); - } +void* UI::getContext() +{ + return reinterpret_cast(ImGui::GetCurrentContext()); +} + +void UI::setContext(void* imguiContext) +{ + ImGui::SetCurrentContext(reinterpret_cast(imguiContext)); +} } \ No newline at end of file From 548ee6357088faec209a01f51f33e97c118f1c99 Mon Sep 17 00:00:00 2001 From: devsh Date: Wed, 9 Oct 2024 22:42:43 +0200 Subject: [PATCH 130/148] correct horrible allocator bugs --- include/nbl/core/alloc/AddressAllocatorBase.h | 2 +- include/nbl/core/alloc/GeneralpurposeAddressAllocator.h | 2 +- include/nbl/core/alloc/LinearAddressAllocator.h | 2 +- include/nbl/video/alloc/CAsyncSingleBufferSubAllocator.h | 2 +- include/nbl/video/alloc/StreamingTransientDataBuffer.h | 5 +++++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/nbl/core/alloc/AddressAllocatorBase.h b/include/nbl/core/alloc/AddressAllocatorBase.h index 1ed7b9603b..7dea484fd9 100644 --- a/include/nbl/core/alloc/AddressAllocatorBase.h +++ b/include/nbl/core/alloc/AddressAllocatorBase.h @@ -117,7 +117,7 @@ namespace core return true; } - inline void* getAddressOffset() const noexcept {return addressOffset;} + inline _size_type getAddressOffset() const noexcept {return addressOffset;} inline const void* getReservedSpacePtr() const noexcept {return reservedSpace;} diff --git a/include/nbl/core/alloc/GeneralpurposeAddressAllocator.h b/include/nbl/core/alloc/GeneralpurposeAddressAllocator.h index 798af39e46..04e727cfc4 100644 --- a/include/nbl/core/alloc/GeneralpurposeAddressAllocator.h +++ b/include/nbl/core/alloc/GeneralpurposeAddressAllocator.h @@ -516,7 +516,7 @@ class GeneralpurposeAddressAllocator : public AddressAllocatorBase tLock(stAccessVerfier,std::try_to_lock_t()); assert(tLock.owns_lock()); #endif // _NBL_DEBUG - multi_deallocate(count,addr,bytes,{}); + m_composed.multi_deallocate(count,addr,bytes); } // TODO: improve signature of this function in the future template diff --git a/include/nbl/video/alloc/StreamingTransientDataBuffer.h b/include/nbl/video/alloc/StreamingTransientDataBuffer.h index 01dc04b5e1..44720f968c 100644 --- a/include/nbl/video/alloc/StreamingTransientDataBuffer.h +++ b/include/nbl/video/alloc/StreamingTransientDataBuffer.h @@ -70,6 +70,9 @@ class StreamingTransientDataBuffer // inline size_type max_size() noexcept {return m_composed.max_size();} + // anyone gonna use it? + inline const auto& getAddressAllocator() const noexcept {return m_composed.getAddressAllocator();} + // perfect forward to `Composed` method template inline value_type multi_allocate(Args&&... args) noexcept @@ -187,6 +190,8 @@ class StreamingTransientDataBufferMT : public core::IReferenceCounted return retval; } + //! you should really `this->get_lock()` if you need the guarantee that the state doesn't change + inline const auto& getAddressAllocator() const noexcept {return m_composed.getAddressAllocator();} template inline size_type multi_allocate(Args&&... args) noexcept From 3ae9e9e8e5a4c92ea23fb1be833c76e7b3d71b4a Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 01:05:23 +0200 Subject: [PATCH 131/148] first refactor draft --- src/nbl/ext/ImGui/ImGui.cpp | 764 +++++++++++++++--------------------- 1 file changed, 311 insertions(+), 453 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 987974329d..010c7715c0 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -25,184 +25,7 @@ namespace nbl::ext::imgui { using mdi_buffer_t = UI::SMdiBuffer; using compose_t = typename mdi_buffer_t::compose_t; -using mdi_size_t = compose_t::size_type; -static constexpr auto InvalidAddress = compose_t::invalid_value; -static constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); -static constexpr auto MdiMaxSize = *std::max_element(MdiSizes.begin(), MdiSizes.end()); -static const auto MdiMaxAlignment = roundUpToPoT(MdiMaxSize); - -struct DrawItemMeta -{ - enum SBufferIx - { - VERTEX, - INDEX, - - COUNT - }; - - //! total left bytes to upload for X-th command list - size_t totalLeftBytesToUpload; - - //! we allocate SBufferIx::COUNT chunks per command list from which we suballocate to operate on - std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { sizeof(ImDrawVert), sizeof(ImDrawIdx) }; - std::vector filled = { false, false }; - - //! those buffers will be suballocated & filled from a block of memory, each block memory request is multiplied with this factor - if a block fails to be allocated the factor decreases (divided by 2 on fail) - float memoryBlockFactor = 1.f; -}; - -struct DrawItem -{ - ImDrawList* cmdList; - DrawItemMeta& meta; - uint32_t cmdListIndex, drawIdOffset; -}; - -class ImGuiCommandListIterator -{ -public: - using value_type = DrawItem; - using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - using iterator_category = std::forward_iterator_tag; - - ImGuiCommandListIterator(const ImDrawData* drawData, std::vector& metaData, uint32_t index = 0u) - : drawData(drawData), metaList(metaData), index(index) {} - - ImGuiCommandListIterator& operator++() - { - auto* currentList = drawData->CmdLists[index]; - drawIdOffset += currentList->CmdBuffer.Size; - - ++index; - return *this; - } - - bool operator!=(const ImGuiCommandListIterator& other) const - { - return index != other.index; - } - - value_type operator*() const - { - return { .cmdList = drawData->CmdLists[index], .meta = metaList[index], .cmdListIndex = index, .drawIdOffset = drawIdOffset }; - } - -private: - const ImDrawData* drawData; - std::vector& metaList; - uint32_t index = {}, drawIdOffset = {}; -}; - -class ImGuiCommandListRange -{ -public: - //! those structs we allocate within single block, in general we assume we fail entire render call if we cannot upload all indirect objects (no dynamic rendering) - its possible to have those structs in separate blocks & to tell vkCmdDrawIndexedIndirect about block strides but we cannot guarantee each one will be the same size - //! with our allocation strategy unless we split indirect call into smaller pieces (however it doesnt make any sense if we assume all objects must be uploaded anyway imo - if all then why to bother?), also there is a very low chance this memory block will ever exceed 1KB even if you have a lot of GUI windows (< 45 draw commands, 22 bytes * limits.totalIndirectDrawCount) since its very small. - struct STightStructs - { - // we have total StructureIx::COUNT of blocks to allocate first before uploading command lists data - enum StructureIx - { - INDIRECT_STRUCTURES = 0u, - ELEMENT_STRUCTURES = 1u, - COUNT - }; - - std::array offsets = { InvalidAddress, InvalidAddress }, sizes = {}, alignments = { alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData) }; - bool allocated = false; - }; - - ImGuiCommandListRange(const ImDrawData* drawData) - : drawData(drawData), metaList(drawData->CmdListsCount) - { - for (uint32_t i = 0; i < drawData->CmdListsCount; i++) - { - auto& meta = metaList[i]; - const ImDrawList* commandList = drawData->CmdLists[i]; - - limits.totalIndirectDrawCount += commandList->CmdBuffer.Size; - meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::VERTEX] = commandList->VtxBuffer.Size * sizeof(ImDrawVert); - meta.totalLeftBytesToUpload += meta.sizes[DrawItemMeta::INDEX] = commandList->IdxBuffer.Size * sizeof(ImDrawIdx); - - assert([&]() -> bool // we should never hit it - { - return (meta.offsets.size() == meta.sizes.size()) - && (meta.filled.size() == meta.offsets.size()) - && (std::reduce(std::begin(meta.sizes), std::end(meta.sizes)) == meta.totalLeftBytesToUpload) - && (std::all_of(std::cbegin(meta.offsets), std::cend(meta.offsets), [](const auto& offset) { return offset == InvalidAddress; })); - }()); // debug check only - } - - limits.totalByteSizeRequest += drawData->TotalVtxCount * sizeof(ImDrawVert); - limits.totalByteSizeRequest += drawData->TotalIdxCount * sizeof(ImDrawIdx); - - requiredStructsBlockInfo.sizes[STightStructs::INDIRECT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(VkDrawIndexedIndirectCommand); - requiredStructsBlockInfo.sizes[STightStructs::ELEMENT_STRUCTURES] = limits.totalIndirectDrawCount * sizeof(PerObjectData); - } - - inline ImGuiCommandListIterator begin() { return ImGuiCommandListIterator(drawData, metaList, 0u); } - inline ImGuiCommandListIterator end() { return ImGuiCommandListIterator(drawData, metaList, drawData->CmdListsCount); } - - //! allocates a chunk from which STightStructs::COUNT blocks will be suballocated, required structs are indirects & elements - bool allocateRequiredBlock(mdi_buffer_t* mdi) - { - requiredStructsBlockInfo.allocated = true; - - auto [blockOffset, blockSize] = std::make_tuple(InvalidAddress, std::min(mdi->compose->max_size(), std::reduce(std::begin(requiredStructsBlockInfo.sizes), std::end(requiredStructsBlockInfo.sizes)))); - - mdi->compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &blockOffset, &blockSize, &MdiMaxAlignment); - - if (blockOffset == InvalidAddress) - return (requiredStructsBlockInfo.allocated = false); - - bigChunkRequestInfo.offsets.emplace_back() = blockOffset; - bigChunkRequestInfo.sizes.emplace_back() = blockSize; - - auto* const mdiData = reinterpret_cast(mdi->compose->getBufferPointer()); - mdi_buffer_t::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, blockOffset, 0u, MdiMaxAlignment, blockSize); - mdi_buffer_t::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, requiredStructsBlockInfo.offsets.size(), requiredStructsBlockInfo.offsets.data(), requiredStructsBlockInfo.sizes.data(), requiredStructsBlockInfo.alignments.data()); - - for (const auto& offset : requiredStructsBlockInfo.offsets) - if (offset == InvalidAddress) - return (requiredStructsBlockInfo.allocated) = false; - - return requiredStructsBlockInfo.allocated; - } - - void latchDeallocations(mdi_buffer_t* mdi, ISemaphore::SWaitInfo waitInfo) - { - mdi->compose->multi_deallocate(bigChunkRequestInfo.offsets.size(), bigChunkRequestInfo.offsets.data(), bigChunkRequestInfo.sizes.data(), waitInfo); - } - - inline const auto& getLimits() { return limits; } - inline const auto& getRequiredStructsBlockInfo() { return requiredStructsBlockInfo; } - - struct - { - std::vector offsets, sizes; - } bigChunkRequestInfo; - -private: - - struct SLimits - { - //! sum of metaList[x].sizes - all bytes which needs to be uploaded to cover all of totalIndirectDrawCount objects, note we don't count element & indirect structers there - size_t totalByteSizeRequest = {}, - - //! amount of total objects to draw with indirect indexed call - totalIndirectDrawCount = {}; - }; - - const ImDrawData* drawData; - std::vector metaList; - - SLimits limits; - STightStructs requiredStructsBlockInfo; -}; static constexpr SPushConstantRange PushConstantRanges[] = { @@ -1125,15 +948,16 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint, const std::span scissors) { + system::logger_opt_ptr logger = m_cachedCreationParams.utilities->getLogger(); if (!commandBuffer) { - m_cachedCreationParams.utilities->getLogger()->log("Invalid command buffer!", ILogger::ELL_ERROR); + logger.log("Invalid command buffer!", ILogger::ELL_ERROR); return false; } if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) { - m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not in recording state!", ILogger::ELL_ERROR); + logger.log("Command buffer is not in recording state!", ILogger::ELL_ERROR); return false; } @@ -1143,18 +967,35 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa if(!recordingSubpass) { - m_cachedCreationParams.utilities->getLogger()->log("Command buffer is not recording a subpass!", ILogger::ELL_ERROR); + logger.log("Command buffer is not recording a subpass!", ILogger::ELL_ERROR); return false; } } + // TODO: clean the typedef up + using streaming_buffer_t = video::StreamingTransientDataBufferST>; + using offset_t = streaming_buffer_t::size_type; + // A little bit of math to workout our max alignment + constexpr auto MdiSizes = std::to_array({ sizeof(VkDrawIndexedIndirectCommand), sizeof(PerObjectData), sizeof(ImDrawIdx), sizeof(ImDrawVert) }); + // shared nPoT alignment needs to be divisible by all smaller ones to satisfy an allocation from all + constexpr offset_t MaxAlignment = std::reduce(MdiSizes.begin(),MdiSizes.end(),1,[](const offset_t a, const offset_t b)->offset_t{return std::lcm(a,b);}); + // allocator initialization needs us to round up to PoT + const auto MaxPOTAlignment = roundUpToPoT(MaxAlignment); + // quick sanity check + auto* streaming = m_mdi.compose.get(); + if (streaming->getAddressAllocator().max_alignment()IsBuilt()) { - m_cachedCreationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", ILogger::ELL_ERROR); + logger.log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", ILogger::ELL_ERROR); return false; } @@ -1168,46 +1009,28 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa float const frameBufferHeight = drawData->DisplaySize.y * drawData->FramebufferScale.y; if (frameBufferWidth > 0 && frameBufferHeight > 0 && drawData->TotalVtxCount > 0) { - const struct + // set our viewport and scissor, there's a deferred todo https://github.com/Devsh-Graphics-Programming/Nabla/issues/751 + SViewport const viewport { - ImVec2 off; // (0,0) unless using multi-viewports - ImVec2 scale; // (1,1) unless using retina display which are often (2,2) - ImVec2 framebuffer; // width, height - - // Project scissor/clipping rectangles into frame-buffer space - ImVec4 getClipRectangle(const ImDrawCmd* cmd) const - { - assert(cmd); - - ImVec4 rectangle; - rectangle.x = (cmd->ClipRect.x - off.x) * scale.x; - rectangle.y = (cmd->ClipRect.y - off.y) * scale.y; - rectangle.z = (cmd->ClipRect.z - off.x) * scale.x; - rectangle.w = (cmd->ClipRect.w - off.y) * scale.y; - - return rectangle; - } - - VkRect2D getScissor(ImVec4 clipRectangle) const + .x = 0, + .y = 0, + .width = frameBufferWidth, + .height = frameBufferHeight, + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + commandBuffer->setViewport(0u, 1u, &viewport); + { + if (scissors.empty()) { - // Negative offsets are illegal for vkCmdSetScissor - if (clipRectangle.x < 0.0f) - clipRectangle.x = 0.0f; - if (clipRectangle.y < 0.0f) - clipRectangle.y = 0.0f; - - {// Apply scissor/clipping rectangle - VkRect2D scissor {}; - scissor.offset.x = static_cast(clipRectangle.x); - scissor.offset.y = static_cast(clipRectangle.y); - scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); - scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); - - return scissor; - } + VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; + commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) } - } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; + else + commandBuffer->setScissor(scissors); + } + // get structs for computing the NDC coordinates ready struct TRS { vector2df_SIMD scale; @@ -1218,7 +1041,6 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa return in * scale + translate; } }; - const TRS trs = [&]() { TRS retV; @@ -1228,281 +1050,317 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa return std::move(retV); }(); + + // everything binds the buffer once at base and works via local offsets + const SBufferBinding binding = + { + .offset = 0u, + .buffer = smart_refctd_ptr(streaming->getBuffer()) + }; + { + constexpr auto IndexType = sizeof(ImDrawIdx)==2u ? EIT_16BIT:EIT_32BIT; + if (!commandBuffer->bindIndexBuffer(binding,IndexType)) + return false; // commandBuffer should log failures if any + } + if (!commandBuffer->bindVertexBuffers(0u,1u,&binding)) + return false; - ImGuiCommandListRange imCmdRange(drawData); + // now proceed with filling the streaming buffer with our data { - auto streamingBuffer = m_mdi.compose; - auto binding = streamingBuffer->getBuffer()->getBoundMemory(); - assert(binding.memory->isCurrentlyMapped()); - - auto* const mdiData = reinterpret_cast(streamingBuffer->getBufferPointer()); - const auto& requiredStructsBlockInfo = imCmdRange.getRequiredStructsBlockInfo(); - auto& chunksInfo = imCmdRange.bigChunkRequestInfo; - const auto& limits = imCmdRange.getLimits(); - - //! we will try to upload all imgui data to mdi streaming buffer but we cannot guarantee an allocation can be done in single request nor buffers data will come from continous memory block (from single chunk) - for (mdi_size_t totalUploadedSize = 0ull; totalUploadedSize < limits.totalByteSizeRequest;) + // Use LinearAddressAllocators to allocate vertex and index spans + using suballocator_t = core::LinearAddressAllocatorST; + constexpr offset_t ImaginarySizeUpperBound = 0x1<<30; + constexpr offset_t GeoAlignment = std::lcm(sizeof(ImDrawIdx),sizeof(ImDrawVert)); + // We allocate in an imaginary infinte linear buffers, then actually commit to the allocations when we no longer can fit in the allocated chunk + struct MetaAllocator { - auto uploadCommandListData = [&](DrawItem drawItem) - { - //! (*) note we make an assumption here, at this point they are allocated & ready to fill, read the ImGuiCommandListRange::STightStructs description for more info - auto* const indirectStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES]); - auto* const elementStructures = reinterpret_cast(mdiData + requiredStructsBlockInfo.offsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES]); - - const auto& [vertexBuffer, indexBuffer] = std::make_tuple(drawItem.cmdList->VtxBuffer, drawItem.cmdList->IdxBuffer); - const auto [vtxAllocationIx, idxAllocationIx] = std::make_tuple(DrawItemMeta::VERTEX, DrawItemMeta::INDEX); - constexpr auto ChunkPaddings = std::to_array({ sizeof(ImDrawVert), sizeof(ImDrawIdx) }); - - if (drawItem.meta.totalLeftBytesToUpload >= 0u) - { - // we have 2 buffers to fill per command list, we will try with as tight streaming memory chunks as possible to not waste block memory (too big chunks allocated but only certain % used in reality) & make it a way our suballocator likes them (we respect required alignments) - for (uint16_t bufferIx = 0u; bufferIx < DrawItemMeta::COUNT; ++bufferIx) - { - if (drawItem.meta.filled[bufferIx]) - continue; - - const auto& bufferSizeTotalUploadRequest = drawItem.meta.sizes[bufferIx]; - const auto& requiredChunkPadding = ChunkPaddings[bufferIx]; + public: + MetaAllocator() : geoAllocator(nullptr,0,0,core::roundUpToPoT(GeoAlignment),ImaginarySizeUpperBound) {} - /* (**) note we add extra requiredChunkPadding to let suballocator start at required multiple of size of packed object, this way suballocator always success if the block can be allocated */ - auto [chunkOffset, chunkSize] = std::make_tuple(InvalidAddress, min(streamingBuffer->max_size(), bufferSizeTotalUploadRequest + requiredChunkPadding)); - - //! (*) the request therefore is tight & contains small padding for the suballocator to find the proper offset start - const size_t unallocatedSize = m_mdi.compose->multi_allocate(std::chrono::steady_clock::now() + std::chrono::microseconds(100u), 1u, &chunkOffset, &chunkSize, &MdiMaxAlignment); - - if (chunkOffset == InvalidAddress) - return; - else - { - // chunk allocated for a buffer? update the state's offset table stack & let suballocator do the job (we made sure the only memory we "waste" is the padding part, at this point suballocator *should* always success) - chunksInfo.offsets.emplace_back() = chunkOffset; - chunksInfo.sizes.emplace_back() = chunkSize; - const auto alignOffsetRequired = requiredChunkPadding - (chunkOffset % requiredChunkPadding); // read (**), this is the key part - - //! (*) we create linear suballocator to fill the allocated chunk of memory - SMdiBuffer::suballocator_traits_t::allocator_type fillSubAllocator(mdiData, chunkOffset, alignOffsetRequired, MdiMaxAlignment, chunkSize); - - //! (*) we suballocate from the allocated chunk with required alignments - SMdiBuffer::suballocator_traits_t::multi_alloc_addr(fillSubAllocator, 1u, drawItem.meta.offsets.data() + bufferIx, drawItem.meta.sizes.data() + bufferIx, drawItem.meta.alignments.data() + bufferIx); - - auto upload = [&]() -> size_t - { - size_t uploaded = {}; + offset_t getElementCount() const {return drawIndirectCount;} - auto updateSuballocation = [&](const uint32_t allocationIx) -> size_t - { - const bool isFilled = drawItem.meta.filled[allocationIx]; - - if (!isFilled) - { - const auto bytesToFill = drawItem.meta.sizes[allocationIx]; - uploaded += bytesToFill; - drawItem.meta.filled[allocationIx] = true; - return bytesToFill; - } - - return 0u; - }; - - auto fillBuffer = [&](const auto* in, const uint32_t allocationIx) - { - auto& offset = drawItem.meta.offsets[allocationIx]; - - if (offset == InvalidAddress) - return false; - else - { - const auto bytesToFill = updateSuballocation(allocationIx); - - if (bytesToFill != 0u) - ::memcpy(mdiData + offset, in, bytesToFill); - } - - return true; - }; - - auto validateObjectOffsets = [&]() -> bool - { - const auto [vtxOffset, idxOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx], drawItem.meta.offsets[idxAllocationIx]); - bool ok = true; - - if (vtxOffset != InvalidAddress) - ok &= ((vtxOffset % sizeof(ImDrawVert)) == 0u); - - if (idxOffset != InvalidAddress) - ok &= ((idxOffset % sizeof(ImDrawIdx)) == 0u); - - _NBL_BREAK_IF(!ok); + // doesn't reset the `memBlock` that gets handled outside + void reset() + { + geoAllocator.reset(); + drawIndirectCount = 0; + } - return ok; // if offsets are valid then must be aligned properly! - }; + struct PushResult + { + // these offsets are relative to the offset for all geometry data + offset_t indexByteOffset; + offset_t vertexByteOffset; + }; + PushResult push(const ImDrawList* list) + { + drawIndirectCount += list->CmdBuffer.Size; + const PushResult retval = { + .indexByteOffset = geoAllocator.alloc_addr(sizeof(ImDrawIdx)*list->IdxBuffer.size(),sizeof(ImDrawIdx)), + .vertexByteOffset = geoAllocator.alloc_addr(sizeof(ImDrawVert)*list->VtxBuffer.size(),sizeof(ImDrawVert)) + }; + // should never happen, the linear address allocator space is enormous + const auto InvalidAddress = suballocator_t::invalid_address; + assert(retval.indexByteOffset!=InvalidAddress && retval.vertexByteOffset!=InvalidAddress); + return retval; + } - assert(validateObjectOffsets()); // debug check only + struct FinalizeResult + { + // these are total, from streaming buffer start + offset_t drawIndirectByteOffset; + offset_t perDrawByteOffset; + offset_t geometryByteOffset; + offset_t totalSize; + }; + // so now if we were (we are not yet) to make a linear address allocator over a free chunk, how much space would our allocations take and what would be their offsets + FinalizeResult finalize() const + { + suballocator_t imaginaryChunk(nullptr,memBlockOffset,0,roundUpToPoT(MaxAlignment),ImaginarySizeUpperBound); + FinalizeResult retval = { + .drawIndirectByteOffset = imaginaryChunk.alloc_addr(sizeof(VkDrawIndexedIndirectCommand)*drawIndirectCount,sizeof(VkDrawIndexedIndirectCommand)), + .perDrawByteOffset = imaginaryChunk.alloc_addr(sizeof(PerObjectData)*drawIndirectCount,sizeof(PerObjectData)), + .geometryByteOffset = imaginaryChunk.alloc_addr(geoAllocator.get_allocated_size(),GeoAlignment), + .totalSize = imaginaryChunk.get_allocated_size() + }; + // should never happen, the linear address allocator space is enormous + const auto InvalidAddress = suballocator_t::invalid_address; + assert(retval.drawIndirectByteOffset!=InvalidAddress && retval.perDrawByteOffset!=InvalidAddress && retval.geometryByteOffset!=InvalidAddress); + // should always allocate at the start, the alignment was correct + assert(retval.drawIndirectByteOffset==memBlockOffset && (retval.drawIndirectByteOffset%sizeof(VkDrawIndexedIndirectCommand))==0); + // element alignment check only, geometry will be checked after every push in `endDrawBatch` + assert((retval.perDrawByteOffset%sizeof(PerObjectData))==0); + return retval; + } - fillBuffer(vertexBuffer.Data, vtxAllocationIx); - fillBuffer(indexBuffer.Data, idxAllocationIx); + bool tryPush(const ImDrawList* list) + { + // backup old state + const auto oldAllocatorCursor = geoAllocator.get_allocated_size(); + // perform and check + push(list); + if (finalize().totalSize>memBlockSize) + { + // unwind the allocations + drawIndirectCount -= list->CmdBuffer.Size; + geoAllocator.reset(oldAllocatorCursor); + return false; + } + return true; + } + + // allocate max sized chunk and set up our linear allocators + offset_t memBlockOffset = streaming_buffer_t::invalid_value; + offset_t memBlockSize = 0; + + private: + suballocator_t geoAllocator; + // however DrawIndirect and ElementData structs need to be close together + offset_t drawIndirectCount = 0; + } metaAlloc; + + // Run Length Encode pattern, you advance iterator until some change occurs, which forces a "block end" behaviour + auto listIt = drawData->CmdLists.begin(); + auto lastBatchEnd = listIt; + + // can't be a static unfortunately + const auto noWait = std::chrono::steady_clock::now(); + // get this set up once + const struct + { + ImVec2 off; // (0,0) unless using multi-viewports + ImVec2 scale; // (1,1) unless using retina display which are often (2,2) + ImVec2 framebuffer; // width, height - return uploaded; - }; + // Project scissor/clipping rectangles into frame-buffer space + ImVec4 getClipRectangle(const ImDrawCmd& cmd) const + { + ImVec4 rectangle; + rectangle.x = (cmd.ClipRect.x - off.x) * scale.x; + rectangle.y = (cmd.ClipRect.y - off.y) * scale.y; + rectangle.z = (cmd.ClipRect.z - off.x) * scale.x; + rectangle.w = (cmd.ClipRect.w - off.y) * scale.y; - const size_t uploaded = upload(); - const size_t deltaLeft = drawItem.meta.totalLeftBytesToUpload - uploaded; + return rectangle; + } - totalUploadedSize += uploaded; - drawItem.meta.totalLeftBytesToUpload = std::clamp(deltaLeft, 0ull, drawItem.meta.totalLeftBytesToUpload); - } - } + VkRect2D getScissor(ImVec4 clipRectangle) const + { + // Negative offsets are illegal for vkCmdSetScissor + if (clipRectangle.x < 0.0f) + clipRectangle.x = 0.0f; + if (clipRectangle.y < 0.0f) + clipRectangle.y = 0.0f; + + {// Apply scissor/clipping rectangle + VkRect2D scissor{}; + scissor.offset.x = static_cast(clipRectangle.x); + scissor.offset.y = static_cast(clipRectangle.y); + scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); + scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); + + return scissor; + } + } + } clip{ .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; - // we consider buffers valid for command list if we suballocated BOTH of them (under the hood filled at first time then skipped to not repeat memcpy) - if buffers are valid then command list is as well - const bool buffersFilled = drawItem.meta.filled[DrawItemMeta::VERTEX] && drawItem.meta.filled[DrawItemMeta::INDEX]; + // actual lambda + auto beginDrawBatch = [&]()->bool + { + // just a conservative lower bound, we will check again if allocation is hopeless to record a draw later + constexpr uint32_t SmallestAlloc = 3*sizeof(ImDrawIdx)+3*sizeof(ImDrawVert)+sizeof(VkDrawIndexedIndirectCommand)+sizeof(PerObjectData); + // 2 tries + for (auto t=0; t<2; t++) + { + // Allocate a chunk as large as possible, a bit of trivia, `max_size` pessimizes the size assuming you're going to ask for allocation with Allocator's Max Alignment + // There's a bit of a delay/inaccuracy with `max_size` so try many sizes before giving up + metaAlloc.memBlockSize = streaming->max_size(); + while (metaAlloc.memBlockSize>=SmallestAlloc) + { + // first time don't wait and require block ASAP, second time wait till timeout point + // NOTE: With dynamic rendering we'd do a shorter wait and overflow submit if we still fail + if (streaming->multi_allocate(t ? waitPoint:noWait,1,&metaAlloc.memBlockOffset,&metaAlloc.memBlockSize,&MaxAlignment)==0) + return true; + // after first-fail always poll events to completion, this will execute all deferred deallocations it can + streaming->cull_frees(); + // go fast, O(logN) tries + metaAlloc.memBlockSize >>= 1; + } + } + logger.log("Failed to allocate even the smallest chunk from streaming buffer for the next drawcall batch.",ILogger::ELL_ERROR); + return false; + }; - if (buffersFilled) + // the batch end is a lambda cause unfortunately whenever you perform the RLE pattern, you need to perform an implicit run-break outside the loop + const auto streamingBaseAddress = streaming->getBuffer()->getDeviceAddress(); + auto* const streamingPtr = reinterpret_cast(streaming->getBufferPointer()); + assert(streamingPtr); + // implementation detail of the allocator stays the same + const auto minBlockSize = streaming->getAddressAllocator().min_size(); + auto endDrawBatch = [&]()->void + { + const auto offsets = metaAlloc.finalize(); + auto* drawIndirectIt = reinterpret_cast(streamingPtr+offsets.drawIndirectByteOffset); + auto* elementIt = reinterpret_cast(streamingPtr+offsets.perDrawByteOffset); + // replay allocations and this time actually memcpy + { + metaAlloc.reset(); + // we use base instance as `gl_DrawID` in case GPU is missing it + uint32_t drawID = 0; + for (auto localListIt=lastBatchEnd; localListIt!=listIt; localListIt++) + { + const auto* list = *localListIt; + auto geo = metaAlloc.push(list); + // now add the global offsets + geo.indexByteOffset += offsets.geometryByteOffset; + geo.vertexByteOffset += offsets.geometryByteOffset; + // alignments should match + assert((geo.indexByteOffset%sizeof(ImDrawIdx))==0); + assert((geo.vertexByteOffset%sizeof(ImDrawVert))==0); + // offsets since start of buffer, but counted in objects of size T + const auto idxGlobalObjectOffset = geo.indexByteOffset/sizeof(ImDrawIdx); + const auto vtxGlobalObjectOffset = geo.vertexByteOffset/sizeof(ImDrawVert); + // IMGUI's API prevents us from more finely splitting draws (vertex buffer is shared for whole list) + const auto& imCmdBuf = list->CmdBuffer; + for (auto j=0; j!=imCmdBuf.size(); j++) { - const auto [vtxGlobalObjectOffset, idxGlobalObjectOffset] = std::make_tuple(drawItem.meta.offsets[vtxAllocationIx] / sizeof(ImDrawVert), drawItem.meta.offsets[idxAllocationIx] / sizeof(ImDrawIdx)); - - for (uint32_t j = 0u; j < drawItem.cmdList->CmdBuffer.Size; j++) + const auto& cmd = imCmdBuf[j]; + drawIndirectIt->firstInstance = drawID++; + drawIndirectIt->indexCount = cmd.ElemCount; + drawIndirectIt->instanceCount = 1u; + drawIndirectIt->vertexOffset = vtxGlobalObjectOffset+cmd.VtxOffset; + drawIndirectIt->firstIndex = idxGlobalObjectOffset+cmd.IdxOffset; + drawIndirectIt++; + // per obj filling involves clipping { - const uint32_t drawID = drawItem.drawIdOffset + j; - - const auto* cmd = &drawItem.cmdList->CmdBuffer[j]; - auto* indirect = indirectStructures + drawID; - auto* element = elementStructures + drawID; - - // we use base instance as draw ID - indirect->firstInstance = drawID; - indirect->indexCount = cmd->ElemCount; - indirect->instanceCount = 1u; - indirect->vertexOffset = vtxGlobalObjectOffset + cmd->VtxOffset; - indirect->firstIndex = idxGlobalObjectOffset + cmd->IdxOffset; - const auto clipRectangle = clip.getClipRectangle(cmd); const auto scissor = clip.getScissor(clipRectangle); - auto packSnorm16 = [](float ndc) -> int16_t { return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils }; - const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); - struct snorm16_t2_packed { int16_t x, y; }; - - reinterpret_cast(element->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; - reinterpret_cast(element->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; - - element->texId = cmd->TextureId.textureID; - element->samplerIx = cmd->TextureId.samplerIx; + reinterpret_cast(elementIt->aabbMin) = { .x = packSnorm16(vMin.x), .y = packSnorm16(vMin.y) }; + reinterpret_cast(elementIt->aabbMax) = { .x = packSnorm16(vMax.x), .y = packSnorm16(vMax.y) }; + elementIt->texId = cmd.TextureId.textureID; + elementIt->samplerIx = cmd.TextureId.samplerIx; } + elementIt++; } + memcpy(streamingPtr+geo.indexByteOffset,list->IdxBuffer.Data,list->IdxBuffer.size_in_bytes()); + memcpy(streamingPtr+geo.vertexByteOffset,list->VtxBuffer.Data,list->VtxBuffer.size_in_bytes()); } - }; - - if(!requiredStructsBlockInfo.allocated) - imCmdRange.allocateRequiredBlock(&m_mdi); - - // attempt to upload data only if we could allocate minimum of required indirect & element structs - if(requiredStructsBlockInfo.allocated) - std::for_each(imCmdRange.begin(), imCmdRange.end(), uploadCommandListData); - - // we let it run at least once - const bool timeout = std::chrono::steady_clock::now() >= waitPoint; + } - if (timeout) + // record draw call { - if (totalUploadedSize >= limits.totalByteSizeRequest) - break; // must be lucky to hit it or on debug + /* + Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to + draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + */ + { + PushConstants constants + { + .elementBDA = streamingBaseAddress+offsets.perDrawByteOffset, + .elementCount = metaAlloc.getElementCount(), + .scale = { trs.scale[0u], trs.scale[1u] }, + .translate = { trs.translate[0u], trs.translate[1u] }, + .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } + }; + + commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX|IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + } - imCmdRange.latchDeallocations(&m_mdi, waitInfo); - streamingBuffer->cull_frees(); - return false; + { + auto mdiBinding = binding; + mdiBinding.offset = offsets.drawIndirectByteOffset; + commandBuffer->drawIndexedIndirect(binding,metaAlloc.getElementCount(),sizeof(VkDrawIndexedIndirectCommand)); + } } - } - //! (*) blocks allocated, we just latch offsets deallocation to keep them alive as long as required - imCmdRange.latchDeallocations(&m_mdi, waitInfo); - } - auto mdiBuffer = smart_refctd_ptr(m_mdi.compose->getBuffer()); - const auto offset = mdiBuffer->getBoundMemory().offset; - { - const SBufferBinding binding = - { - .offset = 0u, - .buffer = smart_refctd_ptr(mdiBuffer) + // See if we can trim our chunk (dealloc unused range at the end immediately). This relies on an implementation detail of GeneralPurposeAllocator, + // you can slice up an allocation and manage slices independently as along as they are larger than a minimum block/allocation size + if (offsets.totalSize>=minBlockSize) + if (const offset_t unusedStart=metaAlloc.memBlockOffset+offsets.totalSize, unusedSize=metaAlloc.memBlockSize-offsets.totalSize; unusedSize>=minBlockSize) + { + streaming->multi_deallocate(1,&unusedStart,&unusedSize); + // trime the leftover actually used block + metaAlloc.memBlockSize = offsets.totalSize; + } + // latch our used chunk free + streaming->multi_deallocate(1,&metaAlloc.memBlockOffset,&metaAlloc.memBlockSize,waitInfo); + // reset to initial state + metaAlloc.memBlockOffset = streaming_buffer_t::invalid_value; + metaAlloc.reset(); }; - constexpr auto IndexType = sizeof(ImDrawIdx) == 2u ? EIT_16BIT : EIT_32BIT; - if (!commandBuffer->bindIndexBuffer(binding, IndexType)) - { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind index buffer!", ILogger::ELL_ERROR); - assert(false); - } - } - - { - const SBufferBinding bindings[] = - {{ - .offset = 0u, - .buffer = smart_refctd_ptr(mdiBuffer) - }}; - - if(!commandBuffer->bindVertexBuffers(0u, 1u, bindings)) - { - m_cachedCreationParams.utilities->getLogger()->log("Could not bind vertex buffer!", ILogger::ELL_ERROR); - assert(false); - } - } - - SViewport const viewport - { - .x = 0, - .y = 0, - .width = frameBufferWidth, - .height = frameBufferHeight, - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - - commandBuffer->setViewport(0u, 1u, &viewport); - { - if (scissors.empty()) + if (!beginDrawBatch()) + return false; + // We will iterate over the lists and split them into multiple MDI calls if necessary + for (const auto listEnd=drawData->CmdLists.end(); listIt!=listEnd;) { - VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; - commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) + if (!metaAlloc.tryPush(*listIt)) + { + if (listIt==lastBatchEnd) + { + logger.log("Obtained maximum allocation from streaming buffer isn't even enough to fit a single IMGUI commandlist",system::ILogger::ELL_ERROR); + return false; + } + endDrawBatch(); + if (!beginDrawBatch()) + return false; + } + // only advance if we fit (otherwise we need to revisit current list for sizing) + else + listIt++; } - else - commandBuffer->setScissor(scissors); - } - - /* - Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to - draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - */ - - const auto& [structureOffsets, limits] = std::make_tuple(imCmdRange.getRequiredStructsBlockInfo().offsets, imCmdRange.getLimits()); - { - PushConstants constants - { - .elementBDA = { mdiBuffer->getDeviceAddress() + structureOffsets[ImGuiCommandListRange::STightStructs::ELEMENT_STRUCTURES] }, - .elementCount = { limits.totalIndirectDrawCount }, - .scale = { trs.scale[0u], trs.scale[1u] }, - .translate = { trs.translate[0u], trs.translate[1u] }, - .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } - }; - - commandBuffer->pushConstants(m_pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + endDrawBatch(); } - - const SBufferBinding binding = - { - .offset = structureOffsets[ImGuiCommandListRange::STightStructs::INDIRECT_STRUCTURES], - .buffer = smart_refctd_ptr(mdiBuffer) - }; - - commandBuffer->drawIndexedIndirect(binding, limits.totalIndirectDrawCount, sizeof(VkDrawIndexedIndirectCommand)); } return true; From aaac792f9b5c1121a229b62882fbfd3d2e3cdc59 Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 02:49:53 +0200 Subject: [PATCH 132/148] I was being super dumb with not advancing `lastBatchEnd` (huge overwrites upon breaks), also spotted my issues with `beginDrawBatch` P.S. also unsure if aggregate initialization has no well defined order --- src/nbl/ext/ImGui/ImGui.cpp | 65 ++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 010c7715c0..6061dec353 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1095,10 +1095,9 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa PushResult push(const ImDrawList* list) { drawIndirectCount += list->CmdBuffer.Size; - const PushResult retval = { - .indexByteOffset = geoAllocator.alloc_addr(sizeof(ImDrawIdx)*list->IdxBuffer.size(),sizeof(ImDrawIdx)), - .vertexByteOffset = geoAllocator.alloc_addr(sizeof(ImDrawVert)*list->VtxBuffer.size(),sizeof(ImDrawVert)) - }; + PushResult retval = {}; + retval.indexByteOffset = geoAllocator.alloc_addr(list->IdxBuffer.size_in_bytes(),sizeof(ImDrawIdx)); + retval.vertexByteOffset = geoAllocator.alloc_addr(list->VtxBuffer.size_in_bytes(),sizeof(ImDrawVert)); // should never happen, the linear address allocator space is enormous const auto InvalidAddress = suballocator_t::invalid_address; assert(retval.indexByteOffset!=InvalidAddress && retval.vertexByteOffset!=InvalidAddress); @@ -1117,12 +1116,11 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa FinalizeResult finalize() const { suballocator_t imaginaryChunk(nullptr,memBlockOffset,0,roundUpToPoT(MaxAlignment),ImaginarySizeUpperBound); - FinalizeResult retval = { - .drawIndirectByteOffset = imaginaryChunk.alloc_addr(sizeof(VkDrawIndexedIndirectCommand)*drawIndirectCount,sizeof(VkDrawIndexedIndirectCommand)), - .perDrawByteOffset = imaginaryChunk.alloc_addr(sizeof(PerObjectData)*drawIndirectCount,sizeof(PerObjectData)), - .geometryByteOffset = imaginaryChunk.alloc_addr(geoAllocator.get_allocated_size(),GeoAlignment), - .totalSize = imaginaryChunk.get_allocated_size() - }; + FinalizeResult retval = {}; + retval.drawIndirectByteOffset = imaginaryChunk.alloc_addr(sizeof(VkDrawIndexedIndirectCommand)*drawIndirectCount,sizeof(VkDrawIndexedIndirectCommand)); + retval.perDrawByteOffset = imaginaryChunk.alloc_addr(sizeof(PerObjectData)*drawIndirectCount,sizeof(PerObjectData)); + retval.geometryByteOffset = imaginaryChunk.alloc_addr(geoAllocator.get_allocated_size(),GeoAlignment); + retval.totalSize = imaginaryChunk.get_allocated_size(); // should never happen, the linear address allocator space is enormous const auto InvalidAddress = suballocator_t::invalid_address; assert(retval.drawIndirectByteOffset!=InvalidAddress && retval.perDrawByteOffset!=InvalidAddress && retval.geometryByteOffset!=InvalidAddress); @@ -1150,7 +1148,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa } // allocate max sized chunk and set up our linear allocators - offset_t memBlockOffset = streaming_buffer_t::invalid_value; + offset_t memBlockOffset; offset_t memBlockSize = 0; private: @@ -1207,14 +1205,18 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa // actual lambda auto beginDrawBatch = [&]()->bool { - // just a conservative lower bound, we will check again if allocation is hopeless to record a draw later - constexpr uint32_t SmallestAlloc = 3*sizeof(ImDrawIdx)+3*sizeof(ImDrawVert)+sizeof(VkDrawIndexedIndirectCommand)+sizeof(PerObjectData); + // push first item, because we need to fit at least one draw + metaAlloc.push(*(listIt++)); + metaAlloc.memBlockOffset = 0; + const uint32_t SmallestAlloc = metaAlloc.finalize().totalSize; + metaAlloc.memBlockOffset = streaming_buffer_t::invalid_value; // 2 tries for (auto t=0; t<2; t++) { // Allocate a chunk as large as possible, a bit of trivia, `max_size` pessimizes the size assuming you're going to ask for allocation with Allocator's Max Alignment // There's a bit of a delay/inaccuracy with `max_size` so try many sizes before giving up - metaAlloc.memBlockSize = streaming->max_size(); + // Also `max_size` doesn't defragment, meaning it cannot be trusted to be accurate, so force at least one try with the size we need + metaAlloc.memBlockSize = core::max(streaming->max_size(),SmallestAlloc); while (metaAlloc.memBlockSize>=SmallestAlloc) { // first time don't wait and require block ASAP, second time wait till timeout point @@ -1240,20 +1242,23 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa auto endDrawBatch = [&]()->void { const auto offsets = metaAlloc.finalize(); + const auto endOffset = offsets.drawIndirectByteOffset+offsets.totalSize; + uint32_t drawID = 0; auto* drawIndirectIt = reinterpret_cast(streamingPtr+offsets.drawIndirectByteOffset); auto* elementIt = reinterpret_cast(streamingPtr+offsets.perDrawByteOffset); // replay allocations and this time actually memcpy { + const auto endByte = streamingPtr+endOffset; metaAlloc.reset(); - // we use base instance as `gl_DrawID` in case GPU is missing it - uint32_t drawID = 0; - for (auto localListIt=lastBatchEnd; localListIt!=listIt; localListIt++) + for (; lastBatchEnd!=listIt; lastBatchEnd++) { - const auto* list = *localListIt; + const auto* list = *lastBatchEnd; auto geo = metaAlloc.push(list); // now add the global offsets geo.indexByteOffset += offsets.geometryByteOffset; geo.vertexByteOffset += offsets.geometryByteOffset; + assert(geo.indexByteOffsetfirstInstance = drawID++; drawIndirectIt->indexCount = cmd.ElemCount; drawIndirectIt->instanceCount = 1u; @@ -1294,8 +1300,17 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa } memcpy(streamingPtr+geo.indexByteOffset,list->IdxBuffer.Data,list->IdxBuffer.size_in_bytes()); memcpy(streamingPtr+geo.vertexByteOffset,list->VtxBuffer.Data,list->VtxBuffer.size_in_bytes()); + // not writing past the end + assert(streamingPtr+geo.indexByteOffset(streamingPtr+offsets.perDrawByteOffset)>=drawIndirectIt); + assert(reinterpret_cast(streamingPtr+offsets.geometryByteOffset)>=elementIt); + + // flush the used range + assert(!streaming->needsManualFlushOrInvalidate()); // record draw call { @@ -1307,7 +1322,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa PushConstants constants { .elementBDA = streamingBaseAddress+offsets.perDrawByteOffset, - .elementCount = metaAlloc.getElementCount(), + .elementCount = drawID, .scale = { trs.scale[0u], trs.scale[1u] }, .translate = { trs.translate[0u], trs.translate[1u] }, .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } @@ -1319,7 +1334,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa { auto mdiBinding = binding; mdiBinding.offset = offsets.drawIndirectByteOffset; - commandBuffer->drawIndexedIndirect(binding,metaAlloc.getElementCount(),sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->drawIndexedIndirect(binding,drawID,sizeof(VkDrawIndexedIndirectCommand)); } } @@ -1328,14 +1343,15 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa if (offsets.totalSize>=minBlockSize) if (const offset_t unusedStart=metaAlloc.memBlockOffset+offsets.totalSize, unusedSize=metaAlloc.memBlockSize-offsets.totalSize; unusedSize>=minBlockSize) { + assert(unusedStart==endOffset); + assert(unusedStart+unusedSize==metaAlloc.memBlockOffset+metaAlloc.memBlockSize); streaming->multi_deallocate(1,&unusedStart,&unusedSize); - // trime the leftover actually used block + // trim the leftover actually used block metaAlloc.memBlockSize = offsets.totalSize; } // latch our used chunk free streaming->multi_deallocate(1,&metaAlloc.memBlockOffset,&metaAlloc.memBlockSize,waitInfo); // reset to initial state - metaAlloc.memBlockOffset = streaming_buffer_t::invalid_value; metaAlloc.reset(); }; @@ -1346,11 +1362,6 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa { if (!metaAlloc.tryPush(*listIt)) { - if (listIt==lastBatchEnd) - { - logger.log("Obtained maximum allocation from streaming buffer isn't even enough to fit a single IMGUI commandlist",system::ILogger::ELL_ERROR); - return false; - } endDrawBatch(); if (!beginDrawBatch()) return false; From 1913ae755e799c4f83210915b46a8a8548e3d734 Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 11:24:35 +0200 Subject: [PATCH 133/148] daily reminder to self to stop programming after 1 am --- examples_tests | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples_tests b/examples_tests index 478ed1878d..67a4128f9c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 478ed1878d31cda7670a760f03999b5ed52759d1 +Subproject commit 67a4128f9c56aca942dc0c7c98367898b87a0b4c diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 6061dec353..4f16482bd4 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1301,8 +1301,8 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa memcpy(streamingPtr+geo.indexByteOffset,list->IdxBuffer.Data,list->IdxBuffer.size_in_bytes()); memcpy(streamingPtr+geo.vertexByteOffset,list->VtxBuffer.Data,list->VtxBuffer.size_in_bytes()); // not writing past the end - assert(streamingPtr+geo.indexByteOffsetIdxBuffer.size_in_bytes()<=endByte); + assert(streamingPtr+geo.vertexByteOffset+list->VtxBuffer.size_in_bytes()<=endByte); } } // the offets were enough and allocation should not overlap @@ -1334,7 +1334,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa { auto mdiBinding = binding; mdiBinding.offset = offsets.drawIndirectByteOffset; - commandBuffer->drawIndexedIndirect(binding,drawID,sizeof(VkDrawIndexedIndirectCommand)); + commandBuffer->drawIndexedIndirect(mdiBinding,drawID,sizeof(VkDrawIndexedIndirectCommand)); } } @@ -1349,6 +1349,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa // trim the leftover actually used block metaAlloc.memBlockSize = offsets.totalSize; } + // latch our used chunk free streaming->multi_deallocate(1,&metaAlloc.memBlockOffset,&metaAlloc.memBlockSize,waitInfo); // reset to initial state From daf8480858b2ba4d63676c4e401b43337419c2f7 Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 11:46:04 +0200 Subject: [PATCH 134/148] 1. stop it with the lambdas 2. std::move on return values is bad --- src/nbl/ext/ImGui/ImGui.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 4f16482bd4..7d491f04ce 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1041,15 +1041,10 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa return in * scale + translate; } }; - const TRS trs = [&]() - { - TRS retV; - - retV.scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; - retV.translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; - - return std::move(retV); - }(); + const TRS trs = { + .scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }, + .translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y }*trs.scale, + }; // everything binds the buffer once at base and works via local offsets const SBufferBinding binding = From 96ff988651dc5667b723377103c47aca0c3c178d Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 12:32:22 +0200 Subject: [PATCH 135/148] useers should not know or case we use LinearAddressAllocatorST under the hood to suballocate --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 33 +++++++++++---------------------- src/nbl/ext/ImGui/ImGui.cpp | 22 ++++++++-------------- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/examples_tests b/examples_tests index 67a4128f9c..8fa5797824 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 67a4128f9c56aca942dc0c7c98367898b87a0b4c +Subproject commit 8fa57978243b7661560caac5680b1c6b5b1186b0 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index d01ec69c87..5a18a68b83 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -21,24 +21,6 @@ class UI final : public core::IReferenceCounted COUNT, }; - struct SMdiBuffer - { - //! composes memory available for the general purpose allocator to suballocate memory ranges - using compose_t = video::StreamingTransientDataBufferST>; - - //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request - using suballocator_traits_t = core::address_allocator_traits>; - - //! streaming mdi buffer - core::smart_refctd_ptr compose; - - //! required buffer allocate flags - static constexpr auto RequiredAllocateFlags = core::bitflag(video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - - //! required buffer usage flags - static constexpr auto RequiredUsageFlags = core::bitflag(asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | asset::IBuffer::EUF_INDEX_BUFFER_BIT | asset::IBuffer::EUF_VERTEX_BUFFER_BIT | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; - }; - struct SResourceParameters { //! for a given pipeline layout we need to know what is intended for UI resources @@ -73,14 +55,22 @@ class UI final : public core::IReferenceCounted struct SCachedCreationParams { + using streaming_buffer_t = video::StreamingTransientDataBufferST>; + + //! required buffer allocate flags + static constexpr auto RequiredAllocateFlags = core::bitflag(video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + + //! required buffer usage flags + static constexpr auto RequiredUsageFlags = core::bitflag(asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | asset::IBuffer::EUF_INDEX_BUFFER_BIT | asset::IBuffer::EUF_VERTEX_BUFFER_BIT | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + //! required, you provide us information about your required UI binding resources which we validate at creation time SResourceParameters resources; //! required core::smart_refctd_ptr utilities; - //! optional, default MDI buffer allocated if not provided - core::smart_refctd_ptr streamingBuffer = nullptr; + //! optional, default MDI buffer allocated if not provided + core::smart_refctd_ptr streamingBuffer = nullptr; //! optional, default single one uint32_t viewportCount = 1u; @@ -160,7 +150,7 @@ class UI final : public core::IReferenceCounted inline video::IGPUImageView* getFontAtlasView() const { return m_fontAtlasTexture.get(); } //! mdi streaming buffer - inline const typename SMdiBuffer::compose_t* getStreamingBuffer() const { return m_mdi.compose.get(); } + inline const auto* getStreamingBuffer() const {return m_cachedCreationParams.streamingBuffer.get();} //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); @@ -176,7 +166,6 @@ class UI final : public core::IReferenceCounted core::smart_refctd_ptr m_pipeline; core::smart_refctd_ptr m_fontAtlasTexture; - SMdiBuffer m_mdi; std::vector> m_subscribers {}; }; } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 7d491f04ce..72d5c41883 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -23,10 +23,6 @@ using namespace nbl::hlsl; namespace nbl::ext::imgui { -using mdi_buffer_t = UI::SMdiBuffer; -using compose_t = typename mdi_buffer_t::compose_t; - - static constexpr SPushConstantRange PushConstantRanges[] = { { @@ -899,12 +895,10 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) return flags; }; - if (m_cachedCreationParams.streamingBuffer) - m_mdi.compose = smart_refctd_ptr(m_cachedCreationParams.streamingBuffer); - else + if (!m_cachedCreationParams.streamingBuffer) { IGPUBuffer::SCreationParams mdiCreationParams = {}; - mdiCreationParams.usage = SMdiBuffer::RequiredUsageFlags; + mdiCreationParams.usage = SCachedCreationParams::RequiredUsageFlags; mdiCreationParams.size = mdiBufferDefaultSize; auto buffer = m_cachedCreationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); @@ -913,7 +907,7 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) auto memoryReqs = buffer->getMemoryReqs(); memoryReqs.memoryTypeBits &= m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), SMdiBuffer::RequiredAllocateFlags); + auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs,buffer.get(),SCachedCreationParams::RequiredAllocateFlags); { const bool allocated = allocation.isValid(); assert(allocated); @@ -923,17 +917,17 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); - m_mdi.compose = make_smart_refctd_ptr(SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + m_cachedCreationParams.streamingBuffer = make_smart_refctd_ptr(SBufferRange{0ull,mdiCreationParams.size,std::move(buffer)},maxStreamingBufferAllocationAlignment,minStreamingBufferAllocationSize); } - auto buffer = m_mdi.compose->getBuffer(); + auto buffer = m_cachedCreationParams.streamingBuffer->getBuffer(); auto binding = buffer->getBoundMemory(); const auto validation = std::to_array ({ - std::make_pair(buffer->getCreationParams().usage.hasFlags(SMdiBuffer::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(buffer->getCreationParams().usage.hasFlags(SCachedCreationParams::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), - std::make_pair(binding.memory->getAllocateFlags().hasFlags(SMdiBuffer::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(SCachedCreationParams::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") }); @@ -982,7 +976,7 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa // allocator initialization needs us to round up to PoT const auto MaxPOTAlignment = roundUpToPoT(MaxAlignment); // quick sanity check - auto* streaming = m_mdi.compose.get(); + auto* streaming = m_cachedCreationParams.streamingBuffer.get(); if (streaming->getAddressAllocator().max_alignment() Date: Thu, 10 Oct 2024 12:44:08 +0200 Subject: [PATCH 136/148] only pass queues to font atlas creation --- include/nbl/ext/ImGui/ImGui.h | 3 +- src/nbl/ext/ImGui/ImGui.cpp | 82 +++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 5a18a68b83..f60472feed 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -159,7 +159,8 @@ class UI final : public core::IReferenceCounted void createMDIBuffer(SCreationParameters& creationParams); void handleMouseEvents(const SUpdateParameters& params) const; void handleKeyEvents(const SUpdateParameters& params) const; - video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams); + // NOTE: in the future this will also need a compute queue to do mip-maps + video::ISemaphore::future_t createFontAtlasTexture(video::IQueue* transfer); SCachedCreationParams m_cachedCreationParams; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 72d5c41883..d1e2481c59 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -335,8 +335,29 @@ void UI::createPipeline(SCreationParameters& creationParams) } } -ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffer* cmdBuffer, SCreationParameters& creationParams) +ISemaphore::future_t UI::createFontAtlasTexture(video::IQueue* transfer) { + system::logger_opt_ptr logger = m_cachedCreationParams.utilities->getLogger(); + auto* device = m_cachedCreationParams.utilities->getLogicalDevice(); + + smart_refctd_ptr transientCmdBuf; + { + using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; + + smart_refctd_ptr pool = device->createCommandPool(transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT | pool_flags_t::TRANSIENT_BIT); + if (!pool) + { + logger.log("Could not create command pool!", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; + } + + if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transientCmdBuf)) + { + logger.log("Could not create transistent command buffer!", ILogger::ELL_ERROR); + return IQueue::RESULT::OTHER_ERROR; + } + } + // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. @@ -370,6 +391,7 @@ ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffe _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; const auto buffer = make_smart_refctd_ptr< CCustomAllocatorCPUBuffer, true> >(image_size, pixels, adopt_memory); + // TODO: In the future use CAssetConverter to replace everything below this line IGPUImage::SCreationParams params; params.flags = static_cast(0u); params.type = IImage::ET_2D; @@ -405,34 +427,34 @@ ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffe region->imageExtent = { params.extent.width, params.extent.height, 1u }; } - auto image = creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); + auto image = device->createImage(std::move(params)); if (!image) { - creationParams.utilities->getLogger()->log("Could not create font image!", ILogger::ELL_ERROR); + logger.log("Could not create font image!", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla ImGUI default font"); - if (!creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) + if (!device->allocate(image->getMemoryReqs(), image.get()).isValid()) { - creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", ILogger::ELL_ERROR); + logger.log("Could not allocate memory for font image!", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } SIntendedSubmitInfo sInfo; { - IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; + IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { transientCmdBuf.get() }; - auto scratchSemaphore = creationParams.utilities->getLogicalDevice()->createSemaphore(0); + auto scratchSemaphore = device->createSemaphore(0); if (!scratchSemaphore) { - creationParams.utilities->getLogger()->log("Could not create scratch semaphore", ILogger::ELL_ERROR); + logger.log("Could not create scratch semaphore", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - sInfo.queue = creationParams.transfer; + sInfo.queue = transfer; sInfo.waitSemaphores = {}; sInfo.commandBuffers = { &cmdInfo, 1 }; sInfo.scratchSemaphore = @@ -460,13 +482,13 @@ ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffe } }; - cmdBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); + transientCmdBuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + transientCmdBuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!m_cachedCreationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - creationParams.utilities->getLogger()->log("Could not upload font image contents", ILogger::ELL_ERROR); + logger.log("Could not upload font image contents", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -475,13 +497,13 @@ ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffe // transition to READ_ONLY_OPTIMAL ready for rendering with sampling barriers[0].oldLayout = barriers[0].newLayout; barriers[0].newLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; - cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); - cmdBuffer->end(); + transientCmdBuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); + transientCmdBuf->end(); const auto submit = sInfo.popSubmit({}); - if (creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) + if (transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", ILogger::ELL_ERROR); + logger.log("Could not submit workload for font texture upload.", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -493,7 +515,7 @@ ISemaphore::future_t UI::createFontAtlasTexture(IGPUCommandBuffe params.subresourceRange = regions.subresource; params.image = smart_refctd_ptr(image); - m_fontAtlasTexture = creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); + m_fontAtlasTexture = device->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -845,23 +867,6 @@ UI::UI(SCreationParameters&& creationParams) assert(false); } - smart_refctd_ptr transistentCMD; - { - using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - - smart_refctd_ptr pool = creationParams.utilities->getLogicalDevice()->createCommandPool(creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); - if (!pool) - { - creationParams.utilities->getLogger()->log("Could not create command pool!", ILogger::ELL_ERROR); - assert(false); - } - - if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) - { - creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", ILogger::ELL_ERROR); - assert(false); - } - } // Dear ImGui context IMGUI_CHECKVERSION(); @@ -869,12 +874,13 @@ UI::UI(SCreationParameters&& creationParams) createPipeline(creationParams); createMDIBuffer(creationParams); - createFontAtlasTexture(transistentCMD.get(), creationParams); + + m_cachedCreationParams = std::move(creationParams); + + createFontAtlasTexture(creationParams.transfer); auto & io = ImGui::GetIO(); io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports - - m_cachedCreationParams = std::move(creationParams); } UI::~UI() = default; From faeff6df06a34b2b84f47af611852204c9f8151f Mon Sep 17 00:00:00 2001 From: devsh Date: Thu, 10 Oct 2024 13:24:28 +0200 Subject: [PATCH 137/148] better use of Descriptor Set Layout introspection APIs --- src/nbl/ext/ImGui/ImGui.cpp | 92 +++++++++++++++---------------------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index d1e2481c59..5f30874ab1 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -733,6 +733,8 @@ void UI::handleKeyEvents(const SUpdateParameters& params) const UI::UI(SCreationParameters&& creationParams) { + system::logger_opt_ptr logger = creationParams.utilities->getLogger(); + auto validateResourcesInfo = [&]() -> bool { auto* pipelineLayout = creationParams.pipelineLayout.get(); @@ -741,17 +743,18 @@ UI::UI(SCreationParameters&& creationParams) { auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) { + static_assert(descriptorType!=IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, "Explicitly not supported."); constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further - auto anyBindingCount = [&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool + auto anyBindingCount = [logger,&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool { bool ok = redirect->getBindingCount(); if (!ok && logError) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); + logger.log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); return false; } @@ -760,11 +763,12 @@ UI::UI(SCreationParameters&& creationParams) if(!descriptorSetLayout) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); + logger.log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); return false; } - const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); + using redirect_t = IDescriptorSetLayoutBase::CBindingRedirect; + const redirect_t* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) { @@ -782,57 +786,37 @@ UI::UI(SCreationParameters&& creationParams) } } - const auto bindingCount = redirect->getBindingCount(); - - bool ok = false; - for (uint32_t i = 0u; i < bindingCount; ++i) + const redirect_t::binding_number_t requestedBinding(descriptorType==IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx:creationParams.resources.samplersInfo.bindingIx); + const auto storageIndex = redirect->findBindingStorageIndex(requestedBinding); + if (!storageIndex) { - const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); - const auto binding = redirect->getBinding(rangeStorageIndex); - const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx : creationParams.resources.samplersInfo.bindingIx; - - if (binding.data == requestedBindingIx) - { - const auto count = redirect->getCount(binding); - - if(!count) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } - - if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) - creationParams.resources.texturesCount = count; - else - creationParams.resources.samplersCount = count; - - const auto stage = redirect->getStageFlags(binding); - - if(!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } - - const auto creation = redirect->getCreateFlags(rangeStorageIndex); - - if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) - { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); - return false; - } - - ok = true; - break; - } + logger.log("No IDescriptor::E_TYPE::%s binding exists for requested `creationParams.resources.%s=%d` in the Provided descriptor set layout!",ILogger::ELL_ERROR,typeLiteral.data(),ixLiteral.data(),requestedBinding.data); + return false; } - - if (!ok) + + const auto creation = redirect->getCreateFlags(storageIndex); + if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) + { + logger.log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet create flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + const auto stage = redirect->getStageFlags(storageIndex); + if (!stage.hasFlags(creationParams.resources.RequiredShaderStageFlags)) + { + logger.log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but doesn't meet stage flags requirements!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + return false; + } + const auto count = redirect->getCount(storageIndex); + if(!count) { - creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index or it is invalid!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); + logger.log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; } + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + creationParams.resources.texturesCount = count; + else + creationParams.resources.samplersCount = count; return true; }; @@ -861,11 +845,11 @@ UI::UI(SCreationParameters&& creationParams) }); for (const auto& [ok, error] : validation) - if (!ok) - { - creationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); - assert(false); - } + if (!ok) + { + logger.log(error, ILogger::ELL_ERROR); + assert(false); + } // Dear ImGui context From 5ba096a4ab8af3e15ea36172519676480c354297 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 06:59:06 +0200 Subject: [PATCH 138/148] update imgui submodule (move autogen imgui config to a header commited into the submodule) --- 3rdparty/CMakeLists.txt | 80 ++++------------------------------------- 3rdparty/imgui | 2 +- 2 files changed, 8 insertions(+), 74 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 884660bdcd..38eb031a15 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -293,7 +293,6 @@ NBL_GENERATE_GIT_TRACKING_META() if(NBL_BUILD_IMGUI) set(NBL_IMGUI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui") - set(NBL_IMGUI_USER_CONFIG_ISD "${CMAKE_CURRENT_BINARY_DIR}/imgui/config/include") set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") @@ -381,79 +380,14 @@ if(NBL_BUILD_IMGUI) set(IMGUIZMO_BUILD_EXAMPLE OFF) add_subdirectory(imguizmo EXCLUDE_FROM_ALL) + + if(NOT EXISTS "${NBL_IMGUI_USER_CONFIG_FILEPATH}") + message(FATAL_ERROR "\"${NBL_IMGUI_USER_CONFIG_FILEPATH}\" doesn't exist!") + endif() + + # note we override imgui config with our own + set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_ROOT}/nabla_imconfig.h") - # override test engine config with our own -string(APPEND NBL_IMPL_IMCONFIG_CONTENT -[=[ -#include "imgui_test_suite_imconfig.h" // use test engine's config - -// note we override it with default imguis (void*) type -#include - -//! Custom "ImTextureID" info struct for Nabla UI backend purposes about resource sampler & texture descriptor binding's array indicies -//! must be 4 bytes size & alignment to pass imgui static asserts (it checks for contiguous blocks in memory to make sure it can do some memcpies) -struct SImResourceInfo -{ - //! texture descriptor binding's array index - uint32_t textureID : 26; - - //! sampler descriptor binding's array index - uint32_t samplerIx : 6; - - SImResourceInfo() : textureID(0u), samplerIx(0u) {} - - SImResourceInfo(uint32_t texID) - { - textureID = texID; - samplerIx = 0u; - } - - explicit operator intptr_t() const - { - return static_cast(textureID); - } - - bool operator==(const SImResourceInfo& other) const - { - return textureID == other.textureID; - } - - bool operator!=(const SImResourceInfo& other) const - { - return textureID != other.textureID; - } - - bool operator<(const SImResourceInfo& other) const - { - return textureID < other.textureID; - } - - bool operator>(const SImResourceInfo& other) const - { - return textureID > other.textureID; - } - - bool operator<=(const SImResourceInfo& other) const - { - return textureID <= other.textureID; - } - - bool operator>=(const SImResourceInfo& other) const - { - return textureID >= other.textureID; - } -}; - -#define ImTextureID SImResourceInfo -#define IMGUI_ENABLE_FREETYPE -#define IMGUI_DISABLE_OBSOLETE_KEYIO -#undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things -]=] -) - set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_USER_CONFIG_ISD}/nabla_imconfig.h") - file(WRITE "${NBL_IMGUI_USER_CONFIG_FILEPATH}" "${NBL_IMPL_IMCONFIG_CONTENT}") - - # USER DEFINED IMGUI CONFIG, IMPACTS THE BUILD target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="${NBL_IMGUI_USER_CONFIG_FILEPATH}" ) diff --git a/3rdparty/imgui b/3rdparty/imgui index cb16be3a3f..a29e9dba30 160000 --- a/3rdparty/imgui +++ b/3rdparty/imgui @@ -1 +1 @@ -Subproject commit cb16be3a3fc1f9cd146ae24d52b615f8a05fa93d +Subproject commit a29e9dba3012eca9f80bdc4c39ca61a1df8e7175 From 2de19f25cfe01b8a7bc2b2f2a2ff66ab596424b6 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 11:39:59 +0200 Subject: [PATCH 139/148] adjust to comments & also support ImGUI shared atlas capability, add more logs, update examples_tests submodule --- 3rdparty/CMakeLists.txt | 6 +- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 38 ++-- src/nbl/ext/ImGui/ImGui.cpp | 245 +++++++++++++++++-------- src/nbl/ext/ImGui/shaders/common.hlsl | 5 + src/nbl/ext/ImGui/shaders/psinput.hlsl | 5 + src/nbl/ext/ImGui/shaders/vertex.hlsl | 10 +- 7 files changed, 215 insertions(+), 96 deletions(-) diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 38eb031a15..1921c0b704 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -381,12 +381,12 @@ if(NBL_BUILD_IMGUI) set(IMGUIZMO_BUILD_EXAMPLE OFF) add_subdirectory(imguizmo EXCLUDE_FROM_ALL) + # note we override imgui config with our own + set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_ROOT}/nabla_imconfig.h") + if(NOT EXISTS "${NBL_IMGUI_USER_CONFIG_FILEPATH}") message(FATAL_ERROR "\"${NBL_IMGUI_USER_CONFIG_FILEPATH}\" doesn't exist!") endif() - - # note we override imgui config with our own - set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_ROOT}/nabla_imconfig.h") target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="${NBL_IMGUI_USER_CONFIG_FILEPATH}" diff --git a/examples_tests b/examples_tests index 8fa5797824..46956ca704 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8fa57978243b7661560caac5680b1c6b5b1186b0 +Subproject commit 46956ca704e92454990a3fb5c62c8a9aec56622e diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index f60472feed..b6700426bc 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -9,7 +9,7 @@ namespace nbl::ext::imgui class UI final : public core::IReferenceCounted { public: - //! Reserved font atlas indicies for default backend textures & samplers descriptor binding's array + //! Reserved font atlas indicies for default backend textures & samplers descriptor binding's array, remember you can still override font's indices at runtime and don't have to use those at all static constexpr auto FontAtlasTexId = 0u, FontAtlasSamplerId = 0u; //! Reserved indexes for default backend samplers descriptor binding's array - use only if you created your pipeline layout with createDefaultPipelineLayout. If you need more or custom samplers then create the pipeline layout yourself @@ -113,8 +113,11 @@ class UI final : public core::IReferenceCounted std::span keyboardEvents = {}; }; - UI(SCreationParameters&& params); - ~UI() override; + //! validates creation parameters + static bool validateCreationParameters(SCreationParameters& _params); + + //! creates the UI instance, we allow to pass external font atlas & then ownership of the atlas is yours (the same way imgui context allows you to do & what you pass here is "ImFontAtlas" instance then!) - it can fail so you are required to check if returned UI instance != nullptr + static core::smart_refctd_ptr create(SCreationParameters&& _params, void* const imSharedFontAtlas = nullptr); //! updates ImGuiIO & records ImGUI *cpu* draw command lists, you have to call it before .render bool update(const SUpdateParameters& params); @@ -143,10 +146,7 @@ class UI final : public core::IReferenceCounted //! ImGUI graphics pipeline inline const video::IGPUGraphicsPipeline* getPipeline() const { return m_pipeline.get(); } - //! image view default font texture - //! TODO: we cannot expose immutable view of our default font texture because user MUST be able to write a descriptor, - //! we can have mutable getter or we can decide to not create any default font texture at all and force users - //! to do it externally (have a static helper to do it which gives you mutable view) + // default ImGUI font atlas view inline video::IGPUImageView* getFontAtlasView() const { return m_fontAtlasTexture.get(); } //! mdi streaming buffer @@ -154,19 +154,35 @@ class UI final : public core::IReferenceCounted //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); void* getContext(); + + protected: + UI(SCreationParameters&& params, core::smart_refctd_ptr pipeline, core::smart_refctd_ptr defaultFont, void* const imFontAtlas, void* const imContext); + ~UI() override; + private: - void createPipeline(SCreationParameters& creationParams); - void createMDIBuffer(SCreationParameters& creationParams); + static core::smart_refctd_ptr createPipeline(SCreationParameters& creationParams); + static bool createMDIBuffer(SCreationParameters& creationParams); + // NOTE: in the future this will also need a compute queue to do mip-maps + static video::ISemaphore::future_t createFontAtlasTexture(SCreationParameters& creationParams, void* const imFontAtlas, core::smart_refctd_ptr& outFontView); + void handleMouseEvents(const SUpdateParameters& params) const; void handleKeyEvents(const SUpdateParameters& params) const; - // NOTE: in the future this will also need a compute queue to do mip-maps - video::ISemaphore::future_t createFontAtlasTexture(video::IQueue* transfer); + // cached creation parameters SCachedCreationParams m_cachedCreationParams; + // graphics pipeline you are required to bind core::smart_refctd_ptr m_pipeline; + + // image view to default font created core::smart_refctd_ptr m_fontAtlasTexture; + // note we track pointer to atlas only if it belongs to the extension instance (is not "shared" in imgui world) + void* const m_imFontAtlasBackPointer; + + // context we created for instance which we must free at destruction + void* const m_imContextBackPointer; + std::vector> m_subscribers {}; }; } diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 5f30874ab1..38b2009c79 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -122,14 +122,14 @@ const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, return smart_refctd_ptr(archive); } -void UI::createPipeline(SCreationParameters& creationParams) +core::smart_refctd_ptr UI::createPipeline(SCreationParameters& creationParams) { auto pipelineLayout = smart_refctd_ptr(creationParams.pipelineLayout); if (!pipelineLayout) { creationParams.utilities->getLogger()->log("Could not create pipeline layout!", ILogger::ELL_ERROR); - assert(false); + return nullptr; } struct @@ -143,8 +143,8 @@ void UI::createPipeline(SCreationParameters& creationParams) //! proxy the system, we will touch it gently auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); - //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV - auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(system)); + auto* set = creationParams.assetManager->getCompilerSet(); + auto compiler = set->getShaderCompiler(IShader::E_CONTENT_TYPE::ECT_HLSL); auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeLoader = includeFinder->getDefaultFileSystemLoader(); includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); @@ -241,8 +241,11 @@ void UI::createPipeline(SCreationParameters& creationParams) return gpu; }; - //! we assume user has all Nabla builtins mounted - we don't check it at release - assert(system->areBuiltinsMounted()); + if (!system->areBuiltinsMounted()) + { + creationParams.utilities->getLogger()->log("Nabla builtins are not mounted!", ILogger::ELL_ERROR); + return nullptr; + } //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); @@ -250,8 +253,17 @@ void UI::createPipeline(SCreationParameters& creationParams) shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); - assert(shaders.vertex); - assert(shaders.fragment); + if (!shaders.vertex) + { + creationParams.utilities->getLogger()->log("Failed to compile vertex shader!", ILogger::ELL_ERROR); + return nullptr; + } + + if (!shaders.fragment) + { + creationParams.utilities->getLogger()->log("Failed to compile fragment shader!", ILogger::ELL_ERROR); + return nullptr; + } } SVertexInputParams vertexInputParams{}; @@ -311,6 +323,7 @@ void UI::createPipeline(SCreationParameters& creationParams) primitiveAssemblyParams.primitiveType = EPT_TRIANGLE_LIST; } + core::smart_refctd_ptr pipeline; { const IGPUShader::SSpecInfo specs[] = { @@ -326,19 +339,23 @@ void UI::createPipeline(SCreationParameters& creationParams) param.renderpass = creationParams.renderpass.get(); param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = creationParams.subpassIx }; }; - - if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &m_pipeline)) + + if (!creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(creationParams.pipelineCache.get(), params, &pipeline)) { creationParams.utilities->getLogger()->log("Could not create pipeline!", ILogger::ELL_ERROR); - assert(false); + return nullptr; } } + + return pipeline; } -ISemaphore::future_t UI::createFontAtlasTexture(video::IQueue* transfer) +ISemaphore::future_t UI::createFontAtlasTexture(SCreationParameters& creationParams, void* const imFontAtlas, smart_refctd_ptr& outFontView) { - system::logger_opt_ptr logger = m_cachedCreationParams.utilities->getLogger(); - auto* device = m_cachedCreationParams.utilities->getLogicalDevice(); + video::IQueue* transfer = creationParams.transfer; + system::logger_opt_ptr logger = creationParams.utilities->getLogger(); + auto* device = creationParams.utilities->getLogicalDevice(); + auto* const fontAtlas = reinterpret_cast(imFontAtlas); smart_refctd_ptr transientCmdBuf; { @@ -358,30 +375,17 @@ ISemaphore::future_t UI::createFontAtlasTexture(video::IQueue* t } } - // Load Fonts - // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. - // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). - // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. - // - Read 'docs/FONTS.md' for more instructions and details. - // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - //io.Fonts->AddFontDefault(); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); - //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); - //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); - ImGuiIO& io = ImGui::GetIO(); + // note its by default but you can still change it at runtime, both the texture & sampler id + SImResourceInfo defaultInfo; + defaultInfo.textureID = FontAtlasTexId; + defaultInfo.samplerIx = FontAtlasSamplerId; // TODO: don't `pixels` need to be freed somehow!? (Use a uniqueptr with custom deleter lambda) uint8_t* pixels = nullptr; int32_t width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - SImResourceInfo info; - info.textureID = FontAtlasTexId; - info.samplerIx = FontAtlasSamplerId; - io.Fonts->SetTexID(info); + fontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); + fontAtlas->SetTexID(defaultInfo); if (!pixels || width<=0 || height<=0) return IQueue::RESULT::OTHER_ERROR; @@ -486,7 +490,7 @@ ISemaphore::future_t UI::createFontAtlasTexture(video::IQueue* t transientCmdBuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!m_cachedCreationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { logger.log("Could not upload font image contents", ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; @@ -515,7 +519,7 @@ ISemaphore::future_t UI::createFontAtlasTexture(video::IQueue* t params.subresourceRange = regions.subresource; params.image = smart_refctd_ptr(image); - m_fontAtlasTexture = device->createImageView(std::move(params)); + outFontView = device->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -731,7 +735,7 @@ void UI::handleKeyEvents(const SUpdateParameters& params) const } } -UI::UI(SCreationParameters&& creationParams) +bool UI::validateCreationParameters(SCreationParameters& creationParams) { system::logger_opt_ptr logger = creationParams.utilities->getLogger(); @@ -743,25 +747,25 @@ UI::UI(SCreationParameters&& creationParams) { auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) { - static_assert(descriptorType!=IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, "Explicitly not supported."); + static_assert(descriptorType != IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, "Explicitly not supported."); constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", - ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; + ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; // we need to check if there is at least single "descriptorType" resource, if so we can validate the resource further - auto anyBindingCount = [logger,&creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool - { - bool ok = redirect->getBindingCount(); - - if (!ok && logError) + auto anyBindingCount = [logger, &creationParams = creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect, bool logError = true) -> bool { - logger.log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); - return false; - } + bool ok = redirect->getBindingCount(); - return ok; - }; + if (!ok && logError) + { + logger.log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single default ImGUI Font Atlas texture resource & corresponsing sampler resource!", ILogger::ELL_ERROR, log.data()); + return false; + } + + return ok; + }; - if(!descriptorSetLayout) + if (!descriptorSetLayout) { logger.log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", ILogger::ELL_ERROR, typeLiteral.data()); return false; @@ -786,14 +790,14 @@ UI::UI(SCreationParameters&& creationParams) } } - const redirect_t::binding_number_t requestedBinding(descriptorType==IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx:creationParams.resources.samplersInfo.bindingIx); + const redirect_t::binding_number_t requestedBinding(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.texturesInfo.bindingIx : creationParams.resources.samplersInfo.bindingIx); const auto storageIndex = redirect->findBindingStorageIndex(requestedBinding); if (!storageIndex) { - logger.log("No IDescriptor::E_TYPE::%s binding exists for requested `creationParams.resources.%s=%d` in the Provided descriptor set layout!",ILogger::ELL_ERROR,typeLiteral.data(),ixLiteral.data(),requestedBinding.data); + logger.log("No IDescriptor::E_TYPE::%s binding exists for requested `creationParams.resources.%s=%d` in the Provided descriptor set layout!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data(), requestedBinding.data); return false; } - + const auto creation = redirect->getCreateFlags(storageIndex); if (!creation.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? creationParams.resources.TexturesRequiredCreateFlags : creationParams.resources.SamplersRequiredCreateFlags)) { @@ -807,7 +811,7 @@ UI::UI(SCreationParameters&& creationParams) return false; } const auto count = redirect->getCount(storageIndex); - if(!count) + if (!count) { logger.log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `creationParams.resources.%s` index but the binding resource count == 0u!", ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data()); return false; @@ -845,31 +849,116 @@ UI::UI(SCreationParameters&& creationParams) }); for (const auto& [ok, error] : validation) - if (!ok) + if (!ok) + { + logger.log(error, ILogger::ELL_ERROR); + return false; + } + + return true; +} + +core::smart_refctd_ptr UI::create(SCreationParameters&& creationParams, void* const imSharedFontAtlas) +{ + auto* const logger = creationParams.utilities->getLogger(); + + if (!validateCreationParameters(creationParams)) + { + logger->log("Failed creation parameters validation!", ILogger::ELL_ERROR); + return nullptr; + } + + auto pipeline = createPipeline(creationParams); + + if (!pipeline) { - logger.log(error, ILogger::ELL_ERROR); - assert(false); + logger->log("Failed pipeline creation!", ILogger::ELL_ERROR); + return nullptr; } + if (!createMDIBuffer(creationParams)) + { + logger->log("Failed at mdi buffer creation!", ILogger::ELL_ERROR); + return nullptr; + } - // Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); + // note that imgui context allows you to share atlas at creation time so we do too, + // hence you can have *custom* default font texture we will create a image view from + // + obvsly ownership of the atlas is yours then + auto* const inAtlas = reinterpret_cast(imSharedFontAtlas); + const bool isFontAtlasShared = inAtlas; + auto* imFontAtlas = isFontAtlasShared ? inAtlas : IM_NEW(ImFontAtlas)(); - createPipeline(creationParams); - createMDIBuffer(creationParams); + smart_refctd_ptr outFontView = nullptr; + auto future = createFontAtlasTexture(creationParams, imFontAtlas, outFontView); + ImGuiContext* context = nullptr; - m_cachedCreationParams = std::move(creationParams); + if (!outFontView) + { + logger->log("Failed default font image view creation!", ILogger::ELL_ERROR); + return nullptr; + } - createFontAtlasTexture(creationParams.transfer); + if (future.wait() == ISemaphore::WAIT_RESULT::SUCCESS) + { + // Dear ImGui context + IMGUI_CHECKVERSION(); - auto & io = ImGui::GetIO(); - io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports + // now create imgui context only if we created default image view to default font atlas image, + // we benefit from sharing font atlas with the context & allow to decidewho owns the atlas + // - the UI extension instance or user (because it comes from outside) + context = ImGui::CreateContext(imFontAtlas); + } + else + { + logger->log("Failed to await on default font image buffer upload!", ILogger::ELL_ERROR); + + if(!isFontAtlasShared) // carefully here + IM_DELETE(imFontAtlas); // if that was supposed to be ours then on fail we kill it + + return nullptr; + } + + if (!context) + { + logger->log("Failed to create ImGUI context!", ILogger::ELL_ERROR); + return nullptr; + } + + // note that you can still change im font atlas at runtime but if the atlas belongs to us + // then we must track the pointer since we own it and must free it at destruction + void* const trackedAtlasPointer = !isFontAtlasShared ? imFontAtlas : nullptr; + + return core::smart_refctd_ptr(new UI(std::move(creationParams), pipeline, outFontView, trackedAtlasPointer, context), core::dont_grab); } -UI::~UI() = default; +UI::UI(SCreationParameters&& creationParams, core::smart_refctd_ptr pipeline, core::smart_refctd_ptr defaultFont, void* const imFontAtlas, void* const imContext) + : m_cachedCreationParams(std::move(creationParams)), m_pipeline(std::move(pipeline)), m_fontAtlasTexture(std::move(defaultFont)), m_imFontAtlasBackPointer(imFontAtlas), m_imContextBackPointer(imContext) +{ + auto& io = ImGui::GetIO(); + + // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports + io.BackendUsingLegacyKeyArrays = 0; +} -void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) +UI::~UI() +{ + // I assume somebody has not killed ImGUI context & atlas but if so then we do nothing + + // we must call it to unlock atlas from potential "render" state before we kill it (obvsly if its ours!) + if(m_imFontAtlasBackPointer) + ImGui::EndFrame(); + + // context belongs to the instance, we must free it + if(m_imContextBackPointer) + ImGui::DestroyContext(reinterpret_cast(m_imContextBackPointer)); + + // and if we own the atlas we must free it as well, if user passed its own at creation time then its "shared" - at this point m_imFontAtlasBackPointer is nullptr and we don't free anything + if (m_imFontAtlasBackPointer) + IM_DELETE(reinterpret_cast(m_imFontAtlasBackPointer)); +} + +bool UI::createMDIBuffer(SCreationParameters& creationParams) { constexpr static uint32_t minStreamingBufferAllocationSize = 128u, maxStreamingBufferAllocationAlignment = 4096u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; @@ -885,19 +974,19 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) return flags; }; - if (!m_cachedCreationParams.streamingBuffer) + if (!creationParams.streamingBuffer) { IGPUBuffer::SCreationParams mdiCreationParams = {}; mdiCreationParams.usage = SCachedCreationParams::RequiredUsageFlags; mdiCreationParams.size = mdiBufferDefaultSize; - auto buffer = m_cachedCreationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + auto buffer = creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); buffer->setObjectDebugName("MDI Upstream Buffer"); auto memoryReqs = buffer->getMemoryReqs(); - memoryReqs.memoryTypeBits &= m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + memoryReqs.memoryTypeBits &= creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto allocation = m_cachedCreationParams.utilities->getLogicalDevice()->allocate(memoryReqs,buffer.get(),SCachedCreationParams::RequiredAllocateFlags); + auto allocation = creationParams.utilities->getLogicalDevice()->allocate(memoryReqs,buffer.get(),SCachedCreationParams::RequiredAllocateFlags); { const bool allocated = allocation.isValid(); assert(allocated); @@ -905,18 +994,18 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) auto memory = allocation.memory; if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) - m_cachedCreationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); + creationParams.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); - m_cachedCreationParams.streamingBuffer = make_smart_refctd_ptr(SBufferRange{0ull,mdiCreationParams.size,std::move(buffer)},maxStreamingBufferAllocationAlignment,minStreamingBufferAllocationSize); + creationParams.streamingBuffer = make_smart_refctd_ptr(SBufferRange{0ull,mdiCreationParams.size,std::move(buffer)},maxStreamingBufferAllocationAlignment,minStreamingBufferAllocationSize); } - auto buffer = m_cachedCreationParams.streamingBuffer->getBuffer(); + auto buffer = creationParams.streamingBuffer->getBuffer(); auto binding = buffer->getBoundMemory(); const auto validation = std::to_array ({ std::make_pair(buffer->getCreationParams().usage.hasFlags(SCachedCreationParams::RequiredUsageFlags), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), - std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_cachedCreationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), std::make_pair(binding.memory->getAllocateFlags().hasFlags(SCachedCreationParams::RequiredAllocateFlags), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") @@ -925,9 +1014,11 @@ void UI::createMDIBuffer(SCreationParameters& m_cachedCreationParams) for (const auto& [ok, error] : validation) if (!ok) { - m_cachedCreationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); - assert(false); + creationParams.utilities->getLogger()->log(error, ILogger::ELL_ERROR); + return false; } + + return true; } bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo waitInfo, const std::chrono::steady_clock::time_point waitPoint, const std::span scissors) diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 8c2cd2159e..c09910ac95 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -1,3 +1,6 @@ +#ifndef _NBL_IMGUI_EXT_COMMON_HLSL_ +#define _NBL_IMGUI_EXT_COMMON_HLSL_ + #include "nbl/builtin/hlsl/glsl_compat/core.hlsl" namespace nbl @@ -24,3 +27,5 @@ namespace imgui } } } + +#endif // _NBL_IMGUI_EXT_COMMON_HLSL_ diff --git a/src/nbl/ext/ImGui/shaders/psinput.hlsl b/src/nbl/ext/ImGui/shaders/psinput.hlsl index cb0fdf11e7..3e7a4f95a0 100644 --- a/src/nbl/ext/ImGui/shaders/psinput.hlsl +++ b/src/nbl/ext/ImGui/shaders/psinput.hlsl @@ -1,3 +1,6 @@ +#ifndef _NBL_IMGUI_EXT_PSINPUT_HLSL_ +#define _NBL_IMGUI_EXT_PSINPUT_HLSL_ + #ifdef __HLSL_VERSION namespace nbl { @@ -17,3 +20,5 @@ namespace imgui } } #endif // __HLSL_VERSION + +#endif // _NBL_IMGUI_EXT_PSINPUT_HLSL_ diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index 6a47cb5b5a..1651060c58 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -1,7 +1,9 @@ #include "common.hlsl" #include "psinput.hlsl" -[[vk::push_constant]] struct nbl::ext::imgui::PushConstants pc; +using namespace nbl::ext::imgui; + +[[vk::push_constant]] struct PushConstants pc; struct VSInput { @@ -16,15 +18,15 @@ struct VSInput to request per object data with BDA */ -nbl::ext::imgui::PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) +PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) { - nbl::ext::imgui::PSInput output; + PSInput output; output.color = input.color; output.uv = input.uv; output.drawID = drawID; // BDA for requesting object data - const nbl::ext::imgui::PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(nbl::ext::imgui::PerObjectData)* drawID); + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); // NDC [-1, 1] range output.position = float4(input.position * pc.scale + pc.translate, 0, 1); From 9ee0327b1c00c589e4719eb29be4f37a13bf0607 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 11:52:08 +0200 Subject: [PATCH 140/148] update createDefaultPipelineLayout, take logical device instead of whole utilities, update examples_tests submodule --- examples_tests | 2 +- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 23 +++++++++++++---------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples_tests b/examples_tests index 46956ca704..71dbc8d179 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 46956ca704e92454990a3fb5c62c8a9aec56622e +Subproject commit 71dbc8d179ac5ee4b00116d11afc08dfc2b71727 diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index b6700426bc..c000873aa1 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -135,7 +135,7 @@ class UI final : public core::IReferenceCounted void setContext(void* imguiContext); //! creates default pipeline layout for the UI resources, "texturesCount" argument is textures descriptor binding's array size. Samplers are immutable and part of the created layout, SResourceParameters::DefaultSamplerIx::COUNT is the size of the samplers descriptor binding's array - static core::smart_refctd_ptr createDefaultPipelineLayout(video::IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount = 0x45); + static core::smart_refctd_ptr createDefaultPipelineLayout(video::ILogicalDevice* const device, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount = 0x45); //! mounts the extension's archive to given system - useful if you want to create your own shaders with common header included static const core::smart_refctd_ptr mount(core::smart_refctd_ptr logger, system::ISystem* system, const std::string_view archiveAlias = ""); diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 38b2009c79..fa9890b276 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -32,9 +32,9 @@ static constexpr SPushConstantRange PushConstantRanges[] = } }; -smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* const utilities, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) +smart_refctd_ptr UI::createDefaultPipelineLayout(ILogicalDevice* const device, const SResourceParameters::SBindingInfo texturesInfo, const SResourceParameters::SBindingInfo samplersInfo, uint32_t texturesCount) { - if (!utilities) + if (!device) return nullptr; if (texturesInfo.bindingIx == samplersInfo.bindingIx) @@ -53,7 +53,7 @@ smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* params.TextureWrapV = ISampler::ETC_REPEAT; params.TextureWrapW = ISampler::ETC_REPEAT; - fontAtlasUISampler = utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler = device->createSampler(params); fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); } @@ -65,7 +65,7 @@ smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; - userTexturesSampler = utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler = device->createSampler(params); userTexturesSampler->setObjectDebugName("Nabla default ImGUI user texture sampler"); } @@ -96,17 +96,20 @@ smart_refctd_ptr UI::createDefaultPipelineLayout(IUtilities* auto layouts = std::to_array>({ nullptr, nullptr, nullptr, nullptr }); if (texturesInfo.setIx == samplersInfo.setIx) - layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding, samplersBinding} }); + layouts[texturesInfo.setIx] = device->createDescriptorSetLayout({ {textureBinding, samplersBinding} }); else { - layouts[texturesInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {textureBinding} }); - layouts[samplersInfo.setIx] = utilities->getLogicalDevice()->createDescriptorSetLayout({ {samplersBinding} }); + layouts[texturesInfo.setIx] = device->createDescriptorSetLayout({ {textureBinding} }); + layouts[samplersInfo.setIx] = device->createDescriptorSetLayout({ {samplersBinding} }); } - assert(layouts[texturesInfo.setIx]); - assert(layouts[samplersInfo.setIx]); + if (!layouts[texturesInfo.setIx]) + return nullptr; + + if (!layouts[samplersInfo.setIx]) + return nullptr; - return utilities->getLogicalDevice()->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); + return device->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); } const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) From b75ae293d7132a3fdffc70b157c9305695dde55e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 12:11:53 +0200 Subject: [PATCH 141/148] update ImGUI.cpp to use nice hlsl vectors --- src/nbl/ext/ImGui/ImGui.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index fa9890b276..934a5db96f 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -1107,21 +1107,21 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa else commandBuffer->setScissor(scissors); } - + // get structs for computing the NDC coordinates ready struct TRS { - vector2df_SIMD scale; - vector2df_SIMD translate; + float32_t2 scale; + float32_t2 translate; - vector2df_SIMD toNDC(vector2df_SIMD in) const + float32_t2 toNDC(float32_t2 in) const { return in * scale + translate; } }; const TRS trs = { - .scale = vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }, - .translate = vector2df_SIMD { -1.0f, -1.0f } - vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y }*trs.scale, + .scale = float32_t2{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }, + .translate = float32_t2 { -1.0f, -1.0f } - float32_t2{ drawData->DisplayPos.x, drawData->DisplayPos.y }*trs.scale, }; // everything binds the buffer once at base and works via local offsets @@ -1358,8 +1358,8 @@ bool UI::render(IGPUCommandBuffer* const commandBuffer, ISemaphore::SWaitInfo wa { return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils }; - const auto vMin = trs.toNDC(vector2df_SIMD(scissor.offset.x, scissor.offset.y)); - const auto vMax = trs.toNDC(vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + const auto vMin = trs.toNDC(float32_t2(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(float32_t2(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); struct snorm16_t2_packed { int16_t x, y; From e5ec627ef8f8b0c924484965ca1b0d8bdfddaf87 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 18:56:50 +0200 Subject: [PATCH 142/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 71dbc8d179..d9cd21be28 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 71dbc8d179ac5ee4b00116d11afc08dfc2b71727 +Subproject commit d9cd21be285a8328f15a081b558d3d3100c0dbde From a8a7c7dc29fb3cb92a071237b1cb993f3cc6e14b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 13 Oct 2024 19:11:34 +0200 Subject: [PATCH 143/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index d9cd21be28..776dc0689a 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d9cd21be285a8328f15a081b558d3d3100c0dbde +Subproject commit 776dc0689a260da8cd614b9c8b600c3739b31190 From c2113466ea0e4345bf4e5d079ccd6f0c7a5d20da Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 14 Oct 2024 21:37:42 +0200 Subject: [PATCH 144/148] move UI extension headers into appropriate place, allow to go with mounted directory --- .../nbl/ext/ImGui/builtin/hlsl}/common.hlsl | 0 .../nbl/ext/ImGui/builtin/hlsl}/fragment.hlsl | 0 .../nbl/ext/ImGui/builtin/hlsl}/psinput.hlsl | 0 .../nbl/ext/ImGui/builtin/hlsl}/vertex.hlsl | 0 src/nbl/ext/ImGui/CMakeLists.txt | 27 ++++++++++------- src/nbl/ext/ImGui/ImGui.cpp | 29 +++++++++++++------ 6 files changed, 36 insertions(+), 20 deletions(-) rename {src/nbl/ext/ImGui/shaders => include/nbl/ext/ImGui/builtin/hlsl}/common.hlsl (100%) rename {src/nbl/ext/ImGui/shaders => include/nbl/ext/ImGui/builtin/hlsl}/fragment.hlsl (100%) rename {src/nbl/ext/ImGui/shaders => include/nbl/ext/ImGui/builtin/hlsl}/psinput.hlsl (100%) rename {src/nbl/ext/ImGui/shaders => include/nbl/ext/ImGui/builtin/hlsl}/vertex.hlsl (100%) diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/include/nbl/ext/ImGui/builtin/hlsl/common.hlsl similarity index 100% rename from src/nbl/ext/ImGui/shaders/common.hlsl rename to include/nbl/ext/ImGui/builtin/hlsl/common.hlsl diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/include/nbl/ext/ImGui/builtin/hlsl/fragment.hlsl similarity index 100% rename from src/nbl/ext/ImGui/shaders/fragment.hlsl rename to include/nbl/ext/ImGui/builtin/hlsl/fragment.hlsl diff --git a/src/nbl/ext/ImGui/shaders/psinput.hlsl b/include/nbl/ext/ImGui/builtin/hlsl/psinput.hlsl similarity index 100% rename from src/nbl/ext/ImGui/shaders/psinput.hlsl rename to include/nbl/ext/ImGui/builtin/hlsl/psinput.hlsl diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/include/nbl/ext/ImGui/builtin/hlsl/vertex.hlsl similarity index 100% rename from src/nbl/ext/ImGui/shaders/vertex.hlsl rename to include/nbl/ext/ImGui/builtin/hlsl/vertex.hlsl diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index d88c2c5a32..3f7dfd088f 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -23,18 +23,23 @@ nbl_create_ext_library_project( target_link_libraries(${LIB_NAME} PUBLIC imtestengine) -# (*) -> I wish we could just take NSC, offline-precompile to SPIRV, embed into builtin resource library (as we did!) but then be smart & adjust at runtime OpDecorate of our resources according to wishes - unfortunately no linker yet we have and we are not going to make one ourselves so we compile imgui shaders at runtime -set(_BR_TARGET_ extImguibuiltinResourceData) -set(_RESOURCE_DIR_ "shaders") - -get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) +# this should be standard for all extensions +get_filename_component(_ARCHIVE_ABSOLUTE_ENTRY_PATH_ "${NBL_EXT_INTERNAL_INCLUDE_DIR}/nbl/ext" ABSOLUTE) get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) +set(_ARCHIVE_ENTRY_KEY_ "ImGui/builtin/hlsl") # then each one has unique archive key + +target_compile_definitions(${LIB_NAME} PRIVATE _ARCHIVE_ABSOLUTE_ENTRY_PATH_="${_ARCHIVE_ABSOLUTE_ENTRY_PATH_}") + +if(NBL_EMBED_BUILTIN_RESOURCES) + # (*) -> I wish we could just take NSC, offline-precompile to SPIRV, embed into builtin resource library (as we did!) but then be smart & adjust at runtime OpDecorate of our resources according to wishes - unfortunately no linker yet we have and we are not going to make one ourselves so we compile imgui shaders at runtime + set(_BR_TARGET_ extImguibuiltinResourceData) -LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") -LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "psinput.hlsl") -LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff -LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide external descriptor set layout + ImGUI textures & sampler state set/binding Ixs (for pipeline layout) at runtime + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "psinput.hlsl") + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide external descriptor set layout + ImGUI textures & sampler state set/binding Ixs (for pipeline layout) at runtime -ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${_RESOURCE_DIR_}" "nbl::ext::imgui::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") -LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) \ No newline at end of file + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_ARCHIVE_ABSOLUTE_ENTRY_PATH_}" "${_ARCHIVE_ENTRY_KEY_}" "nbl::ext::imgui::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) +endif() \ No newline at end of file diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 934a5db96f..54bed30025 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -8,7 +8,7 @@ #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" -#include "shaders/common.hlsl" +#include "nbl/ext/ImGui/builtin/hlsl/common.hlsl" #include "nbl/ext/ImGui/builtin/builtinResources.h" #include "nbl/ext/ImGui/builtin/CArchive.h" #include "imgui/imgui.h" @@ -112,6 +112,9 @@ smart_refctd_ptr UI::createDefaultPipelineLayout(ILogicalDev return device->createPipelineLayout(PushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); } +// note we use archive entry explicitly for temporary compiler include search path & asset cwd to use keys directly +constexpr std::string_view NBL_ARCHIVE_ENTRY = nbl::ext::imgui::builtin::pathPrefix; + const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) { assert(system); @@ -119,8 +122,17 @@ const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, if(!system) return nullptr; + // extension should mount everything for you, regardless if content goes from virtual filesystem + // or disk directly - and you should never rely on application framework to expose extension data + + #ifdef NBL_EMBED_BUILTIN_RESOURCES auto archive = make_smart_refctd_ptr(smart_refctd_ptr(logger)); system->mount(smart_refctd_ptr(archive), archiveAlias.data()); + #else + auto NBL_EXTENSION_MOUNT_DIRECTORY_ENTRY = (path(_ARCHIVE_ABSOLUTE_ENTRY_PATH_) / NBL_ARCHIVE_ENTRY).make_preferred(); + auto archive = make_smart_refctd_ptr(std::move(NBL_EXTENSION_MOUNT_DIRECTORY_ENTRY), smart_refctd_ptr(logger), system); + system->mount(smart_refctd_ptr(archive), NBL_ARCHIVE_ENTRY); + #endif return smart_refctd_ptr(archive); } @@ -140,9 +152,7 @@ core::smart_refctd_ptr UI::createPipeline(SCreation smart_refctd_ptr vertex, fragment; } shaders; - { - constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; - + { //! proxy the system, we will touch it gently auto system = smart_refctd_ptr(creationParams.assetManager->getSystem()); @@ -150,13 +160,13 @@ core::smart_refctd_ptr UI::createPipeline(SCreation auto compiler = set->getShaderCompiler(IShader::E_CONTENT_TYPE::ECT_HLSL); auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); auto includeLoader = includeFinder->getDefaultFileSystemLoader(); - includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); + includeFinder->addSearchPath(NBL_ARCHIVE_ENTRY.data(), includeLoader); auto createShader = [&]() -> smart_refctd_ptr { IAssetLoader::SAssetLoadParams params = {}; params.logger = creationParams.utilities->getLogger(); - params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); + params.workingDirectory = NBL_ARCHIVE_ENTRY.data(); auto bundle = creationParams.assetManager->getAsset(key.value, params); const auto assets = bundle.getContents(); @@ -250,11 +260,12 @@ core::smart_refctd_ptr UI::createPipeline(SCreation return nullptr; } - //! but we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount ourselves temporary archive to compile our extension sources then unmount it - auto archive = mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ALIAS.data()); + //! but we should never assume user will mount our internal data since its the extension and not user's job to do it so we do to compile our extension sources + if(!system->isDirectory(path(NBL_ARCHIVE_ENTRY.data()))) + mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get()); + shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); - system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); if (!shaders.vertex) { From 6f86ba9d7d3169dbad1a120f77c5c6164d5911c7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 14 Oct 2024 21:45:19 +0200 Subject: [PATCH 145/148] ah sry I forgot about my utility "archiveAlias" ok ok --- src/nbl/ext/ImGui/ImGui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 54bed30025..36e98eeb59 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -131,7 +131,7 @@ const smart_refctd_ptr UI::mount(smart_refctd_ptr logger, #else auto NBL_EXTENSION_MOUNT_DIRECTORY_ENTRY = (path(_ARCHIVE_ABSOLUTE_ENTRY_PATH_) / NBL_ARCHIVE_ENTRY).make_preferred(); auto archive = make_smart_refctd_ptr(std::move(NBL_EXTENSION_MOUNT_DIRECTORY_ENTRY), smart_refctd_ptr(logger), system); - system->mount(smart_refctd_ptr(archive), NBL_ARCHIVE_ENTRY); + system->mount(smart_refctd_ptr(archive), archiveAlias.data()); #endif return smart_refctd_ptr(archive); @@ -262,7 +262,7 @@ core::smart_refctd_ptr UI::createPipeline(SCreation //! but we should never assume user will mount our internal data since its the extension and not user's job to do it so we do to compile our extension sources if(!system->isDirectory(path(NBL_ARCHIVE_ENTRY.data()))) - mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get()); + mount(smart_refctd_ptr(creationParams.utilities->getLogger()), system.get(), NBL_ARCHIVE_ENTRY); shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); From c80ec6247e1892c618ccad8d5858a980fc95c5a2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 14 Oct 2024 21:57:22 +0200 Subject: [PATCH 146/148] update examples_tests submodule --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 776dc0689a..63d678cceb 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 776dc0689a260da8cd614b9c8b600c3739b31190 +Subproject commit 63d678cceb07cc8f96a6e16fcb7b65d366dae16e From 163a600518dc1c5b569f9403f3f585e22aca06e1 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 14 Oct 2024 22:06:45 +0200 Subject: [PATCH 147/148] promote createFontAtlasTexture's creation params arg const --- include/nbl/ext/ImGui/ImGui.h | 2 +- src/nbl/ext/ImGui/ImGui.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index c000873aa1..1c0fbfa354 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -163,7 +163,7 @@ class UI final : public core::IReferenceCounted static core::smart_refctd_ptr createPipeline(SCreationParameters& creationParams); static bool createMDIBuffer(SCreationParameters& creationParams); // NOTE: in the future this will also need a compute queue to do mip-maps - static video::ISemaphore::future_t createFontAtlasTexture(SCreationParameters& creationParams, void* const imFontAtlas, core::smart_refctd_ptr& outFontView); + static video::ISemaphore::future_t createFontAtlasTexture(const SCreationParameters& creationParams, void* const imFontAtlas, core::smart_refctd_ptr& outFontView); void handleMouseEvents(const SUpdateParameters& params) const; void handleKeyEvents(const SUpdateParameters& params) const; diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 36e98eeb59..267a69baad 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -364,7 +364,7 @@ core::smart_refctd_ptr UI::createPipeline(SCreation return pipeline; } -ISemaphore::future_t UI::createFontAtlasTexture(SCreationParameters& creationParams, void* const imFontAtlas, smart_refctd_ptr& outFontView) +ISemaphore::future_t UI::createFontAtlasTexture(const SCreationParameters& creationParams, void* const imFontAtlas, smart_refctd_ptr& outFontView) { video::IQueue* transfer = creationParams.transfer; system::logger_opt_ptr logger = creationParams.utilities->getLogger(); From 10654a224cf7881959461d93d536c0a593d2dab4 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 14 Oct 2024 22:08:12 +0200 Subject: [PATCH 148/148] update examples_tests submodule to point to gpgpuimgui merge commit --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 63d678cceb..236b54543e 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 63d678cceb07cc8f96a6e16fcb7b65d366dae16e +Subproject commit 236b54543e7428e96dbd186c37281d59a13aaf5e