Skip to content

Commit f4ae878

Browse files
authored
Merge pull request #900 from Devsh-Graphics-Programming/new_debug_draw
New debug draw extension for AABBs
2 parents 68e2e20 + 1badc7a commit f4ae878

File tree

10 files changed

+797
-1
lines changed

10 files changed

+797
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ option(NBL_FAST_MATH "Enable fast low-precision math" OFF) # the reason OFF is b
175175
option(NBL_BUILD_EXAMPLES "Enable building examples" ON)
176176
option(NBL_BUILD_MITSUBA_LOADER "Enable nbl::ext::MitsubaLoader?" OFF) # TODO: once it compies turn this ON by default!
177177
option(NBL_BUILD_IMGUI "Enable nbl::ext::ImGui?" ON)
178+
option(NBL_BUILD_DEBUG_DRAW "Enable Nabla Debug Draw extension?" ON)
178179

179180
option(NBL_BUILD_OPTIX "Enable nbl::ext::OptiX?" OFF)
180181
if(NBL_COMPILE_WITH_CUDA)

include/nbl/config/BuildConfigOptions.h.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959

6060
#cmakedefine _NBL_BUILD_DPL_
6161

62+
#cmakedefine NBL_BUILD_DEBUG_DRAW
63+
6264
// !
6365
// TODO: This has to disapppear from the main header and go to the OptiX extension header + config
6466
#cmakedefine OPTIX_INCLUDE_DIR "@OPTIX_INCLUDE_DIR@"
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O.
2+
// This file is part of the "Nabla Engine".
3+
// For conditions of distribution and use, see copyright notice in nabla.h
4+
5+
#ifndef _NBL_EXT_DEBUG_DRAW_DRAW_AABB_H_
6+
#define _NBL_EXT_DEBUG_DRAW_DRAW_AABB_H_
7+
8+
#include "nbl/video/declarations.h"
9+
#include "nbl/builtin/hlsl/cpp_compat.hlsl"
10+
#include "nbl/builtin/hlsl/shapes/aabb.hlsl"
11+
#include "nbl/builtin/hlsl/math/linalg/fast_affine.hlsl"
12+
#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl"
13+
14+
namespace nbl::ext::debug_draw
15+
{
16+
class DrawAABB final : public core::IReferenceCounted
17+
{
18+
public:
19+
static constexpr inline uint32_t IndicesCount = 24u;
20+
21+
enum DrawMode : uint16_t
22+
{
23+
ADM_DRAW_SINGLE = 0b01,
24+
ADM_DRAW_BATCH = 0b10,
25+
ADM_DRAW_BOTH = 0b11
26+
};
27+
28+
struct SCachedCreationParameters
29+
{
30+
using streaming_buffer_t = video::StreamingTransientDataBufferST<core::allocator<uint8_t>>;
31+
32+
static constexpr inline auto RequiredAllocateFlags = core::bitflag<video::IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS>(video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT);
33+
static constexpr inline auto RequiredUsageFlags = core::bitflag(asset::IBuffer::EUF_STORAGE_BUFFER_BIT) | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT;
34+
35+
DrawMode drawMode = ADM_DRAW_BOTH;
36+
37+
core::smart_refctd_ptr<video::IUtilities> utilities;
38+
39+
//! optional, default MDI buffer allocated if not provided
40+
core::smart_refctd_ptr<streaming_buffer_t> streamingBuffer = nullptr;
41+
};
42+
43+
struct SCreationParameters : SCachedCreationParameters
44+
{
45+
video::IQueue* transfer = nullptr; // only used to make the 24 element index buffer and instanced pipeline on create
46+
core::smart_refctd_ptr<asset::IAssetManager> assetManager = nullptr;
47+
48+
core::smart_refctd_ptr<video::IGPUPipelineLayout> singlePipelineLayout = nullptr;
49+
core::smart_refctd_ptr<video::IGPUPipelineLayout> batchPipelineLayout = nullptr;
50+
core::smart_refctd_ptr<video::IGPURenderpass> renderpass = nullptr;
51+
52+
inline bool validate() const
53+
{
54+
const auto validation = std::to_array
55+
({
56+
std::make_pair(bool(assetManager), "Invalid `creationParams.assetManager` is nullptr!"),
57+
std::make_pair(bool(utilities), "Invalid `creationParams.utilities` is nullptr!"),
58+
std::make_pair(bool(transfer), "Invalid `creationParams.transfer` is nullptr!"),
59+
std::make_pair(bool(renderpass), "Invalid `creationParams.renderpass` is nullptr!"),
60+
std::make_pair(bool(utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[transfer->getFamilyIndex()].queueFlags.hasFlags(video::IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `creationParams.transfer` is not capable of transfer operations!")
61+
});
62+
63+
system::logger_opt_ptr logger = utilities->getLogger();
64+
for (const auto& [ok, error] : validation)
65+
if (!ok)
66+
{
67+
logger.log(error, system::ILogger::ELL_ERROR);
68+
return false;
69+
}
70+
71+
assert(bool(assetManager->getSystem()));
72+
73+
return true;
74+
}
75+
};
76+
77+
struct DrawParameters
78+
{
79+
video::IGPUCommandBuffer* commandBuffer = nullptr;
80+
hlsl::float32_t4x4 cameraMat;
81+
float lineWidth = 1.f;
82+
};
83+
84+
// creates an instance that can draw one AABB via push constant or multiple using streaming buffer
85+
static core::smart_refctd_ptr<DrawAABB> create(SCreationParameters&& params);
86+
87+
// creates pipeline layout from push constant range
88+
static core::smart_refctd_ptr<video::IGPUPipelineLayout> createPipelineLayoutFromPCRange(video::ILogicalDevice* device, const asset::SPushConstantRange& pcRange);
89+
90+
// creates default pipeline layout for pipeline specified by draw mode (note: if mode==BOTH, returns layout for BATCH mode)
91+
static core::smart_refctd_ptr<video::IGPUPipelineLayout> createDefaultPipelineLayout(video::ILogicalDevice* device, DrawMode mode = ADM_DRAW_BATCH);
92+
93+
//! mounts the extension's archive to given system - useful if you want to create your own shaders with common header included
94+
static const core::smart_refctd_ptr<system::IFileArchive> mount(core::smart_refctd_ptr<system::ILogger> logger, system::ISystem* system, video::ILogicalDevice* device, const std::string_view archiveAlias = "");
95+
96+
inline const SCachedCreationParameters& getCreationParameters() const { return m_cachedCreationParams; }
97+
98+
// records draw command for single AABB, user has to set pipeline outside
99+
bool renderSingle(const DrawParameters& params, const hlsl::shapes::AABB<3, float>& aabb, const hlsl::float32_t4& color);
100+
101+
// records draw command for rendering batch of AABB instances as InstanceData
102+
// user has to set span of filled-in InstanceData; camera matrix used in push constant
103+
inline bool render(const DrawParameters& params, video::ISemaphore::SWaitInfo waitInfo, std::span<const InstanceData> aabbInstances)
104+
{
105+
system::logger_opt_ptr logger = m_cachedCreationParams.utilities->getLogger();
106+
if (!(m_cachedCreationParams.drawMode & ADM_DRAW_BATCH))
107+
{
108+
logger.log("DrawAABB has not been enabled for draw batches!", system::ILogger::ELL_ERROR);
109+
return false;
110+
}
111+
112+
using offset_t = SCachedCreationParameters::streaming_buffer_t::size_type;
113+
constexpr offset_t MaxAlignment = sizeof(InstanceData);
114+
// allocator initialization needs us to round up to PoT
115+
const auto MaxPOTAlignment = hlsl::roundUpToPoT(MaxAlignment);
116+
auto* streaming = m_cachedCreationParams.streamingBuffer.get();
117+
if (streaming->getAddressAllocator().max_alignment() < MaxPOTAlignment)
118+
{
119+
logger.log("Draw AABB Streaming Buffer cannot guarantee the alignments we require!");
120+
return false;
121+
}
122+
123+
auto* const streamingPtr = reinterpret_cast<uint8_t*>(streaming->getBufferPointer());
124+
assert(streamingPtr);
125+
126+
auto& commandBuffer = params.commandBuffer;
127+
commandBuffer->bindGraphicsPipeline(m_batchPipeline.get());
128+
commandBuffer->setLineWidth(params.lineWidth);
129+
asset::SBufferBinding<video::IGPUBuffer> indexBinding = { .offset = 0, .buffer = m_indicesBuffer };
130+
commandBuffer->bindIndexBuffer(indexBinding, asset::EIT_32BIT);
131+
132+
auto srcIt = aabbInstances.begin();
133+
auto setInstancesRange = [&](InstanceData* data, uint32_t count) -> void {
134+
for (uint32_t i = 0; i < count; i++)
135+
{
136+
auto inst = data + i;
137+
*inst = *srcIt;
138+
inst->transform = hlsl::mul(params.cameraMat, inst->transform);
139+
srcIt++;
140+
141+
if (srcIt == aabbInstances.end())
142+
break;
143+
}
144+
};
145+
146+
const uint32_t numInstances = aabbInstances.size();
147+
uint32_t remainingInstancesBytes = numInstances * sizeof(InstanceData);
148+
while (srcIt != aabbInstances.end())
149+
{
150+
uint32_t blockByteSize = core::alignUp(remainingInstancesBytes, MaxAlignment);
151+
bool allocated = false;
152+
153+
offset_t blockOffset = SCachedCreationParameters::streaming_buffer_t::invalid_value;
154+
const uint32_t smallestAlloc = hlsl::max<uint32_t>(core::alignUp(sizeof(InstanceData), MaxAlignment), streaming->getAddressAllocator().min_size());
155+
while (blockByteSize >= smallestAlloc)
156+
{
157+
std::chrono::steady_clock::time_point waitTill = std::chrono::steady_clock::now() + std::chrono::milliseconds(1u);
158+
if (streaming->multi_allocate(waitTill, 1, &blockOffset, &blockByteSize, &MaxAlignment) == 0u)
159+
{
160+
allocated = true;
161+
break;
162+
}
163+
164+
streaming->cull_frees();
165+
blockByteSize >>= 1;
166+
}
167+
168+
if (!allocated)
169+
{
170+
logger.log("Failed to allocate a chunk from streaming buffer for the next drawcall batch.", system::ILogger::ELL_ERROR);
171+
return false;
172+
}
173+
174+
const uint32_t instanceCount = blockByteSize / sizeof(InstanceData);
175+
auto* const streamingInstancesPtr = reinterpret_cast<InstanceData*>(streamingPtr + blockOffset);
176+
setInstancesRange(streamingInstancesPtr, instanceCount);
177+
178+
if (streaming->needsManualFlushOrInvalidate())
179+
{
180+
const video::ILogicalDevice::MappedMemoryRange flushRange(streaming->getBuffer()->getBoundMemory().memory, blockOffset, blockByteSize);
181+
m_cachedCreationParams.utilities->getLogicalDevice()->flushMappedMemoryRanges(1, &flushRange);
182+
}
183+
184+
remainingInstancesBytes -= instanceCount * sizeof(InstanceData);
185+
186+
SInstancedPC pc;
187+
pc.pInstanceBuffer = m_cachedCreationParams.streamingBuffer->getBuffer()->getDeviceAddress() + blockOffset;
188+
189+
commandBuffer->pushConstants(m_batchPipeline->getLayout(), asset::IShader::E_SHADER_STAGE::ESS_VERTEX, offsetof(ext::debug_draw::PushConstants, ipc), sizeof(SInstancedPC), &pc);
190+
commandBuffer->drawIndexed(IndicesCount, instanceCount, 0, 0, 0);
191+
192+
streaming->multi_deallocate(1, &blockOffset, &blockByteSize, waitInfo);
193+
}
194+
195+
return true;
196+
}
197+
198+
static inline hlsl::float32_t3x4 getTransformFromAABB(const hlsl::shapes::AABB<3, float>& aabb)
199+
{
200+
const auto diagonal = aabb.getExtent();
201+
hlsl::float32_t3x4 transform;
202+
transform[0][3] = aabb.minVx.x;
203+
transform[1][3] = aabb.minVx.y;
204+
transform[2][3] = aabb.minVx.z;
205+
transform[0][0] = diagonal.x;
206+
transform[1][1] = diagonal.y;
207+
transform[2][2] = diagonal.z;
208+
return transform;
209+
}
210+
211+
protected:
212+
struct ConstructorParams
213+
{
214+
SCachedCreationParameters creationParams;
215+
core::smart_refctd_ptr<video::IGPUGraphicsPipeline> singlePipeline = nullptr;
216+
core::smart_refctd_ptr<video::IGPUGraphicsPipeline> batchPipeline = nullptr;
217+
core::smart_refctd_ptr<video::IGPUBuffer> indicesBuffer = nullptr;
218+
};
219+
220+
DrawAABB(ConstructorParams&& params) :
221+
m_cachedCreationParams(std::move(params.creationParams)),
222+
m_singlePipeline(std::move(params.singlePipeline)),
223+
m_batchPipeline(std::move(params.batchPipeline)),
224+
m_indicesBuffer(std::move(params.indicesBuffer))
225+
{}
226+
~DrawAABB() override {}
227+
228+
private:
229+
static core::smart_refctd_ptr<video::IGPUGraphicsPipeline> createPipeline(SCreationParameters& params, const video::IGPUPipelineLayout* pipelineLayout, const DrawMode mode);
230+
static bool createStreamingBuffer(SCreationParameters& params);
231+
static core::smart_refctd_ptr<video::IGPUBuffer> createIndicesBuffer(SCreationParameters& params);
232+
233+
core::smart_refctd_ptr<video::IGPUBuffer> m_indicesBuffer;
234+
235+
SCachedCreationParameters m_cachedCreationParams;
236+
237+
core::smart_refctd_ptr<video::IGPUGraphicsPipeline> m_singlePipeline;
238+
core::smart_refctd_ptr<video::IGPUGraphicsPipeline> m_batchPipeline;
239+
};
240+
}
241+
242+
#endif
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#ifndef _NBL_DEBUG_DRAW_EXT_COMMON_HLSL
2+
#define _NBL_DEBUG_DRAW_EXT_COMMON_HLSL
3+
4+
#include "nbl/builtin/hlsl/cpp_compat.hlsl"
5+
#ifdef __HLSL_VERSION
6+
#include "nbl/builtin/hlsl/math/linalg/fast_affine.hlsl"
7+
#include "nbl/builtin/hlsl/glsl_compat/core.hlsl"
8+
#include "nbl/builtin/hlsl/bda/__ptr.hlsl"
9+
#endif
10+
11+
namespace nbl
12+
{
13+
namespace ext
14+
{
15+
namespace debug_draw
16+
{
17+
18+
struct InstanceData
19+
{
20+
hlsl::float32_t4x4 transform;
21+
hlsl::float32_t4 color;
22+
};
23+
24+
struct SSinglePC
25+
{
26+
InstanceData instance;
27+
};
28+
29+
struct SInstancedPC
30+
{
31+
uint64_t pInstanceBuffer;
32+
};
33+
34+
struct PushConstants
35+
{
36+
SSinglePC spc;
37+
SInstancedPC ipc;
38+
};
39+
40+
#ifdef __HLSL_VERSION
41+
struct PSInput
42+
{
43+
float32_t4 position : SV_Position;
44+
nointerpolation float32_t4 color : TEXCOORD0;
45+
};
46+
47+
float32_t3 getUnitAABBVertex()
48+
{
49+
return (hlsl::promote<uint32_t3>(hlsl::glsl::gl_VertexIndex()) >> uint32_t3(0,2,1)) & 0x1u;
50+
}
51+
#endif
52+
53+
}
54+
}
55+
}
56+
#endif
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "nbl/ext/DebugDraw/builtin/hlsl/common.hlsl"
2+
3+
using namespace nbl::hlsl;
4+
using namespace nbl::ext::debug_draw;
5+
6+
[[vk::push_constant]] PushConstants pc;
7+
8+
[shader("vertex")]
9+
PSInput aabb_vertex_single()
10+
{
11+
PSInput output;
12+
float32_t3 vertex = getUnitAABBVertex();
13+
14+
output.position = math::linalg::promoted_mul(pc.spc.instance.transform, vertex);
15+
output.color = pc.spc.instance.color;
16+
17+
return output;
18+
}
19+
20+
[shader("vertex")]
21+
PSInput aabb_vertex_instances()
22+
{
23+
PSInput output;
24+
const float32_t3 vertex = getUnitAABBVertex();
25+
InstanceData instance = vk::BufferPointer<InstanceData>(pc.ipc.pInstanceBuffer + sizeof(InstanceData) * glsl::gl_InstanceIndex()).Get();
26+
27+
output.position = math::linalg::promoted_mul(instance.transform, vertex);
28+
output.color = instance.color;
29+
30+
return output;
31+
}
32+
33+
[shader("pixel")]
34+
float32_t4 aabb_fragment(PSInput input) : SV_TARGET
35+
{
36+
float32_t4 outColor = input.color;
37+
38+
return outColor;
39+
}

include/nbl/system/ISystem.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class NBL_API2 ISystem : public core::IReferenceCounted
7070
//
7171
virtual inline bool isDirectory(const system::path& p) const
7272
{
73+
// 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)
7374
if (isPathReadOnly(p))
7475
return p.extension()==""; // TODO: this is a temporary decision until we figure out how to check if a file is directory in android APK
7576
else

src/nbl/ext/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ if(NBL_BUILD_TEXT_RENDERING)
5454
add_subdirectory(TextRendering)
5555
endif()
5656

57+
if(NBL_BUILD_DEBUG_DRAW)
58+
add_subdirectory(DebugDraw)
59+
set(NBL_EXT_DEBUG_DRAW_INCLUDE_DIRS
60+
${NBL_EXT_DEBUG_DRAW_INCLUDE_DIRS}
61+
PARENT_SCOPE
62+
)
63+
set(NBL_EXT_DEBUG_DRAW_LIB
64+
${NBL_EXT_DEBUG_DRAW_LIB}
65+
PARENT_SCOPE
66+
)
67+
endif()
68+
5769
propagate_changed_variables_to_parent_scope()
5870

5971
NBL_ADJUST_FOLDERS(ext)

0 commit comments

Comments
 (0)