feat: revamp 2D pipeline to use SDF's for rendering
This commit is contained in:
@@ -73,7 +73,7 @@ set(SRC_FILES
|
|||||||
"src/Graphics/Managers/FrameManager.cpp"
|
"src/Graphics/Managers/FrameManager.cpp"
|
||||||
"src/Graphics/Managers/ImageManager.cpp"
|
"src/Graphics/Managers/ImageManager.cpp"
|
||||||
"src/Graphics/Managers/LineRenderingManager.cpp"
|
"src/Graphics/Managers/LineRenderingManager.cpp"
|
||||||
"src/Graphics/Managers/QuadRenderingManager.cpp"
|
"src/Graphics/Managers/Renderer2D.cpp"
|
||||||
|
|
||||||
"src/Graphics/Resources/GPUImage.cpp"
|
"src/Graphics/Resources/GPUImage.cpp"
|
||||||
"src/Graphics/Resources/NBuffer.cpp"
|
"src/Graphics/Resources/NBuffer.cpp"
|
||||||
@@ -82,7 +82,7 @@ set(SRC_FILES
|
|||||||
|
|
||||||
"src/Graphics/Pipelines/MeshPipeline.cpp"
|
"src/Graphics/Pipelines/MeshPipeline.cpp"
|
||||||
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
||||||
"src/Graphics/Pipelines/QuadRendererPass.cpp"
|
"src/Graphics/Pipelines/Renderer2DPass.cpp"
|
||||||
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
|
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
|
||||||
"src/Graphics/Pipelines/LightCullingPipeline.cpp"
|
"src/Graphics/Pipelines/LightCullingPipeline.cpp"
|
||||||
"src/Graphics/Pipelines/ShadowMapPipeline.cpp"
|
"src/Graphics/Pipelines/ShadowMapPipeline.cpp"
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_GOOGLE_include_directive : require
|
|
||||||
#extension GL_EXT_nonuniform_qualifier : enable
|
|
||||||
|
|
||||||
#include "bindless.glsl"
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 inUV;
|
|
||||||
layout(location = 1) in vec4 inColor;
|
|
||||||
layout(location = 2) flat in uint inTexId;
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 outFragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = sampleTexture2DLinear(inTexId, inUV);
|
|
||||||
outFragColor = texColor * inColor;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#version 460
|
|
||||||
#extension GL_GOOGLE_include_directive : require
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 inPosition;
|
|
||||||
layout(location = 1) in vec2 inUV;
|
|
||||||
layout(location = 2) in vec4 inColor;
|
|
||||||
layout(location = 3) in uint inTexId;
|
|
||||||
|
|
||||||
layout(location = 0) out vec2 outUV;
|
|
||||||
layout(location = 1) out vec4 outColor;
|
|
||||||
layout(location = 2) flat out uint outTexId;
|
|
||||||
|
|
||||||
layout(push_constant) uniform QuadPushConstants {
|
|
||||||
mat4 viewProjection;
|
|
||||||
} pcs;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = pcs.viewProjection * vec4(inPosition, 0.0, 1.0);
|
|
||||||
outUV = inUV;
|
|
||||||
outColor = inColor;
|
|
||||||
outTexId = inTexId;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#version 460
|
||||||
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
#extension GL_EXT_nonuniform_qualifier : enable
|
||||||
|
|
||||||
|
#include "bindless.glsl"
|
||||||
|
#include "shape2d.glsl"
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 inLocalPos;
|
||||||
|
layout (location = 1) in vec2 inQuadPos;
|
||||||
|
layout (location = 2) flat in uint inShapeIndex;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
float sdRoundedRect(vec2 p, vec2 halfSize, float radius)
|
||||||
|
{
|
||||||
|
radius = min(radius, min(halfSize.x, halfSize.y));
|
||||||
|
vec2 q = abs(p) - halfSize + radius;
|
||||||
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximate, but exact on the boundary which is what the edge and outline need
|
||||||
|
float sdEllipse(vec2 p, vec2 radii)
|
||||||
|
{
|
||||||
|
radii = max(radii, vec2(1e-4));
|
||||||
|
float k0 = length(p / radii);
|
||||||
|
float k1 = length(p / (radii * radii));
|
||||||
|
return k0 < 1e-4 ? -min(radii.x, radii.y) : k0 * (k0 - 1.0) / k1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sdTriangle(vec2 p, vec2 p0, vec2 p1, vec2 p2)
|
||||||
|
{
|
||||||
|
vec2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2;
|
||||||
|
vec2 v0 = p - p0, v1 = p - p1, v2 = p - p2;
|
||||||
|
vec2 pq0 = v0 - e0 * clamp(dot(v0, e0) / max(dot(e0, e0), 1e-8), 0.0, 1.0);
|
||||||
|
vec2 pq1 = v1 - e1 * clamp(dot(v1, e1) / max(dot(e1, e1), 1e-8), 0.0, 1.0);
|
||||||
|
vec2 pq2 = v2 - e2 * clamp(dot(v2, e2) / max(dot(e2, e2), 1e-8), 0.0, 1.0);
|
||||||
|
float winding = sign(e0.x * e2.y - e0.y * e2.x);
|
||||||
|
vec2 d = min(min(vec2(dot(pq0, pq0), winding * (v0.x * e0.y - v0.y * e0.x)),
|
||||||
|
vec2(dot(pq1, pq1), winding * (v1.x * e1.y - v1.y * e1.x))),
|
||||||
|
vec2(dot(pq2, pq2), winding * (v2.x * e2.y - v2.y * e2.x)));
|
||||||
|
return -sqrt(d.x) * sign(d.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Shape2D shape = pcs.shapeBuffer.shapes[inShapeIndex];
|
||||||
|
|
||||||
|
float dist;
|
||||||
|
if (shape.type == SHAPE_ELLIPSE) {
|
||||||
|
dist = sdEllipse(inLocalPos, shape.halfSize);
|
||||||
|
} else if (shape.type == SHAPE_TRIANGLE) {
|
||||||
|
dist = sdTriangle(inLocalPos, shape.p0, shape.p1, shape.p2);
|
||||||
|
} else {
|
||||||
|
dist = sdRoundedRect(inLocalPos, shape.halfSize, shape.cornerRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outlines grow inwards so the outer edge matches the filled shape
|
||||||
|
if (shape.outline > 0.0) {
|
||||||
|
dist = abs(dist + shape.outline * 0.5) - shape.outline * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
float edgeWidth = max(fwidth(dist), 1e-4);
|
||||||
|
float coverage = clamp(0.5 - dist / edgeWidth, 0.0, 1.0);
|
||||||
|
|
||||||
|
vec2 uv = mix(shape.uvRect.xy, shape.uvRect.zw, clamp(inQuadPos, 0.0, 1.0));
|
||||||
|
vec4 texColor = sampleTexture2D(shape.textureId, shape.samplerId, uv);
|
||||||
|
|
||||||
|
outFragColor = texColor * shape.color;
|
||||||
|
outFragColor.a *= coverage;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
#extension GL_EXT_scalar_block_layout : require
|
||||||
|
|
||||||
|
#define SHAPE_RECT 0
|
||||||
|
#define SHAPE_ELLIPSE 1
|
||||||
|
#define SHAPE_TRIANGLE 2
|
||||||
|
|
||||||
|
// Mirrors Shape2D in Renderer2D.h
|
||||||
|
struct Shape2D {
|
||||||
|
vec2 center;
|
||||||
|
vec2 halfSize;
|
||||||
|
vec4 color;
|
||||||
|
vec4 uvRect;
|
||||||
|
vec2 p0;
|
||||||
|
vec2 p1;
|
||||||
|
vec2 p2;
|
||||||
|
float rotation;
|
||||||
|
float outline;
|
||||||
|
float cornerRadius;
|
||||||
|
uint type;
|
||||||
|
uint textureId;
|
||||||
|
uint samplerId;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (buffer_reference, scalar) readonly buffer Shape2DBuffer {
|
||||||
|
Shape2D shapes[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (push_constant, scalar) uniform Shape2DPushConstants {
|
||||||
|
mat4 projection;
|
||||||
|
Shape2DBuffer shapeBuffer;
|
||||||
|
} pcs;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#version 460
|
||||||
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
|
||||||
|
#include "shape2d.glsl"
|
||||||
|
|
||||||
|
layout (location = 0) out vec2 outLocalPos;
|
||||||
|
layout (location = 1) out vec2 outQuadPos;
|
||||||
|
layout (location = 2) flat out uint outShapeIndex;
|
||||||
|
|
||||||
|
// Room for the antialiased edge outside the shape's bounds
|
||||||
|
const float EDGE_MARGIN = 1.0;
|
||||||
|
|
||||||
|
const vec2 CORNERS[6] = vec2[](
|
||||||
|
vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
|
||||||
|
vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(-1.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Shape2D shape = pcs.shapeBuffer.shapes[gl_InstanceIndex];
|
||||||
|
|
||||||
|
vec2 corner = CORNERS[gl_VertexIndex];
|
||||||
|
vec2 localPos = corner * (shape.halfSize + EDGE_MARGIN);
|
||||||
|
|
||||||
|
float c = cos(shape.rotation);
|
||||||
|
float s = sin(shape.rotation);
|
||||||
|
vec2 rotated = vec2(localPos.x * c - localPos.y * s, localPos.x * s + localPos.y * c);
|
||||||
|
|
||||||
|
gl_Position = pcs.projection * vec4(shape.center + rotated, 0.0, 1.0);
|
||||||
|
outLocalPos = localPos;
|
||||||
|
outQuadPos = localPos / max(shape.halfSize, vec2(1e-6)) * 0.5 + 0.5;
|
||||||
|
outShapeIndex = gl_InstanceIndex;
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef DESTRUM_QUADRENDERINGMANAGER_H
|
|
||||||
#define DESTRUM_QUADRENDERINGMANAGER_H
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
#include <destrum/Graphics/ids.h>
|
|
||||||
#include <destrum/Singleton.h>
|
|
||||||
|
|
||||||
struct QuadSubmission {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 size;
|
|
||||||
float rotation;
|
|
||||||
glm::vec4 color;
|
|
||||||
ImageID textureId;
|
|
||||||
};
|
|
||||||
|
|
||||||
class QuadRenderingManager final : public Singleton<QuadRenderingManager> {
|
|
||||||
public:
|
|
||||||
friend class Singleton<QuadRenderingManager>;
|
|
||||||
|
|
||||||
void SubmitQuad(
|
|
||||||
const glm::vec2& position,
|
|
||||||
const glm::vec2& size,
|
|
||||||
const glm::vec4& color = glm::vec4{1.0f},
|
|
||||||
ImageID textureId = NULL_IMAGE_ID);
|
|
||||||
|
|
||||||
void SubmitQuad(
|
|
||||||
const glm::vec2& position,
|
|
||||||
const glm::vec2& size,
|
|
||||||
float rotation,
|
|
||||||
const glm::vec4& color = glm::vec4{1.0f},
|
|
||||||
ImageID textureId = NULL_IMAGE_ID);
|
|
||||||
|
|
||||||
[[nodiscard]] const std::vector<QuadSubmission>& GetQuads() const { return quads; }
|
|
||||||
void ClearQuads();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QuadRenderingManager() = default;
|
|
||||||
|
|
||||||
std::vector<QuadSubmission> quads;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // DESTRUM_QUADRENDERINGMANAGER_H
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#ifndef DESTRUM_RENDERER2D_H
|
||||||
|
#define DESTRUM_RENDERER2D_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/ids.h>
|
||||||
|
#include <destrum/Singleton.h>
|
||||||
|
|
||||||
|
enum class Shape2DType : std::uint32_t {
|
||||||
|
Rect = 0,
|
||||||
|
Ellipse = 1,
|
||||||
|
Triangle = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TextureFilter : std::uint32_t {
|
||||||
|
Nearest = 0,
|
||||||
|
Linear = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LineCap {
|
||||||
|
Butt,
|
||||||
|
Square,
|
||||||
|
Round,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Texture2D {
|
||||||
|
ImageID id{NULL_IMAGE_ID};
|
||||||
|
glm::vec2 uvMin{0.0f, 0.0f};
|
||||||
|
glm::vec2 uvMax{1.0f, 1.0f};
|
||||||
|
TextureFilter filter{TextureFilter::Linear};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShapeStyle {
|
||||||
|
glm::vec4 color{1.0f};
|
||||||
|
float outline{0.0f};
|
||||||
|
float rotation{0.0f};
|
||||||
|
float cornerRadius{0.0f};
|
||||||
|
Texture2D texture{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mirrors Shape2D in shape2d.glsl (scalar layout)
|
||||||
|
struct Shape2D {
|
||||||
|
glm::vec2 center;
|
||||||
|
glm::vec2 halfSize;
|
||||||
|
glm::vec4 color;
|
||||||
|
glm::vec4 uvRect;
|
||||||
|
glm::vec2 p0;
|
||||||
|
glm::vec2 p1;
|
||||||
|
glm::vec2 p2;
|
||||||
|
float rotation;
|
||||||
|
float outline;
|
||||||
|
float cornerRadius;
|
||||||
|
Shape2DType type;
|
||||||
|
std::uint32_t textureId;
|
||||||
|
TextureFilter filter;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Shape2D) == 96);
|
||||||
|
|
||||||
|
// Screen space, in draw image pixels, origin top left, y down. Drawn in submission order.
|
||||||
|
class Renderer2D final : public Singleton<Renderer2D> {
|
||||||
|
public:
|
||||||
|
friend class Singleton<Renderer2D>;
|
||||||
|
|
||||||
|
void DrawRect(const glm::vec2& topLeft, const glm::vec2& size, const glm::vec4& color);
|
||||||
|
void DrawRect(const glm::vec2& topLeft, const glm::vec2& size, const ShapeStyle& style);
|
||||||
|
void DrawRectOutline(const glm::vec2& topLeft, const glm::vec2& size, float thickness, const glm::vec4& color);
|
||||||
|
void DrawTexturedRect(
|
||||||
|
const glm::vec2& topLeft,
|
||||||
|
const glm::vec2& size,
|
||||||
|
ImageID texture,
|
||||||
|
const glm::vec4& tint = glm::vec4{1.0f});
|
||||||
|
|
||||||
|
void DrawEllipse(const glm::vec2& center, const glm::vec2& radii, const glm::vec4& color);
|
||||||
|
void DrawEllipse(const glm::vec2& center, const glm::vec2& radii, const ShapeStyle& style);
|
||||||
|
void DrawEllipseOutline(const glm::vec2& center, const glm::vec2& radii, float thickness, const glm::vec4& color);
|
||||||
|
void DrawCircle(const glm::vec2& center, float radius, const glm::vec4& color);
|
||||||
|
|
||||||
|
void DrawTriangle(const glm::vec2& a, const glm::vec2& b, const glm::vec2& c, const glm::vec4& color);
|
||||||
|
void DrawTriangle(const glm::vec2& a, const glm::vec2& b, const glm::vec2& c, const ShapeStyle& style);
|
||||||
|
void DrawTriangleOutline(
|
||||||
|
const glm::vec2& a,
|
||||||
|
const glm::vec2& b,
|
||||||
|
const glm::vec2& c,
|
||||||
|
float thickness,
|
||||||
|
const glm::vec4& color);
|
||||||
|
|
||||||
|
void DrawLine(
|
||||||
|
const glm::vec2& from,
|
||||||
|
const glm::vec2& to,
|
||||||
|
float thickness,
|
||||||
|
const glm::vec4& color,
|
||||||
|
LineCap cap = LineCap::Butt);
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<Shape2D>& GetShapes() const { return shapes; }
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Renderer2D() = default;
|
||||||
|
|
||||||
|
void submit(Shape2DType type, const glm::vec2& center, const glm::vec2& halfSize, const ShapeStyle& style);
|
||||||
|
|
||||||
|
std::vector<Shape2D> shapes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DESTRUM_RENDERER2D_H
|
||||||
+11
-17
@@ -1,5 +1,5 @@
|
|||||||
#ifndef DESTRUM_QUADRENDERERPASS_H
|
#ifndef DESTRUM_RENDERER2DPASS_H
|
||||||
#define DESTRUM_QUADRENDERERPASS_H
|
#define DESTRUM_RENDERER2DPASS_H
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -8,15 +8,14 @@
|
|||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
#include <destrum/Graphics/Resources/Buffer.h>
|
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||||
|
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
class Pipeline;
|
class Pipeline;
|
||||||
struct GPUImage;
|
struct GPUImage;
|
||||||
class RenderResources;
|
class RenderResources;
|
||||||
|
|
||||||
class QuadRendererPass final {
|
class Renderer2DPass final {
|
||||||
public:
|
public:
|
||||||
void init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat);
|
void init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat);
|
||||||
void draw(
|
void draw(
|
||||||
@@ -27,21 +26,16 @@ public:
|
|||||||
const glm::mat4& projection);
|
const glm::mat4& projection);
|
||||||
void cleanup(GfxDevice& gfxDevice);
|
void cleanup(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
private:
|
// Top left origin, y down, in target pixels
|
||||||
struct QuadVertex {
|
[[nodiscard]] static glm::mat4 screenProjection(const GPUImage& target);
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 uv;
|
|
||||||
glm::vec4 color;
|
|
||||||
std::uint32_t textureId;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ensureCapacity(GfxDevice& gfxDevice, std::size_t requiredQuads);
|
private:
|
||||||
|
void ensureCapacity(GfxDevice& gfxDevice, std::size_t requiredShapes);
|
||||||
|
|
||||||
VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
|
VkPipelineLayout pipelineLayout{VK_NULL_HANDLE};
|
||||||
std::unique_ptr<Pipeline> pipeline;
|
std::unique_ptr<Pipeline> pipeline;
|
||||||
GPUBuffer vertexBuffer{};
|
NBuffer shapeBuffer;
|
||||||
GPUBuffer indexBuffer{};
|
std::size_t shapeCapacity{0};
|
||||||
std::size_t quadCapacity{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DESTRUM_QUADRENDERERPASS_H
|
#endif // DESTRUM_RENDERER2DPASS_H
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
||||||
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
||||||
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
||||||
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
|
#include <destrum/Graphics/Pipelines/Renderer2DPass.h>
|
||||||
#include <destrum/Graphics/Pipelines/ShadowMapPipeline.h>
|
#include <destrum/Graphics/Pipelines/ShadowMapPipeline.h>
|
||||||
#include <destrum/Graphics/ids.h>
|
#include <destrum/Graphics/ids.h>
|
||||||
#include <destrum/Graphics/MeshDrawCommand.h>
|
#include <destrum/Graphics/MeshDrawCommand.h>
|
||||||
@@ -105,7 +105,7 @@ public:
|
|||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawQuads(
|
void draw2D(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
GfxDevice& gfxDevice,
|
GfxDevice& gfxDevice,
|
||||||
const glm::mat4& projection);
|
const glm::mat4& projection);
|
||||||
@@ -163,7 +163,7 @@ private:
|
|||||||
std::unique_ptr<MeshPipeline> meshPipeline;
|
std::unique_ptr<MeshPipeline> meshPipeline;
|
||||||
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
||||||
std::unique_ptr<LineRenderingPass> lineRenderingPass;
|
std::unique_ptr<LineRenderingPass> lineRenderingPass;
|
||||||
std::unique_ptr<QuadRendererPass> quadRendererPass;
|
std::unique_ptr<Renderer2DPass> renderer2DPass;
|
||||||
|
|
||||||
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
||||||
std::unique_ptr<LightCullingPipeline> lightCullingPipeline;
|
std::unique_ptr<LightCullingPipeline> lightCullingPipeline;
|
||||||
|
|||||||
@@ -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/Renderer.h>
|
||||||
|
|
||||||
#include <destrum/Graphics/Managers/LineRenderingManager.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 <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -50,8 +50,8 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
|||||||
lineRenderingPass = std::make_unique<LineRenderingPass>();
|
lineRenderingPass = std::make_unique<LineRenderingPass>();
|
||||||
lineRenderingPass->init(gfxDevice, drawImageFormat);
|
lineRenderingPass->init(gfxDevice, drawImageFormat);
|
||||||
|
|
||||||
quadRendererPass = std::make_unique<QuadRendererPass>();
|
renderer2DPass = std::make_unique<Renderer2DPass>();
|
||||||
quadRendererPass->init(gfxDevice, _resources, drawImageFormat);
|
renderer2DPass->init(gfxDevice, _resources, drawImageFormat);
|
||||||
|
|
||||||
GameState::GetInstance().SetRenderer(this);
|
GameState::GetInstance().SetRenderer(this);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
@@ -70,7 +70,7 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
|||||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||||
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||||
shadowLightDirection.reset();
|
shadowLightDirection.reset();
|
||||||
QuadRenderingManager::GetInstance().ClearQuads();
|
Renderer2D::GetInstance().Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameRenderer::endDrawing()
|
void GameRenderer::endDrawing()
|
||||||
@@ -330,28 +330,25 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Renderer2DPass::draw");
|
||||||
vkutil::ScopedCmdLabel label(cmd, "QuadRendererPass::draw", vkutil::labelColors::ui);
|
vkutil::ScopedCmdLabel label(cmd, "Renderer2DPass::draw", vkutil::labelColors::ui);
|
||||||
|
|
||||||
quadRendererPass->draw(
|
renderer2DPass->draw(
|
||||||
cmd,
|
cmd,
|
||||||
gfxDevice,
|
gfxDevice,
|
||||||
*resources,
|
*resources,
|
||||||
drawImage,
|
drawImage,
|
||||||
glm::ortho(0.0f,
|
Renderer2DPass::screenProjection(drawImage));
|
||||||
static_cast<float>(drawImage.extent.width),
|
|
||||||
static_cast<float>(drawImage.extent.height),
|
|
||||||
0.0f, -1.0f, 1.0f));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameRenderer::drawQuads(
|
void GameRenderer::draw2D(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
GfxDevice& gfxDevice,
|
GfxDevice& gfxDevice,
|
||||||
const glm::mat4& projection)
|
const glm::mat4& projection)
|
||||||
{
|
{
|
||||||
const auto& drawImage = resources->getImage(drawImageId);
|
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)
|
void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
||||||
@@ -369,8 +366,8 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
|||||||
if (lineRenderingPass)
|
if (lineRenderingPass)
|
||||||
lineRenderingPass->cleanup(gfxDevice);
|
lineRenderingPass->cleanup(gfxDevice);
|
||||||
|
|
||||||
if (quadRendererPass)
|
if (renderer2DPass)
|
||||||
quadRendererPass->cleanup(gfxDevice);
|
renderer2DPass->cleanup(gfxDevice);
|
||||||
|
|
||||||
if (lightCullingPipeline)
|
if (lightCullingPipeline)
|
||||||
lightCullingPipeline->cleanup(gfxDevice);
|
lightCullingPipeline->cleanup(gfxDevice);
|
||||||
@@ -399,7 +396,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
|||||||
skyboxPipeline.reset();
|
skyboxPipeline.reset();
|
||||||
skinningPipeline.reset();
|
skinningPipeline.reset();
|
||||||
lineRenderingPass.reset();
|
lineRenderingPass.reset();
|
||||||
quadRendererPass.reset();
|
renderer2DPass.reset();
|
||||||
lightCullingPipeline.reset();
|
lightCullingPipeline.reset();
|
||||||
shadowMapPipeline.reset();
|
shadowMapPipeline.reset();
|
||||||
shadowLightDirection.reset();
|
shadowLightDirection.reset();
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ private:
|
|||||||
std::unique_ptr<CubeMap> skyboxCubemap;
|
std::unique_ptr<CubeMap> skyboxCubemap;
|
||||||
MainSceneObjects mainScene{};
|
MainSceneObjects mainScene{};
|
||||||
|
|
||||||
ImageID debugQuadTexture{NULL_IMAGE_ID};
|
ImageID debug2DTexture{NULL_IMAGE_ID};
|
||||||
|
|
||||||
char scenePath[512]{"scenes/debug_scene.json"};
|
char scenePath[512]{"scenes/debug_scene.json"};
|
||||||
char newSceneName[128]{"EmptyScene"};
|
char newSceneName[128]{"EmptyScene"};
|
||||||
std::string sceneStatus;
|
std::string sceneStatus;
|
||||||
bool renderColliders{false};
|
bool renderColliders{false};
|
||||||
bool renderQuads{true};
|
bool render2DShapes{true};
|
||||||
bool renderDebugLines{true};
|
bool renderDebugLines{true};
|
||||||
bool showTestWindow{true};
|
bool showTestWindow{true};
|
||||||
bool useFreeCamera{false};
|
bool useFreeCamera{false};
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ void LightKeeper::customInit()
|
|||||||
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
||||||
mainScene = BuildMainScene(scene, gfxDevice, resources);
|
mainScene = BuildMainScene(scene, gfxDevice, resources);
|
||||||
|
|
||||||
debugQuadTexture = resources.loadImageFromFile(
|
debug2DTexture = resources.loadImageFromFile(
|
||||||
gfxDevice, AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png"));
|
gfxDevice, AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <destrum/Components/Physics/SphereCollider.h>
|
#include <destrum/Components/Physics/SphereCollider.h>
|
||||||
#include <destrum/FS/AssetFS.h>
|
#include <destrum/FS/AssetFS.h>
|
||||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
#include <destrum/Graphics/Managers/Renderer2D.h>
|
||||||
#include <destrum/Input/InputManager.h>
|
#include <destrum/Input/InputManager.h>
|
||||||
#include <destrum/Scene/Scene.h>
|
#include <destrum/Scene/Scene.h>
|
||||||
#include <destrum/Serialization/SceneSerializer.h>
|
#include <destrum/Serialization/SceneSerializer.h>
|
||||||
@@ -127,7 +127,7 @@ void LightKeeper::drawDebugMenuBar()
|
|||||||
|
|
||||||
if (ImGui::BeginMenu("Render")) {
|
if (ImGui::BeginMenu("Render")) {
|
||||||
ImGui::MenuItem("Colliders", nullptr, &renderColliders);
|
ImGui::MenuItem("Colliders", nullptr, &renderColliders);
|
||||||
ImGui::MenuItem("2D Quads", nullptr, &renderQuads);
|
ImGui::MenuItem("2D Shapes", nullptr, &render2DShapes);
|
||||||
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
||||||
|
|
||||||
bool lightHeatmap = renderer.getRenderLightHeatmap();
|
bool lightHeatmap = renderer.getRenderLightHeatmap();
|
||||||
@@ -246,12 +246,6 @@ void LightKeeper::submitDebugDraws()
|
|||||||
if (renderDebugLines) {
|
if (renderDebugLines) {
|
||||||
LineRenderingManager::GetInstance().SubmitLine({0, 0, 0}, {0, 10, 0}, {255, 0, 0, 255});
|
LineRenderingManager::GetInstance().SubmitLine({0, 0, 0}, {0, 10, 0}, {255, 0, 0, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderQuads && debugQuadTexture != NULL_IMAGE_ID) {
|
|
||||||
QuadRenderingManager::GetInstance().SubmitQuad(
|
|
||||||
glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f,
|
|
||||||
glm::vec4{1.0f}, debugQuadTexture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triggers orange, then colored by body type: static blue, dynamic green, kinematic purple
|
// Triggers orange, then colored by body type: static blue, dynamic green, kinematic purple
|
||||||
|
|||||||
+49
-1
@@ -7,6 +7,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
#include <glm/gtc/packing.hpp>
|
#include <glm/gtc/packing.hpp>
|
||||||
|
|
||||||
#include <destrum/Event.h>
|
#include <destrum/Event.h>
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
#include <destrum/Components/Animator.h>
|
#include <destrum/Components/Animator.h>
|
||||||
#include <destrum/Components/CameraComponent.h>
|
#include <destrum/Components/CameraComponent.h>
|
||||||
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
#include <destrum/Graphics/Resources/PrimitiveMeshes.h>
|
||||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
#include <destrum/Graphics/Managers/Renderer2D.h>
|
||||||
#include <destrum/Components/LightComponent.h>
|
#include <destrum/Components/LightComponent.h>
|
||||||
#include <destrum/Components/Physics/Rigidbody.h>
|
#include <destrum/Components/Physics/Rigidbody.h>
|
||||||
#include <destrum/Graphics/Material.h>
|
#include <destrum/Graphics/Material.h>
|
||||||
@@ -431,6 +432,52 @@ namespace {
|
|||||||
SceneManager::GetInstance().Destroy();
|
SceneManager::GetInstance().Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testRenderer2DShapes()
|
||||||
|
{
|
||||||
|
auto& r2d = Renderer2D::GetInstance();
|
||||||
|
r2d.Clear();
|
||||||
|
|
||||||
|
r2d.DrawRect({10.0f, 20.0f}, {30.0f, 40.0f}, glm::vec4{0.5f});
|
||||||
|
const Shape2D& rect = r2d.GetShapes().back();
|
||||||
|
check(rect.type == Shape2DType::Rect &&
|
||||||
|
rect.center == glm::vec2{25.0f, 40.0f} &&
|
||||||
|
rect.halfSize == glm::vec2{15.0f, 20.0f} &&
|
||||||
|
rect.textureId == NULL_IMAGE_ID &&
|
||||||
|
rect.outline == 0.0f,
|
||||||
|
"rects must be anchored at their top left corner and filled by default");
|
||||||
|
|
||||||
|
r2d.DrawEllipseOutline({50.0f, 50.0f}, {20.0f, 10.0f}, 2.0f, glm::vec4{1.0f});
|
||||||
|
const Shape2D& ellipse = r2d.GetShapes().back();
|
||||||
|
check(ellipse.type == Shape2DType::Ellipse &&
|
||||||
|
ellipse.center == glm::vec2{50.0f} &&
|
||||||
|
ellipse.halfSize == glm::vec2{20.0f, 10.0f} &&
|
||||||
|
ellipse.outline == 2.0f,
|
||||||
|
"ellipses must be centered with their radii as half size");
|
||||||
|
|
||||||
|
r2d.DrawTriangle({0.0f, 10.0f}, {5.0f, 0.0f}, {10.0f, 10.0f}, glm::vec4{1.0f});
|
||||||
|
const Shape2D& triangle = r2d.GetShapes().back();
|
||||||
|
check(triangle.type == Shape2DType::Triangle &&
|
||||||
|
triangle.center == glm::vec2{5.0f} &&
|
||||||
|
triangle.halfSize == glm::vec2{5.0f} &&
|
||||||
|
triangle.p0 == glm::vec2{-5.0f, 5.0f} &&
|
||||||
|
triangle.p1 == glm::vec2{0.0f, -5.0f} &&
|
||||||
|
triangle.p2 == glm::vec2{5.0f, 5.0f},
|
||||||
|
"triangle points must be stored relative to their bounding box center");
|
||||||
|
|
||||||
|
r2d.DrawLine({0.0f, 0.0f}, {0.0f, 10.0f}, 4.0f, glm::vec4{1.0f}, LineCap::Round);
|
||||||
|
const Shape2D& line = r2d.GetShapes().back();
|
||||||
|
check(line.type == Shape2DType::Rect &&
|
||||||
|
line.center == glm::vec2{0.0f, 5.0f} &&
|
||||||
|
close(line.halfSize.x, 7.0f) && close(line.halfSize.y, 2.0f) &&
|
||||||
|
close(line.rotation, glm::half_pi<float>()) &&
|
||||||
|
close(line.cornerRadius, 2.0f),
|
||||||
|
"round capped lines must be rotated rects extended by half their thickness");
|
||||||
|
|
||||||
|
check(r2d.GetShapes().size() == 4, "every draw call must submit exactly one shape");
|
||||||
|
r2d.Clear();
|
||||||
|
check(r2d.GetShapes().empty(), "clear must drop all submitted shapes");
|
||||||
|
}
|
||||||
|
|
||||||
void testCapsuleColliderDebugLines()
|
void testCapsuleColliderDebugLines()
|
||||||
{
|
{
|
||||||
GameObject object{"capsule"};
|
GameObject object{"capsule"};
|
||||||
@@ -1131,6 +1178,7 @@ int main()
|
|||||||
testPrimitiveBoxMesh();
|
testPrimitiveBoxMesh();
|
||||||
testColliderCenterOffset();
|
testColliderCenterOffset();
|
||||||
testCapsuleColliderDebugLines();
|
testCapsuleColliderDebugLines();
|
||||||
|
testRenderer2DShapes();
|
||||||
std::cout << "destrum tests passed\n";
|
std::cout << "destrum tests passed\n";
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user