diff --git a/examples/RenderTest/main.cpp b/examples/RenderTest/main.cpp index ebbe6f01d..8b8fc0ace 100644 --- a/examples/RenderTest/main.cpp +++ b/examples/RenderTest/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -249,6 +250,8 @@ int main() Nz::Mouse::SetRelativeMouseMode(true); + Nz::DebugDrawer debugDrawer(*renderDevice); + while (window.IsOpen()) { Nz::WindowEvent event; @@ -329,6 +332,14 @@ int main() continue; } + debugDrawer.Reset(frame); + debugDrawer.SetViewerData(ubo.viewMatrix * ubo.projectionMatrix); + + Nz::Boxf aabb = spaceship->GetAABB(); + aabb.Transform(ubo.modelMatrix); + + debugDrawer.DrawBox(aabb, Nz::Color::Green); + ubo.viewMatrix = Nz::Matrix4f::TransformInverse(viewerPos, camAngles); if (uboUpdate) @@ -351,6 +362,8 @@ int main() uboUpdate = false; } + debugDrawer.Prepare(frame); + const Nz::RenderTarget* windowRT = window.GetRenderTarget(); frame.Execute([&](Nz::CommandBufferBuilder& builder) { @@ -375,6 +388,8 @@ int main() builder.SetViewport(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); builder.DrawIndexed(meshIB->GetIndexCount()); + + debugDrawer.Draw(builder); } builder.EndRenderPass(); } diff --git a/include/Nazara/Renderer/DebugDrawer.hpp b/include/Nazara/Renderer/DebugDrawer.hpp index 89c48935f..1df7802d2 100644 --- a/include/Nazara/Renderer/DebugDrawer.hpp +++ b/include/Nazara/Renderer/DebugDrawer.hpp @@ -9,53 +9,91 @@ #include #include -#include #include -#include -#include +#include +#include #include +#include +#include +#include +#include namespace Nz { + class CommandBufferBuilder; + class RenderBuffer; + class RenderFrame; + class RenderDevice; + class RenderPipeline; + class RenderPipelineLayout; + class ShaderBinding; class Skeleton; - class StaticMesh; class NAZARA_RENDERER_API DebugDrawer { public: - static void Draw(const BoundingVolumef& volume); - static void Draw(const Boxf& box); - static void Draw(const Boxi& box); - static void Draw(const Boxui& box); - static void Draw(const Frustumf& frustum); - static void Draw(const OrientedBoxf& orientedBox); - static void Draw(const Skeleton* skeleton); - static void Draw(const Vector3f& position, float size = 0.1f); - static void DrawAxes(const Vector3f& position = Vector3f::Zero(), float size = 1.f); - static void DrawBinormals(const StaticMesh* subMesh); - static void DrawCone(const Vector3f& origin, const Quaternionf& rotation, float angle, float length); - static void DrawLine(const Vector3f& p1, const Vector3f& p2); - static void DrawPoints(const Vector3f* ptr, unsigned int pointCount); - static void DrawNormals(const StaticMesh* subMesh, float normalLength = 0.01f); - static void DrawTangents(const StaticMesh* subMesh); + DebugDrawer(RenderDevice& renderDevice, std::size_t maxVertexPerDraw = DefaultVertexBlockSize); + DebugDrawer(const DebugDrawer&) = delete; + DebugDrawer(DebugDrawer&&) = delete; + ~DebugDrawer(); - static void EnableDepthBuffer(bool depthBuffer); + void Draw(CommandBufferBuilder& builder); - static float GetLineWidth(); - static float GetPointSize(); - static Color GetPrimaryColor(); - static Color GetSecondaryColor(); + inline void DrawBox(const Boxf& box, const Color& color); + inline void DrawLine(const Vector3f& start, const Vector3f& end, const Color& color); + inline void DrawLine(const Vector3f& start, const Vector3f& end, const Color& startColor, const Color& endColor); + void DrawSkeleton(const Skeleton& skeleton, const Color& color); - static bool Initialize(); - static bool IsDepthBufferEnabled(); + void Prepare(RenderFrame& renderFrame); - static void SetLineWidth(float width); - static void SetPointSize(float size); - static void SetPrimaryColor(const Color& color); - static void SetSecondaryColor(const Color& color); + void Reset(RenderFrame& renderFrame); - static void Uninitialize(); + void SetViewerData(const Matrix4f& viewProjMatrix); + + DebugDrawer& operator=(const DebugDrawer&) = delete; + DebugDrawer& operator=(DebugDrawer&&) = delete; + + static constexpr std::size_t DefaultVertexBlockSize = 4096; + + private: + struct ViewerData + { + std::shared_ptr buffer; + std::shared_ptr binding; + }; + + struct DataPool + { + std::vector> vertexBuffers; + std::vector viewerData; + }; + + struct DrawCall + { + std::shared_ptr vertexBuffer; + std::uint64_t vertexCount; + }; + + struct PendingUpload + { + UploadPool::Allocation* allocation; + RenderBuffer* vertexBuffer; + }; + + std::shared_ptr m_dataPool; + std::shared_ptr m_renderPipeline; + std::shared_ptr m_renderPipelineLayout; + std::size_t m_vertexPerBlock; + std::vector m_drawCalls; + std::vector m_pendingUploads; + std::vector m_viewerData; + std::vector m_lineVertices; + RenderDevice& m_renderDevice; + ViewerData m_currentViewerData; + bool m_viewerDataUpdated; }; } +#include + #endif // NAZARA_RENDERER_DEBUGDRAWER_HPP diff --git a/include/Nazara/Renderer/DebugDrawer.inl b/include/Nazara/Renderer/DebugDrawer.inl new file mode 100644 index 000000000..ba6dc1e7d --- /dev/null +++ b/include/Nazara/Renderer/DebugDrawer.inl @@ -0,0 +1,46 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Renderer module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + inline void DebugDrawer::DrawBox(const Boxf& box, const Color& color) + { + Vector3f max = box.GetMaximum(); + Vector3f min = box.GetMinimum(); + + DrawLine({ min.x, min.y, min.z }, { max.x, min.y, min.z }, color); + DrawLine({ min.x, min.y, min.z }, { min.x, max.y, min.z }, color); + DrawLine({ min.x, min.y, min.z }, { min.x, min.y, max.z }, color); + DrawLine({ max.x, max.y, max.z }, { min.x, max.y, max.z }, color); + DrawLine({ max.x, max.y, max.z }, { max.x, min.y, max.z }, color); + DrawLine({ max.x, max.y, max.z }, { max.x, max.y, min.z }, color); + DrawLine({ min.x, min.y, max.z }, { max.x, min.y, max.z }, color); + DrawLine({ min.x, min.y, max.z }, { min.x, max.y, max.z }, color); + DrawLine({ min.x, max.y, min.z }, { max.x, max.y, min.z }, color); + DrawLine({ min.x, max.y, min.z }, { min.x, max.y, max.z }, color); + DrawLine({ max.x, min.y, min.z }, { max.x, max.y, min.z }, color); + DrawLine({ max.x, min.y, min.z }, { max.x, min.y, max.z }, color); + } + + inline void DebugDrawer::DrawLine(const Vector3f& start, const Vector3f& end, const Color& color) + { + return DrawLine(start, end, color, color); + } + + inline void DebugDrawer::DrawLine(const Vector3f& start, const Vector3f& end, const Color& startColor, const Color& endColor) + { + auto& startVertex = m_lineVertices.emplace_back(); + startVertex.color = startColor; + startVertex.position = start; + + auto& endVertex = m_lineVertices.emplace_back(); + endVertex.color = endColor; + endVertex.position = end; + } +} + +#include diff --git a/src/Nazara/Renderer/DebugDrawer.cpp b/src/Nazara/Renderer/DebugDrawer.cpp new file mode 100644 index 000000000..81cb8c47a --- /dev/null +++ b/src/Nazara/Renderer/DebugDrawer.cpp @@ -0,0 +1,234 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Renderer module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace + { + const UInt8 r_debugDrawShader[] = { + #include + }; + } + + DebugDrawer::DebugDrawer(RenderDevice& renderDevice, std::size_t maxVertexPerDraw) : + m_vertexPerBlock(maxVertexPerDraw), + m_renderDevice(renderDevice), + m_viewerDataUpdated(false) + { + nzsl::Unserializer unserializer(r_debugDrawShader, sizeof(r_debugDrawShader)); + nzsl::Ast::ModulePtr shaderModule = nzsl::Ast::UnserializeShader(unserializer); + + auto debugDrawShader = m_renderDevice.InstantiateShaderModule(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, *shaderModule, {}); + if (!debugDrawShader) + throw std::runtime_error("failed to instantiate debug draw shader"); + + RenderPipelineLayoutInfo layoutInfo; + layoutInfo.bindings.assign({ + { + 0, 0, + ShaderBindingType::UniformBuffer, + nzsl::ShaderStageType::Vertex + } + }); + + m_renderPipelineLayout = m_renderDevice.InstantiateRenderPipelineLayout(std::move(layoutInfo)); + if (!m_renderPipelineLayout) + throw std::runtime_error("failed to instantiate render pipeline layout"); + + RenderPipelineInfo pipelineInfo; + pipelineInfo.pipelineLayout = m_renderPipelineLayout; + pipelineInfo.shaderModules.push_back(std::move(debugDrawShader)); + pipelineInfo.depthBuffer = true; + pipelineInfo.depthWrite = false; + + pipelineInfo.blending = true; + pipelineInfo.blend.srcColor = BlendFunc::SrcAlpha; + pipelineInfo.blend.dstColor = BlendFunc::InvSrcAlpha; + + pipelineInfo.primitiveMode = PrimitiveMode::LineList; + pipelineInfo.vertexBuffers.push_back({ + 0, + VertexDeclaration::Get(Nz::VertexLayout::XYZ_Color) + }); + + m_renderPipeline = m_renderDevice.InstantiateRenderPipeline(pipelineInfo); + + m_dataPool = std::make_shared(); + } + + DebugDrawer::~DebugDrawer() + { + m_drawCalls.clear(); + m_dataPool.reset(); + m_currentViewerData = {}; + } + + void DebugDrawer::Draw(CommandBufferBuilder& builder) + { + if (m_drawCalls.empty()) + return; + + builder.BindShaderBinding(0, *m_currentViewerData.binding); + builder.BindPipeline(*m_renderPipeline); + + for (auto& drawCall : m_drawCalls) + { + builder.BindVertexBuffer(0, *drawCall.vertexBuffer); + builder.Draw(drawCall.vertexCount); + } + } + + void DebugDrawer::DrawSkeleton(const Skeleton& skeleton, const Color& color) + { + std::size_t jointCount = skeleton.GetJointCount(); + for (std::size_t i = 0; i < jointCount; ++i) + { + const Joint* joint = skeleton.GetJoint(i); + const Node* parent = joint->GetParent(); + if (parent) + DrawLine(joint->GetPosition(CoordSys::Global), parent->GetPosition(CoordSys::Global), color); + } + } + + void DebugDrawer::Prepare(RenderFrame& renderFrame) + { + UploadPool& uploadPool = renderFrame.GetUploadPool(); + + if (!m_lineVertices.empty()) + { + std::size_t vertexCount = m_lineVertices.size(); + m_drawCalls.clear(); + m_drawCalls.reserve(vertexCount / m_vertexPerBlock + 1); + + m_pendingUploads.clear(); + m_pendingUploads.reserve(vertexCount / m_vertexPerBlock + 1); + + // Handle vertex buffers + const VertexStruct_XYZ_Color* lineVertices = m_lineVertices.data(); + while (vertexCount > 0) + { + auto& drawCall = m_drawCalls.emplace_back(); + + // Try to reuse vertex buffers from pool if any + if (!m_dataPool->vertexBuffers.empty()) + { + drawCall.vertexBuffer = std::move(m_dataPool->vertexBuffers.back()); + m_dataPool->vertexBuffers.pop_back(); + } + else + drawCall.vertexBuffer = m_renderDevice.InstantiateBuffer(BufferType::Vertex, m_vertexPerBlock * sizeof(VertexStruct_XYZ_Color), BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write); + + drawCall.vertexCount = std::min(vertexCount, m_vertexPerBlock); + + auto& pendingUpload = m_pendingUploads.emplace_back(); + pendingUpload.allocation = &uploadPool.Allocate(drawCall.vertexCount * sizeof(VertexStruct_XYZ_Color)); + pendingUpload.vertexBuffer = drawCall.vertexBuffer.get(); + std::memcpy(pendingUpload.allocation->mappedPtr, lineVertices, drawCall.vertexCount * sizeof(VertexStruct_XYZ_Color)); + + lineVertices += drawCall.vertexCount; + vertexCount -= drawCall.vertexCount; + } + m_lineVertices.clear(); + } + + // Handle viewer data + if (m_viewerDataUpdated) + { + if (!m_dataPool->viewerData.empty()) + { + m_currentViewerData = std::move(m_dataPool->viewerData.back()); + m_dataPool->viewerData.pop_back(); + } + else + { + m_currentViewerData.buffer = m_renderDevice.InstantiateBuffer(BufferType::Uniform, m_viewerData.size(), BufferUsage::DeviceLocal | BufferUsage::Dynamic | BufferUsage::Write); + m_currentViewerData.binding = m_renderPipelineLayout->AllocateShaderBinding(0); + m_currentViewerData.binding->Update({ + { + 0, + Nz::ShaderBinding::UniformBufferBinding { + m_currentViewerData.buffer.get(), + 0, m_currentViewerData.buffer->GetSize() + } + } + }); + } + } + + if (m_viewerDataUpdated || !m_pendingUploads.empty()) + { + renderFrame.Execute([&](CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("Debug drawer upload", Color::Yellow); + { + if (m_viewerDataUpdated) + { + const UploadPool::Allocation& viewerDataAllocation = uploadPool.Allocate(m_viewerData.size()); + std::memcpy(viewerDataAllocation.mappedPtr, m_viewerData.data(), m_viewerData.size()); + + builder.CopyBuffer(viewerDataAllocation, m_currentViewerData.buffer.get()); + } + + for (auto& pendingUpload : m_pendingUploads) + builder.CopyBuffer(*pendingUpload.allocation, pendingUpload.vertexBuffer); + m_pendingUploads.clear(); + + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + }, QueueType::Graphics); + } + + m_viewerDataUpdated = false; + } + + void DebugDrawer::Reset(RenderFrame& renderFrame) + { + if (m_currentViewerData.binding) + { + // keep pipeline layout alive as needs to stay alive until all shader bindings have been freed + renderFrame.PushReleaseCallback([pool = m_dataPool, data = std::move(m_currentViewerData), pipelineLayout = m_renderPipelineLayout]() mutable + { + pool->viewerData.push_back(std::move(data)); + }); + } + m_currentViewerData.binding = {}; + + for (auto& drawCall : m_drawCalls) + { + renderFrame.PushReleaseCallback([pool = m_dataPool, buffer = std::move(drawCall.vertexBuffer)]() mutable + { + pool->vertexBuffers.push_back(std::move(buffer)); + }); + } + m_drawCalls.clear(); + + m_lineVertices.clear(); + } + + void DebugDrawer::SetViewerData(const Matrix4f& viewProjMatrix) + { + // Setup viewer data buffer for current frame + nzsl::FieldOffsets viewerDataFields(nzsl::StructLayout::Std140); + std::size_t viewProjOffset = viewerDataFields.AddMatrix(nzsl::StructFieldType::Float1, 4, 4, true); + + m_viewerData.resize(viewerDataFields.GetSize()); + AccessByOffset(m_viewerData.data(), viewProjOffset) = viewProjMatrix; + + m_viewerDataUpdated = true; + } +} diff --git a/src/Nazara/Renderer/Resources/Shaders/DebugDraw.nzsl b/src/Nazara/Renderer/Resources/Shaders/DebugDraw.nzsl new file mode 100644 index 000000000..d566100a0 --- /dev/null +++ b/src/Nazara/Renderer/Resources/Shaders/DebugDraw.nzsl @@ -0,0 +1,59 @@ +[nzsl_version("1.0")] +module DebugDraw; + +[export] +[layout(std140)] +struct ViewerData +{ + viewProjMatrix: mat4[f32] +} + +external +{ + [binding(0)] viewerData: uniform[ViewerData], +} + +// Fragment stage +struct FragIn +{ + [location(0)] color: vec4[f32] +} + +struct FragOut +{ + [location(0)] color: vec4[f32] +} + +[entry(frag)] +fn main(input: FragIn) -> FragOut +{ + let output: FragOut; + output.color = input.color; + return output; +} + +// Vertex stage +struct VertIn +{ + [location(0)] + pos: vec3[f32], + + [location(1)] + color: vec4[f32] +} + +struct VertOut +{ + [location(0)] color: vec4[f32], + [builtin(position)] position: vec4[f32] +} + +[entry(vert)] +fn main(input: VertIn) -> VertOut +{ + let output: VertOut; + output.position = viewerData.viewProjMatrix * vec4[f32](input.pos, 1.0); + output.color = input.color; + + return output; +}