diff --git a/shaders/debug_lines.frag b/shaders/debug_lines.frag new file mode 100644 index 0000000..67dbe16 --- /dev/null +++ b/shaders/debug_lines.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec4 inColor; +layout(location = 0) out vec4 outColor; + +void main() +{ + outColor = inColor; +} + diff --git a/shaders/debug_lines.vert b/shaders/debug_lines.vert new file mode 100644 index 0000000..4f080fc --- /dev/null +++ b/shaders/debug_lines.vert @@ -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; +} + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85323f4..8dffe5b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/core/context.h b/src/core/context.h index fc50684..09f8aec 100644 --- a/src/core/context.h +++ b/src/core/context.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{}; diff --git a/src/core/debug_draw/debug_draw.cpp b/src/core/debug_draw/debug_draw.cpp new file mode 100644 index 0000000..60beb70 --- /dev/null +++ b/src/core/debug_draw/debug_draw.cpp @@ -0,0 +1,617 @@ +#include "debug_draw.h" + +#include +#include + +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 &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(layer)) != 0u; + } + + template + static std::vector *select_bucket(const CmdT &cmd, + const DebugDrawSystem::Settings &settings, + std::vector &depth_vertices, + std::vector &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 &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 &dst, + const std::array &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 &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(i) / static_cast(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 &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 &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(i) / static_cast(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 &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(i) / static_cast(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(i) / static_cast(half_seg)) * 3.14159265358979323846f; + const float t1 = (static_cast(i + 1) / static_cast(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 +void DebugDrawSystem::prune_list(std::vector &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 &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 depth_vertices; + std::vector overlay_vertices; + + const int seg = clamp_segments(_settings.segments); + + for (const CmdLine &cmd : _lines) + { + std::vector *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 *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 *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 *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 *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 *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 *dst = select_bucket(cmd, _settings, depth_vertices, overlay_vertices); + if (!dst) continue; + + std::array 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(depth_vertices.size()); + out.overlay_vertex_count = static_cast(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; +} diff --git a/src/core/debug_draw/debug_draw.h b/src/core/debug_draw/debug_draw.h new file mode 100644 index 0000000..adb142f --- /dev/null +++ b/src/core/debug_draw/debug_draw.h @@ -0,0 +1,191 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +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(DebugDrawLayer::Physics) + | static_cast(DebugDrawLayer::Picking) + | static_cast(DebugDrawLayer::Lights) + | static_cast(DebugDrawLayer::Particles) + | static_cast(DebugDrawLayer::Volumetrics) + | static_cast(DebugDrawLayer::Misc); + int segments = 32; + }; + + struct LineVertexLists + { + std::vector 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 &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 corners_world{}; + }; + + template + static void prune_list(std::vector &cmds, float dt_seconds); + + Settings _settings{}; + std::vector _lines; + std::vector _aabbs; + std::vector _spheres; + std::vector _capsules; + std::vector _circles; + std::vector _cones; + std::vector _obbs; +}; diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 6074a38..132a4ef 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -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(); _input = std::make_unique(); _context->input = _input.get(); + + _debugDraw.reset(new DebugDrawSystem()); + _context->debug_draw = _debugDraw.get(); + _context->device = _deviceManager; _context->resources = _resourceManager; _context->descriptors = std::make_shared(); { @@ -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(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 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()) + { + 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()) { @@ -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()) + { + 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()) + { + debugDraw->register_graph(_renderGraph.get(), finalColor, hDepth, false /*HDR*/); + } } } diff --git a/src/core/engine.h b/src/core/engine.h index a26c787..958aea7 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -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; std::unique_ptr _picking; std::unique_ptr _input; + std::unique_ptr _debugDraw; std::unique_ptr _pipelineManager; std::unique_ptr _assetManager; std::unique_ptr _asyncLoader; diff --git a/src/core/engine_ui.cpp b/src/core/engine_ui.cpp index 15f892e..8acde16 100644 --- a/src/core/engine_ui.cpp +++ b/src/core/engine_ui.cpp @@ -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(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) diff --git a/src/render/passes/debug_draw.cpp b/src/render/passes/debug_draw.cpp new file mode 100644 index 0000000..6e8bbb5 --- /dev/null +++ b/src/render/passes/debug_draw.cpp @@ -0,0 +1,234 @@ +#include "debug_draw.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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(lists.vertices.size() * sizeof(DebugDrawVertex)); + if (bytes == 0) + { + return; + } + + ResourceManager *rm = ctxLocal->getResources(); + DeviceManager *dm = ctxLocal->getDevice(); + + AllocatedBuffer vb = rm->create_buffer( + static_cast(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(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); + } + } +} diff --git a/src/render/passes/debug_draw.h b/src/render/passes/debug_draw.h new file mode 100644 index 0000000..cf1fc21 --- /dev/null +++ b/src/render/passes/debug_draw.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +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; +}; + diff --git a/src/render/renderpass.cpp b/src/render/renderpass.cpp index e7d81d0..39021a0 100644 --- a/src/render/renderpass.cpp +++ b/src/render/renderpass.cpp @@ -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->init(context); + addPass(std::move(debugDrawPass)); + auto transparentPass = std::make_unique(); transparentPass->init(context); addPass(std::move(transparentPass));