feat: revamp 2D pipeline to use SDF's for rendering
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
#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,133 @@
|
||||
#include <destrum/Graphics/Managers/Renderer2D.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void Renderer2D::DrawRect(const glm::vec2& topLeft, const glm::vec2& size, const glm::vec4& color)
|
||||
{
|
||||
DrawRect(topLeft, size, ShapeStyle{.color = color});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawRect(const glm::vec2& topLeft, const glm::vec2& size, const ShapeStyle& style)
|
||||
{
|
||||
submit(Shape2DType::Rect, topLeft + size * 0.5f, size * 0.5f, style);
|
||||
}
|
||||
|
||||
void Renderer2D::DrawRectOutline(
|
||||
const glm::vec2& topLeft,
|
||||
const glm::vec2& size,
|
||||
float thickness,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
DrawRect(topLeft, size, ShapeStyle{.color = color, .outline = thickness});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawTexturedRect(
|
||||
const glm::vec2& topLeft,
|
||||
const glm::vec2& size,
|
||||
ImageID texture,
|
||||
const glm::vec4& tint)
|
||||
{
|
||||
DrawRect(topLeft, size, ShapeStyle{.color = tint, .texture = {.id = texture}});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawEllipse(const glm::vec2& center, const glm::vec2& radii, const glm::vec4& color)
|
||||
{
|
||||
DrawEllipse(center, radii, ShapeStyle{.color = color});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawEllipse(const glm::vec2& center, const glm::vec2& radii, const ShapeStyle& style)
|
||||
{
|
||||
submit(Shape2DType::Ellipse, center, radii, style);
|
||||
}
|
||||
|
||||
void Renderer2D::DrawEllipseOutline(
|
||||
const glm::vec2& center,
|
||||
const glm::vec2& radii,
|
||||
float thickness,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
DrawEllipse(center, radii, ShapeStyle{.color = color, .outline = thickness});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawCircle(const glm::vec2& center, float radius, const glm::vec4& color)
|
||||
{
|
||||
DrawEllipse(center, glm::vec2{radius}, color);
|
||||
}
|
||||
|
||||
void Renderer2D::DrawTriangle(const glm::vec2& a, const glm::vec2& b, const glm::vec2& c, const glm::vec4& color)
|
||||
{
|
||||
DrawTriangle(a, b, c, ShapeStyle{.color = color});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawTriangle(const glm::vec2& a, const glm::vec2& b, const glm::vec2& c, const ShapeStyle& style)
|
||||
{
|
||||
const glm::vec2 min = glm::min(a, glm::min(b, c));
|
||||
const glm::vec2 max = glm::max(a, glm::max(b, c));
|
||||
const glm::vec2 center = (min + max) * 0.5f;
|
||||
|
||||
submit(Shape2DType::Triangle, center, (max - min) * 0.5f, style);
|
||||
shapes.back().p0 = a - center;
|
||||
shapes.back().p1 = b - center;
|
||||
shapes.back().p2 = c - center;
|
||||
}
|
||||
|
||||
void Renderer2D::DrawTriangleOutline(
|
||||
const glm::vec2& a,
|
||||
const glm::vec2& b,
|
||||
const glm::vec2& c,
|
||||
float thickness,
|
||||
const glm::vec4& color)
|
||||
{
|
||||
DrawTriangle(a, b, c, ShapeStyle{.color = color, .outline = thickness});
|
||||
}
|
||||
|
||||
void Renderer2D::DrawLine(
|
||||
const glm::vec2& from,
|
||||
const glm::vec2& to,
|
||||
float thickness,
|
||||
const glm::vec4& color,
|
||||
LineCap cap)
|
||||
{
|
||||
const glm::vec2 delta = to - from;
|
||||
const float halfThickness = thickness * 0.5f;
|
||||
const float capExtension = cap == LineCap::Butt ? 0.0f : halfThickness;
|
||||
|
||||
submit(
|
||||
Shape2DType::Rect,
|
||||
(from + to) * 0.5f,
|
||||
{glm::length(delta) * 0.5f + capExtension, halfThickness},
|
||||
ShapeStyle{
|
||||
.color = color,
|
||||
.rotation = std::atan2(delta.y, delta.x),
|
||||
.cornerRadius = cap == LineCap::Round ? halfThickness : 0.0f,
|
||||
});
|
||||
}
|
||||
|
||||
void Renderer2D::Clear()
|
||||
{
|
||||
shapes.clear();
|
||||
}
|
||||
|
||||
void Renderer2D::submit(
|
||||
Shape2DType type,
|
||||
const glm::vec2& center,
|
||||
const glm::vec2& halfSize,
|
||||
const ShapeStyle& style)
|
||||
{
|
||||
shapes.push_back(Shape2D{
|
||||
.center = center,
|
||||
.halfSize = glm::abs(halfSize),
|
||||
.color = style.color,
|
||||
.uvRect = {style.texture.uvMin.x, style.texture.uvMin.y, style.texture.uvMax.x, style.texture.uvMax.y},
|
||||
.p0 = {},
|
||||
.p1 = {},
|
||||
.p2 = {},
|
||||
.rotation = style.rotation,
|
||||
.outline = std::max(style.outline, 0.0f),
|
||||
.cornerRadius = std::max(style.cornerRadius, 0.0f),
|
||||
.type = type,
|
||||
.textureId = style.texture.id,
|
||||
.filter = style.texture.filter,
|
||||
});
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
#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);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), vertexBuffer.buffer, "quad vertices");
|
||||
|
||||
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);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), indexBuffer.buffer, "quad indices");
|
||||
|
||||
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);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), vertexBuffer.buffer, "quad vertices");
|
||||
|
||||
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);
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), indexBuffer.buffer, "quad indices");
|
||||
|
||||
quadCapacity = newCapacity;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <destrum/Graphics/Pipelines/Renderer2DPass.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#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/Renderer2D.h>
|
||||
|
||||
#include "volk.h"
|
||||
|
||||
namespace {
|
||||
constexpr std::size_t InitialShapeCapacity = 256;
|
||||
constexpr std::uint32_t VerticesPerShape = 6;
|
||||
|
||||
struct PushConstants {
|
||||
glm::mat4 projection;
|
||||
VkDeviceAddress shapes;
|
||||
};
|
||||
|
||||
constexpr VkShaderStageFlags PushConstantStages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
constexpr VkBufferUsageFlags ShapeBufferUsage =
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||
}
|
||||
|
||||
void Renderer2DPass::init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat)
|
||||
{
|
||||
try {
|
||||
shapeBuffer.init(gfxDevice, ShapeBufferUsage, InitialShapeCapacity * sizeof(Shape2D), "2d shapes");
|
||||
shapeCapacity = InitialShapeCapacity;
|
||||
|
||||
const auto vertexShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/shape2d.vert");
|
||||
const auto fragmentShader =
|
||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/shape2d.frag");
|
||||
|
||||
const auto pushConstantRange = VkPushConstantRange{
|
||||
.stageFlags = PushConstantStages,
|
||||
.offset = 0,
|
||||
.size = sizeof(PushConstants),
|
||||
};
|
||||
|
||||
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 = "2D Shape 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 = {};
|
||||
pipelineConfig.vertexAttributeDescriptions = {};
|
||||
|
||||
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 Renderer2DPass::draw(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
RenderResources& resources,
|
||||
const GPUImage& target,
|
||||
const glm::mat4& projection)
|
||||
{
|
||||
auto& renderer2D = Renderer2D::GetInstance();
|
||||
if (!pipeline || renderer2D.GetShapes().empty()) {
|
||||
renderer2D.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Shape2D> shapes = renderer2D.GetShapes();
|
||||
renderer2D.Clear();
|
||||
|
||||
if (shapes.size() > std::numeric_limits<std::uint32_t>::max()) {
|
||||
throw std::overflow_error("Too many 2D shapes submitted for rendering");
|
||||
}
|
||||
|
||||
const auto whiteTextureId = static_cast<std::uint32_t>(resources.getWhiteTextureID());
|
||||
for (auto& shape : shapes) {
|
||||
if (shape.textureId == NULL_IMAGE_ID) {
|
||||
shape.textureId = whiteTextureId;
|
||||
}
|
||||
}
|
||||
|
||||
ensureCapacity(gfxDevice, shapes.size());
|
||||
shapeBuffer.uploadNewData(
|
||||
cmd,
|
||||
gfxDevice.getCurrentFrameIndex(),
|
||||
shapes.data(),
|
||||
shapes.size() * sizeof(Shape2D));
|
||||
|
||||
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);
|
||||
|
||||
const auto pushConstants = PushConstants{
|
||||
.projection = projection,
|
||||
.shapes = shapeBuffer.getBuffer().address,
|
||||
};
|
||||
vkCmdPushConstants(cmd, pipelineLayout, PushConstantStages, 0, sizeof(PushConstants), &pushConstants);
|
||||
|
||||
vkCmdDraw(cmd, VerticesPerShape, static_cast<std::uint32_t>(shapes.size()), 0, 0);
|
||||
|
||||
vkCmdEndRendering(cmd);
|
||||
}
|
||||
|
||||
void Renderer2DPass::cleanup(GfxDevice& gfxDevice)
|
||||
{
|
||||
pipeline.reset();
|
||||
|
||||
if (pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice(), pipelineLayout, nullptr);
|
||||
}
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
|
||||
shapeBuffer.cleanup(gfxDevice);
|
||||
shapeCapacity = 0;
|
||||
}
|
||||
|
||||
glm::mat4 Renderer2DPass::screenProjection(const GPUImage& target)
|
||||
{
|
||||
// Vulkan clip space has +y pointing down, so bottom = 0 puts the origin top left
|
||||
return glm::ortho(
|
||||
0.0f, static_cast<float>(target.extent.width),
|
||||
0.0f, static_cast<float>(target.extent.height),
|
||||
-1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Renderer2DPass::ensureCapacity(GfxDevice& gfxDevice, std::size_t requiredShapes)
|
||||
{
|
||||
if (requiredShapes <= shapeCapacity) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t newCapacity = std::max(shapeCapacity, InitialShapeCapacity);
|
||||
while (newCapacity < requiredShapes) {
|
||||
if (newCapacity > std::numeric_limits<std::size_t>::max() / 2) {
|
||||
newCapacity = requiredShapes;
|
||||
break;
|
||||
}
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
gfxDevice.waitIdle();
|
||||
shapeBuffer.cleanup(gfxDevice);
|
||||
shapeBuffer.init(gfxDevice, ShapeBufferUsage, newCapacity * sizeof(Shape2D), "2d shapes");
|
||||
shapeCapacity = newCapacity;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <destrum/Graphics/Renderer.h>
|
||||
|
||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||
#include <destrum/Graphics/Managers/Renderer2D.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -50,8 +50,8 @@ 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);
|
||||
renderer2DPass = std::make_unique<Renderer2DPass>();
|
||||
renderer2DPass->init(gfxDevice, _resources, drawImageFormat);
|
||||
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
initialized = true;
|
||||
@@ -70,7 +70,7 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
shadowLightDirection.reset();
|
||||
QuadRenderingManager::GetInstance().ClearQuads();
|
||||
Renderer2D::GetInstance().Clear();
|
||||
}
|
||||
|
||||
void GameRenderer::endDrawing()
|
||||
@@ -330,28 +330,25 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
||||
}
|
||||
|
||||
{
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
|
||||
vkutil::ScopedCmdLabel label(cmd, "QuadRendererPass::draw", vkutil::labelColors::ui);
|
||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Renderer2DPass::draw");
|
||||
vkutil::ScopedCmdLabel label(cmd, "Renderer2DPass::draw", vkutil::labelColors::ui);
|
||||
|
||||
quadRendererPass->draw(
|
||||
renderer2DPass->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));
|
||||
Renderer2DPass::screenProjection(drawImage));
|
||||
}
|
||||
}
|
||||
|
||||
void GameRenderer::drawQuads(
|
||||
void GameRenderer::draw2D(
|
||||
VkCommandBuffer cmd,
|
||||
GfxDevice& gfxDevice,
|
||||
const glm::mat4& projection)
|
||||
{
|
||||
const auto& drawImage = resources->getImage(drawImageId);
|
||||
quadRendererPass->draw(cmd, gfxDevice, *resources, drawImage, projection);
|
||||
renderer2DPass->draw(cmd, gfxDevice, *resources, drawImage, projection);
|
||||
}
|
||||
|
||||
void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
@@ -369,8 +366,8 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
if (lineRenderingPass)
|
||||
lineRenderingPass->cleanup(gfxDevice);
|
||||
|
||||
if (quadRendererPass)
|
||||
quadRendererPass->cleanup(gfxDevice);
|
||||
if (renderer2DPass)
|
||||
renderer2DPass->cleanup(gfxDevice);
|
||||
|
||||
if (lightCullingPipeline)
|
||||
lightCullingPipeline->cleanup(gfxDevice);
|
||||
@@ -399,7 +396,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||
skyboxPipeline.reset();
|
||||
skinningPipeline.reset();
|
||||
lineRenderingPass.reset();
|
||||
quadRendererPass.reset();
|
||||
renderer2DPass.reset();
|
||||
lightCullingPipeline.reset();
|
||||
shadowMapPipeline.reset();
|
||||
shadowLightDirection.reset();
|
||||
|
||||
Reference in New Issue
Block a user