ADD: debug draw
This commit is contained in:
10
shaders/debug_lines.frag
Normal file
10
shaders/debug_lines.frag
Normal file
@@ -0,0 +1,10 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec4 inColor;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = inColor;
|
||||
}
|
||||
|
||||
31
shaders/debug_lines.vert
Normal file
31
shaders/debug_lines.vert
Normal file
@@ -0,0 +1,31 @@
|
||||
#version 450
|
||||
|
||||
#extension GL_EXT_buffer_reference : require
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
struct DebugVertex
|
||||
{
|
||||
vec3 position;
|
||||
float _pad0;
|
||||
vec4 color;
|
||||
};
|
||||
|
||||
layout(buffer_reference, std430) readonly buffer DebugVertexBuffer
|
||||
{
|
||||
DebugVertex vertices[];
|
||||
};
|
||||
|
||||
layout(push_constant) uniform DebugPush
|
||||
{
|
||||
mat4 viewproj;
|
||||
DebugVertexBuffer vertexBuffer;
|
||||
} pc;
|
||||
|
||||
void main()
|
||||
{
|
||||
DebugVertex v = pc.vertexBuffer.vertices[gl_VertexIndex];
|
||||
gl_Position = pc.viewproj * vec4(v.position, 1.0);
|
||||
outColor = v.color;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ add_executable (vulkan_engine
|
||||
core/ui/imgui_system.cpp
|
||||
core/picking/picking_system.h
|
||||
core/picking/picking_system.cpp
|
||||
core/debug_draw/debug_draw.h
|
||||
core/debug_draw/debug_draw.cpp
|
||||
core/game_api.h
|
||||
core/game_api.cpp
|
||||
# core/device
|
||||
@@ -93,6 +95,8 @@ add_executable (vulkan_engine
|
||||
render/passes/imgui_pass.cpp
|
||||
render/passes/tonemap.h
|
||||
render/passes/tonemap.cpp
|
||||
render/passes/debug_draw.h
|
||||
render/passes/debug_draw.cpp
|
||||
# render graph
|
||||
render/graph/types.h
|
||||
render/graph/graph.h
|
||||
|
||||
@@ -34,6 +34,7 @@ class RayTracingManager;
|
||||
class TextureCache;
|
||||
class IBLManager;
|
||||
class InputSystem;
|
||||
class DebugDrawSystem;
|
||||
|
||||
struct ShadowSettings
|
||||
{
|
||||
@@ -142,6 +143,7 @@ public:
|
||||
RenderGraph* renderGraph = nullptr; // render graph (built per-frame)
|
||||
SDL_Window* window = nullptr; // SDL window handle
|
||||
InputSystem* input = nullptr; // input system (engine-owned)
|
||||
DebugDrawSystem* debug_draw = nullptr; // debug 3D draw collector (engine-owned)
|
||||
|
||||
// Frequently used values
|
||||
VkExtent2D drawExtent{};
|
||||
|
||||
617
src/core/debug_draw/debug_draw.cpp
Normal file
617
src/core/debug_draw/debug_draw.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
#include "debug_draw.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
static float clamp_nonnegative_finite(float v, float fallback = 0.0f)
|
||||
{
|
||||
if (!std::isfinite(v)) return fallback;
|
||||
return std::max(0.0f, v);
|
||||
}
|
||||
|
||||
static float ttl_from_seconds(float seconds)
|
||||
{
|
||||
if (!std::isfinite(seconds) || seconds <= 0.0f)
|
||||
{
|
||||
return -1.0f; // one-frame
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
static int clamp_segments(int segments)
|
||||
{
|
||||
if (segments < 3) return 3;
|
||||
if (segments > 256) return 256;
|
||||
return segments;
|
||||
}
|
||||
|
||||
static glm::vec3 safe_normalize(const glm::vec3 &v, const glm::vec3 &fallback)
|
||||
{
|
||||
const float len2 = glm::dot(v, v);
|
||||
if (!std::isfinite(len2) || len2 <= 1.0e-12f) return fallback;
|
||||
return v * (1.0f / std::sqrt(len2));
|
||||
}
|
||||
|
||||
static void basis_from_normal(const glm::vec3 &n, glm::vec3 &out_u, glm::vec3 &out_v)
|
||||
{
|
||||
const glm::vec3 nn = safe_normalize(n, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
const glm::vec3 a = (std::abs(nn.y) < 0.999f) ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
out_u = safe_normalize(glm::cross(nn, a), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
out_v = safe_normalize(glm::cross(nn, out_u), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
static void push_line(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 &a,
|
||||
const glm::vec3 &b,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
DebugDrawVertex v0{};
|
||||
v0.position = a;
|
||||
v0.color = color;
|
||||
DebugDrawVertex v1{};
|
||||
v1.position = b;
|
||||
v1.color = color;
|
||||
dst.push_back(v0);
|
||||
dst.push_back(v1);
|
||||
}
|
||||
|
||||
static bool layer_enabled(uint32_t layer_mask, DebugDrawLayer layer)
|
||||
{
|
||||
return (layer_mask & static_cast<uint32_t>(layer)) != 0u;
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static std::vector<DebugDrawVertex> *select_bucket(const CmdT &cmd,
|
||||
const DebugDrawSystem::Settings &settings,
|
||||
std::vector<DebugDrawVertex> &depth_vertices,
|
||||
std::vector<DebugDrawVertex> &overlay_vertices)
|
||||
{
|
||||
if (!layer_enabled(settings.layer_mask, cmd.layer))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (cmd.depth == DebugDepth::DepthTested)
|
||||
{
|
||||
if (!settings.show_depth_tested) return nullptr;
|
||||
return &depth_vertices;
|
||||
}
|
||||
if (!settings.show_overlay) return nullptr;
|
||||
return &overlay_vertices;
|
||||
}
|
||||
|
||||
static void emit_aabb(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 ¢er_local,
|
||||
const glm::vec3 &half_extents,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
glm::vec3 e = half_extents;
|
||||
e.x = std::max(0.0f, e.x);
|
||||
e.y = std::max(0.0f, e.y);
|
||||
e.z = std::max(0.0f, e.z);
|
||||
|
||||
const glm::vec3 c = center_local;
|
||||
const glm::vec3 corners[8] = {
|
||||
c + glm::vec3(-e.x, -e.y, -e.z),
|
||||
c + glm::vec3(+e.x, -e.y, -e.z),
|
||||
c + glm::vec3(-e.x, +e.y, -e.z),
|
||||
c + glm::vec3(+e.x, +e.y, -e.z),
|
||||
c + glm::vec3(-e.x, -e.y, +e.z),
|
||||
c + glm::vec3(+e.x, -e.y, +e.z),
|
||||
c + glm::vec3(-e.x, +e.y, +e.z),
|
||||
c + glm::vec3(+e.x, +e.y, +e.z),
|
||||
};
|
||||
|
||||
constexpr uint8_t edges[12][2] = {
|
||||
{0, 1}, {1, 3}, {3, 2}, {2, 0},
|
||||
{4, 5}, {5, 7}, {7, 6}, {6, 4},
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7},
|
||||
};
|
||||
|
||||
for (auto &eidx : edges)
|
||||
{
|
||||
push_line(dst, corners[eidx[0]], corners[eidx[1]], color);
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_obb(std::vector<DebugDrawVertex> &dst,
|
||||
const std::array<glm::vec3, 8> &corners_local,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
constexpr uint8_t edges[12][2] = {
|
||||
{0, 1}, {1, 3}, {3, 2}, {2, 0},
|
||||
{4, 5}, {5, 7}, {7, 6}, {6, 4},
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7},
|
||||
};
|
||||
|
||||
for (auto &eidx : edges)
|
||||
{
|
||||
push_line(dst, corners_local[eidx[0]], corners_local[eidx[1]], color);
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_circle(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 ¢er_local,
|
||||
const glm::vec3 &normal,
|
||||
float radius,
|
||||
int segments,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
radius = clamp_nonnegative_finite(radius, 0.0f);
|
||||
if (radius <= 0.0f) return;
|
||||
|
||||
glm::vec3 u{}, v{};
|
||||
basis_from_normal(normal, u, v);
|
||||
|
||||
const int seg = clamp_segments(segments);
|
||||
const float two_pi = 6.2831853071795864769f;
|
||||
|
||||
glm::vec3 prev{};
|
||||
for (int i = 0; i <= seg; ++i)
|
||||
{
|
||||
const float t = (static_cast<float>(i) / static_cast<float>(seg)) * two_pi;
|
||||
const glm::vec3 p = center_local + (u * std::cos(t) + v * std::sin(t)) * radius;
|
||||
if (i > 0)
|
||||
{
|
||||
push_line(dst, prev, p, color);
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_sphere(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 ¢er_local,
|
||||
float radius,
|
||||
int segments,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
radius = clamp_nonnegative_finite(radius, 0.0f);
|
||||
if (radius <= 0.0f) return;
|
||||
|
||||
// 3 great circles
|
||||
emit_circle(dst, center_local, glm::vec3(0.0f, 0.0f, 1.0f), radius, segments, color); // XY
|
||||
emit_circle(dst, center_local, glm::vec3(0.0f, 1.0f, 0.0f), radius, segments, color); // XZ
|
||||
emit_circle(dst, center_local, glm::vec3(1.0f, 0.0f, 0.0f), radius, segments, color); // YZ
|
||||
}
|
||||
|
||||
static void emit_cone(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 &apex_local,
|
||||
const glm::vec3 &direction_local,
|
||||
float length,
|
||||
float angle_degrees,
|
||||
int segments,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
length = clamp_nonnegative_finite(length, 0.0f);
|
||||
if (length <= 0.0f) return;
|
||||
|
||||
float angle = angle_degrees;
|
||||
if (!std::isfinite(angle)) angle = 0.0f;
|
||||
angle = std::clamp(angle, 0.0f, 89.9f);
|
||||
const float radius = length * std::tan(glm::radians(angle));
|
||||
|
||||
const glm::vec3 dir = safe_normalize(direction_local, glm::vec3(0.0f, -1.0f, 0.0f));
|
||||
const glm::vec3 base_center = apex_local + dir * length;
|
||||
|
||||
// Axis
|
||||
push_line(dst, apex_local, base_center, color);
|
||||
|
||||
// Base circle + spokes
|
||||
glm::vec3 u{}, v{};
|
||||
basis_from_normal(dir, u, v);
|
||||
const int seg = clamp_segments(segments);
|
||||
const float two_pi = 6.2831853071795864769f;
|
||||
|
||||
glm::vec3 first{};
|
||||
glm::vec3 prev{};
|
||||
for (int i = 0; i < seg; ++i)
|
||||
{
|
||||
const float t = (static_cast<float>(i) / static_cast<float>(seg)) * two_pi;
|
||||
const glm::vec3 p = base_center + (u * std::cos(t) + v * std::sin(t)) * radius;
|
||||
if (i == 0) first = p;
|
||||
if (i > 0)
|
||||
{
|
||||
push_line(dst, prev, p, color);
|
||||
}
|
||||
push_line(dst, apex_local, p, color);
|
||||
prev = p;
|
||||
}
|
||||
push_line(dst, prev, first, color);
|
||||
}
|
||||
|
||||
static void emit_capsule(std::vector<DebugDrawVertex> &dst,
|
||||
const glm::vec3 &p0_local,
|
||||
const glm::vec3 &p1_local,
|
||||
float radius,
|
||||
int segments,
|
||||
const glm::vec4 &color)
|
||||
{
|
||||
radius = clamp_nonnegative_finite(radius, 0.0f);
|
||||
if (radius <= 0.0f) return;
|
||||
|
||||
const glm::vec3 axis = p1_local - p0_local;
|
||||
const float axis_len2 = glm::dot(axis, axis);
|
||||
if (!std::isfinite(axis_len2) || axis_len2 <= 1.0e-10f)
|
||||
{
|
||||
emit_sphere(dst, p0_local, radius, segments, color);
|
||||
return;
|
||||
}
|
||||
|
||||
const float axis_len = std::sqrt(axis_len2);
|
||||
const glm::vec3 u = axis * (1.0f / axis_len);
|
||||
|
||||
// Basis around the capsule axis
|
||||
const glm::vec3 a = (std::abs(u.y) < 0.999f) ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
const glm::vec3 v = safe_normalize(glm::cross(u, a), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
const glm::vec3 w = safe_normalize(glm::cross(u, v), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
const int seg = clamp_segments(segments);
|
||||
const float two_pi = 6.2831853071795864769f;
|
||||
|
||||
// Rings + side lines
|
||||
glm::vec3 prev0{}, prev1{}, first0{}, first1{};
|
||||
for (int i = 0; i < seg; ++i)
|
||||
{
|
||||
const float t = (static_cast<float>(i) / static_cast<float>(seg)) * two_pi;
|
||||
const glm::vec3 offset = (v * std::cos(t) + w * std::sin(t)) * radius;
|
||||
const glm::vec3 a0 = p0_local + offset;
|
||||
const glm::vec3 a1 = p1_local + offset;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
first0 = a0;
|
||||
first1 = a1;
|
||||
}
|
||||
else
|
||||
{
|
||||
push_line(dst, prev0, a0, color);
|
||||
push_line(dst, prev1, a1, color);
|
||||
}
|
||||
push_line(dst, a0, a1, color);
|
||||
prev0 = a0;
|
||||
prev1 = a1;
|
||||
}
|
||||
push_line(dst, prev0, first0, color);
|
||||
push_line(dst, prev1, first1, color);
|
||||
|
||||
// Endcap arcs (2 meridians per end)
|
||||
const int half_seg = std::max(3, seg / 2);
|
||||
for (int i = 0; i < half_seg; ++i)
|
||||
{
|
||||
const float t0 = (static_cast<float>(i) / static_cast<float>(half_seg)) * 3.14159265358979323846f;
|
||||
const float t1 = (static_cast<float>(i + 1) / static_cast<float>(half_seg)) * 3.14159265358979323846f;
|
||||
|
||||
// p0 hemisphere faces -u
|
||||
const glm::vec3 p0_v0 = p0_local + (v * std::cos(t0) - u * std::sin(t0)) * radius;
|
||||
const glm::vec3 p0_v1 = p0_local + (v * std::cos(t1) - u * std::sin(t1)) * radius;
|
||||
const glm::vec3 p0_w0 = p0_local + (w * std::cos(t0) - u * std::sin(t0)) * radius;
|
||||
const glm::vec3 p0_w1 = p0_local + (w * std::cos(t1) - u * std::sin(t1)) * radius;
|
||||
push_line(dst, p0_v0, p0_v1, color);
|
||||
push_line(dst, p0_w0, p0_w1, color);
|
||||
|
||||
// p1 hemisphere faces +u
|
||||
const glm::vec3 p1_v0 = p1_local + (v * std::cos(t0) + u * std::sin(t0)) * radius;
|
||||
const glm::vec3 p1_v1 = p1_local + (v * std::cos(t1) + u * std::sin(t1)) * radius;
|
||||
const glm::vec3 p1_w0 = p1_local + (w * std::cos(t0) + u * std::sin(t0)) * radius;
|
||||
const glm::vec3 p1_w1 = p1_local + (w * std::cos(t1) + u * std::sin(t1)) * radius;
|
||||
push_line(dst, p1_v0, p1_v1, color);
|
||||
push_line(dst, p1_w0, p1_w1, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t DebugDrawSystem::command_count() const
|
||||
{
|
||||
return _lines.size() + _aabbs.size() + _spheres.size() + _capsules.size() + _circles.size() + _cones.size() + _obbs.size();
|
||||
}
|
||||
|
||||
void DebugDrawSystem::clear()
|
||||
{
|
||||
_lines.clear();
|
||||
_aabbs.clear();
|
||||
_spheres.clear();
|
||||
_capsules.clear();
|
||||
_circles.clear();
|
||||
_cones.clear();
|
||||
_obbs.clear();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void DebugDrawSystem::prune_list(std::vector<T> &cmds, float dt_seconds)
|
||||
{
|
||||
if (cmds.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!std::isfinite(dt_seconds) || dt_seconds < 0.0f)
|
||||
{
|
||||
dt_seconds = 0.0f;
|
||||
}
|
||||
|
||||
size_t dst = 0;
|
||||
for (size_t i = 0; i < cmds.size(); ++i)
|
||||
{
|
||||
T cmd = cmds[i];
|
||||
|
||||
// one-frame commands are removed on begin_frame()
|
||||
if (cmd.ttl_seconds < 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dt_seconds > 0.0f)
|
||||
{
|
||||
cmd.ttl_seconds -= dt_seconds;
|
||||
}
|
||||
|
||||
if (cmd.ttl_seconds <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cmds[dst++] = cmd;
|
||||
}
|
||||
cmds.resize(dst);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::begin_frame(float dt_seconds)
|
||||
{
|
||||
prune_list(_lines, dt_seconds);
|
||||
prune_list(_aabbs, dt_seconds);
|
||||
prune_list(_spheres, dt_seconds);
|
||||
prune_list(_capsules, dt_seconds);
|
||||
prune_list(_circles, dt_seconds);
|
||||
prune_list(_cones, dt_seconds);
|
||||
prune_list(_obbs, dt_seconds);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_line(const WorldVec3 &a_world,
|
||||
const WorldVec3 &b_world,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdLine cmd{};
|
||||
cmd.a_world = a_world;
|
||||
cmd.b_world = b_world;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_lines.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_ray(const WorldVec3 &origin_world,
|
||||
const glm::dvec3 &dir_world,
|
||||
double length,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
if (!std::isfinite(length) || length <= 0.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
glm::dvec3 d = dir_world;
|
||||
const double len2 = glm::dot(d, d);
|
||||
if (!std::isfinite(len2) || len2 <= 1.0e-18)
|
||||
{
|
||||
d = glm::dvec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
d *= 1.0 / std::sqrt(len2);
|
||||
}
|
||||
|
||||
add_line(origin_world,
|
||||
origin_world + d * length,
|
||||
color,
|
||||
seconds,
|
||||
depth,
|
||||
layer);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_aabb(const WorldVec3 ¢er_world,
|
||||
const glm::vec3 &half_extents,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdAabb cmd{};
|
||||
cmd.center_world = center_world;
|
||||
cmd.half_extents = half_extents;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_aabbs.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_sphere(const WorldVec3 ¢er_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdSphere cmd{};
|
||||
cmd.center_world = center_world;
|
||||
cmd.radius = radius;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_spheres.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_capsule(const WorldVec3 &p0_world,
|
||||
const WorldVec3 &p1_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdCapsule cmd{};
|
||||
cmd.p0_world = p0_world;
|
||||
cmd.p1_world = p1_world;
|
||||
cmd.radius = radius;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_capsules.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_circle(const WorldVec3 ¢er_world,
|
||||
const glm::dvec3 &normal_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdCircle cmd{};
|
||||
cmd.center_world = center_world;
|
||||
cmd.normal_world = normal_world;
|
||||
cmd.radius = radius;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_circles.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_cone(const WorldVec3 &apex_world,
|
||||
const glm::dvec3 &direction_world,
|
||||
float length,
|
||||
float angle_degrees,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdCone cmd{};
|
||||
cmd.apex_world = apex_world;
|
||||
cmd.direction_world = direction_world;
|
||||
cmd.length = length;
|
||||
cmd.angle_degrees = angle_degrees;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_cones.push_back(cmd);
|
||||
}
|
||||
|
||||
void DebugDrawSystem::add_obb_corners(const std::array<WorldVec3, 8> &corners_world,
|
||||
const glm::vec4 &color,
|
||||
float seconds,
|
||||
DebugDepth depth,
|
||||
DebugDrawLayer layer)
|
||||
{
|
||||
CmdObb cmd{};
|
||||
cmd.corners_world = corners_world;
|
||||
cmd.color = color;
|
||||
cmd.depth = depth;
|
||||
cmd.layer = layer;
|
||||
cmd.ttl_seconds = ttl_from_seconds(seconds);
|
||||
_obbs.push_back(cmd);
|
||||
}
|
||||
|
||||
DebugDrawSystem::LineVertexLists DebugDrawSystem::build_line_vertices(const WorldVec3 &origin_world) const
|
||||
{
|
||||
LineVertexLists out{};
|
||||
if (!_settings.enabled)
|
||||
{
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<DebugDrawVertex> depth_vertices;
|
||||
std::vector<DebugDrawVertex> overlay_vertices;
|
||||
|
||||
const int seg = clamp_segments(_settings.segments);
|
||||
|
||||
for (const CmdLine &cmd : _lines)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 a = world_to_local(cmd.a_world, origin_world);
|
||||
const glm::vec3 b = world_to_local(cmd.b_world, origin_world);
|
||||
push_line(*dst, a, b, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdAabb &cmd : _aabbs)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 c = world_to_local(cmd.center_world, origin_world);
|
||||
emit_aabb(*dst, c, cmd.half_extents, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdSphere &cmd : _spheres)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 c = world_to_local(cmd.center_world, origin_world);
|
||||
emit_sphere(*dst, c, cmd.radius, seg, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdCapsule &cmd : _capsules)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 p0 = world_to_local(cmd.p0_world, origin_world);
|
||||
const glm::vec3 p1 = world_to_local(cmd.p1_world, origin_world);
|
||||
emit_capsule(*dst, p0, p1, cmd.radius, seg, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdCircle &cmd : _circles)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 c = world_to_local(cmd.center_world, origin_world);
|
||||
const glm::vec3 n = glm::vec3(cmd.normal_world);
|
||||
emit_circle(*dst, c, n, cmd.radius, seg, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdCone &cmd : _cones)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
const glm::vec3 apex = world_to_local(cmd.apex_world, origin_world);
|
||||
const glm::vec3 dir = glm::vec3(cmd.direction_world);
|
||||
emit_cone(*dst, apex, dir, cmd.length, cmd.angle_degrees, seg, cmd.color);
|
||||
}
|
||||
|
||||
for (const CmdObb &cmd : _obbs)
|
||||
{
|
||||
std::vector<DebugDrawVertex> *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices);
|
||||
if (!dst) continue;
|
||||
|
||||
std::array<glm::vec3, 8> corners_local{};
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
{
|
||||
corners_local[i] = world_to_local(cmd.corners_world[i], origin_world);
|
||||
}
|
||||
emit_obb(*dst, corners_local, cmd.color);
|
||||
}
|
||||
|
||||
out.depth_vertex_count = static_cast<uint32_t>(depth_vertices.size());
|
||||
out.overlay_vertex_count = static_cast<uint32_t>(overlay_vertices.size());
|
||||
out.vertices.reserve(depth_vertices.size() + overlay_vertices.size());
|
||||
out.vertices.insert(out.vertices.end(), depth_vertices.begin(), depth_vertices.end());
|
||||
out.vertices.insert(out.vertices.end(), overlay_vertices.begin(), overlay_vertices.end());
|
||||
return out;
|
||||
}
|
||||
191
src/core/debug_draw/debug_draw.h
Normal file
191
src/core/debug_draw/debug_draw.h
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include <core/world.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
enum class DebugDepth : uint8_t
|
||||
{
|
||||
DepthTested = 0,
|
||||
AlwaysOnTop = 1,
|
||||
};
|
||||
|
||||
enum class DebugDrawLayer : uint32_t
|
||||
{
|
||||
Physics = 1u << 0u,
|
||||
Picking = 1u << 1u,
|
||||
Lights = 1u << 2u,
|
||||
Particles = 1u << 3u,
|
||||
Volumetrics = 1u << 4u,
|
||||
Misc = 1u << 5u,
|
||||
};
|
||||
|
||||
struct DebugDrawVertex
|
||||
{
|
||||
glm::vec3 position{0.0f};
|
||||
float _pad0{0.0f};
|
||||
glm::vec4 color{1.0f};
|
||||
};
|
||||
static_assert(sizeof(DebugDrawVertex) == 32);
|
||||
|
||||
class DebugDrawSystem
|
||||
{
|
||||
public:
|
||||
struct Settings
|
||||
{
|
||||
bool enabled = false;
|
||||
bool show_depth_tested = true;
|
||||
bool show_overlay = true;
|
||||
uint32_t layer_mask = static_cast<uint32_t>(DebugDrawLayer::Physics)
|
||||
| static_cast<uint32_t>(DebugDrawLayer::Picking)
|
||||
| static_cast<uint32_t>(DebugDrawLayer::Lights)
|
||||
| static_cast<uint32_t>(DebugDrawLayer::Particles)
|
||||
| static_cast<uint32_t>(DebugDrawLayer::Volumetrics)
|
||||
| static_cast<uint32_t>(DebugDrawLayer::Misc);
|
||||
int segments = 32;
|
||||
};
|
||||
|
||||
struct LineVertexLists
|
||||
{
|
||||
std::vector<DebugDrawVertex> vertices;
|
||||
uint32_t depth_vertex_count = 0;
|
||||
uint32_t overlay_vertex_count = 0;
|
||||
};
|
||||
|
||||
Settings &settings() { return _settings; }
|
||||
const Settings &settings() const { return _settings; }
|
||||
|
||||
void clear();
|
||||
|
||||
// Called once per frame before new submissions to expire one-frame and timed commands.
|
||||
void begin_frame(float dt_seconds);
|
||||
|
||||
void add_line(const WorldVec3 &a_world,
|
||||
const WorldVec3 &b_world,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Physics);
|
||||
|
||||
void add_ray(const WorldVec3 &origin_world,
|
||||
const glm::dvec3 &dir_world,
|
||||
double length,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Physics);
|
||||
|
||||
void add_aabb(const WorldVec3 ¢er_world,
|
||||
const glm::vec3 &half_extents,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Physics);
|
||||
|
||||
void add_sphere(const WorldVec3 ¢er_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Physics);
|
||||
|
||||
void add_capsule(const WorldVec3 &p0_world,
|
||||
const WorldVec3 &p1_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Physics);
|
||||
|
||||
void add_circle(const WorldVec3 ¢er_world,
|
||||
const glm::dvec3 &normal_world,
|
||||
float radius,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Misc);
|
||||
|
||||
void add_cone(const WorldVec3 &apex_world,
|
||||
const glm::dvec3 &direction_world,
|
||||
float length,
|
||||
float angle_degrees,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Misc);
|
||||
|
||||
void add_obb_corners(const std::array<WorldVec3, 8> &corners_world,
|
||||
const glm::vec4 &color,
|
||||
float seconds = 0.0f,
|
||||
DebugDepth depth = DebugDepth::DepthTested,
|
||||
DebugDrawLayer layer = DebugDrawLayer::Misc);
|
||||
|
||||
// Expand currently queued commands into render-local line vertices.
|
||||
LineVertexLists build_line_vertices(const WorldVec3 &origin_world) const;
|
||||
|
||||
size_t command_count() const;
|
||||
|
||||
private:
|
||||
struct CmdBase
|
||||
{
|
||||
DebugDepth depth{DebugDepth::DepthTested};
|
||||
DebugDrawLayer layer{DebugDrawLayer::Misc};
|
||||
glm::vec4 color{1.0f};
|
||||
float ttl_seconds = -1.0f; // <0 = one-frame
|
||||
};
|
||||
|
||||
struct CmdLine : CmdBase
|
||||
{
|
||||
WorldVec3 a_world{0.0, 0.0, 0.0};
|
||||
WorldVec3 b_world{0.0, 0.0, 0.0};
|
||||
};
|
||||
struct CmdAabb : CmdBase
|
||||
{
|
||||
WorldVec3 center_world{0.0, 0.0, 0.0};
|
||||
glm::vec3 half_extents{0.5f, 0.5f, 0.5f};
|
||||
};
|
||||
struct CmdSphere : CmdBase
|
||||
{
|
||||
WorldVec3 center_world{0.0, 0.0, 0.0};
|
||||
float radius{1.0f};
|
||||
};
|
||||
struct CmdCapsule : CmdBase
|
||||
{
|
||||
WorldVec3 p0_world{0.0, 0.0, 0.0};
|
||||
WorldVec3 p1_world{0.0, 0.0, 0.0};
|
||||
float radius{0.5f};
|
||||
};
|
||||
struct CmdCircle : CmdBase
|
||||
{
|
||||
WorldVec3 center_world{0.0, 0.0, 0.0};
|
||||
glm::dvec3 normal_world{0.0, 1.0, 0.0};
|
||||
float radius{1.0f};
|
||||
};
|
||||
struct CmdCone : CmdBase
|
||||
{
|
||||
WorldVec3 apex_world{0.0, 0.0, 0.0};
|
||||
glm::dvec3 direction_world{0.0, -1.0, 0.0};
|
||||
float length{1.0f};
|
||||
float angle_degrees{15.0f};
|
||||
};
|
||||
struct CmdObb : CmdBase
|
||||
{
|
||||
std::array<WorldVec3, 8> corners_world{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static void prune_list(std::vector<T> &cmds, float dt_seconds);
|
||||
|
||||
Settings _settings{};
|
||||
std::vector<CmdLine> _lines;
|
||||
std::vector<CmdAabb> _aabbs;
|
||||
std::vector<CmdSphere> _spheres;
|
||||
std::vector<CmdCapsule> _capsules;
|
||||
std::vector<CmdCircle> _circles;
|
||||
std::vector<CmdCone> _cones;
|
||||
std::vector<CmdObb> _obbs;
|
||||
};
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "vk_mem_alloc.h"
|
||||
#include "core/ui/imgui_system.h"
|
||||
#include "core/picking/picking_system.h"
|
||||
#include "core/debug_draw/debug_draw.h"
|
||||
#include "render/passes/geometry.h"
|
||||
#include "render/passes/imgui_pass.h"
|
||||
#include "render/passes/lighting.h"
|
||||
@@ -50,7 +51,9 @@
|
||||
#include "render/passes/transparent.h"
|
||||
#include "render/passes/fxaa.h"
|
||||
#include "render/passes/tonemap.h"
|
||||
#include "render/passes/debug_draw.h"
|
||||
#include "render/passes/shadow.h"
|
||||
#include "scene/mesh_bvh.h"
|
||||
#include "device/resource.h"
|
||||
#include "device/images.h"
|
||||
#include "context.h"
|
||||
@@ -65,6 +68,11 @@ VulkanEngine *loadedEngine = nullptr;
|
||||
|
||||
VulkanEngine::~VulkanEngine() = default;
|
||||
|
||||
void DebugDrawDeleter::operator()(DebugDrawSystem *p) const
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
|
||||
static VkExtent2D clamp_nonzero_extent(VkExtent2D extent)
|
||||
{
|
||||
if (extent.width == 0) extent.width = 1;
|
||||
@@ -243,6 +251,10 @@ void VulkanEngine::init()
|
||||
_context = std::make_shared<EngineContext>();
|
||||
_input = std::make_unique<InputSystem>();
|
||||
_context->input = _input.get();
|
||||
|
||||
_debugDraw.reset(new DebugDrawSystem());
|
||||
_context->debug_draw = _debugDraw.get();
|
||||
|
||||
_context->device = _deviceManager;
|
||||
_context->resources = _resourceManager;
|
||||
_context->descriptors = std::make_shared<DescriptorAllocatorGrowable>(); {
|
||||
@@ -921,6 +933,14 @@ void VulkanEngine::cleanup()
|
||||
_picking->cleanup();
|
||||
_picking.reset();
|
||||
}
|
||||
if (_debugDraw)
|
||||
{
|
||||
if (_context)
|
||||
{
|
||||
_context->debug_draw = nullptr;
|
||||
}
|
||||
_debugDraw.reset();
|
||||
}
|
||||
|
||||
// Flush all frame deletion queues first while VMA allocator is still alive
|
||||
for (int i = 0; i < FRAME_OVERLAP; i++)
|
||||
@@ -1030,6 +1050,11 @@ void VulkanEngine::draw()
|
||||
|
||||
_sceneManager->update_scene();
|
||||
|
||||
if (_debugDraw && _sceneManager)
|
||||
{
|
||||
_debugDraw->begin_frame(_sceneManager->getDeltaTime());
|
||||
}
|
||||
|
||||
// Update IBL based on camera position and user-defined reflection volumes.
|
||||
if (_iblManager && _sceneManager)
|
||||
{
|
||||
@@ -1304,6 +1329,183 @@ void VulkanEngine::draw()
|
||||
}
|
||||
imguiPass = _renderPassManager->getImGuiPass();
|
||||
|
||||
// Emit per-frame debug primitives (lights/particles/volumes/picking BVH) into the debug draw system.
|
||||
if (_context && _context->debug_draw && _context->debug_draw->settings().enabled && _sceneManager)
|
||||
{
|
||||
DebugDrawSystem *dd = _context->debug_draw;
|
||||
SceneManager *scene = _sceneManager.get();
|
||||
const WorldVec3 origin_world = scene ? scene->get_world_origin() : WorldVec3{0.0, 0.0, 0.0};
|
||||
|
||||
const uint32_t layer_mask = dd->settings().layer_mask;
|
||||
auto layer_on = [layer_mask](DebugDrawLayer layer) {
|
||||
return (layer_mask & static_cast<uint32_t>(layer)) != 0u;
|
||||
};
|
||||
|
||||
// Picking: BVH root bounds + picked surface bounds (if available)
|
||||
if (layer_on(DebugDrawLayer::Picking) && _picking && _picking->debug_draw_bvh())
|
||||
{
|
||||
const auto &pick = _picking->last_pick();
|
||||
if (pick.valid && pick.mesh)
|
||||
{
|
||||
const glm::mat4 &M = pick.worldTransform;
|
||||
auto obb_from_local_aabb = [&](const glm::vec3 ¢er_local, const glm::vec3 &half_extents) {
|
||||
const glm::vec3 c = center_local;
|
||||
const glm::vec3 e = glm::max(half_extents, glm::vec3(0.0f));
|
||||
const glm::vec3 corners_local[8] = {
|
||||
c + glm::vec3(-e.x, -e.y, -e.z),
|
||||
c + glm::vec3(+e.x, -e.y, -e.z),
|
||||
c + glm::vec3(-e.x, +e.y, -e.z),
|
||||
c + glm::vec3(+e.x, +e.y, -e.z),
|
||||
c + glm::vec3(-e.x, -e.y, +e.z),
|
||||
c + glm::vec3(+e.x, -e.y, +e.z),
|
||||
c + glm::vec3(-e.x, +e.y, +e.z),
|
||||
c + glm::vec3(+e.x, +e.y, +e.z),
|
||||
};
|
||||
std::array<WorldVec3, 8> corners_world{};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const glm::vec3 p_local = glm::vec3(M * glm::vec4(corners_local[i], 1.0f));
|
||||
corners_world[i] = local_to_world(p_local, origin_world);
|
||||
}
|
||||
return corners_world;
|
||||
};
|
||||
|
||||
if (pick.surfaceIndex < pick.mesh->surfaces.size())
|
||||
{
|
||||
const Bounds &b = pick.mesh->surfaces[pick.surfaceIndex].bounds;
|
||||
dd->add_obb_corners(obb_from_local_aabb(b.origin, b.extents),
|
||||
glm::vec4(1.0f, 1.0f, 0.0f, 0.75f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Picking);
|
||||
}
|
||||
|
||||
if (pick.mesh->bvh && !pick.mesh->bvh->nodes.empty())
|
||||
{
|
||||
const auto &root = pick.mesh->bvh->nodes[0];
|
||||
const glm::vec3 bmin(root.bounds.min.x, root.bounds.min.y, root.bounds.min.z);
|
||||
const glm::vec3 bmax(root.bounds.max.x, root.bounds.max.y, root.bounds.max.z);
|
||||
const glm::vec3 c = (bmin + bmax) * 0.5f;
|
||||
const glm::vec3 e = (bmax - bmin) * 0.5f;
|
||||
dd->add_obb_corners(obb_from_local_aabb(c, e),
|
||||
glm::vec4(0.0f, 1.0f, 1.0f, 0.75f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Picking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lights: spheres + spot cones
|
||||
if (scene && layer_on(DebugDrawLayer::Lights))
|
||||
{
|
||||
for (const auto &pl : scene->getPointLights())
|
||||
{
|
||||
dd->add_sphere(pl.position_world,
|
||||
pl.radius,
|
||||
glm::vec4(pl.color, 0.35f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Lights);
|
||||
}
|
||||
for (const auto &sl : scene->getSpotLights())
|
||||
{
|
||||
dd->add_cone(sl.position_world,
|
||||
glm::dvec3(sl.direction),
|
||||
sl.radius,
|
||||
sl.outer_angle_deg,
|
||||
glm::vec4(sl.color, 0.35f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Lights);
|
||||
dd->add_sphere(sl.position_world,
|
||||
0.15f,
|
||||
glm::vec4(sl.color, 0.9f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Lights);
|
||||
}
|
||||
}
|
||||
|
||||
// Particles: emitter + spawn radius + emission cone
|
||||
if (layer_on(DebugDrawLayer::Particles))
|
||||
{
|
||||
if (auto *particles = _renderPassManager->getPass<ParticlePass>())
|
||||
{
|
||||
for (const auto &sys : particles->systems())
|
||||
{
|
||||
if (!sys.enabled || sys.count == 0) continue;
|
||||
|
||||
const WorldVec3 emitter_world = local_to_world(sys.params.emitter_pos_local, origin_world);
|
||||
glm::vec4 c = sys.params.color;
|
||||
c.a = 0.5f;
|
||||
|
||||
dd->add_sphere(emitter_world,
|
||||
std::max(0.05f, sys.params.spawn_radius * 0.5f),
|
||||
c,
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Particles);
|
||||
|
||||
dd->add_circle(emitter_world,
|
||||
glm::dvec3(sys.params.emitter_dir_local),
|
||||
sys.params.spawn_radius,
|
||||
glm::vec4(1.0f, 0.6f, 0.1f, 0.35f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Particles);
|
||||
|
||||
dd->add_cone(emitter_world,
|
||||
glm::dvec3(sys.params.emitter_dir_local),
|
||||
std::max(0.5f, sys.params.spawn_radius * 3.0f),
|
||||
sys.params.cone_angle_degrees,
|
||||
glm::vec4(1.0f, 0.6f, 0.1f, 0.35f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Particles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volumetrics: volume AABB + wind vector
|
||||
if (scene && _context->enableVolumetrics && layer_on(DebugDrawLayer::Volumetrics))
|
||||
{
|
||||
const glm::vec3 cam_local = scene->get_camera_local_position();
|
||||
for (uint32_t i = 0; i < EngineContext::MAX_VOXEL_VOLUMES; ++i)
|
||||
{
|
||||
const auto &vs = _context->voxelVolumes[i];
|
||||
if (!vs.enabled) continue;
|
||||
|
||||
glm::vec3 center_local = vs.volumeCenterLocal;
|
||||
if (vs.followCameraXZ)
|
||||
{
|
||||
center_local.x += cam_local.x;
|
||||
center_local.z += cam_local.z;
|
||||
}
|
||||
|
||||
const WorldVec3 center_world = local_to_world(center_local, origin_world);
|
||||
dd->add_aabb(center_world,
|
||||
vs.volumeHalfExtents,
|
||||
glm::vec4(0.4f, 0.8f, 1.0f, 0.35f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Volumetrics);
|
||||
|
||||
const float wind_len = glm::length(vs.windVelocityLocal);
|
||||
if (std::isfinite(wind_len) && wind_len > 1.0e-4f)
|
||||
{
|
||||
dd->add_ray(center_world,
|
||||
glm::dvec3(vs.windVelocityLocal),
|
||||
std::clamp(wind_len, 0.5f, 25.0f),
|
||||
glm::vec4(0.2f, 1.0f, 0.2f, 0.9f),
|
||||
0.0f,
|
||||
DebugDepth::AlwaysOnTop,
|
||||
DebugDrawLayer::Volumetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional Tonemap pass: sample HDR draw -> LDR intermediate
|
||||
if (auto *tonemap = _renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
@@ -1314,11 +1516,23 @@ void VulkanEngine::draw()
|
||||
{
|
||||
finalColor = fxaa->register_graph(_renderGraph.get(), finalColor);
|
||||
}
|
||||
|
||||
// Debug lines after tonemap/FXAA so they don't trigger bloom.
|
||||
if (auto *debugDraw = _renderPassManager->getPass<DebugDrawPass>())
|
||||
{
|
||||
debugDraw->register_graph(_renderGraph.get(), finalColor, hDepth, true /*LDR*/);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If tonemapping is disabled, present whichever HDR buffer we ended up with.
|
||||
finalColor = hdrTarget;
|
||||
|
||||
// Debug lines onto HDR target when tonemapping is disabled.
|
||||
if (auto *debugDraw = _renderPassManager->getPass<DebugDrawPass>())
|
||||
{
|
||||
debugDraw->register_graph(_renderGraph.get(), finalColor, hDepth, false /*HDR*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
#include "core/picking/picking_system.h"
|
||||
|
||||
class InputSystem;
|
||||
class DebugDrawSystem;
|
||||
|
||||
struct DebugDrawDeleter
|
||||
{
|
||||
void operator()(DebugDrawSystem *p) const;
|
||||
};
|
||||
|
||||
// Number of frames-in-flight. Affects per-frame command buffers, fences,
|
||||
// semaphores, and transient descriptor pools in FrameResources.
|
||||
@@ -87,6 +93,7 @@ public:
|
||||
std::unique_ptr<SceneManager> _sceneManager;
|
||||
std::unique_ptr<PickingSystem> _picking;
|
||||
std::unique_ptr<InputSystem> _input;
|
||||
std::unique_ptr<DebugDrawSystem, DebugDrawDeleter> _debugDraw;
|
||||
std::unique_ptr<PipelineManager> _pipelineManager;
|
||||
std::unique_ptr<AssetManager> _assetManager;
|
||||
std::unique_ptr<AsyncAssetLoader> _asyncLoader;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "engine.h"
|
||||
#include "core/picking/picking_system.h"
|
||||
#include "core/debug_draw/debug_draw.h"
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
#include "SDL2/SDL_vulkan.h"
|
||||
@@ -1413,6 +1414,68 @@ namespace
|
||||
{
|
||||
ImGui::TextUnformatted("Picking system not available");
|
||||
}
|
||||
|
||||
// Debug draw settings (engine-owned collector + render pass)
|
||||
if (eng->_context && eng->_context->debug_draw)
|
||||
{
|
||||
DebugDrawSystem *dd = eng->_context->debug_draw;
|
||||
auto &s = dd->settings();
|
||||
|
||||
bool enabled = s.enabled;
|
||||
if (ImGui::Checkbox("Enable debug draw", &enabled))
|
||||
{
|
||||
s.enabled = enabled;
|
||||
}
|
||||
if (s.enabled)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Commands: %zu", dd->command_count());
|
||||
|
||||
int seg = s.segments;
|
||||
if (ImGui::SliderInt("Circle segments", &seg, 3, 128))
|
||||
{
|
||||
s.segments = seg;
|
||||
}
|
||||
|
||||
bool depth_tested = s.show_depth_tested;
|
||||
bool overlay = s.show_overlay;
|
||||
if (ImGui::Checkbox("Depth-tested", &depth_tested))
|
||||
{
|
||||
s.show_depth_tested = depth_tested;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Overlay", &overlay))
|
||||
{
|
||||
s.show_overlay = overlay;
|
||||
}
|
||||
|
||||
auto layer_checkbox = [&s](const char *label, DebugDrawLayer layer) {
|
||||
const uint32_t bit = static_cast<uint32_t>(layer);
|
||||
bool on = (s.layer_mask & bit) != 0u;
|
||||
if (ImGui::Checkbox(label, &on))
|
||||
{
|
||||
if (on) s.layer_mask |= bit;
|
||||
else s.layer_mask &= ~bit;
|
||||
}
|
||||
};
|
||||
|
||||
ImGui::TextUnformatted("Layers");
|
||||
layer_checkbox("Physics##dd_layer_physics", DebugDrawLayer::Physics);
|
||||
ImGui::SameLine();
|
||||
layer_checkbox("Picking##dd_layer_picking", DebugDrawLayer::Picking);
|
||||
ImGui::SameLine();
|
||||
layer_checkbox("Lights##dd_layer_lights", DebugDrawLayer::Lights);
|
||||
layer_checkbox("Particles##dd_layer_particles", DebugDrawLayer::Particles);
|
||||
ImGui::SameLine();
|
||||
layer_checkbox("Volumetrics##dd_layer_volumetrics", DebugDrawLayer::Volumetrics);
|
||||
ImGui::SameLine();
|
||||
layer_checkbox("Misc##dd_layer_misc", DebugDrawLayer::Misc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextUnformatted("Debug draw system not available");
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
// Spawn glTF instances (runtime)
|
||||
|
||||
234
src/render/passes/debug_draw.cpp
Normal file
234
src/render/passes/debug_draw.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "debug_draw.h"
|
||||
|
||||
#include <core/assets/manager.h>
|
||||
#include <core/context.h>
|
||||
#include <core/debug_draw/debug_draw.h>
|
||||
#include <core/device/device.h>
|
||||
#include <core/device/resource.h>
|
||||
#include <core/device/swapchain.h>
|
||||
#include <core/frame/resources.h>
|
||||
#include <core/pipeline/manager.h>
|
||||
#include <render/graph/graph.h>
|
||||
#include <render/graph/resources.h>
|
||||
#include <scene/vk_scene.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct DebugDrawPushConstants
|
||||
{
|
||||
glm::mat4 viewproj;
|
||||
VkDeviceAddress vertex_buffer;
|
||||
};
|
||||
static_assert(offsetof(DebugDrawPushConstants, vertex_buffer) == 64);
|
||||
static_assert(sizeof(DebugDrawPushConstants) >= 72);
|
||||
static_assert((sizeof(DebugDrawPushConstants) % 4) == 0);
|
||||
|
||||
constexpr const char *k_debug_vert = "debug_lines.vert.spv";
|
||||
constexpr const char *k_debug_frag = "debug_lines.frag.spv";
|
||||
|
||||
constexpr const char *k_ldr_depth = "debug_lines.ldr.depth";
|
||||
constexpr const char *k_ldr_overlay = "debug_lines.ldr.overlay";
|
||||
constexpr const char *k_hdr_depth = "debug_lines.hdr.depth";
|
||||
constexpr const char *k_hdr_overlay = "debug_lines.hdr.overlay";
|
||||
}
|
||||
|
||||
void DebugDrawPass::init(EngineContext *context)
|
||||
{
|
||||
_context = context;
|
||||
if (!_context || !_context->getAssets() || !_context->pipelines || !_context->getSwapchain())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const VkFormat ldrFormat = _context->getSwapchain()->swapchainImageFormat();
|
||||
const VkFormat hdrFormat = _context->getSwapchain()->drawImage().imageFormat;
|
||||
const VkFormat depthFormat = _context->getSwapchain()->depthImage().imageFormat;
|
||||
|
||||
GraphicsPipelineCreateInfo base{};
|
||||
base.vertexShaderPath = _context->getAssets()->shaderPath(k_debug_vert);
|
||||
base.fragmentShaderPath = _context->getAssets()->shaderPath(k_debug_frag);
|
||||
base.setLayouts = {};
|
||||
|
||||
VkPushConstantRange pcr{};
|
||||
pcr.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
pcr.offset = 0;
|
||||
pcr.size = sizeof(DebugDrawPushConstants);
|
||||
base.pushConstants = {pcr};
|
||||
|
||||
auto make_cfg = [depthFormat](VkFormat colorFormat, bool depth_test) {
|
||||
return [colorFormat, depthFormat, depth_test](PipelineBuilder &b) {
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_LINE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.enable_blending_alphablend();
|
||||
if (depth_test)
|
||||
{
|
||||
b.enable_depthtest(false, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.disable_depthtest();
|
||||
}
|
||||
b.set_color_attachment_format(colorFormat);
|
||||
b.set_depth_format(depthFormat);
|
||||
};
|
||||
};
|
||||
|
||||
GraphicsPipelineCreateInfo ldrDepth = base;
|
||||
ldrDepth.configure = make_cfg(ldrFormat, true);
|
||||
_context->pipelines->createGraphicsPipeline(k_ldr_depth, ldrDepth);
|
||||
|
||||
GraphicsPipelineCreateInfo ldrOverlay = base;
|
||||
ldrOverlay.configure = make_cfg(ldrFormat, false);
|
||||
_context->pipelines->createGraphicsPipeline(k_ldr_overlay, ldrOverlay);
|
||||
|
||||
GraphicsPipelineCreateInfo hdrDepth = base;
|
||||
hdrDepth.configure = make_cfg(hdrFormat, true);
|
||||
_context->pipelines->createGraphicsPipeline(k_hdr_depth, hdrDepth);
|
||||
|
||||
GraphicsPipelineCreateInfo hdrOverlay = base;
|
||||
hdrOverlay.configure = make_cfg(hdrFormat, false);
|
||||
_context->pipelines->createGraphicsPipeline(k_hdr_overlay, hdrOverlay);
|
||||
}
|
||||
|
||||
void DebugDrawPass::cleanup()
|
||||
{
|
||||
_deletionQueue.flush();
|
||||
}
|
||||
|
||||
void DebugDrawPass::execute(VkCommandBuffer)
|
||||
{
|
||||
// Executed via render graph.
|
||||
}
|
||||
|
||||
void DebugDrawPass::register_graph(RenderGraph *graph,
|
||||
RGImageHandle target_color,
|
||||
RGImageHandle depth,
|
||||
bool is_ldr_target)
|
||||
{
|
||||
if (!graph || !_context || !target_color.valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_context->debug_draw || !_context->debug_draw->settings().enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Always declare depth so depth-tested lines can be drawn when present.
|
||||
// Pipelines are compatible even if the overlay variant disables depth testing.
|
||||
graph->add_pass(
|
||||
"DebugDraw",
|
||||
RGPassType::Graphics,
|
||||
[target_color, depth](RGPassBuilder &builder, EngineContext *) {
|
||||
builder.write_color(target_color);
|
||||
if (depth.valid())
|
||||
{
|
||||
builder.write_depth(depth, false /*load existing depth*/);
|
||||
}
|
||||
},
|
||||
[this, is_ldr_target](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
|
||||
(void)res;
|
||||
draw_debug(cmd, ctx, res, is_ldr_target);
|
||||
});
|
||||
}
|
||||
|
||||
void DebugDrawPass::draw_debug(VkCommandBuffer cmd,
|
||||
EngineContext *ctx,
|
||||
const RGPassResources &,
|
||||
bool is_ldr_target)
|
||||
{
|
||||
EngineContext *ctxLocal = ctx ? ctx : _context;
|
||||
if (!ctxLocal || !ctxLocal->currentFrame || !ctxLocal->getDevice() || !ctxLocal->getResources() || !ctxLocal->pipelines)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugDrawSystem *dd = ctxLocal->debug_draw;
|
||||
if (!dd || !dd->settings().enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WorldVec3 origin_world{0.0, 0.0, 0.0};
|
||||
if (ctxLocal->scene)
|
||||
{
|
||||
origin_world = ctxLocal->scene->get_world_origin();
|
||||
}
|
||||
|
||||
DebugDrawSystem::LineVertexLists lists = dd->build_line_vertices(origin_world);
|
||||
if (lists.vertices.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const VkDeviceSize bytes = static_cast<VkDeviceSize>(lists.vertices.size() * sizeof(DebugDrawVertex));
|
||||
if (bytes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceManager *rm = ctxLocal->getResources();
|
||||
DeviceManager *dm = ctxLocal->getDevice();
|
||||
|
||||
AllocatedBuffer vb = rm->create_buffer(
|
||||
static_cast<size_t>(bytes),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
if (vb.buffer == VK_NULL_HANDLE || vb.allocation == VK_NULL_HANDLE || vb.info.pMappedData == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(vb.info.pMappedData, lists.vertices.data(), static_cast<size_t>(bytes));
|
||||
vmaFlushAllocation(dm->allocator(), vb.allocation, 0, bytes);
|
||||
|
||||
ctxLocal->currentFrame->_deletionQueue.push_function([rm, vb]() {
|
||||
rm->destroy_buffer(vb);
|
||||
});
|
||||
|
||||
VkBufferDeviceAddressInfo addrInfo{.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO};
|
||||
addrInfo.buffer = vb.buffer;
|
||||
VkDeviceAddress addr = vkGetBufferDeviceAddress(dm->device(), &addrInfo);
|
||||
|
||||
DebugDrawPushConstants pc{};
|
||||
pc.viewproj = ctxLocal->getSceneData().viewproj;
|
||||
pc.vertex_buffer = addr;
|
||||
|
||||
VkExtent2D extent = ctxLocal->getDrawExtent();
|
||||
VkViewport vp{0.f, 0.f, (float)extent.width, (float)extent.height, 0.f, 1.f};
|
||||
VkRect2D sc{{0, 0}, extent};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
|
||||
const char *depthPipeName = is_ldr_target ? k_ldr_depth : k_hdr_depth;
|
||||
const char *overlayPipeName = is_ldr_target ? k_ldr_overlay : k_hdr_overlay;
|
||||
|
||||
if (lists.depth_vertex_count > 0)
|
||||
{
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout layout = VK_NULL_HANDLE;
|
||||
if (ctxLocal->pipelines->getGraphics(depthPipeName, pipeline, layout))
|
||||
{
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
||||
vkCmdDraw(cmd, lists.depth_vertex_count, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (lists.overlay_vertex_count > 0)
|
||||
{
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout layout = VK_NULL_HANDLE;
|
||||
if (ctxLocal->pipelines->getGraphics(overlayPipeName, pipeline, layout))
|
||||
{
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
||||
vkCmdDraw(cmd, lists.overlay_vertex_count, 1, lists.depth_vertex_count, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/render/passes/debug_draw.h
Normal file
35
src/render/passes/debug_draw.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <render/renderpass.h>
|
||||
#include <render/graph/types.h>
|
||||
|
||||
class EngineContext;
|
||||
class RenderGraph;
|
||||
class RGPassResources;
|
||||
|
||||
// Debug line rendering pass (wire primitives generated by DebugDrawSystem).
|
||||
// Draws onto either the final LDR target (after tonemap/FXAA) or the HDR draw target
|
||||
// when tonemapping is disabled.
|
||||
class DebugDrawPass final : public IRenderPass
|
||||
{
|
||||
public:
|
||||
void init(EngineContext *context) override;
|
||||
void cleanup() override;
|
||||
void execute(VkCommandBuffer) override;
|
||||
const char *getName() const override { return "DebugDraw"; }
|
||||
|
||||
void register_graph(RenderGraph *graph,
|
||||
RGImageHandle target_color,
|
||||
RGImageHandle depth,
|
||||
bool is_ldr_target);
|
||||
|
||||
private:
|
||||
void draw_debug(VkCommandBuffer cmd,
|
||||
EngineContext *ctx,
|
||||
const RGPassResources &res,
|
||||
bool is_ldr_target);
|
||||
|
||||
EngineContext *_context = nullptr;
|
||||
DeletionQueue _deletionQueue;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "passes/clouds.h"
|
||||
#include "passes/particles.h"
|
||||
#include "passes/fxaa.h"
|
||||
#include "passes/debug_draw.h"
|
||||
#include "passes/transparent.h"
|
||||
#include "passes/tonemap.h"
|
||||
#include "passes/shadow.h"
|
||||
@@ -53,6 +54,10 @@ void RenderPassManager::init(EngineContext *context)
|
||||
fxaaPass->init(context);
|
||||
addPass(std::move(fxaaPass));
|
||||
|
||||
auto debugDrawPass = std::make_unique<DebugDrawPass>();
|
||||
debugDrawPass->init(context);
|
||||
addPass(std::move(debugDrawPass));
|
||||
|
||||
auto transparentPass = std::make_unique<TransparentPass>();
|
||||
transparentPass->init(context);
|
||||
addPass(std::move(transparentPass));
|
||||
|
||||
Reference in New Issue
Block a user