feat: add 2D rendering pipeline

This commit is contained in:
2026-09-03 00:58:46 +02:00
parent d515a36403
commit c9fb6e991a
11 changed files with 557 additions and 3 deletions
@@ -0,0 +1,31 @@
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
void QuadRenderingManager::SubmitQuad(
const glm::vec2& position,
const glm::vec2& size,
const glm::vec4& color,
ImageID textureId)
{
SubmitQuad(position, size, 0.0f, color, textureId);
}
void QuadRenderingManager::SubmitQuad(
const glm::vec2& position,
const glm::vec2& size,
float rotation,
const glm::vec4& color,
ImageID textureId)
{
quads.push_back(QuadSubmission{
.position = position,
.size = size,
.rotation = rotation,
.color = color,
.textureId = textureId,
});
}
void QuadRenderingManager::ClearQuads()
{
quads.clear();
}
@@ -0,0 +1,334 @@
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <destrum/FS/AssetFS.h>
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/GPUImage.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/RenderResources.h>
#include <destrum/Graphics/Util.h>
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
#include "volk.h"
namespace {
constexpr std::size_t InitialQuadCapacity = 256;
constexpr std::size_t VerticesPerQuad = 4;
constexpr std::size_t IndicesPerQuad = 6;
glm::vec2 rotatePoint(const glm::vec2& point, float cosA, float sinA) {
return glm::vec2{
point.x * cosA - point.y * sinA,
point.x * sinA + point.y * cosA,
};
}
}
void QuadRendererPass::init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat)
{
try {
const std::size_t vertexBytes = InitialQuadCapacity * VerticesPerQuad * sizeof(QuadVertex);
vertexBuffer = gfxDevice.createBuffer(
vertexBytes,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
const std::size_t indexBytes = InitialQuadCapacity * IndicesPerQuad * sizeof(std::uint32_t);
indexBuffer = gfxDevice.createBuffer(
indexBytes,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
quadCapacity = InitialQuadCapacity;
const auto vertexShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/quad.vert");
const auto fragmentShader =
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/quad.frag");
const auto pushConstantRange = VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
.offset = 0,
.size = sizeof(glm::mat4),
};
const auto layouts = std::array{
resources.getBindlessDescSetLayout()
};
const auto pipelineLayoutInfo = VkPipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = static_cast<std::uint32_t>(layouts.size()),
.pSetLayouts = layouts.data(),
.pushConstantRangeCount = 1,
.pPushConstantRanges = &pushConstantRange,
};
VK_CHECK(vkCreatePipelineLayout(
gfxDevice.getDevice(),
&pipelineLayoutInfo,
nullptr,
&pipelineLayout));
PipelineConfigInfo pipelineConfig{};
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.name = "Quad Rendering Pipeline";
pipelineConfig.pipelineLayout = pipelineLayout;
pipelineConfig.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_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(QuadVertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
}
};
pipelineConfig.vertexAttributeDescriptions = {
VkVertexInputAttributeDescription{
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32_SFLOAT,
.offset = offsetof(QuadVertex, position),
},
VkVertexInputAttributeDescription{
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32_SFLOAT,
.offset = offsetof(QuadVertex, uv),
},
VkVertexInputAttributeDescription{
.location = 2,
.binding = 0,
.format = VK_FORMAT_R32G32B32A32_SFLOAT,
.offset = offsetof(QuadVertex, color),
},
VkVertexInputAttributeDescription{
.location = 3,
.binding = 0,
.format = VK_FORMAT_R32_UINT,
.offset = offsetof(QuadVertex, textureId),
},
};
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 QuadRendererPass::draw(
VkCommandBuffer cmd,
GfxDevice& gfxDevice,
RenderResources& resources,
const GPUImage& target,
const glm::mat4& projection)
{
auto& quadManager = QuadRenderingManager::GetInstance();
const auto& quads = quadManager.GetQuads();
if (!pipeline || quads.empty()) {
quadManager.ClearQuads();
return;
}
const std::size_t numQuads = quads.size();
if (numQuads > std::numeric_limits<std::size_t>::max() / VerticesPerQuad) {
throw std::overflow_error("Too many quads submitted for rendering");
}
const std::size_t numVertices = numQuads * VerticesPerQuad;
const std::size_t numIndices = numQuads * IndicesPerQuad;
if (numVertices > std::numeric_limits<std::uint32_t>::max()) {
throw std::overflow_error("Too many quad vertices submitted for rendering");
}
ensureCapacity(gfxDevice, numQuads);
const ImageID whiteTextureId = resources.getWhiteTextureID();
auto* vertexMapped = reinterpret_cast<std::uint8_t*>(vertexBuffer.info.pMappedData);
auto* indexMapped = reinterpret_cast<std::uint8_t*>(indexBuffer.info.pMappedData);
constexpr std::array<glm::vec2, 4> localCorners = {{
glm::vec2{-0.5f, -0.5f},
glm::vec2{ 0.5f, -0.5f},
glm::vec2{ 0.5f, 0.5f},
glm::vec2{-0.5f, 0.5f},
}};
constexpr std::array<glm::vec2, 4> uvs = {{
glm::vec2{0.0f, 1.0f},
glm::vec2{1.0f, 1.0f},
glm::vec2{1.0f, 0.0f},
glm::vec2{0.0f, 0.0f},
}};
auto* vertWrite = reinterpret_cast<QuadVertex*>(vertexMapped);
auto* idxWrite = reinterpret_cast<std::uint32_t*>(indexMapped);
std::uint32_t vertexBase = 0;
for (const auto& quad : quads) {
const float cosA = std::cos(quad.rotation);
const float sinA = std::sin(quad.rotation);
const glm::vec2 halfSize = quad.size * 0.5f;
const ImageID texId = (quad.textureId == NULL_IMAGE_ID) ? whiteTextureId : quad.textureId;
for (std::size_t v = 0; v < VerticesPerQuad; ++v) {
glm::vec2 localPos = localCorners[v] * halfSize;
glm::vec2 rotatedPos = rotatePoint(localPos, cosA, sinA);
glm::vec2 worldPos = quad.position + rotatedPos;
vertWrite[vertexBase + v] = QuadVertex{
.position = worldPos,
.uv = uvs[v],
.color = quad.color,
.textureId = static_cast<std::uint32_t>(texId),
};
}
const std::uint32_t idxOffset = vertexBase / 4 * 6;
idxWrite[idxOffset + 0] = vertexBase + 0;
idxWrite[idxOffset + 1] = vertexBase + 1;
idxWrite[idxOffset + 2] = vertexBase + 2;
idxWrite[idxOffset + 3] = vertexBase + 0;
idxWrite[idxOffset + 4] = vertexBase + 2;
idxWrite[idxOffset + 5] = vertexBase + 3;
vertexBase += VerticesPerQuad;
}
auto& memMgr = gfxDevice.getMemoryManager();
memMgr.flushAllocation(vertexBuffer, 0, numVertices * sizeof(QuadVertex));
memMgr.flushAllocation(indexBuffer, 0, numIndices * sizeof(std::uint32_t));
auto renderInfo = vkutil::createRenderingInfo({
.renderExtent = target.getExtent2D(),
.colorImageView = target.imageView,
});
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
pipeline->bind(cmd);
resources.bindBindlessDescSet(cmd, pipelineLayout);
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);
vkCmdPushConstants(
cmd,
pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT,
0,
sizeof(glm::mat4),
&projection);
const VkBuffer vertexBufferHandle = vertexBuffer.buffer;
const VkDeviceSize vertexBufferOffset = 0;
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBufferHandle, &vertexBufferOffset);
const VkBuffer indexBufferHandle = indexBuffer.buffer;
vkCmdBindIndexBuffer(cmd, indexBufferHandle, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(cmd, static_cast<std::uint32_t>(numIndices), 1, 0, 0, 0);
vkCmdEndRendering(cmd);
quadManager.ClearQuads();
}
void QuadRendererPass::cleanup(GfxDevice& gfxDevice)
{
pipeline.reset();
if (pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(gfxDevice.getDevice(), pipelineLayout, nullptr);
}
pipelineLayout = VK_NULL_HANDLE;
if (vertexBuffer.buffer != VK_NULL_HANDLE) {
gfxDevice.destroyBuffer(vertexBuffer);
}
if (indexBuffer.buffer != VK_NULL_HANDLE) {
gfxDevice.destroyBuffer(indexBuffer);
}
quadCapacity = 0;
}
void QuadRendererPass::ensureCapacity(
GfxDevice& gfxDevice,
std::size_t requiredQuads)
{
if (requiredQuads <= quadCapacity) {
return;
}
std::size_t newCapacity = std::max(quadCapacity, InitialQuadCapacity);
while (newCapacity < requiredQuads) {
if (newCapacity > std::numeric_limits<std::size_t>::max() / 2) {
newCapacity = requiredQuads;
break;
}
newCapacity *= 2;
}
gfxDevice.waitIdle();
if (vertexBuffer.buffer != VK_NULL_HANDLE) {
gfxDevice.destroyBuffer(vertexBuffer);
}
vertexBuffer = gfxDevice.createBuffer(
newCapacity * VerticesPerQuad * sizeof(QuadVertex),
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
if (indexBuffer.buffer != VK_NULL_HANDLE) {
gfxDevice.destroyBuffer(indexBuffer);
}
indexBuffer = gfxDevice.createBuffer(
newCapacity * IndicesPerQuad * sizeof(std::uint32_t),
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
quadCapacity = newCapacity;
}
+37 -3
View File
@@ -1,11 +1,14 @@
#include <destrum/Graphics/Renderer.h>
#include <destrum/Graphics/Managers/LineRenderingManager.h>
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
#include <destrum/Graphics/Util.h>
#include <algorithm>
#include <numeric>
#include <glm/gtc/matrix_transform.hpp>
#include "volk.h"
#include "destrum/Util/GameState.h"
#include "spdlog/spdlog.h"
@@ -31,7 +34,7 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
meshPipeline = std::make_unique<MeshPipeline>();
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
skyboxPipeline = std::make_unique<SkyboxPipeline>();
skyboxPipeline = std::make_unique<SkyboxPipeline>();
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
skinningPipeline = std::make_unique<SkinningPipeline>();
@@ -40,6 +43,9 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
lineRenderingPass = std::make_unique<LineRenderingPass>();
lineRenderingPass->init(gfxDevice, drawImageFormat);
quadRendererPass = std::make_unique<QuadRendererPass>();
quadRendererPass->init(gfxDevice, _resources, drawImageFormat);
GameState::GetInstance().SetRenderer(this);
initialized = true;
} catch (...) {
@@ -53,6 +59,7 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
flushMaterialUpdates(gfxDevice);
meshDrawCommands.clear();
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
QuadRenderingManager::GetInstance().ClearQuads();
}
void GameRenderer::endDrawing()
@@ -209,7 +216,30 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
camera,
LineRenderingManager::GetInstance());
}
// vkutil::cmdEndLabel(cmd);
{
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
quadRendererPass->draw(
cmd,
gfxDevice,
*resources,
drawImage,
glm::ortho(0.0f,
static_cast<float>(drawImage.extent.width),
static_cast<float>(drawImage.extent.height),
0.0f, -1.0f, 1.0f));
}
// vkutil::cmdEndLabel(cmd);
}
void GameRenderer::drawQuads(
VkCommandBuffer cmd,
GfxDevice& gfxDevice,
const glm::mat4& projection)
{
const auto& drawImage = resources->getImage(drawImageId);
quadRendererPass->draw(cmd, gfxDevice, *resources, drawImage, projection);
}
void GameRenderer::cleanup(GfxDevice& gfxDevice)
@@ -220,12 +250,15 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
vkDeviceWaitIdle(device);
}
if (skinningPipeline)
if (skinningPipeline)
skinningPipeline->cleanup(gfxDevice);
if (lineRenderingPass)
lineRenderingPass->cleanup(gfxDevice);
if (quadRendererPass)
quadRendererPass->cleanup(gfxDevice);
if (skyboxPipeline)
skyboxPipeline->cleanup(device);
@@ -247,6 +280,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
skyboxPipeline.reset();
skinningPipeline.reset();
lineRenderingPass.reset();
quadRendererPass.reset();
pendingMaterialUploads.clear();
meshDrawCommands.clear();
sortedMeshDrawCommands.clear();