diff --git a/CMakeLists.txt b/CMakeLists.txt index 74170788d8..74c30b1203 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ option(NBL_FAST_MATH "Enable fast low-precision math" OFF) # the reason OFF is b option(NBL_BUILD_EXAMPLES "Enable building examples" ON) option(NBL_BUILD_MITSUBA_LOADER "Enable nbl::ext::MitsubaLoader?" OFF) # TODO: once it compies turn this ON by default! option(NBL_BUILD_IMGUI "Enable nbl::ext::ImGui?" ON) +option(NBL_BUILD_DEBUG_DRAW "Enable Nabla Debug Draw extension?" ON) option(NBL_BUILD_OPTIX "Enable nbl::ext::OptiX?" OFF) if(NBL_COMPILE_WITH_CUDA) diff --git a/examples_tests b/examples_tests index a784f916b3..36171d756e 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit a784f916b3b7da30ac365e7337c24c5c9ee41c95 +Subproject commit 36171d756e2aa5d1005d0e4dde711f66b9a627dc diff --git a/include/nbl/config/BuildConfigOptions.h.in b/include/nbl/config/BuildConfigOptions.h.in index c67c942217..d130ff4ce2 100644 --- a/include/nbl/config/BuildConfigOptions.h.in +++ b/include/nbl/config/BuildConfigOptions.h.in @@ -59,6 +59,8 @@ #cmakedefine _NBL_BUILD_DPL_ +#cmakedefine NBL_BUILD_DEBUG_DRAW + // ! // TODO: This has to disapppear from the main header and go to the OptiX extension header + config #cmakedefine OPTIX_INCLUDE_DIR "@OPTIX_INCLUDE_DIR@" diff --git a/include/nbl/ext/DebugDraw/CDrawAABB.h b/include/nbl/ext/DebugDraw/CDrawAABB.h new file mode 100644 index 0000000000..a8e3205f22 --- /dev/null +++ b/include/nbl/ext/DebugDraw/CDrawAABB.h @@ -0,0 +1,94 @@ +// Copyright (C) 2018-2025 - 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 + +#ifndef _NBL_EXT_DRAW_AABB_H_ +#define _NBL_EXT_DRAW_AABB_H_ + +#include "nbl/video/declarations.h" +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include "nbl/builtin/hlsl/shapes/aabb.hlsl" +#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl" + +namespace nbl::ext::debug_draw +{ +class DrawAABB final : public core::IReferenceCounted +{ + public: + static constexpr inline uint32_t IndicesCount = 24u; + static constexpr inline uint32_t VerticesCount = 8u; + + enum DrawMode : uint16_t + { + ADM_DRAW_SINGLE = 0b01, + ADM_DRAW_BATCH = 0b10, + ADM_DRAW_BOTH = 0b11 + }; + + struct SCachedCreationParameters + { + using streaming_buffer_t = video::StreamingTransientDataBufferST>; + + static constexpr inline auto RequiredAllocateFlags = core::bitflag(video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); + static constexpr inline auto RequiredUsageFlags = core::bitflag(asset::IBuffer::EUF_STORAGE_BUFFER_BIT) | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + + DrawMode drawMode = ADM_DRAW_BOTH; + + core::smart_refctd_ptr utilities; + + //! optional, default MDI buffer allocated if not provided + core::smart_refctd_ptr streamingBuffer = nullptr; + }; + + struct SCreationParameters : SCachedCreationParameters + { + video::IQueue* transfer = nullptr; + core::smart_refctd_ptr assetManager = nullptr; + + core::smart_refctd_ptr singlePipelineLayout; + core::smart_refctd_ptr batchPipelineLayout; + core::smart_refctd_ptr renderpass = nullptr; + }; + + // creates an instance that can draw one AABB via push constant or multiple using streaming buffer + static core::smart_refctd_ptr create(SCreationParameters&& params); + + // creates pipeline layout from push constant range + static core::smart_refctd_ptr createPipelineLayoutFromPCRange(video::ILogicalDevice* device, const asset::SPushConstantRange& pcRange); + + // creates default pipeline layout for streaming version + static core::smart_refctd_ptr createDefaultPipelineLayout(video::ILogicalDevice* device); + + //! 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 = ""); + + inline const SCachedCreationParameters& getCreationParameters() const { return m_cachedCreationParams; } + + // records draw command for single AABB, user has to set pipeline outside + bool renderSingle(video::IGPUCommandBuffer* commandBuffer, const hlsl::shapes::AABB<3, float>& aabb, const hlsl::float32_t4& color, const hlsl::float32_t4x4& cameraMat); + + bool render(video::IGPUCommandBuffer* commandBuffer, video::ISemaphore::SWaitInfo waitInfo, std::span aabbInstances, const hlsl::float32_t4x4& cameraMat); + + static hlsl::float32_t4x4 getTransformFromAABB(const hlsl::shapes::AABB<3, float>& aabb); + + protected: + DrawAABB(SCreationParameters&& _params, core::smart_refctd_ptr singlePipeline, core::smart_refctd_ptr batchPipeline, + core::smart_refctd_ptr indicesBuffer); + ~DrawAABB() override; + + private: + static bool validateCreationParameters(SCreationParameters& params); + static core::smart_refctd_ptr createPipeline(SCreationParameters& params, const video::IGPUPipelineLayout* pipelineLayout, const std::string& vsPath, const std::string& fsPath); + static bool createStreamingBuffer(SCreationParameters& params); + static core::smart_refctd_ptr createIndicesBuffer(SCreationParameters& params); + + core::smart_refctd_ptr m_indicesBuffer; + + SCachedCreationParameters m_cachedCreationParams; + + core::smart_refctd_ptr m_singlePipeline; + core::smart_refctd_ptr m_batchPipeline; +}; +} + +#endif diff --git a/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.fragment.hlsl b/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.fragment.hlsl new file mode 100644 index 0000000000..09a12f3d07 --- /dev/null +++ b/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.fragment.hlsl @@ -0,0 +1,13 @@ +#pragma shader_stage(fragment) + +#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl" + +using namespace nbl::ext::debug_draw; + +[shader("pixel")] +float32_t4 main(PSInput input) : SV_TARGET +{ + float32_t4 outColor = input.color; + + return outColor; +} \ No newline at end of file diff --git a/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.vertex.hlsl b/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.vertex.hlsl new file mode 100644 index 0000000000..5f67aa2f1e --- /dev/null +++ b/include/nbl/ext/DebugDraw/builtin/hlsl/aabb_instances.vertex.hlsl @@ -0,0 +1,35 @@ +#pragma shader_stage(vertex) + +#include "nbl/builtin/hlsl/math/linalg/fast_affine.hlsl" +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" +#include "nbl/builtin/hlsl/bda/__ptr.hlsl" +#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl" + +using namespace nbl::hlsl; +using namespace nbl::ext::debug_draw; + +[[vk::push_constant]] SPushConstants pc; + +[shader("vertex")] +PSInput main() +{ + const float32_t3 unitAABBVertices[8] = { + float32_t3(0.0, 0.0, 0.0), + float32_t3(1.0, 0.0, 0.0), + float32_t3(0.0, 0.0, 1.0), + float32_t3(1.0, 0.0, 1.0), + float32_t3(0.0, 1.0, 0.0), + float32_t3(1.0, 1.0, 0.0), + float32_t3(0.0, 1.0, 1.0), + float32_t3(1.0, 1.0, 1.0) + }; + + PSInput output; + float32_t3 vertex = unitAABBVertices[glsl::gl_VertexIndex()]; + InstanceData instance = vk::BufferPointer(pc.pInstanceBuffer + sizeof(InstanceData) * glsl::gl_InstanceIndex()).Get(); + + output.position = math::linalg::promoted_mul(instance.transform, vertex); + output.color = instance.color; + + return output; +} \ No newline at end of file diff --git a/include/nbl/ext/DebugDraw/builtin/hlsl/common.hlsl b/include/nbl/ext/DebugDraw/builtin/hlsl/common.hlsl new file mode 100644 index 0000000000..a178d45465 --- /dev/null +++ b/include/nbl/ext/DebugDraw/builtin/hlsl/common.hlsl @@ -0,0 +1,40 @@ +#ifndef _NBL_DEBUG_DRAW_EXT_COMMON_HLSL +#define _NBL_DEBUG_DRAW_EXT_COMMON_HLSL + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" + +namespace nbl +{ +namespace ext +{ +namespace debug_draw +{ + +struct InstanceData +{ + hlsl::float32_t4x4 transform; + hlsl::float32_t4 color; +}; + +struct SSinglePushConstants +{ + InstanceData instance; +}; + +struct SPushConstants +{ + uint64_t pInstanceBuffer; +}; + +#ifdef __HLSL_VERSION +struct PSInput +{ + float32_t4 position : SV_Position; + float32_t4 color : TEXCOORD0; +}; +#endif + +} +} +} +#endif diff --git a/include/nbl/ext/DebugDraw/builtin/hlsl/single.vertex.hlsl b/include/nbl/ext/DebugDraw/builtin/hlsl/single.vertex.hlsl new file mode 100644 index 0000000000..64ca75d5ab --- /dev/null +++ b/include/nbl/ext/DebugDraw/builtin/hlsl/single.vertex.hlsl @@ -0,0 +1,34 @@ +#pragma shader_stage(vertex) + +#include "nbl/builtin/hlsl/math/linalg/fast_affine.hlsl" +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" +#include "nbl/builtin/hlsl/bda/__ptr.hlsl" +#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl" + +using namespace nbl::hlsl; +using namespace nbl::ext::debug_draw; + +[[vk::push_constant]] SSinglePushConstants pc; + +[shader("vertex")] +PSInput main() +{ + const float32_t3 unitAABBVertices[8] = { + float32_t3(0.0, 0.0, 0.0), + float32_t3(1.0, 0.0, 0.0), + float32_t3(0.0, 0.0, 1.0), + float32_t3(1.0, 0.0, 1.0), + float32_t3(0.0, 1.0, 0.0), + float32_t3(1.0, 1.0, 0.0), + float32_t3(0.0, 1.0, 1.0), + float32_t3(1.0, 1.0, 1.0) + }; + + PSInput output; + float32_t3 vertex = unitAABBVertices[glsl::gl_VertexIndex()]; + + output.position = math::linalg::promoted_mul(pc.instance.transform, vertex); + output.color = pc.instance.color; + + return output; +} \ No newline at end of file diff --git a/include/nbl/system/ISystem.h b/include/nbl/system/ISystem.h index 4e02221d7c..65f0351582 100644 --- a/include/nbl/system/ISystem.h +++ b/include/nbl/system/ISystem.h @@ -70,6 +70,7 @@ class NBL_API2 ISystem : public core::IReferenceCounted // virtual inline bool isDirectory(const system::path& p) const { + // TODO: fix bug, input "nbl/ext/DebugDraw/builtin/hlsl" -> returs true when no such dir present in mounted stuff due to how it uses parent paths in loop (goes up up till matches "nbl" builtin archive and thinks it resolved the requested dir) if (isPathReadOnly(p)) return p.extension()==""; // TODO: this is a temporary decision until we figure out how to check if a file is directory in android APK else diff --git a/src/nbl/ext/CMakeLists.txt b/src/nbl/ext/CMakeLists.txt index e0bcd223f6..af46b29aab 100644 --- a/src/nbl/ext/CMakeLists.txt +++ b/src/nbl/ext/CMakeLists.txt @@ -54,6 +54,18 @@ if(NBL_BUILD_TEXT_RENDERING) add_subdirectory(TextRendering) endif() +if(NBL_BUILD_DEBUG_DRAW) + add_subdirectory(DebugDraw) + set(NBL_EXT_DEBUG_DRAW_INCLUDE_DIRS + ${NBL_EXT_DEBUG_DRAW_INCLUDE_DIRS} + PARENT_SCOPE + ) + set(NBL_EXT_DEBUG_DRAW_LIB + ${NBL_EXT_DEBUG_DRAW_LIB} + PARENT_SCOPE + ) +endif() + propagate_changed_variables_to_parent_scope() NBL_ADJUST_FOLDERS(ext) \ No newline at end of file diff --git a/src/nbl/ext/DebugDraw/CDrawAABB.cpp b/src/nbl/ext/DebugDraw/CDrawAABB.cpp new file mode 100644 index 0000000000..d231f21e3e --- /dev/null +++ b/src/nbl/ext/DebugDraw/CDrawAABB.cpp @@ -0,0 +1,429 @@ +// Copyright (C) 2018-2025 - 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 + +#include "nbl/ext/DebugDraw/CDrawAABB.h" +#include "nbl/builtin/hlsl/math/linalg/fast_affine.hlsl" + +#ifdef NBL_EMBED_BUILTIN_RESOURCES +#include "nbl/ext/debug_draw/builtin/CArchive.h" +#endif + +using namespace nbl; +using namespace core; +using namespace video; +using namespace system; +using namespace asset; +using namespace hlsl; + +namespace nbl::ext::debug_draw +{ + +core::smart_refctd_ptr DrawAABB::create(SCreationParameters&& params) +{ + auto* const logger = params.utilities->getLogger(); + + if (!validateCreationParameters(params)) + { + logger->log("Failed creation parameters validation!", ILogger::ELL_ERROR); + return nullptr; + } + + smart_refctd_ptr singlePipeline = nullptr; + if (params.drawMode & ADM_DRAW_SINGLE) + { + singlePipeline = createPipeline(params, params.singlePipelineLayout.get(), "single.vertex.hlsl", "aabb_instances.fragment.hlsl"); + if (!singlePipeline) + { + logger->log("Failed to create pipeline!", ILogger::ELL_ERROR); + return nullptr; + } + } + + smart_refctd_ptr batchPipeline = nullptr; + if (params.drawMode & ADM_DRAW_BATCH) + { + batchPipeline = createPipeline(params, params.batchPipelineLayout.get(), "aabb_instances.vertex.hlsl", "aabb_instances.fragment.hlsl"); + if (!batchPipeline) + { + logger->log("Failed to create pipeline!", ILogger::ELL_ERROR); + return nullptr; + } + } + + if (!createStreamingBuffer(params)) + { + logger->log("Failed to create streaming buffer!", ILogger::ELL_ERROR); + return nullptr; + } + + auto indicesBuffer = createIndicesBuffer(params); + if (!indicesBuffer) + { + logger->log("Failed to create indices buffer!", ILogger::ELL_ERROR); + return nullptr; + } + + return core::smart_refctd_ptr(new DrawAABB(std::move(params), singlePipeline, batchPipeline, indicesBuffer)); +} + +DrawAABB::DrawAABB(SCreationParameters&& params, core::smart_refctd_ptr singlePipeline, smart_refctd_ptr batchPipeline, smart_refctd_ptr indicesBuffer) + : m_cachedCreationParams(std::move(params)), m_singlePipeline(std::move(singlePipeline)), m_batchPipeline(std::move(batchPipeline)), + m_indicesBuffer(std::move(indicesBuffer)) +{ +} + +DrawAABB::~DrawAABB() +{ +} + +// 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 = _ARCHIVE_ENTRY_KEY_; + +const smart_refctd_ptr DrawAABB::mount(smart_refctd_ptr logger, ISystem* system, const std::string_view archiveAlias) +{ + assert(system); + + 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), archiveAlias.data()); +#endif + + return smart_refctd_ptr(archive); +} + +bool DrawAABB::validateCreationParameters(SCreationParameters& creationParams) +{ + 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 DrawAABB::S_CREATION_PARAMETERS!") + }); + + system::logger_opt_ptr logger = creationParams.utilities->getLogger(); + for (const auto& [ok, error] : validation) + if (!ok) + { + logger.log(error, ILogger::ELL_ERROR); + return false; + } + + return true; +} + +smart_refctd_ptr DrawAABB::createPipeline(SCreationParameters& params, const IGPUPipelineLayout* pipelineLayout, const std::string& vsPath, const std::string& fsPath) +{ + auto system = smart_refctd_ptr(params.assetManager->getSystem()); + auto* set = params.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_ENTRY.data(), includeLoader); + + auto compileShader = [&](const std::string& filePath, IShader::E_SHADER_STAGE stage) -> smart_refctd_ptr + { + IAssetLoader::SAssetLoadParams lparams = {}; + lparams.logger = params.utilities->getLogger(); + lparams.workingDirectory = NBL_ARCHIVE_ENTRY.data(); + auto bundle = params.assetManager->getAsset(filePath, lparams); + if (bundle.getContents().empty() || bundle.getAssetType() != IAsset::ET_SHADER) + { + params.utilities->getLogger()->log("Shader %s not found!", ILogger::ELL_ERROR, filePath.c_str()); + exit(-1); + } + + const auto assets = bundle.getContents(); + assert(assets.size() == 1); + smart_refctd_ptr shaderSrc = IAsset::castDown(assets[0]); + if (!shaderSrc) + return nullptr; + + CHLSLCompiler::SOptions options = {}; + options.stage = stage; + options.preprocessorOptions.sourceIdentifier = filePath; + options.preprocessorOptions.logger = params.utilities->getLogger(); + options.preprocessorOptions.includeFinder = includeFinder.get(); + shaderSrc = compiler->compileToSPIRV((const char*)shaderSrc->getContent()->getPointer(), options); + + return params.utilities->getLogicalDevice()->compileShader({ shaderSrc.get() }); + }; + + if (!system->exists(path(NBL_ARCHIVE_ENTRY) / "common.hlsl", {})) + mount(smart_refctd_ptr(params.utilities->getLogger()), system.get(), NBL_ARCHIVE_ENTRY); + + auto vertexShader = compileShader(vsPath, IShader::E_SHADER_STAGE::ESS_VERTEX); + auto fragmentShader = compileShader(fsPath, IShader::E_SHADER_STAGE::ESS_FRAGMENT); + + if (!vertexShader || !fragmentShader) + { + params.utilities->getLogger()->log("Could not compile shaders!", ILogger::ELL_ERROR); + return nullptr; + } + + video::IGPUGraphicsPipeline::SCreationParams pipelineParams[1] = {}; + pipelineParams[0].layout = pipelineLayout; + pipelineParams[0].vertexShader = { .shader = vertexShader.get(), .entryPoint = "main" }; + pipelineParams[0].fragmentShader = { .shader = fragmentShader.get(), .entryPoint = "main" }; + pipelineParams[0].cached = { + .primitiveAssembly = { + .primitiveType = asset::E_PRIMITIVE_TOPOLOGY::EPT_LINE_LIST, + } + }; + pipelineParams[0].renderpass = params.renderpass.get(); + + smart_refctd_ptr pipeline; + params.utilities->getLogicalDevice()->createGraphicsPipelines(nullptr, pipelineParams, &pipeline); + if (!pipeline) + { + params.utilities->getLogger()->log("Could not create streaming pipeline!", ILogger::ELL_ERROR); + return nullptr; + } + + return pipeline; +} + +bool DrawAABB::createStreamingBuffer(SCreationParameters& params) +{ + const uint32_t minStreamingBufferAllocationSize = 128u, maxStreamingBufferAllocationAlignment = 4096u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; + + 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; + + return flags; + }; + + if (!params.streamingBuffer) + { + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = SCachedCreationParameters::RequiredUsageFlags; + mdiCreationParams.size = mdiBufferDefaultSize; + + auto buffer = params.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + buffer->setObjectDebugName("AABB Streaming Buffer"); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= params.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto allocation = params.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), SCachedCreationParameters::RequiredAllocateFlags); + { + const bool allocated = allocation.isValid(); + assert(allocated); + } + auto memory = allocation.memory; + + if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) + params.utilities->getLogger()->log("Could not map device memory!", ILogger::ELL_ERROR); + + params.streamingBuffer = make_smart_refctd_ptr(SBufferRange{0ull, mdiCreationParams.size, std::move(buffer)}, maxStreamingBufferAllocationAlignment, minStreamingBufferAllocationSize); + } + + auto buffer = params.streamingBuffer->getBuffer(); + auto binding = buffer->getBoundMemory(); + + const auto validation = std::to_array + ({ + std::make_pair(buffer->getCreationParams().usage.hasFlags(SCachedCreationParameters::RequiredUsageFlags), "Streaming buffer must be created with IBuffer::EUF_STORAGE_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & params.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "Streaming buffer must have up-streaming memory type bits enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(SCachedCreationParameters::RequiredAllocateFlags), "Streaming buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->isCurrentlyMapped(), "Streaming 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())), "Streaming buffer's memory current mapping access flags don't meet requirements!") + }); + + for (const auto& [ok, error] : validation) + if (!ok) + { + params.utilities->getLogger()->log(error, ILogger::ELL_ERROR); + return false; + } + + return true; +} + +smart_refctd_ptr DrawAABB::createIndicesBuffer(SCreationParameters& params) +{ + std::array unitAABBIndices; + unitAABBIndices[0] = 0; + unitAABBIndices[1] = 1; + unitAABBIndices[2] = 0; + unitAABBIndices[3] = 2; + + unitAABBIndices[4] = 3; + unitAABBIndices[5] = 1; + unitAABBIndices[6] = 3; + unitAABBIndices[7] = 2; + + unitAABBIndices[8] = 4; + unitAABBIndices[9] = 5; + unitAABBIndices[10] = 4; + unitAABBIndices[11] = 6; + + unitAABBIndices[12] = 7; + unitAABBIndices[13] = 5; + unitAABBIndices[14] = 7; + unitAABBIndices[15] = 6; + + unitAABBIndices[16] = 0; + unitAABBIndices[17] = 4; + unitAABBIndices[18] = 1; + unitAABBIndices[19] = 5; + + unitAABBIndices[20] = 2; + unitAABBIndices[21] = 6; + unitAABBIndices[22] = 3; + unitAABBIndices[23] = 7; + + IGPUBuffer::SCreationParams bufparams; + bufparams.size = sizeof(uint32_t) * unitAABBIndices.size(); + bufparams.usage = IGPUBuffer::EUF_INDEX_BUFFER_BIT | IGPUBuffer::EUF_TRANSFER_DST_BIT; + + smart_refctd_ptr indicesBuffer; + params.utilities->createFilledDeviceLocalBufferOnDedMem( + SIntendedSubmitInfo{ .queue = params.transfer }, + std::move(bufparams), + unitAABBIndices.data() + ).move_into(indicesBuffer); + + return indicesBuffer; +} + +core::smart_refctd_ptr DrawAABB::createPipelineLayoutFromPCRange(video::ILogicalDevice* device, const asset::SPushConstantRange& pcRange) +{ + return device->createPipelineLayout({ &pcRange , 1 }, nullptr, nullptr, nullptr, nullptr); +} + +core::smart_refctd_ptr DrawAABB::createDefaultPipelineLayout(video::ILogicalDevice* device) +{ + SPushConstantRange pcRange = { + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX, + .offset = 0, + .size = sizeof(SPushConstants) + }; + return device->createPipelineLayout({ &pcRange , 1 }, nullptr, nullptr, nullptr, nullptr); +} + +bool DrawAABB::renderSingle(IGPUCommandBuffer* commandBuffer, const hlsl::shapes::AABB<3, float>& aabb, const hlsl::float32_t4& color, const hlsl::float32_t4x4& cameraMat) +{ + if (!(m_cachedCreationParams.drawMode & ADM_DRAW_SINGLE)) + { + m_cachedCreationParams.utilities->getLogger()->log("DrawAABB has not been enabled for draw single!", ILogger::ELL_ERROR); + return false; + } + + commandBuffer->bindGraphicsPipeline(m_singlePipeline.get()); + commandBuffer->setLineWidth(1.f); + asset::SBufferBinding indexBinding = { .offset = 0, .buffer = m_indicesBuffer }; + commandBuffer->bindIndexBuffer(indexBinding, asset::EIT_32BIT); + + SSinglePushConstants pc; + + hlsl::float32_t4x4 instanceTransform = getTransformFromAABB(aabb); + pc.instance.transform = hlsl::mul(cameraMat, instanceTransform); + pc.instance.color = color; + + commandBuffer->pushConstants(m_singlePipeline->getLayout(), ESS_VERTEX, 0, sizeof(SSinglePushConstants), &pc); + commandBuffer->drawIndexed(IndicesCount, 1, 0, 0, 0); + + return true; +} + +bool DrawAABB::render(IGPUCommandBuffer* commandBuffer, ISemaphore::SWaitInfo waitInfo, std::span aabbInstances, const hlsl::float32_t4x4& cameraMat) +{ + if (!(m_cachedCreationParams.drawMode & ADM_DRAW_BATCH)) + { + m_cachedCreationParams.utilities->getLogger()->log("DrawAABB has not been enabled for draw batches!", ILogger::ELL_ERROR); + return false; + } + + using offset_t = SCachedCreationParameters::streaming_buffer_t::size_type; + constexpr auto MdiSizes = std::to_array({ sizeof(float32_t3), sizeof(InstanceData) }); + // 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); + + auto* streaming = m_cachedCreationParams.streamingBuffer.get(); + + auto* const streamingPtr = reinterpret_cast(streaming->getBufferPointer()); + assert(streamingPtr); + + commandBuffer->bindGraphicsPipeline(m_batchPipeline.get()); + commandBuffer->setLineWidth(1.f); + asset::SBufferBinding indexBinding = { .offset = 0, .buffer = m_indicesBuffer }; + commandBuffer->bindIndexBuffer(indexBinding, asset::EIT_32BIT); + + std::vector instances(aabbInstances.size()); + for (uint32_t i = 0; i < aabbInstances.size(); i++) + { + auto& inst = instances[i]; + inst = aabbInstances[i]; + inst.transform = hlsl::mul(cameraMat, inst.transform); + } + + auto instancesIt = instances.begin(); + const uint32_t instancesPerIter = streaming->getBuffer()->getSize() / sizeof(InstanceData); + using suballocator_t = core::LinearAddressAllocatorST; + while (instancesIt != instances.end()) + { + const uint32_t instanceCount = min(instancesPerIter, instances.size()); + offset_t inputOffset = 0u; + offset_t ImaginarySizeUpperBound = 0x1 << 30; + suballocator_t imaginaryChunk(nullptr, inputOffset, 0, roundUpToPoT(MaxAlignment), ImaginarySizeUpperBound); + uint32_t instancesByteOffset = imaginaryChunk.alloc_addr(sizeof(InstanceData) * instanceCount, sizeof(InstanceData)); + const uint32_t totalSize = imaginaryChunk.get_allocated_size(); + + inputOffset = SCachedCreationParameters::streaming_buffer_t::invalid_value; + std::chrono::steady_clock::time_point waitTill = std::chrono::steady_clock::now() + std::chrono::milliseconds(1u); + streaming->multi_allocate(waitTill, 1, &inputOffset, &totalSize, &MaxAlignment); + + memcpy(streamingPtr + instancesByteOffset, std::addressof(*instancesIt), sizeof(InstanceData) * instanceCount); + instancesIt += instanceCount; + + assert(!streaming->needsManualFlushOrInvalidate()); + + SPushConstants pc; + pc.pInstanceBuffer = m_cachedCreationParams.streamingBuffer->getBuffer()->getDeviceAddress() + instancesByteOffset; + + commandBuffer->pushConstants(m_batchPipeline->getLayout(), ESS_VERTEX, 0, sizeof(SPushConstants), &pc); + commandBuffer->drawIndexed(IndicesCount, instanceCount, 0, 0, 0); + + streaming->multi_deallocate(1, &inputOffset, &totalSize, waitInfo); + } + + return true; +} + +hlsl::float32_t4x4 DrawAABB::getTransformFromAABB(const hlsl::shapes::AABB<3, float>& aabb) +{ + const auto diagonal = aabb.getExtent(); + hlsl::float32_t4x4 transform; + transform[0][3] = aabb.minVx.x; + transform[1][3] = aabb.minVx.y; + transform[2][3] = aabb.minVx.z; + transform[3][3] = 1.f; + transform[0][0] = diagonal.x; + transform[1][1] = diagonal.y; + transform[2][2] = diagonal.z; + return transform; +} + +} diff --git a/src/nbl/ext/DebugDraw/CMakeLists.txt b/src/nbl/ext/DebugDraw/CMakeLists.txt new file mode 100644 index 0000000000..4cb2ee54cf --- /dev/null +++ b/src/nbl/ext/DebugDraw/CMakeLists.txt @@ -0,0 +1,43 @@ +include(${NBL_ROOT_PATH}/cmake/common.cmake) + +set(NBL_EXT_INTERNAL_INCLUDE_DIR "${NBL_ROOT_PATH}/include") + +set(NBL_EXT_DEBUG_DRAW_H + ${NBL_EXT_INTERNAL_INCLUDE_DIR}/nbl/ext/DebugDraw/CDrawAABB.h +) + +set(NBL_EXT_DEBUG_DRAW_SRC + "${CMAKE_CURRENT_SOURCE_DIR}/CDrawAABB.cpp" +) + +nbl_create_ext_library_project( + DEBUG_DRAW + "${NBL_EXT_DEBUG_DRAW_H}" + "${NBL_EXT_DEBUG_DRAW_SRC}" + "${NBL_EXT_DEBUG_DRAW_EXTERNAL_INCLUDE}" + "" + "" +) + +# this should be standard for all extensions +set(_ARCHIVE_ENTRY_KEY_ "nbl/ext/DebugDraw/builtin/hlsl") # then each one has unique archive key +get_filename_component(_ARCHIVE_ABSOLUTE_ENTRY_PATH_ "${NBL_EXT_INTERNAL_INCLUDE_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) + +target_compile_definitions(${LIB_NAME} PRIVATE _ARCHIVE_ABSOLUTE_ENTRY_PATH_="${_ARCHIVE_ABSOLUTE_ENTRY_PATH_}") +target_compile_definitions(${LIB_NAME} PRIVATE _ARCHIVE_ENTRY_KEY_="${_ARCHIVE_ENTRY_KEY_}") + +if(NBL_EMBED_BUILTIN_RESOURCES) + set(_BR_TARGET_ extDebugDrawbuiltinResourceData) + + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "single.vertex.hlsl") # (*) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "aabb_instances.vertex.hlsl") # (*) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "aabb_instances.fragment.hlsl") # (*) + + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_ARCHIVE_ABSOLUTE_ENTRY_PATH_}" "${_ARCHIVE_ENTRY_KEY_}" "nbl::ext::debug_draw::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) +endif() + +set(NBL_EXT_DEBUG_DRAW_TARGET ${LIB_NAME} CACHE INTERNAL "Nabla's Debug Draw logical target name")