feat: add line renderer / visualisations for box collidors as a test

This commit is contained in:
2026-08-14 03:16:04 +02:00
parent 7f65a151b5
commit 287e5c6885
12 changed files with 596 additions and 69 deletions
+2
View File
@@ -37,12 +37,14 @@ set(SRC_FILES
"src/Graphics/Managers/MemoryManager.cpp"
"src/Graphics/Managers/FrameManager.cpp"
"src/Graphics/Managers/ImageManager.cpp"
"src/Graphics/Managers/LineRenderingManager.cpp"
"src/Graphics/Resources/GPUImage.cpp"
"src/Graphics/Resources/NBuffer.cpp"
"src/Graphics/Resources/Cubemap.cpp"
"src/Graphics/Pipelines/MeshPipeline.cpp"
"src/Graphics/Pipelines/LineRenderingPass.cpp"
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
"src/Graphics/Pipelines/SkinningPipeline.cpp"
"src/Graphics/Pipelines/ImguiPass.cpp"
+9
View File
@@ -0,0 +1,9 @@
#version 460
layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = inColor;
}
+16
View File
@@ -0,0 +1,16 @@
#version 460
layout (location = 0) in vec4 inPosition;
layout (location = 1) in vec4 inColor;
layout (location = 0) out vec4 outColor;
layout (push_constant) uniform LinePushConstants {
mat4 viewProjection;
} pcs;
void main()
{
gl_Position = pcs.viewProjection * inPosition;
outColor = inColor;
}
@@ -0,0 +1,37 @@
#ifndef DESTRUM_LINERENDERINGMANAGER_H
#define DESTRUM_LINERENDERINGMANAGER_H
#include <vector>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <destrum/Singleton.h>
struct Line {
glm::vec3 start{};
glm::vec3 end{};
glm::vec4 color{1.0f};
};
class LineRenderingManager final : public Singleton<LineRenderingManager> {
public:
friend class Singleton<LineRenderingManager>;
void SubmitLine(const Line& line);
void SubmitLine(
const glm::vec3& start,
const glm::vec3& end,
const glm::vec4& color = glm::vec4{1.0f});
[[nodiscard]] const std::vector<Line>& GetLines() const { return lines; }
void ClearLines();
private:
LineRenderingManager() = default;
std::vector<Line> lines;
};
#endif // DESTRUM_LINERENDERINGMANAGER_H
@@ -0,0 +1,42 @@
#ifndef DESTRUM_LINERENDERINGPASS_H
#define DESTRUM_LINERENDERINGPASS_H
#include <cstddef>
#include <memory>
#include <vulkan/vulkan.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Resources/NBuffer.h>
class Camera;
class GfxDevice;
struct GPUImage;
class Pipeline;
class LineRenderingPass final {
public:
void init(GfxDevice& gfxDevice, VkFormat drawImageFormat);
void draw(
VkCommandBuffer cmd,
GfxDevice& gfxDevice,
const GPUImage& target,
const Camera& camera,
LineRenderingManager& lineManager);
void cleanup(GfxDevice& gfxDevice);
private:
struct LineVertex {
glm::vec4 position;
glm::vec4 color;
};
void ensureVertexCapacity(GfxDevice& gfxDevice, std::size_t requiredVertices);
VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
std::unique_ptr<Pipeline> pipeline;
NBuffer vertexBuffer;
std::size_t vertexCapacity{0};
};
#endif // DESTRUM_LINERENDERINGPASS_H
@@ -4,6 +4,7 @@
#include <glm/vec3.hpp>
#include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/MeshDrawCommand.h>
@@ -111,6 +112,7 @@ private:
std::unique_ptr<MeshPipeline> meshPipeline;
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
std::unique_ptr<LineRenderingPass> lineRenderingPass;
std::unique_ptr<SkinningPipeline> skinningPipeline;
bool initialized{false};
@@ -0,0 +1,23 @@
#include <destrum/Graphics/Managers/LineRenderingManager.h>
void LineRenderingManager::SubmitLine(const Line& line)
{
lines.push_back(line);
}
void LineRenderingManager::SubmitLine(
const glm::vec3& start,
const glm::vec3& end,
const glm::vec4& color)
{
SubmitLine(Line{
.start = start,
.end = end,
.color = color,
});
}
void LineRenderingManager::ClearLines()
{
lines.clear();
}
@@ -0,0 +1,232 @@
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <destrum/FS/AssetFS.h>
#include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/GPUImage.h>
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/Util.h>
#include "volk.h"
namespace {
constexpr std::size_t InitialVertexCapacity = 256;
}
void LineRenderingPass::init(GfxDevice& gfxDevice, VkFormat drawImageFormat)
{
try {
vertexBuffer.init(
gfxDevice,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
InitialVertexCapacity * sizeof(LineVertex),
"line vertices");
vertexCapacity = InitialVertexCapacity;
const auto vertexShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/line.vert");
const auto fragmentShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/line.frag");
const auto pushConstantRange = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
.offset = 0,
.size = sizeof(glm::mat4),
};
const auto pipelineLayoutInfo = VkPipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &pushConstantRange,
};
VK_CHECK(vkCreatePipelineLayout(
gfxDevice.getDevice(),
&pipelineLayoutInfo,
nullptr,
&pipelineLayout));
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.name = "Line Rendering Pipeline";
pipelineConfig.pipelineLayout = pipelineLayout;
pipelineConfig.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
pipelineConfig.depthStencilInfo.depthTestEnable = VK_FALSE;
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_ALWAYS;
pipelineConfig.colorAttachments = {drawImageFormat};
pipelineConfig.depthAttachment = VK_FORMAT_UNDEFINED;
pipelineConfig.vertexBindingDescriptions = {
VkVertexInputBindingDescription{
.binding = 0,
.stride = sizeof(LineVertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
}
};
pipelineConfig.vertexAttributeDescriptions = {
VkVertexInputAttributeDescription{
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
.offset = offsetof(LineVertex, position),
},
VkVertexInputAttributeDescription{
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
.offset = offsetof(LineVertex, color),
},
};
pipelineConfig.colorBlendAttachment.blendEnable = VK_TRUE;
pipelineConfig.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
pipelineConfig.colorBlendAttachment.dstColorBlendFactor =
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
pipelineConfig.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
pipelineConfig.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
pipelineConfig.colorBlendAttachment.dstAlphaBlendFactor =
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
pipelineConfig.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
pipeline = std::make_unique<Pipeline>(
gfxDevice,
vertexShader.string(),
fragmentShader.string(),
pipelineConfig);
} catch (...) {
cleanup(gfxDevice);
throw;
}
}
void LineRenderingPass::draw(
VkCommandBuffer cmd,
GfxDevice& gfxDevice,
const GPUImage& target,
const Camera& camera,
LineRenderingManager& lineManager)
{
const auto& lines = lineManager.GetLines();
if (!pipeline || lines.empty()) {
lineManager.ClearLines();
return;
}
if (lines.size() > std::numeric_limits<std::size_t>::max() / 2) {
throw std::overflow_error("Too many lines submitted for rendering");
}
std::vector<LineVertex> vertices;
vertices.reserve(lines.size() * 2);
for (const Line& line : lines) {
vertices.push_back(LineVertex{
.position = glm::vec4{line.start, 1.0f},
.color = line.color,
});
vertices.push_back(LineVertex{
.position = glm::vec4{line.end, 1.0f},
.color = line.color,
});
}
if (vertices.size() > std::numeric_limits<std::uint32_t>::max()) {
throw std::overflow_error("Too many line vertices submitted for rendering");
}
ensureVertexCapacity(gfxDevice, vertices.size());
vertexBuffer.uploadNewData(
cmd,
gfxDevice.getCurrentFrameIndex(),
vertices.data(),
vertices.size() * sizeof(LineVertex));
auto renderInfo = vkutil::createRenderingInfo({
.renderExtent = target.getExtent2D(),
.colorImageView = target.imageView,
});
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
pipeline->bind(cmd);
const auto viewport = VkViewport{
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(target.extent.width),
.height = static_cast<float>(target.extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
vkCmdSetViewport(cmd, 0, 1, &viewport);
const auto scissor = VkRect2D{
.offset = {},
.extent = target.getExtent2D(),
};
vkCmdSetScissor(cmd, 0, 1, &scissor);
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
const glm::mat4 viewProjection = camera.GetViewProjectionMatrix();
vkCmdPushConstants(
cmd,
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT,
0,
sizeof(viewProjection),
&viewProjection);
const VkBuffer vertexBufferHandle = vertexBuffer.getBuffer().buffer;
const VkDeviceSize vertexBufferOffset = 0;
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBufferHandle, &vertexBufferOffset);
vkCmdDraw(cmd, static_cast<std::uint32_t>(vertices.size()), 1, 0, 0);
vkCmdEndRendering(cmd);
lineManager.ClearLines();
}
void LineRenderingPass::cleanup(GfxDevice& gfxDevice)
{
pipeline.reset();
if (pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(gfxDevice.getDevice(), pipelineLayout, nullptr);
}
pipelineLayout = VK_NULL_HANDLE;
vertexBuffer.cleanup(gfxDevice);
vertexCapacity = 0;
}
void LineRenderingPass::ensureVertexCapacity(
GfxDevice& gfxDevice,
std::size_t requiredVertices)
{
if (requiredVertices <= vertexCapacity) {
return;
}
std::size_t newCapacity = std::max(vertexCapacity, InitialVertexCapacity);
while (newCapacity < requiredVertices) {
if (newCapacity > std::numeric_limits<std::size_t>::max() / 2) {
newCapacity = requiredVertices;
break;
}
newCapacity *= 2;
}
gfxDevice.waitIdle();
vertexBuffer.cleanup(gfxDevice);
vertexBuffer.init(
gfxDevice,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
newCapacity * sizeof(LineVertex),
"line vertices");
vertexCapacity = newCapacity;
}
+19
View File
@@ -1,5 +1,6 @@
#include <destrum/Graphics/Renderer.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Util.h>
#include <algorithm>
@@ -36,6 +37,9 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
skinningPipeline = std::make_unique<SkinningPipeline>();
skinningPipeline->init(gfxDevice);
lineRenderingPass = std::make_unique<LineRenderingPass>();
lineRenderingPass->init(gfxDevice, drawImageFormat);
GameState::GetInstance().SetRenderer(this);
initialized = true;
} catch (...) {
@@ -194,6 +198,17 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
vkCmdEndRendering(cmd);
}
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "LineRenderingPass::draw");
lineRenderingPass->draw(
cmd,
gfxDevice,
drawImage,
camera,
LineRenderingManager::GetInstance());
}
// vkutil::cmdEndLabel(cmd);
}
@@ -208,6 +223,9 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
if (skinningPipeline)
skinningPipeline->cleanup(gfxDevice);
if (lineRenderingPass)
lineRenderingPass->cleanup(gfxDevice);
if (skyboxPipeline)
skyboxPipeline->cleanup(device);
@@ -228,6 +246,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
meshPipeline.reset();
skyboxPipeline.reset();
skinningPipeline.reset();
lineRenderingPass.reset();
pendingMaterialUploads.clear();
meshDrawCommands.clear();
sortedMeshDrawCommands.clear();