From be181b177f74ebdecc7494cd9e1d202c56188195 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Mon, 5 Jan 2026 20:13:58 +0000 Subject: [PATCH] feat: Implement Vulkan debug messenger and validation layers - Added Vulkan debug callback and messenger setup to VulkanDeviceService. - Integrated validation layers into Vulkan instance creation. - Enhanced VulkanGraphicsBackend with methods to recreate swapchain and wait for GPU idle. - Updated VulkanGuiService to include render pass in resize method. - Removed deprecated VulkanGraphicsBackend_old.cpp file. - Refactored interfaces to support new methods for swapchain management and rendering. --- src/services/impl/graphics_service.cpp | 30 +- src/services/impl/gui_renderer.cpp | 293 ++++++-- src/services/impl/gui_renderer.cpp.backup | 596 --------------- src/services/impl/gui_renderer.hpp | 2 +- src/services/impl/gui_renderer_old.cpp | 635 ---------------- src/services/impl/gui_renderer_service.cpp | 7 +- src/services/impl/gui_renderer_service.hpp | 2 +- src/services/impl/gxm_graphics_backend.cpp | 36 +- src/services/impl/gxm_graphics_backend.hpp | 13 +- src/services/impl/render_command_service.cpp | 14 + src/services/impl/render_command_service.hpp | 1 + .../impl/render_coordinator_service.cpp | 16 +- src/services/impl/swapchain_service.cpp | 5 + src/services/impl/vulkan_device_service.cpp | 100 +++ src/services/impl/vulkan_device_service.hpp | 3 + src/services/impl/vulkan_graphics_backend.cpp | 113 ++- src/services/impl/vulkan_graphics_backend.hpp | 10 +- .../impl/vulkan_graphics_backend_old.cpp | 688 ------------------ src/services/impl/vulkan_gui_service.cpp | 7 +- src/services/impl/vulkan_gui_service.hpp | 2 +- .../interfaces/i_graphics_backend.hpp | 51 +- .../interfaces/i_gui_renderer_service.hpp | 2 +- src/services/interfaces/i_gui_service.hpp | 2 +- .../interfaces/i_render_command_service.hpp | 7 + 24 files changed, 628 insertions(+), 2007 deletions(-) delete mode 100644 src/services/impl/gui_renderer.cpp.backup delete mode 100644 src/services/impl/gui_renderer_old.cpp delete mode 100644 src/services/impl/vulkan_graphics_backend_old.cpp diff --git a/src/services/impl/graphics_service.cpp b/src/services/impl/graphics_service.cpp index 6edaf64..5e9a51e 100644 --- a/src/services/impl/graphics_service.cpp +++ b/src/services/impl/graphics_service.cpp @@ -78,7 +78,18 @@ void GraphicsService::RecreateSwapchain() { throw std::runtime_error("Graphics service not initialized"); } - // TODO: Implement swapchain recreation via backend + auto size = windowService_ ? windowService_->GetSize() : std::pair{0, 0}; + if (logger_) { + logger_->Trace("GraphicsService", "RecreateSwapchain", + "windowSize=" + std::to_string(size.first) + "x" + + std::to_string(size.second)); + } + if (size.first == 0 || size.second == 0) { + logger_->Warn("GraphicsService::RecreateSwapchain: Skipping recreation for zero-size window"); + return; + } + + backend_->RecreateSwapchain(size.first, size.second); } void GraphicsService::LoadShaders(const std::unordered_map& shaders) { @@ -173,7 +184,7 @@ void GraphicsService::WaitIdle() { return; } - // TODO: Implement via backend + backend_->WaitIdle(); } GraphicsDeviceHandle GraphicsService::GetDevice() const { @@ -193,8 +204,7 @@ GraphicsDeviceHandle GraphicsService::GetPhysicalDevice() const { return nullptr; } - // TODO: Return physical device from backend - return nullptr; + return backend_->GetPhysicalDevice(); } std::pair GraphicsService::GetSwapchainExtent() const { @@ -204,8 +214,7 @@ std::pair GraphicsService::GetSwapchainExtent() const { return {0, 0}; } - // TODO: Return extent from backend - return {800, 600}; // Placeholder + return backend_->GetSwapchainExtent(); } uint32_t GraphicsService::GetSwapchainFormat() const { @@ -215,8 +224,7 @@ uint32_t GraphicsService::GetSwapchainFormat() const { return 0; } - // TODO: Return format from backend - return 0; // Placeholder + return backend_->GetSwapchainFormat(); } void* GraphicsService::GetCurrentCommandBuffer() const { @@ -226,8 +234,7 @@ void* GraphicsService::GetCurrentCommandBuffer() const { return nullptr; } - // TODO: Return command buffer from backend - return nullptr; + return backend_->GetCurrentCommandBuffer(); } void* GraphicsService::GetGraphicsQueue() const { @@ -237,8 +244,7 @@ void* GraphicsService::GetGraphicsQueue() const { return nullptr; } - // TODO: Return queue from backend - return nullptr; + return backend_->GetGraphicsQueue(); } } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gui_renderer.cpp b/src/services/impl/gui_renderer.cpp index 69feb69..e746ede 100644 --- a/src/services/impl/gui_renderer.cpp +++ b/src/services/impl/gui_renderer.cpp @@ -137,6 +137,96 @@ ParsedSvg ParseSvgFile(const std::filesystem::path& path) { return result; } +GuiCommand::RectData IntersectRect(const GuiCommand::RectData& a, const GuiCommand::RectData& b) { + GuiCommand::RectData result; + result.x = std::max(a.x, b.x); + result.y = std::max(a.y, b.y); + float right = std::min(a.x + a.width, b.x + b.width); + float bottom = std::min(a.y + a.height, b.y + b.height); + result.width = std::max(0.0f, right - result.x); + result.height = std::max(0.0f, bottom - result.y); + return result; +} + +bool RectHasArea(const GuiCommand::RectData& rect) { + return rect.width > 0.0f && rect.height > 0.0f; +} + +struct ClipPoint { + float x = 0.0f; + float y = 0.0f; +}; + +enum class ClipEdge { + Left, + Right, + Top, + Bottom +}; + +bool IsInsideClip(const ClipPoint& point, const GuiCommand::RectData& rect, ClipEdge edge) { + switch (edge) { + case ClipEdge::Left: + return point.x >= rect.x; + case ClipEdge::Right: + return point.x <= rect.x + rect.width; + case ClipEdge::Top: + return point.y >= rect.y; + case ClipEdge::Bottom: + return point.y <= rect.y + rect.height; + } + return false; +} + +ClipPoint IntersectClipEdge(const ClipPoint& a, const ClipPoint& b, + const GuiCommand::RectData& rect, ClipEdge edge) { + ClipPoint result = a; + float dx = b.x - a.x; + float dy = b.y - a.y; + + if (edge == ClipEdge::Left || edge == ClipEdge::Right) { + float clipX = (edge == ClipEdge::Left) ? rect.x : (rect.x + rect.width); + float t = (dx != 0.0f) ? (clipX - a.x) / dx : 0.0f; + result.x = clipX; + result.y = a.y + t * dy; + } else { + float clipY = (edge == ClipEdge::Top) ? rect.y : (rect.y + rect.height); + float t = (dy != 0.0f) ? (clipY - a.y) / dy : 0.0f; + result.x = a.x + t * dx; + result.y = clipY; + } + return result; +} + +std::vector ClipPolygonToRect(const std::vector& polygon, + const GuiCommand::RectData& rect) { + std::vector output = polygon; + for (ClipEdge edge : {ClipEdge::Left, ClipEdge::Right, ClipEdge::Top, ClipEdge::Bottom}) { + if (output.empty()) { + break; + } + std::vector input = output; + output.clear(); + + for (size_t i = 0; i < input.size(); ++i) { + const ClipPoint& current = input[i]; + const ClipPoint& previous = input[(i + input.size() - 1) % input.size()]; + bool currentInside = IsInsideClip(current, rect, edge); + bool previousInside = IsInsideClip(previous, rect, edge); + + if (currentInside) { + if (!previousInside) { + output.push_back(IntersectClipEdge(previous, current, rect, edge)); + } + output.push_back(current); + } else if (previousInside) { + output.push_back(IntersectClipEdge(previous, current, rect, edge)); + } + } + } + return output; +} + std::vector ReadFile(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) { @@ -231,13 +321,15 @@ void GuiRenderer::RenderToSwapchain(VkCommandBuffer commandBuffer, VkRenderPass vkCmdDrawIndexed(commandBuffer, static_cast(indices_.size()), 1, 0, 0, 0); } -void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format) { - if (width == viewportWidth_ && height == viewportHeight_ && format == swapchainFormat_) { +void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { + if (width == viewportWidth_ && height == viewportHeight_ && + format == swapchainFormat_ && renderPass == renderPass_) { return; } UpdateFormat(format); viewportWidth_ = width; viewportHeight_ = height; + renderPass_ = renderPass; // Recreate pipeline for new viewport size if (pipeline_ != VK_NULL_HANDLE) { @@ -258,30 +350,90 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector& commands, u }; }; + std::vector clipStack; + clipStack.push_back({0.0f, 0.0f, static_cast(width), static_cast(height)}); + + auto addQuad = [&](const GuiCommand::RectData& rect, const GuiColor& color, + const GuiCommand::RectData& clipRect) { + GuiCommand::RectData clipped = IntersectRect(rect, clipRect); + if (!RectHasArea(clipped)) { + return; + } + auto [x0, y0] = toNDC(clipped.x, clipped.y); + auto [x1, y1] = toNDC(clipped.x + clipped.width, clipped.y + clipped.height); + + uint32_t baseIndex = static_cast(vertices_.size()); + vertices_.push_back({x0, y0, 0.0f, color.r, color.g, color.b, color.a}); + vertices_.push_back({x1, y0, 0.0f, color.r, color.g, color.b, color.a}); + vertices_.push_back({x1, y1, 0.0f, color.r, color.g, color.b, color.a}); + vertices_.push_back({x0, y1, 0.0f, color.r, color.g, color.b, color.a}); + + indices_.push_back(baseIndex + 0); + indices_.push_back(baseIndex + 1); + indices_.push_back(baseIndex + 2); + indices_.push_back(baseIndex + 0); + indices_.push_back(baseIndex + 2); + indices_.push_back(baseIndex + 3); + }; + + auto addClippedPolygon = [&](const std::vector& polygon, + const GuiColor& color, + const GuiCommand::RectData& clipRect) { + std::vector clipped = ClipPolygonToRect(polygon, clipRect); + if (clipped.size() < 3) { + return; + } + uint32_t baseIndex = static_cast(vertices_.size()); + for (const auto& point : clipped) { + auto [x, y] = toNDC(point.x, point.y); + vertices_.push_back({x, y, 0.0f, color.r, color.g, color.b, color.a}); + } + for (size_t i = 1; i + 1 < clipped.size(); ++i) { + indices_.push_back(baseIndex); + indices_.push_back(baseIndex + static_cast(i)); + indices_.push_back(baseIndex + static_cast(i + 1)); + } + }; + for (const auto& cmd : commands) { + if (cmd.type == GuiCommand::Type::ClipPush) { + GuiCommand::RectData nextClip = IntersectRect(clipStack.back(), cmd.rect); + clipStack.push_back(nextClip); + continue; + } + if (cmd.type == GuiCommand::Type::ClipPop) { + if (clipStack.size() > 1) { + clipStack.pop_back(); + } + continue; + } + + const GuiCommand::RectData& activeClip = clipStack.back(); + if (!RectHasArea(activeClip)) { + continue; + } + if (cmd.type == GuiCommand::Type::Rect) { - // Generate a quad (2 triangles) for the rectangle - auto [x0, y0] = toNDC(cmd.rect.x, cmd.rect.y); - auto [x1, y1] = toNDC(cmd.rect.x + cmd.rect.width, cmd.rect.y + cmd.rect.height); - - uint32_t baseIndex = static_cast(vertices_.size()); - - // Add 4 vertices for the quad - vertices_.push_back({x0, y0, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({x1, y0, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({x1, y1, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({x0, y1, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - - // Add 6 indices for 2 triangles - indices_.push_back(baseIndex + 0); - indices_.push_back(baseIndex + 1); - indices_.push_back(baseIndex + 2); - indices_.push_back(baseIndex + 0); - indices_.push_back(baseIndex + 2); - indices_.push_back(baseIndex + 3); + addQuad(cmd.rect, cmd.color, activeClip); + if (cmd.borderWidth > 0.0f && cmd.borderColor.a > 0.0f) { + float border = std::min(cmd.borderWidth, std::min(cmd.rect.width, cmd.rect.height)); + if (border > 0.0f) { + float innerHeight = std::max(0.0f, cmd.rect.height - border * 2.0f); + GuiCommand::RectData top{cmd.rect.x, cmd.rect.y, cmd.rect.width, border}; + GuiCommand::RectData bottom{cmd.rect.x, cmd.rect.y + cmd.rect.height - border, + cmd.rect.width, border}; + GuiCommand::RectData left{cmd.rect.x, cmd.rect.y + border, border, innerHeight}; + GuiCommand::RectData right{cmd.rect.x + cmd.rect.width - border, + cmd.rect.y + border, border, innerHeight}; + addQuad(top, cmd.borderColor, activeClip); + addQuad(bottom, cmd.borderColor, activeClip); + addQuad(left, cmd.borderColor, activeClip); + addQuad(right, cmd.borderColor, activeClip); + } + } } else if (cmd.type == GuiCommand::Type::Text) { // Render text using 8x8 bitmap font - if (cmd.text.empty() || !cmd.hasBounds) { + if (cmd.text.empty()) { continue; } @@ -295,19 +447,37 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector& commands, u // Calculate text position based on alignment // Text commands use bounds field, not rect field - float startX = cmd.bounds.x; - float startY = cmd.bounds.y; + GuiCommand::RectData textBounds = cmd.bounds; + if (!cmd.hasBounds) { + textBounds = { + cmd.rect.x, + cmd.rect.y, + cmd.fontSize * static_cast(std::max(1, cmd.text.size())), + cmd.fontSize + }; + } + + float startX = textBounds.x; + float startY = textBounds.y; if (cmd.alignX == "center") { - startX += (cmd.bounds.width - textWidth) * 0.5f; + startX += (textBounds.width - textWidth) * 0.5f; } else if (cmd.alignX == "right") { - startX += cmd.bounds.width - textWidth; + startX += textBounds.width - textWidth; } if (cmd.alignY == "center") { - startY += (cmd.bounds.height - charHeight) * 0.5f; + startY += (textBounds.height - charHeight) * 0.5f; } else if (cmd.alignY == "bottom") { - startY += cmd.bounds.height - charHeight; + startY += textBounds.height - charHeight; + } + + GuiCommand::RectData textClip = activeClip; + if (cmd.hasClipRect) { + textClip = IntersectRect(textClip, cmd.clipRect); + } + if (!RectHasArea(textClip)) { + continue; } // Render each character as a small quad @@ -333,32 +503,61 @@ void GuiRenderer::GenerateGuiGeometry(const std::vector& commands, u float pw = pixelWidth; float ph = pixelHeight; - auto [px0, py0] = toNDC(px, py); - auto [px1, py1] = toNDC(px + pw, py + ph); - - uint32_t baseIndex = static_cast(vertices_.size()); - - // Add 4 vertices for the pixel quad - vertices_.push_back({px0, py0, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({px1, py0, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({px1, py1, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - vertices_.push_back({px0, py1, 0.0f, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a}); - - // Add 6 indices for 2 triangles - indices_.push_back(baseIndex + 0); - indices_.push_back(baseIndex + 1); - indices_.push_back(baseIndex + 2); - indices_.push_back(baseIndex + 0); - indices_.push_back(baseIndex + 2); - indices_.push_back(baseIndex + 3); + GuiCommand::RectData pixelRect{px, py, pw, ph}; + addQuad(pixelRect, cmd.color, textClip); } } } } x += charWidth + charSpacing; } + } else if (cmd.type == GuiCommand::Type::Svg) { + if (cmd.svgPath.empty()) { + continue; + } + const ParsedSvg* svg = LoadSvg(cmd.svgPath); + if (!svg || svg->circles.empty() || svg->viewWidth <= 0.0f || svg->viewHeight <= 0.0f) { + continue; + } + + GuiCommand::RectData clippedTarget = IntersectRect(cmd.rect, activeClip); + if (!RectHasArea(clippedTarget)) { + continue; + } + + float scaleX = clippedTarget.width / svg->viewWidth; + float scaleY = clippedTarget.height / svg->viewHeight; + float scale = std::min(scaleX, scaleY); + + for (const auto& circle : svg->circles) { + float cx = clippedTarget.x + circle.cx * scaleX; + float cy = clippedTarget.y + circle.cy * scaleY; + float radius = circle.r * scale; + if (radius <= 0.0f) { + continue; + } + + GuiColor color = circle.color; + if (cmd.svgTint.a > 0.0f) { + color.r *= cmd.svgTint.r; + color.g *= cmd.svgTint.g; + color.b *= cmd.svgTint.b; + color.a *= cmd.svgTint.a; + } + + int segments = std::max(12, static_cast(radius * 0.25f)); + const float twoPi = 6.283185307179586f; + ClipPoint center{cx, cy}; + + for (int i = 0; i < segments; ++i) { + float angle0 = twoPi * static_cast(i) / static_cast(segments); + float angle1 = twoPi * static_cast(i + 1) / static_cast(segments); + ClipPoint p0{cx + std::cos(angle0) * radius, cy + std::sin(angle0) * radius}; + ClipPoint p1{cx + std::cos(angle1) * radius, cy + std::sin(angle1) * radius}; + addClippedPolygon({center, p0, p1}, color, activeClip); + } + } } - // TODO: Implement SVG, ClipPush, ClipPop command types } } diff --git a/src/services/impl/gui_renderer.cpp.backup b/src/services/impl/gui_renderer.cpp.backup deleted file mode 100644 index 8d7d54d..0000000 --- a/src/services/impl/gui_renderer.cpp.backup +++ /dev/null @@ -1,596 +0,0 @@ -#include "gui_renderer.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "font8x8_basic.h" - -namespace sdl3cpp::services::impl { -namespace { - -bool ExtractAttribute(const std::string& source, const char* name, std::string& outValue) { - std::string key = name; - size_t pos = source.find(key); - while (pos != std::string::npos) { - size_t eq = source.find('=', pos + key.size()); - if (eq == std::string::npos) { - break; - } - size_t valueStart = eq + 1; - while (valueStart < source.size() && - std::isspace(static_cast(source[valueStart]))) { - valueStart++; - } - if (valueStart >= source.size()) { - break; - } - char quote = source[valueStart]; - if (quote != '\"' && quote != '\'') { - break; - } - size_t valueEnd = source.find(quote, valueStart + 1); - if (valueEnd == std::string::npos) { - break; - } - outValue = source.substr(valueStart + 1, valueEnd - valueStart - 1); - return true; - } - return false; -} - -float ParseFloatValue(const std::string& text) { - try { - size_t idx = 0; - return std::stof(text, &idx); - } catch (...) { - return 0.0f; - } -} - -GuiColor ParseColorString(const std::string& text, const GuiColor& fallback) { - if (text.empty() || text[0] != '#') { - return fallback; - } - try { - if (text.size() == 7) { - unsigned int rgb = std::stoul(text.substr(1), nullptr, 16); - return {((rgb >> 16) & 0xFF) / 255.0f, ((rgb >> 8) & 0xFF) / 255.0f, - (rgb & 0xFF) / 255.0f, 1.0f}; - } - if (text.size() == 9) { - unsigned int rgba = std::stoul(text.substr(1), nullptr, 16); - return {((rgba >> 24) & 0xFF) / 255.0f, ((rgba >> 16) & 0xFF) / 255.0f, - ((rgba >> 8) & 0xFF) / 255.0f, (rgba & 0xFF) / 255.0f}; - } - } catch (...) { - } - return fallback; -} - -ParsedSvg ParseSvgFile(const std::filesystem::path& path) { - std::ifstream file(path); - if (!file) { - throw std::runtime_error("Failed to open SVG file: " + path.string()); - } - std::string data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ParsedSvg result; - std::string value; - if (ExtractAttribute(data, "viewBox", value)) { - float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f; - std::sscanf(value.c_str(), "%f %f %f %f", &x, &y, &w, &h); - if (w > 0.0f && h > 0.0f) { - result.viewWidth = w; - result.viewHeight = h; - } - } - if (ExtractAttribute(data, "width", value)) { - result.viewWidth = ParseFloatValue(value); - } - if (ExtractAttribute(data, "height", value)) { - result.viewHeight = ParseFloatValue(value); - } - if (result.viewWidth <= 0.0f) { - result.viewWidth = 128.0f; - } - if (result.viewHeight <= 0.0f) { - result.viewHeight = 128.0f; - } - - size_t search = 0; - while (true) { - size_t tagStart = data.find("', tagStart); - if (tagEnd == std::string::npos) { - break; - } - std::string tag = data.substr(tagStart, tagEnd - tagStart); - SvgCircle circle; - std::string attr; - if (ExtractAttribute(tag, "cx", attr)) { - circle.cx = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "cy", attr)) { - circle.cy = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "r", attr)) { - circle.r = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "fill", attr)) { - circle.color = ParseColorString(attr, circle.color); - } - result.circles.push_back(circle); - search = tagEnd + 1; - } - return result; -} - -GuiCommand::RectData IntersectRect(const GuiCommand::RectData& a, - const GuiCommand::RectData& b) { - GuiCommand::RectData result; - result.x = std::max(a.x, b.x); - result.y = std::max(a.y, b.y); - float right = std::min(a.x + a.width, b.x + b.width); - float bottom = std::min(a.y + a.height, b.y + b.height); - result.width = std::max(0.0f, right - result.x); - result.height = std::max(0.0f, bottom - result.y); - return result; -} - -int ClampToRange(int value, int minimum, int maximum) { - return std::min(std::max(value, minimum), maximum); -} - -} // namespace - -class GuiRenderer::Canvas { -public: - using RectData = GuiCommand::RectData; - - void Resize(uint32_t width, uint32_t height) { - width_ = width; - height_ = height; - pixels_.assign(static_cast(width_) * static_cast(height_) * 4, 0); - clipStack_.clear(); - clipStack_.push_back({0.0f, 0.0f, static_cast(width_), static_cast(height_)}); - } - - void Clear() { - std::fill(pixels_.begin(), pixels_.end(), 0); - clipStack_.clear(); - clipStack_.push_back({0.0f, 0.0f, static_cast(width_), static_cast(height_)}); - } - - void PushClip(const RectData& rect) { - clipStack_.push_back(rect); - } - - void PopClip() { - if (clipStack_.size() > 1) { - clipStack_.pop_back(); - } - } - - void FillRect(const RectData& rect, const GuiColor& fillColor, - const GuiColor& borderColor, float borderWidth) { - DrawFilledRect(rect, fillColor); - if (borderWidth > 0.0f && borderColor.a > 0.0f) { - DrawFilledRect({rect.x, rect.y, rect.width, borderWidth}, borderColor); - DrawFilledRect({rect.x, rect.y + rect.height - borderWidth, rect.width, borderWidth}, borderColor); - DrawFilledRect({rect.x, rect.y + borderWidth, borderWidth, rect.height - borderWidth * 2.0f}, borderColor); - DrawFilledRect({rect.x + rect.width - borderWidth, rect.y + borderWidth, borderWidth, - rect.height - borderWidth * 2.0f}, borderColor); - } - } - - void DrawText(const std::string& text, const GuiColor& color, const RectData& bounds, - const std::string& alignX, const std::string& alignY, float fontSize) { - if (text.empty() || width_ == 0 || height_ == 0) { - return; - } - float scale = std::max(1.0f, fontSize / 8.0f); - float glyphWidth = 8.0f * scale; - float glyphHeight = 8.0f * scale; - float textWidth = glyphWidth * static_cast(text.size()); - float x = bounds.x; - if (alignX == "center") { - x += (bounds.width - textWidth) * 0.5f; - } else if (alignX == "right") { - x += bounds.width - textWidth; - } - float y = bounds.y; - if (alignY == "center") { - y += (bounds.height - glyphHeight) * 0.5f; - } else if (alignY == "bottom") { - y += bounds.height - glyphHeight; - } - for (size_t i = 0; i < text.size(); ++i) { - unsigned char code = static_cast(text[i]); - if (code >= 128) { - continue; - } - float glyphX = x + glyphWidth * static_cast(i); - for (int row = 0; row < 8; ++row) { - uint8_t pattern = static_cast(font8x8_basic[code][row]); - for (int col = 0; col < 8; ++col) { - if ((pattern & (1 << col)) == 0) { - continue; - } - RectData pixelRect{ - glyphX + static_cast(col) * scale, - y + static_cast(row) * scale, - scale, - scale, - }; - DrawFilledRect(pixelRect, color); - } - } - } - } - - void DrawSvg(const ParsedSvg& svg, const RectData& target, const GuiColor& tint) { - if (svg.circles.empty() || svg.viewWidth <= 0.0f || svg.viewHeight <= 0.0f || width_ == 0 || - height_ == 0) { - return; - } - RectData clipped = ClipRect(target); - if (clipped.width <= 0.0f || clipped.height <= 0.0f) { - return; - } - float scaleX = clipped.width / svg.viewWidth; - float scaleY = clipped.height / svg.viewHeight; - float scale = std::min(scaleX, scaleY); - for (const auto& circle : svg.circles) { - float cx = clipped.x + circle.cx * scaleX; - float cy = clipped.y + circle.cy * scaleY; - float radius = circle.r * scale; - GuiColor color = circle.color; - if (tint.a > 0.0f) { - color.r *= tint.r; - color.g *= tint.g; - color.b *= tint.b; - color.a *= tint.a; - } - int yStart = ClampToRange(static_cast(std::floor(cy - radius)), 0, static_cast(height_)); - int yEnd = ClampToRange(static_cast(std::ceil(cy + radius)), 0, static_cast(height_)); - for (int row = yStart; row < yEnd; ++row) { - float dy = (static_cast(row) + 0.5f) - cy; - float horizontalSpan = radius * radius - dy * dy; - if (horizontalSpan <= 0.0f) { - continue; - } - float span = std::sqrt(horizontalSpan); - RectData slice{ - cx - span, - static_cast(row), - 2.0f * span, - 1.0f, - }; - DrawFilledRect(slice, color); - } - } - } - - const std::vector& Pixels() const { - return pixels_; - } - -private: - RectData ClipRect(const RectData& rect) const { - RectData clipped = rect; - for (const auto& entry : clipStack_) { - clipped = IntersectRect(clipped, entry); - } - return clipped; - } - - void DrawFilledRect(const RectData& rect, const GuiColor& color) { - if (rect.width <= 0.0f || rect.height <= 0.0f) { - return; - } - RectData clipped = ClipRect(rect); - if (clipped.width <= 0.0f || clipped.height <= 0.0f) { - return; - } - int startX = ClampToRange(static_cast(std::floor(clipped.x)), 0, static_cast(width_)); - int startY = ClampToRange(static_cast(std::floor(clipped.y)), 0, static_cast(height_)); - int endX = ClampToRange(static_cast(std::ceil(clipped.x + clipped.width)), 0, static_cast(width_)); - int endY = ClampToRange(static_cast(std::ceil(clipped.y + clipped.height)), 0, static_cast(height_)); - for (int y = startY; y < endY; ++y) { - for (int x = startX; x < endX; ++x) { - BlendPixel(x, y, color); - } - } - } - - void BlendPixel(int x, int y, const GuiColor& color) { - size_t index = (static_cast(y) * width_ + static_cast(x)) * 4; - auto clampByte = [](float value) -> uint8_t { - return static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f); - }; - float destR = pixels_[index] / 255.0f; - float destG = pixels_[index + 1] / 255.0f; - float destB = pixels_[index + 2] / 255.0f; - float destA = pixels_[index + 3] / 255.0f; - float srcA = std::clamp(color.a, 0.0f, 1.0f); - float invSrc = 1.0f - srcA; - float outR = color.r * srcA + destR * invSrc; - float outG = color.g * srcA + destG * invSrc; - float outB = color.b * srcA + destB * invSrc; - float outA = srcA + destA * invSrc; - pixels_[index] = clampByte(outR); - pixels_[index + 1] = clampByte(outG); - pixels_[index + 2] = clampByte(outB); - pixels_[index + 3] = clampByte(outA); - } - - uint32_t width_ = 0; - uint32_t height_ = 0; - std::vector pixels_; - std::vector clipStack_; -}; - -GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat, - const std::filesystem::path& scriptDirectory, - std::shared_ptr bufferService) - : device_(device), - physicalDevice_(physicalDevice), - swapchainFormat_(swapchainFormat), - scriptDirectory_(scriptDirectory), - bufferService_(std::move(bufferService)) { -} - -GuiRenderer::~GuiRenderer() { - CleanupBuffers(); - CleanupPipeline(); -} - -bool GuiRenderer::IsReady() const { - return pipeline_ != VK_NULL_HANDLE && !vertices_.empty(); -} - - void GuiRenderer::Prepare(const std::vector& commands, uint32_t width, - uint32_t height) { - if (width == 0 || height == 0 || !canvas_) { - return; - } - EnsureCanvas(width, height); - canvas_->Clear(); - for (const auto& command : commands) { - switch (command.type) { - case GuiCommand::Type::Rect: - canvas_->FillRect(command.rect, command.color, command.borderColor, command.borderWidth); - break; - case GuiCommand::Type::Text: { - if (command.hasClipRect) { - canvas_->PushClip(command.clipRect); - } - if (command.hasBounds) { - canvas_->DrawText(command.text, command.color, command.bounds, command.alignX, - command.alignY, command.fontSize); - } else { - GuiCommand::RectData fallback{ - command.rect.x, command.rect.y, - command.fontSize * static_cast(std::max(1, command.text.size())), command.fontSize}; - canvas_->DrawText(command.text, command.color, fallback, command.alignX, - command.alignY, command.fontSize); - } - if (command.hasClipRect) { - canvas_->PopClip(); - } - break; - } - case GuiCommand::Type::ClipPush: - canvas_->PushClip(command.rect); - break; - case GuiCommand::Type::ClipPop: - canvas_->PopClip(); - break; - case GuiCommand::Type::Svg: - if (command.svgPath.empty()) { - break; - } - if (const ParsedSvg* svg = LoadSvg(command.svgPath)) { - canvas_->DrawSvg(*svg, command.rect, command.svgTint); - } - break; - } - } - UpdateStagingBuffer(); - } - - void GuiRenderer::BlitToSwapchain(VkCommandBuffer commandBuffer, VkImage image) { - if (!IsReady()) { - return; - } - - // Check if GUI canvas has any visible pixels (non-zero alpha) - // If all pixels are transparent, skip the blit entirely to avoid overwriting the scene - const auto& pixels = canvas_->Pixels(); - size_t pixelCount = static_cast(canvasWidth_) * canvasHeight_; - bool hasVisiblePixels = false; - for (size_t i = 0; i < pixelCount; ++i) { - if (pixels[i * 4 + 3] > 0) { // Check alpha channel - hasVisiblePixels = true; - break; - } - } - - if (!hasVisiblePixels) { - // GUI is fully transparent, don't blit to avoid black overlay - return; - } - - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = {0, 0, 0}; - region.imageExtent = {canvasWidth_, canvasHeight_, 1}; - - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer_, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, - ®ion); - - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = 0; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, - 0, nullptr, 0, nullptr, 1, &barrier); - } - - void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format) { - if (width == canvasWidth_ && height == canvasHeight_ && format == swapchainFormat_) { - return; - } - UpdateFormat(format); - EnsureCanvas(width, height); - } - - void GuiRenderer::EnsureCanvas(uint32_t width, uint32_t height) { - if (width == canvasWidth_ && height == canvasHeight_) { - return; - } - canvasWidth_ = width; - canvasHeight_ = height; - if (canvas_) { - canvas_->Resize(width, height); - } - size_t bufferSize = static_cast(canvasWidth_) * canvasHeight_ * 4; - CreateStagingBuffer(bufferSize); - } - - void GuiRenderer::UpdateStagingBuffer() { - if (!stagingMapped_ || !canvas_) { - return; - } - const auto& pixels = canvas_->Pixels(); - size_t pixelCount = static_cast(canvasWidth_) * canvasHeight_; - uint8_t* dest = reinterpret_cast(stagingMapped_); - - // Clear destination to fully transparent so pixels with alpha=0 don't overwrite the scene - std::memset(dest, 0, pixelCount * 4); - - for (size_t i = 0; i < pixelCount; ++i) { - size_t offset = i * 4; - uint8_t r = pixels[offset]; - uint8_t g = pixels[offset + 1]; - uint8_t b = pixels[offset + 2]; - uint8_t a = pixels[offset + 3]; - - // Skip fully transparent pixels to avoid overwriting the 3D scene - if (a == 0) { - continue; - } - - switch (swapchainFormat_) { - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_B8G8R8A8_SRGB: - dest[offset] = b; - dest[offset + 1] = g; - dest[offset + 2] = r; - dest[offset + 3] = a; - break; - case VK_FORMAT_R8G8B8A8_UNORM: - case VK_FORMAT_R8G8B8A8_SRGB: - default: - dest[offset] = r; - dest[offset + 1] = g; - dest[offset + 2] = b; - dest[offset + 3] = a; - break; - } - } - } - - void GuiRenderer::CreateStagingBuffer(size_t size) { - DestroyStagingBuffer(); - if (size == 0) { - return; - } - if (!bufferService_) { - throw std::runtime_error("Buffer service not available for GUI staging buffer"); - } - bufferService_->CreateBuffer(static_cast(size), - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - stagingBuffer_, stagingMemory_); - stagingSize_ = size; - vkMapMemory(device_, stagingMemory_, 0, stagingSize_, 0, &stagingMapped_); - } - - void GuiRenderer::DestroyStagingBuffer() { - if (stagingMapped_) { - vkUnmapMemory(device_, stagingMemory_); - stagingMapped_ = nullptr; - } - if (stagingBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, stagingBuffer_, nullptr); - stagingBuffer_ = VK_NULL_HANDLE; - } - if (stagingMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, stagingMemory_, nullptr); - stagingMemory_ = VK_NULL_HANDLE; - } - stagingSize_ = 0; - } - - void GuiRenderer::UpdateFormat(VkFormat format) { - if (swapchainFormat_ == format) { - return; - } - swapchainFormat_ = format; - DestroyStagingBuffer(); - if (canvasWidth_ > 0 && canvasHeight_ > 0) { - size_t bufferSize = static_cast(canvasWidth_) * canvasHeight_ * 4; - CreateStagingBuffer(bufferSize); - } - } - - const ParsedSvg* GuiRenderer::LoadSvg(const std::string& relativePath) { - auto it = svgCache_.find(relativePath); - if (it != svgCache_.end()) { - return &it->second; - } - std::filesystem::path path = scriptDirectory_ / relativePath; - try { - ParsedSvg parsed = ParseSvgFile(path); - auto inserted = svgCache_.emplace(relativePath, std::move(parsed)); - return &inserted.first->second; - } catch (...) { - return nullptr; - } - } - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gui_renderer.hpp b/src/services/impl/gui_renderer.hpp index d37a962..264de1a 100644 --- a/src/services/impl/gui_renderer.hpp +++ b/src/services/impl/gui_renderer.hpp @@ -44,7 +44,7 @@ public: void Prepare(const std::vector& commands, uint32_t width, uint32_t height); void RenderToSwapchain(VkCommandBuffer commandBuffer, VkRenderPass renderPass); - void Resize(uint32_t width, uint32_t height, VkFormat format); + void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass); bool IsReady() const; private: diff --git a/src/services/impl/gui_renderer_old.cpp b/src/services/impl/gui_renderer_old.cpp deleted file mode 100644 index baeac21..0000000 --- a/src/services/impl/gui_renderer_old.cpp +++ /dev/null @@ -1,635 +0,0 @@ -#include "gui_renderer.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "font8x8_basic.h" - -namespace sdl3cpp::services::impl { -namespace { - -bool ExtractAttribute(const std::string& source, const char* name, std::string& outValue) { - std::string key = name; - size_t pos = source.find(key); - while (pos != std::string::npos) { - size_t eq = source.find('=', pos + key.size()); - if (eq == std::string::npos) { - break; - } - size_t valueStart = eq + 1; - while (valueStart < source.size() && - std::isspace(static_cast(source[valueStart]))) { - valueStart++; - } - if (valueStart >= source.size()) { - break; - } - char quote = source[valueStart]; - if (quote != '\"' && quote != '\'') { - break; - } - size_t valueEnd = source.find(quote, valueStart + 1); - if (valueEnd == std::string::npos) { - break; - } - outValue = source.substr(valueStart + 1, valueEnd - valueStart - 1); - return true; - } - return false; -} - -float ParseFloatValue(const std::string& text) { - try { - size_t idx = 0; - return std::stof(text, &idx); - } catch (...) { - return 0.0f; - } -} - -GuiColor ParseColorString(const std::string& text, const GuiColor& fallback) { - if (text.empty() || text[0] != '#') { - return fallback; - } - try { - if (text.size() == 7) { - unsigned int rgb = std::stoul(text.substr(1), nullptr, 16); - return {((rgb >> 16) & 0xFF) / 255.0f, ((rgb >> 8) & 0xFF) / 255.0f, - (rgb & 0xFF) / 255.0f, 1.0f}; - } - if (text.size() == 9) { - unsigned int rgba = std::stoul(text.substr(1), nullptr, 16); - return {((rgba >> 24) & 0xFF) / 255.0f, ((rgba >> 16) & 0xFF) / 255.0f, - ((rgba >> 8) & 0xFF) / 255.0f, (rgba & 0xFF) / 255.0f}; - } - } catch (...) { - } - return fallback; -} - -ParsedSvg ParseSvgFile(const std::filesystem::path& path) { - std::ifstream file(path); - if (!file) { - throw std::runtime_error("Failed to open SVG file: " + path.string()); - } - std::string data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - ParsedSvg result; - std::string value; - if (ExtractAttribute(data, "viewBox", value)) { - float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f; - std::sscanf(value.c_str(), "%f %f %f %f", &x, &y, &w, &h); - if (w > 0.0f && h > 0.0f) { - result.viewWidth = w; - result.viewHeight = h; - } - } - if (ExtractAttribute(data, "width", value)) { - result.viewWidth = ParseFloatValue(value); - } - if (ExtractAttribute(data, "height", value)) { - result.viewHeight = ParseFloatValue(value); - } - if (result.viewWidth <= 0.0f) { - result.viewWidth = 128.0f; - } - if (result.viewHeight <= 0.0f) { - result.viewHeight = 128.0f; - } - - size_t search = 0; - while (true) { - size_t tagStart = data.find("', tagStart); - if (tagEnd == std::string::npos) { - break; - } - std::string tag = data.substr(tagStart, tagEnd - tagStart); - SvgCircle circle; - std::string attr; - if (ExtractAttribute(tag, "cx", attr)) { - circle.cx = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "cy", attr)) { - circle.cy = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "r", attr)) { - circle.r = ParseFloatValue(attr); - } - if (ExtractAttribute(tag, "fill", attr)) { - circle.color = ParseColorString(attr, circle.color); - } - result.circles.push_back(circle); - search = tagEnd + 1; - } - return result; -} - -GuiCommand::RectData IntersectRect(const GuiCommand::RectData& a, - const GuiCommand::RectData& b) { - GuiCommand::RectData result; - result.x = std::max(a.x, b.x); - result.y = std::max(a.y, b.y); - float right = std::min(a.x + a.width, b.x + b.width); - float bottom = std::min(a.y + a.height, b.y + b.height); - result.width = std::max(0.0f, right - result.x); - result.height = std::max(0.0f, bottom - result.y); - return result; -} - -int ClampToRange(int value, int minimum, int maximum) { - return std::min(std::max(value, minimum), maximum); -} - -} // namespace - -class GuiRenderer::Canvas { -public: - using RectData = GuiCommand::RectData; - - void Resize(uint32_t width, uint32_t height) { - width_ = width; - height_ = height; - pixels_.assign(static_cast(width_) * static_cast(height_) * 4, 0); - clipStack_.clear(); - clipStack_.push_back({0.0f, 0.0f, static_cast(width_), static_cast(height_)}); - } - - void Clear() { - std::fill(pixels_.begin(), pixels_.end(), 0); - clipStack_.clear(); - clipStack_.push_back({0.0f, 0.0f, static_cast(width_), static_cast(height_)}); - } - - void PushClip(const RectData& rect) { - clipStack_.push_back(rect); - } - - void PopClip() { - if (clipStack_.size() > 1) { - clipStack_.pop_back(); - } - } - - void FillRect(const RectData& rect, const GuiColor& fillColor, - const GuiColor& borderColor, float borderWidth) { - DrawFilledRect(rect, fillColor); - if (borderWidth > 0.0f && borderColor.a > 0.0f) { - DrawFilledRect({rect.x, rect.y, rect.width, borderWidth}, borderColor); - DrawFilledRect({rect.x, rect.y + rect.height - borderWidth, rect.width, borderWidth}, borderColor); - DrawFilledRect({rect.x, rect.y + borderWidth, borderWidth, rect.height - borderWidth * 2.0f}, borderColor); - DrawFilledRect({rect.x + rect.width - borderWidth, rect.y + borderWidth, borderWidth, - rect.height - borderWidth * 2.0f}, borderColor); - } - } - - void DrawText(const std::string& text, const GuiColor& color, const RectData& bounds, - const std::string& alignX, const std::string& alignY, float fontSize) { - if (text.empty() || width_ == 0 || height_ == 0) { - return; - } - float scale = std::max(1.0f, fontSize / 8.0f); - float glyphWidth = 8.0f * scale; - float glyphHeight = 8.0f * scale; - float textWidth = glyphWidth * static_cast(text.size()); - float x = bounds.x; - if (alignX == "center") { - x += (bounds.width - textWidth) * 0.5f; - } else if (alignX == "right") { - x += bounds.width - textWidth; - } - float y = bounds.y; - if (alignY == "center") { - y += (bounds.height - glyphHeight) * 0.5f; - } else if (alignY == "bottom") { - y += bounds.height - glyphHeight; - } - for (size_t i = 0; i < text.size(); ++i) { - unsigned char code = static_cast(text[i]); - if (code >= 128) { - continue; - } - float glyphX = x + glyphWidth * static_cast(i); - for (int row = 0; row < 8; ++row) { - uint8_t pattern = static_cast(font8x8_basic[code][row]); - for (int col = 0; col < 8; ++col) { - if ((pattern & (1 << col)) == 0) { - continue; - } - RectData pixelRect{ - glyphX + static_cast(col) * scale, - y + static_cast(row) * scale, - scale, - scale, - }; - DrawFilledRect(pixelRect, color); - } - } - } - } - - void DrawSvg(const ParsedSvg& svg, const RectData& target, const GuiColor& tint) { - if (svg.circles.empty() || svg.viewWidth <= 0.0f || svg.viewHeight <= 0.0f || width_ == 0 || - height_ == 0) { - return; - } - RectData clipped = ClipRect(target); - if (clipped.width <= 0.0f || clipped.height <= 0.0f) { - return; - } - float scaleX = clipped.width / svg.viewWidth; - float scaleY = clipped.height / svg.viewHeight; - float scale = std::min(scaleX, scaleY); - for (const auto& circle : svg.circles) { - float cx = clipped.x + circle.cx * scaleX; - float cy = clipped.y + circle.cy * scaleY; - float radius = circle.r * scale; - GuiColor color = circle.color; - if (tint.a > 0.0f) { - color.r *= tint.r; - color.g *= tint.g; - color.b *= tint.b; - color.a *= tint.a; - } - int yStart = ClampToRange(static_cast(std::floor(cy - radius)), 0, static_cast(height_)); - int yEnd = ClampToRange(static_cast(std::ceil(cy + radius)), 0, static_cast(height_)); - for (int row = yStart; row < yEnd; ++row) { - float dy = (static_cast(row) + 0.5f) - cy; - float horizontalSpan = radius * radius - dy * dy; - if (horizontalSpan <= 0.0f) { - continue; - } - float span = std::sqrt(horizontalSpan); - RectData slice{ - cx - span, - static_cast(row), - 2.0f * span, - 1.0f, - }; - DrawFilledRect(slice, color); - } - } - } - - const std::vector& Pixels() const { - return pixels_; - } - -private: - RectData ClipRect(const RectData& rect) const { - RectData clipped = rect; - for (const auto& entry : clipStack_) { - clipped = IntersectRect(clipped, entry); - } - return clipped; - } - - void DrawFilledRect(const RectData& rect, const GuiColor& color) { - if (rect.width <= 0.0f || rect.height <= 0.0f) { - return; - } - RectData clipped = ClipRect(rect); - if (clipped.width <= 0.0f || clipped.height <= 0.0f) { - return; - } - int startX = ClampToRange(static_cast(std::floor(clipped.x)), 0, static_cast(width_)); - int startY = ClampToRange(static_cast(std::floor(clipped.y)), 0, static_cast(height_)); - int endX = ClampToRange(static_cast(std::ceil(clipped.x + clipped.width)), 0, static_cast(width_)); - int endY = ClampToRange(static_cast(std::ceil(clipped.y + clipped.height)), 0, static_cast(height_)); - for (int y = startY; y < endY; ++y) { - for (int x = startX; x < endX; ++x) { - BlendPixel(x, y, color); - } - } - } - - void BlendPixel(int x, int y, const GuiColor& color) { - size_t index = (static_cast(y) * width_ + static_cast(x)) * 4; - auto clampByte = [](float value) -> uint8_t { - return static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f); - }; - float destR = pixels_[index] / 255.0f; - float destG = pixels_[index + 1] / 255.0f; - float destB = pixels_[index + 2] / 255.0f; - float destA = pixels_[index + 3] / 255.0f; - float srcA = std::clamp(color.a, 0.0f, 1.0f); - float invSrc = 1.0f - srcA; - float outR = color.r * srcA + destR * invSrc; - float outG = color.g * srcA + destG * invSrc; - float outB = color.b * srcA + destB * invSrc; - float outA = srcA + destA * invSrc; - pixels_[index] = clampByte(outR); - pixels_[index + 1] = clampByte(outG); - pixels_[index + 2] = clampByte(outB); - pixels_[index + 3] = clampByte(outA); - } - - uint32_t width_ = 0; - uint32_t height_ = 0; - std::vector pixels_; - std::vector clipStack_; -}; - -GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat, - VkRenderPass renderPass, const std::filesystem::path& scriptDirectory, - std::shared_ptr bufferService) - : device_(device), - physicalDevice_(physicalDevice), - swapchainFormat_(swapchainFormat), - renderPass_(renderPass), - scriptDirectory_(scriptDirectory), - bufferService_(std::move(bufferService)) { -} - -GuiRenderer::~GuiRenderer() { - CleanupBuffers(); - CleanupPipeline(); -} - -bool GuiRenderer::IsReady() const { - return pipeline_ != VK_NULL_HANDLE && !vertices_.empty(); -} - - void GuiRenderer::Prepare(const std::vector& commands, uint32_t width, - uint32_t height) { - if (width == 0 || height == 0 || !canvas_) { - return; - } - EnsureCanvas(width, height); - canvas_->Clear(); - for (const auto& command : commands) { - switch (command.type) { - case GuiCommand::Type::Rect: - canvas_->FillRect(command.rect, command.color, command.borderColor, command.borderWidth); - break; - case GuiCommand::Type::Text: { - if (command.hasClipRect) { - canvas_->PushClip(command.clipRect); - } - if (command.hasBounds) { - canvas_->DrawText(command.text, command.color, command.bounds, command.alignX, - command.alignY, command.fontSize); - } else { - GuiCommand::RectData fallback{ - command.rect.x, command.rect.y, - command.fontSize * static_cast(std::max(1, command.text.size())), command.fontSize}; - canvas_->DrawText(command.text, command.color, fallback, command.alignX, - command.alignY, command.fontSize); - } - if (command.hasClipRect) { - canvas_->PopClip(); - } - break; - } - case GuiCommand::Type::ClipPush: - canvas_->PushClip(command.rect); - break; - case GuiCommand::Type::ClipPop: - canvas_->PopClip(); - break; - case GuiCommand::Type::Svg: - if (command.svgPath.empty()) { - break; - } - if (const ParsedSvg* svg = LoadSvg(command.svgPath)) { - canvas_->DrawSvg(*svg, command.rect, command.svgTint); - } - break; - } - } - UpdateStagingBuffer(); - } - - void GuiRenderer::BlitToSwapchain(VkCommandBuffer commandBuffer, VkImage image) { - if (!IsReady()) { - return; - } - - // Check if GUI canvas has any visible pixels (non-zero alpha) - // If all pixels are transparent, skip the blit entirely to avoid overwriting the scene - const auto& pixels = canvas_->Pixels(); - size_t pixelCount = static_cast(canvasWidth_) * canvasHeight_; - bool hasVisiblePixels = false; - for (size_t i = 0; i < pixelCount; ++i) { - if (pixels[i * 4 + 3] > 0) { // Check alpha channel - hasVisiblePixels = true; - break; - } - } - - if (!hasVisiblePixels) { - // GUI is fully transparent, don't blit to avoid black overlay - return; - } - - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = {0, 0, 0}; - region.imageExtent = {canvasWidth_, canvasHeight_, 1}; - - vkCmdCopyBufferToImage(commandBuffer, stagingBuffer_, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, - ®ion); - - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - barrier.dstAccessMask = 0; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, - 0, nullptr, 0, nullptr, 1, &barrier); - } - - void GuiRenderer::Resize(uint32_t width, uint32_t height, VkFormat format) { - if (width == canvasWidth_ && height == canvasHeight_ && format == swapchainFormat_) { - return; - } - UpdateFormat(format); - EnsureCanvas(width, height); - } - - void GuiRenderer::EnsureCanvas(uint32_t width, uint32_t height) { - if (width == canvasWidth_ && height == canvasHeight_) { - return; - } - canvasWidth_ = width; - canvasHeight_ = height; - if (canvas_) { - canvas_->Resize(width, height); - } - size_t bufferSize = static_cast(canvasWidth_) * canvasHeight_ * 4; - CreateStagingBuffer(bufferSize); - } - - void GuiRenderer::UpdateStagingBuffer() { - if (!stagingMapped_ || !canvas_) { - return; - } - const auto& pixels = canvas_->Pixels(); - size_t pixelCount = static_cast(canvasWidth_) * canvasHeight_; - uint8_t* dest = reinterpret_cast(stagingMapped_); - - // Clear destination to fully transparent so pixels with alpha=0 don't overwrite the scene - std::memset(dest, 0, pixelCount * 4); - - for (size_t i = 0; i < pixelCount; ++i) { - size_t offset = i * 4; - uint8_t r = pixels[offset]; - uint8_t g = pixels[offset + 1]; - uint8_t b = pixels[offset + 2]; - uint8_t a = pixels[offset + 3]; - - // Skip fully transparent pixels to avoid overwriting the 3D scene - if (a == 0) { - continue; - } - - switch (swapchainFormat_) { - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_B8G8R8A8_SRGB: - dest[offset] = b; - dest[offset + 1] = g; - dest[offset + 2] = r; - dest[offset + 3] = a; - break; - case VK_FORMAT_R8G8B8A8_UNORM: - case VK_FORMAT_R8G8B8A8_SRGB: - default: - dest[offset] = r; - dest[offset + 1] = g; - dest[offset + 2] = b; - dest[offset + 3] = a; - break; - } - } - } - - void GuiRenderer::CreateStagingBuffer(size_t size) { - DestroyStagingBuffer(); - if (size == 0) { - return; - } - if (!bufferService_) { - throw std::runtime_error("Buffer service not available for GUI staging buffer"); - } - bufferService_->CreateBuffer(static_cast(size), - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - stagingBuffer_, stagingMemory_); - stagingSize_ = size; - vkMapMemory(device_, stagingMemory_, 0, stagingSize_, 0, &stagingMapped_); - } - - void GuiRenderer::DestroyStagingBuffer() { - if (stagingMapped_) { - vkUnmapMemory(device_, stagingMemory_); - stagingMapped_ = nullptr; - } - if (stagingBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, stagingBuffer_, nullptr); - stagingBuffer_ = VK_NULL_HANDLE; - } - if (stagingMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, stagingMemory_, nullptr); - stagingMemory_ = VK_NULL_HANDLE; - } - stagingSize_ = 0; - } - - void GuiRenderer::UpdateFormat(VkFormat format) { - if (swapchainFormat_ == format) { - return; - } - swapchainFormat_ = format; - DestroyStagingBuffer(); - if (canvasWidth_ > 0 && canvasHeight_ > 0) { - size_t bufferSize = static_cast(canvasWidth_) * canvasHeight_ * 4; - CreateStagingBuffer(bufferSize); - } - } - -void GuiRenderer::CleanupPipeline() { - if (pipeline_ != VK_NULL_HANDLE) { - vkDestroyPipeline(device_, pipeline_, nullptr); - pipeline_ = VK_NULL_HANDLE; - } - if (pipelineLayout_ != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(device_, pipelineLayout_, nullptr); - pipelineLayout_ = VK_NULL_HANDLE; - } - if (vertShaderModule_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device_, vertShaderModule_, nullptr); - vertShaderModule_ = VK_NULL_HANDLE; - } - if (fragShaderModule_ != VK_NULL_HANDLE) { - vkDestroyShaderModule(device_, fragShaderModule_, nullptr); - fragShaderModule_ = VK_NULL_HANDLE; - } -} - -void GuiRenderer::CleanupBuffers() { - if (vertexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, vertexBuffer_, nullptr); - vertexBuffer_ = VK_NULL_HANDLE; - } - if (vertexMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, vertexMemory_, nullptr); - vertexMemory_ = VK_NULL_HANDLE; - } - if (indexBuffer_ != VK_NULL_HANDLE) { - vkDestroyBuffer(device_, indexBuffer_, nullptr); - indexBuffer_ = VK_NULL_HANDLE; - } - if (indexMemory_ != VK_NULL_HANDLE) { - vkFreeMemory(device_, indexMemory_, nullptr); - indexMemory_ = VK_NULL_HANDLE; - } -} - -const ParsedSvg* GuiRenderer::LoadSvg(const std::string& relativePath) { - auto it = svgCache_.find(relativePath); - if (it != svgCache_.end()) { - return &it->second; - } - std::filesystem::path path = scriptDirectory_ / relativePath; - try { - ParsedSvg parsed = ParseSvgFile(path); - auto inserted = svgCache_.emplace(relativePath, std::move(parsed)); - return &inserted.first->second; - } catch (...) { - return nullptr; - } -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gui_renderer_service.cpp b/src/services/impl/gui_renderer_service.cpp index f635da5..9d4ac77 100644 --- a/src/services/impl/gui_renderer_service.cpp +++ b/src/services/impl/gui_renderer_service.cpp @@ -60,17 +60,18 @@ void GuiRendererService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImag renderer_->RenderToSwapchain(commandBuffer, VK_NULL_HANDLE); } -void GuiRendererService::Resize(uint32_t width, uint32_t height, VkFormat format) { +void GuiRendererService::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { if (logger_) { logger_->Trace("GuiRendererService", "Resize", "width=" + std::to_string(width) + ", height=" + std::to_string(height) + - ", format=" + std::to_string(static_cast(format))); + ", format=" + std::to_string(static_cast(format)) + + ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false")); } if (!renderer_) { return; } - renderer_->Resize(width, height, format); + renderer_->Resize(width, height, format, renderPass); } void GuiRendererService::Shutdown() noexcept { diff --git a/src/services/impl/gui_renderer_service.hpp b/src/services/impl/gui_renderer_service.hpp index 4a6322e..b9e9108 100644 --- a/src/services/impl/gui_renderer_service.hpp +++ b/src/services/impl/gui_renderer_service.hpp @@ -27,7 +27,7 @@ public: void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) override; - void Resize(uint32_t width, uint32_t height, VkFormat format) override; + void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) override; void Shutdown() noexcept override; diff --git a/src/services/impl/gxm_graphics_backend.cpp b/src/services/impl/gxm_graphics_backend.cpp index e858112..5796c45 100644 --- a/src/services/impl/gxm_graphics_backend.cpp +++ b/src/services/impl/gxm_graphics_backend.cpp @@ -31,7 +31,9 @@ GxmGraphicsBackend::GxmGraphicsBackend() initialized_(false), vdmRingBuffer_(nullptr), vertexRingBuffer_(nullptr), fragmentRingBuffer_(nullptr), fragmentUsseRingBuffer_(nullptr), vdmRingBufferUid_(0), vertexRingBufferUid_(0), fragmentRingBufferUid_(0), - fragmentUsseRingBufferUid_(0) { + fragmentUsseRingBufferUid_(0), + displayWidth_(DISPLAY_WIDTH), + displayHeight_(DISPLAY_HEIGHT) { } GxmGraphicsBackend::~GxmGraphicsBackend() { @@ -252,6 +254,16 @@ void GxmGraphicsBackend::Shutdown() { initialized_ = false; } +void GxmGraphicsBackend::RecreateSwapchain(uint32_t width, uint32_t height) { + std::cout << "GXM: Swapchain recreation not supported on Vita (" << width << "x" << height << ")" << std::endl; +} + +void GxmGraphicsBackend::WaitIdle() { + if (context_) { + sceGxmFinish(context_); + } +} + GraphicsDeviceHandle GxmGraphicsBackend::CreateDevice() { std::cout << "GXM: Creating device handle" << std::endl; return static_cast(context_); @@ -496,6 +508,26 @@ void GxmGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandl } } +GraphicsDeviceHandle GxmGraphicsBackend::GetPhysicalDevice() const { + return nullptr; +} + +std::pair GxmGraphicsBackend::GetSwapchainExtent() const { + return {displayWidth_, displayHeight_}; +} + +uint32_t GxmGraphicsBackend::GetSwapchainFormat() const { + return 0; +} + +void* GxmGraphicsBackend::GetCurrentCommandBuffer() const { + return nullptr; +} + +void* GxmGraphicsBackend::GetGraphicsQueue() const { + return nullptr; +} + // Helper methods int GxmGraphicsBackend::createDisplayBuffers() { @@ -599,4 +631,4 @@ void GxmGraphicsBackend::destroyShaderPrograms() { fragmentShaderIds_.clear(); } -} // namespace sdl3cpp::services::impl \ No newline at end of file +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/gxm_graphics_backend.hpp b/src/services/impl/gxm_graphics_backend.hpp index 26fa288..4e49216 100644 --- a/src/services/impl/gxm_graphics_backend.hpp +++ b/src/services/impl/gxm_graphics_backend.hpp @@ -20,6 +20,8 @@ public: void Initialize(void* window, const GraphicsConfig& config) override; void Shutdown() override; + void RecreateSwapchain(uint32_t width, uint32_t height) override; + void WaitIdle() override; GraphicsDeviceHandle CreateDevice() override; void DestroyDevice(GraphicsDeviceHandle device) override; @@ -40,6 +42,12 @@ public: GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexCount, const std::array& modelMatrix) override; + GraphicsDeviceHandle GetPhysicalDevice() const override; + std::pair GetSwapchainExtent() const override; + uint32_t GetSwapchainFormat() const override; + void* GetCurrentCommandBuffer() const override; + void* GetGraphicsQueue() const override; + private: // GXM-specific members SceGxmContext* context_; @@ -69,6 +77,9 @@ private: SceUID vertexRingBufferUid_; SceUID fragmentRingBufferUid_; SceUID fragmentUsseRingBufferUid_; + + uint32_t displayWidth_ = 0; + uint32_t displayHeight_ = 0; // Helper methods int createDisplayBuffers(); @@ -78,4 +89,4 @@ private: void destroyShaderPrograms(); }; -} // namespace sdl3cpp::services::impl \ No newline at end of file +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/render_command_service.cpp b/src/services/impl/render_command_service.cpp index 246bf7f..b01b41c 100644 --- a/src/services/impl/render_command_service.cpp +++ b/src/services/impl/render_command_service.cpp @@ -297,6 +297,20 @@ VkCommandBuffer RenderCommandService::GetCurrentCommandBuffer() const { return commandBuffers_[currentFrame_]; } +void RenderCommandService::OnSwapchainRecreated() { + logger_->Trace("RenderCommandService", "OnSwapchainRecreated"); + + Cleanup(); + currentFrame_ = 0; + + if (guiRendererService_) { + VkExtent2D extent = swapchainService_->GetSwapchainExtent(); + VkFormat format = swapchainService_->GetSwapchainImageFormat(); + VkRenderPass renderPass = swapchainService_->GetRenderPass(); + guiRendererService_->Resize(extent.width, extent.height, format, renderPass); + } +} + void RenderCommandService::CreateCommandPool() { logger_->Trace("RenderCommandService", "CreateCommandPool"); diff --git a/src/services/impl/render_command_service.hpp b/src/services/impl/render_command_service.hpp index c47efb5..86ac4e0 100644 --- a/src/services/impl/render_command_service.hpp +++ b/src/services/impl/render_command_service.hpp @@ -51,6 +51,7 @@ public: logger_->Trace("RenderCommandService", "GetMaxFramesInFlight"); return maxFramesInFlight_; } + void OnSwapchainRecreated() override; // IShutdownable interface void Shutdown() noexcept override; diff --git a/src/services/impl/render_coordinator_service.cpp b/src/services/impl/render_coordinator_service.cpp index f19ca80..a70babc 100644 --- a/src/services/impl/render_coordinator_service.cpp +++ b/src/services/impl/render_coordinator_service.cpp @@ -56,7 +56,13 @@ void RenderCoordinatorService::RenderFrame(float time) { shadersLoaded_ = true; } - graphicsService_->BeginFrame(); + if (!graphicsService_->BeginFrame()) { + if (logger_) { + logger_->Warn("RenderCoordinatorService::RenderFrame: Swapchain out of date during BeginFrame"); + } + graphicsService_->RecreateSwapchain(); + return; + } if (guiService_ && guiScriptService_ && guiScriptService_->HasGuiCommands()) { auto guiCommands = guiScriptService_->LoadGuiCommands(); @@ -100,7 +106,13 @@ void RenderCoordinatorService::RenderFrame(float time) { graphicsService_->RenderScene(renderCommands, viewProj); } - graphicsService_->EndFrame(); + if (!graphicsService_->EndFrame()) { + if (logger_) { + logger_->Warn("RenderCoordinatorService::RenderFrame: Swapchain out of date during EndFrame"); + } + graphicsService_->RecreateSwapchain(); + return; + } if (logger_) { logger_->Trace("RenderCoordinatorService", "RenderFrame", "", "Exiting"); diff --git a/src/services/impl/swapchain_service.cpp b/src/services/impl/swapchain_service.cpp index 8b5aeb2..9c6edf6 100644 --- a/src/services/impl/swapchain_service.cpp +++ b/src/services/impl/swapchain_service.cpp @@ -1,5 +1,6 @@ #include "swapchain_service.hpp" #include +#include #include #include @@ -393,6 +394,10 @@ VkExtent2D SwapchainService::ChooseExtent(const VkSurfaceCapabilitiesKHR& capabi ", minHeight=" + std::to_string(capabilities.minImageExtent.height) + ", maxWidth=" + std::to_string(capabilities.maxImageExtent.width) + ", maxHeight=" + std::to_string(capabilities.maxImageExtent.height)); + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + return VkExtent2D{ std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) diff --git a/src/services/impl/vulkan_device_service.cpp b/src/services/impl/vulkan_device_service.cpp index 672b3fb..1c09d05 100644 --- a/src/services/impl/vulkan_device_service.cpp +++ b/src/services/impl/vulkan_device_service.cpp @@ -1,10 +1,61 @@ #include "vulkan_device_service.hpp" #include +#include #include #include #include #include +namespace { + +VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + auto* logger = static_cast(pUserData); + std::string message = "Vulkan validation: "; + message += pCallbackData && pCallbackData->pMessage ? pCallbackData->pMessage : "Unknown message"; + + if (logger) { + if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + logger->Error(message); + } else if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + logger->Warn(message); + } else { + logger->Debug(message); + } + } else { + std::cerr << message << std::endl; + } + + (void)messageType; + return VK_FALSE; +} + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks* pAllocator) { + auto func = reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +} // namespace + namespace sdl3cpp::services::impl { VulkanDeviceService::VulkanDeviceService(std::shared_ptr logger) @@ -42,6 +93,7 @@ void VulkanDeviceService::Initialize(const std::vector& deviceExten device_ = VK_NULL_HANDLE; graphicsQueue_ = VK_NULL_HANDLE; presentQueue_ = VK_NULL_HANDLE; + debugMessenger_ = VK_NULL_HANDLE; // Get required extensions from SDL uint32_t extensionCount = 0; @@ -51,8 +103,16 @@ void VulkanDeviceService::Initialize(const std::vector& deviceExten } std::vector requiredExtensions(extensions, extensions + extensionCount); + if (validationLayersEnabled_) { + auto it = std::find(requiredExtensions.begin(), requiredExtensions.end(), + VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + if (it == requiredExtensions.end()) { + requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + } CreateInstance(requiredExtensions); + SetupDebugMessenger(); logger_->Trace("VulkanDeviceService", "Initialize", "instanceCreated=" + std::string(instance_ != VK_NULL_HANDLE ? "true" : "false") + ", selectionDeferredUntilSurface=true"); @@ -131,6 +191,13 @@ void VulkanDeviceService::CreateInstance(const std::vector& require createInfo.ppEnabledLayerNames = layerList.data(); createInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); createInfo.ppEnabledExtensionNames = requiredExtensions.data(); + createInfo.pNext = nullptr; + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (validationLayersEnabled_ && !layerList.empty()) { + PopulateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = &debugCreateInfo; + } if (vkCreateInstance(&createInfo, nullptr, &instance_) != VK_SUCCESS) { throw std::runtime_error("Failed to create Vulkan instance"); @@ -313,6 +380,34 @@ void VulkanDeviceService::CreateLogicalDevice() { vkGetDeviceQueue(device_, indices.presentFamily, 0, &presentQueue_); } +void VulkanDeviceService::PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) const { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = DebugCallback; + createInfo.pUserData = logger_.get(); +} + +void VulkanDeviceService::SetupDebugMessenger() { + logger_->Trace("VulkanDeviceService", "SetupDebugMessenger", + "validationLayersEnabled=" + std::string(validationLayersEnabled_ ? "true" : "false")); + if (!validationLayersEnabled_) { + return; + } + + VkDebugUtilsMessengerCreateInfoEXT createInfo{}; + PopulateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance_, &createInfo, nullptr, &debugMessenger_) != VK_SUCCESS) { + throw std::runtime_error("Failed to set up Vulkan debug messenger"); + } +} + void VulkanDeviceService::Shutdown() noexcept { logger_->Trace("VulkanDeviceService", "Shutdown"); if (device_ != VK_NULL_HANDLE) { @@ -325,6 +420,11 @@ void VulkanDeviceService::Shutdown() noexcept { surface_ = VK_NULL_HANDLE; } + if (debugMessenger_ != VK_NULL_HANDLE) { + DestroyDebugUtilsMessengerEXT(instance_, debugMessenger_, nullptr); + debugMessenger_ = VK_NULL_HANDLE; + } + if (instance_ != VK_NULL_HANDLE) { vkDestroyInstance(instance_, nullptr); instance_ = VK_NULL_HANDLE; diff --git a/src/services/impl/vulkan_device_service.hpp b/src/services/impl/vulkan_device_service.hpp index f927a0d..f5d5dac 100644 --- a/src/services/impl/vulkan_device_service.hpp +++ b/src/services/impl/vulkan_device_service.hpp @@ -67,12 +67,15 @@ private: VkDevice device_ = VK_NULL_HANDLE; VkQueue graphicsQueue_ = VK_NULL_HANDLE; VkQueue presentQueue_ = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debugMessenger_ = VK_NULL_HANDLE; std::vector deviceExtensions_; bool validationLayersEnabled_ = false; // Helper methods void CreateInstance(const std::vector& requiredExtensions); + void SetupDebugMessenger(); + void PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) const; void PickPhysicalDevice(); bool IsDeviceSuitable(VkPhysicalDevice device) const; QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device) const; diff --git a/src/services/impl/vulkan_graphics_backend.cpp b/src/services/impl/vulkan_graphics_backend.cpp index 5804fc5..1d2675b 100644 --- a/src/services/impl/vulkan_graphics_backend.cpp +++ b/src/services/impl/vulkan_graphics_backend.cpp @@ -1,7 +1,9 @@ #include "vulkan_graphics_backend.hpp" +#include "../../core/vertex.hpp" #include #include +#include #include #include @@ -54,7 +56,7 @@ void VulkanGraphicsBackend::Initialize(void* window, const GraphicsConfig& confi // Get window size for swapchain int width, height; - SDL_GetWindowSize(window_, &width, &height); + SDL_GetWindowSizeInPixels(window_, &width, &height); // Initialize swapchain swapchainService_->CreateSwapchain(static_cast(width), static_cast(height)); @@ -80,6 +82,33 @@ void VulkanGraphicsBackend::Shutdown() { initialized_ = false; } +void VulkanGraphicsBackend::RecreateSwapchain(uint32_t width, uint32_t height) { + logger_->Trace("VulkanGraphicsBackend", "RecreateSwapchain", + "width=" + std::to_string(width) + + ", height=" + std::to_string(height)); + + if (!initialized_) { + return; + } + + if (width == 0 || height == 0) { + logger_->Warn("VulkanGraphicsBackend::RecreateSwapchain: Skipping swapchain recreation for zero size"); + return; + } + + deviceService_->WaitIdle(); + swapchainService_->RecreateSwapchain(width, height); + + VkExtent2D extent = swapchainService_->GetSwapchainExtent(); + pipelineService_->RecreatePipelines(swapchainService_->GetRenderPass(), extent); + renderCommandService_->OnSwapchainRecreated(); +} + +void VulkanGraphicsBackend::WaitIdle() { + logger_->Trace("VulkanGraphicsBackend", "WaitIdle"); + deviceService_->WaitIdle(); +} + GraphicsDeviceHandle VulkanGraphicsBackend::CreateDevice() { logger_->Trace("VulkanGraphicsBackend", "CreateDevice"); // Device is already created in Initialize, just return a handle @@ -99,10 +128,12 @@ GraphicsPipelineHandle VulkanGraphicsBackend::CreatePipeline(GraphicsDeviceHandl // Compile pipeline with render pass from swapchain service // Note: This assumes swapchain service has created the render pass - VkExtent2D extent; - // TODO: Get extent from swapchain service - extent.width = 800; // Temporary - extent.height = 600; // Temporary + VkExtent2D extent = swapchainService_->GetSwapchainExtent(); + if (logger_) { + logger_->Trace("VulkanGraphicsBackend", "CreatePipeline", + "swapchainExtent=" + std::to_string(extent.width) + "x" + + std::to_string(extent.height)); + } pipelineService_->CompileAll(swapchainService_->GetRenderPass(), extent); @@ -124,18 +155,52 @@ void VulkanGraphicsBackend::DestroyPipeline(GraphicsDeviceHandle device, Graphic GraphicsBufferHandle VulkanGraphicsBackend::CreateVertexBuffer(GraphicsDeviceHandle device, const std::vector& data) { logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", "data.size=" + std::to_string(data.size())); - // For now, we'll use the buffer service's existing vertex buffer functionality - // This is a bit of a mismatch - the buffer service expects core::Vertex, but we get raw bytes - // TODO: Extend buffer service to handle raw buffer creation or create a new method + if (data.empty()) { + logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: No vertex data provided"); + return nullptr; + } + + if (data.size() % sizeof(core::Vertex) != 0) { + logger_->Error("VulkanGraphicsBackend::CreateVertexBuffer: Vertex data size is not aligned to Vertex"); + return nullptr; + } + + const size_t vertexCount = data.size() / sizeof(core::Vertex); + if (logger_) { + logger_->Trace("VulkanGraphicsBackend", "CreateVertexBuffer", + "vertexCount=" + std::to_string(vertexCount)); + } + + std::vector vertices(vertexCount); + std::memcpy(vertices.data(), data.data(), data.size()); + bufferService_->UploadVertexData(vertices); - // Return a dummy handle for now return reinterpret_cast(bufferService_->GetVertexBuffer()); } GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector& data) { logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", "data.size=" + std::to_string(data.size())); - // Similar issue as vertex buffer + if (data.empty()) { + logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: No index data provided"); + return nullptr; + } + + if (data.size() % sizeof(uint16_t) != 0) { + logger_->Error("VulkanGraphicsBackend::CreateIndexBuffer: Index data size is not aligned to uint16_t"); + return nullptr; + } + + const size_t indexCount = data.size() / sizeof(uint16_t); + if (logger_) { + logger_->Trace("VulkanGraphicsBackend", "CreateIndexBuffer", + "indexCount=" + std::to_string(indexCount)); + } + + std::vector indices(indexCount); + std::memcpy(indices.data(), data.data(), data.size()); + bufferService_->UploadIndexData(indices); + return reinterpret_cast(bufferService_->GetIndexBuffer()); } @@ -190,4 +255,30 @@ void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHa frameCommands_.push_back(command); } -} // namespace sdl3cpp::services::impl \ No newline at end of file +GraphicsDeviceHandle VulkanGraphicsBackend::GetPhysicalDevice() const { + logger_->Trace("VulkanGraphicsBackend", "GetPhysicalDevice"); + return reinterpret_cast(deviceService_->GetPhysicalDevice()); +} + +std::pair VulkanGraphicsBackend::GetSwapchainExtent() const { + logger_->Trace("VulkanGraphicsBackend", "GetSwapchainExtent"); + VkExtent2D extent = swapchainService_->GetSwapchainExtent(); + return {extent.width, extent.height}; +} + +uint32_t VulkanGraphicsBackend::GetSwapchainFormat() const { + logger_->Trace("VulkanGraphicsBackend", "GetSwapchainFormat"); + return static_cast(swapchainService_->GetSwapchainImageFormat()); +} + +void* VulkanGraphicsBackend::GetCurrentCommandBuffer() const { + logger_->Trace("VulkanGraphicsBackend", "GetCurrentCommandBuffer"); + return reinterpret_cast(renderCommandService_->GetCurrentCommandBuffer()); +} + +void* VulkanGraphicsBackend::GetGraphicsQueue() const { + logger_->Trace("VulkanGraphicsBackend", "GetGraphicsQueue"); + return reinterpret_cast(deviceService_->GetGraphicsQueue()); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_graphics_backend.hpp b/src/services/impl/vulkan_graphics_backend.hpp index 6044b53..d103d36 100644 --- a/src/services/impl/vulkan_graphics_backend.hpp +++ b/src/services/impl/vulkan_graphics_backend.hpp @@ -31,6 +31,8 @@ public: void Initialize(void* window, const GraphicsConfig& config) override; void Shutdown() override; + void RecreateSwapchain(uint32_t width, uint32_t height) override; + void WaitIdle() override; GraphicsDeviceHandle CreateDevice() override; void DestroyDevice(GraphicsDeviceHandle device) override; @@ -51,6 +53,12 @@ public: GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexCount, const std::array& modelMatrix) override; + GraphicsDeviceHandle GetPhysicalDevice() const override; + std::pair GetSwapchainExtent() const override; + uint32_t GetSwapchainFormat() const override; + void* GetCurrentCommandBuffer() const override; + void* GetGraphicsQueue() const override; + private: std::shared_ptr deviceService_; std::shared_ptr swapchainService_; @@ -67,4 +75,4 @@ private: std::unordered_map pipelineToShaderKey_; }; -} // namespace sdl3cpp::services::impl \ No newline at end of file +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_graphics_backend_old.cpp b/src/services/impl/vulkan_graphics_backend_old.cpp deleted file mode 100644 index b1e25da..0000000 --- a/src/services/impl/vulkan_graphics_backend_old.cpp +++ /dev/null @@ -1,688 +0,0 @@ -#include "vulkan_graphics_backend.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; - -const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME -}; - -struct QueueFamilyIndices { - std::optional graphicsFamily; - std::optional presentFamily; - - bool isComplete() { - return graphicsFamily.has_value() && presentFamily.has_value(); - } -}; - -struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; -}; - -VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - - return VK_FALSE; -} - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_FOUND; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -#ifdef NDEBUG -const bool enableValidationLayers = false; -#else -const bool enableValidationLayers = true; -#endif - -static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageType, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) { - std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; - return VK_FALSE; -} - -VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { - auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); - if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != nullptr) { - func(instance, debugMessenger, pAllocator); - } -} - -VulkanGraphicsBackend::VulkanGraphicsBackend() - : instance_(VK_NULL_HANDLE), physicalDevice_(VK_NULL_HANDLE), device_(VK_NULL_HANDLE), - graphicsQueue_(VK_NULL_HANDLE), surface_(VK_NULL_HANDLE), swapchain_(VK_NULL_HANDLE), - renderPass_(VK_NULL_HANDLE), commandPool_(VK_NULL_HANDLE), currentFrame_(0), initialized_(false) { -} - -VulkanGraphicsBackend::~VulkanGraphicsBackend() { - if (initialized_) { - Shutdown(); - } -} - -void VulkanGraphicsBackend::Initialize(void* window, const GraphicsConfig& config) { - if (initialized_) return; - - window_ = static_cast(window); - enableValidationLayers = config.enableValidationLayers; - - CreateInstance(window_); - SetupDebugMessenger(); - CreateSurface(window_); - PickPhysicalDevice(); - CreateLogicalDevice(); - CreateSwapChain(); - CreateImageViews(); - CreateRenderPass(); - CreateFramebuffers(); - CreateCommandPool(); - CreateCommandBuffers(); - CreateSyncObjects(); - - initialized_ = true; -} - -void VulkanGraphicsBackend::Shutdown() { - if (!initialized_) return; - - vkDeviceWaitIdle(device_); - - CleanupSwapChain(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vkDestroySemaphore(device_, renderFinishedSemaphores_[i], nullptr); - vkDestroySemaphore(device_, imageAvailableSemaphores_[i], nullptr); - vkDestroyFence(device_, inFlightFences_[i], nullptr); - } - - vkDestroyCommandPool(device_, commandPool_, nullptr); - vkDestroyDevice(device_, nullptr); - - if (enableValidationLayers) { - DestroyDebugUtilsMessengerEXT(instance_, debugMessenger_, nullptr); - } - - vkDestroySurfaceKHR(instance_, surface_, nullptr); - vkDestroyInstance(instance_, nullptr); - - initialized_ = false; -} - -GraphicsDeviceHandle VulkanGraphicsBackend::CreateDevice() { - // Return device handle - return static_cast(device_); -} - -void VulkanGraphicsBackend::DestroyDevice(GraphicsDeviceHandle device) { - // Device is destroyed in Shutdown -} - -GraphicsPipelineHandle VulkanGraphicsBackend::CreatePipeline(GraphicsDeviceHandle device, const ShaderPaths& shaderPaths) { - // TODO: Create pipeline - return nullptr; -} - -void VulkanGraphicsBackend::DestroyPipeline(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline) { - // TODO: Destroy pipeline -} - -GraphicsBufferHandle VulkanGraphicsBackend::CreateVertexBuffer(GraphicsDeviceHandle device, const std::vector& data) { - // TODO: Create vertex buffer - return nullptr; -} - -GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector& data) { - // TODO: Create index buffer - return nullptr; -} - -void VulkanGraphicsBackend::DestroyBuffer(GraphicsDeviceHandle device, GraphicsBufferHandle buffer) { - // TODO: Destroy buffer -} - -bool VulkanGraphicsBackend::BeginFrame(GraphicsDeviceHandle device) { - // TODO: Begin frame - return true; -} - -bool VulkanGraphicsBackend::EndFrame(GraphicsDeviceHandle device) { - // TODO: End frame - return true; -} - -void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, - GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, - uint32_t indexCount, const std::array& modelMatrix) { - // TODO: Draw -} - -void VulkanGraphicsBackend::CreateInstance(SDL_Window* window) { - if (enableValidationLayers && !CheckValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "SDL3CPlusPlus"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.pEngineName = "No Engine"; - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - auto extensions = GetRequiredExtensions(window); - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - PopulateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - createInfo.pNext = nullptr; - } - - if (vkCreateInstance(&createInfo, nullptr, &instance_) != VK_SUCCESS) { - throw std::runtime_error("failed to create instance!"); - } -} - -void VulkanGraphicsBackend::PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; -} - -void VulkanGraphicsBackend::SetupDebugMessenger() { - if (!enableValidationLayers) return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - PopulateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(instance_, &createInfo, nullptr, &debugMessenger_) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug messenger!"); - } -} - -void VulkanGraphicsBackend::CreateSurface(SDL_Window* window) { - if (!SDL_Vulkan_CreateSurface(window, instance_, &surface_)) { - throw std::runtime_error("failed to create window surface!"); - } -} - -void VulkanGraphicsBackend::PickPhysicalDevice() { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr); - - if (deviceCount == 0) { - throw std::runtime_error("failed to find GPUs with Vulkan support!"); - } - - std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data()); - - for (const auto& device : devices) { - if (IsDeviceSuitable(device)) { - physicalDevice_ = device; - break; - } - } - - if (physicalDevice_ == VK_NULL_HANDLE) { - throw std::runtime_error("failed to find a suitable GPU!"); - } -} - -void VulkanGraphicsBackend::CreateLogicalDevice() { - QueueFamilyIndices indices = FindQueueFamilies(physicalDevice_); - - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - float queuePriority = 1.0f; - for (uint32_t queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = queueFamily; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriority; - queueCreateInfos.push_back(queueCreateInfo); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createInfo.ppEnabledExtensionNames = deviceExtensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateDevice(physicalDevice_, &createInfo, nullptr, &device_) != VK_SUCCESS) { - throw std::runtime_error("failed to create logical device!"); - } - - vkGetDeviceQueue(device_, indices.graphicsFamily.value(), 0, &graphicsQueue_); - vkGetDeviceQueue(device_, indices.presentFamily.value(), 0, &presentQueue_); -} - -void VulkanGraphicsBackend::CreateSwapChain() { - SwapChainSupportDetails swapChainSupport = QuerySwapChainSupport(physicalDevice_); - - VkSurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = ChooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = ChooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createInfo.surface = surface_; - createInfo.minImageCount = imageCount; - createInfo.imageFormat = surfaceFormat.format; - createInfo.imageColorSpace = surfaceFormat.colorSpace; - createInfo.imageExtent = extent; - createInfo.imageArrayLayers = 1; - createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - QueueFamilyIndices indices = FindQueueFamilies(physicalDevice_); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - - if (indices.graphicsFamily != indices.presentFamily) { - createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createInfo.queueFamilyIndexCount = 2; - createInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - if (vkCreateSwapchainKHR(device_, &createInfo, nullptr, &swapchain_) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); - } - - vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, nullptr); - swapchainImages_.resize(imageCount); - vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, swapchainImages_.data()); - - swapchainImageFormat_ = surfaceFormat.format; - swapchainExtent_ = extent; -} - -void VulkanGraphicsBackend::CreateImageViews() { - swapchainImageViews_.resize(swapchainImages_.size()); - - for (size_t i = 0; i < swapchainImages_.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = swapchainImages_[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapchainImageFormat_; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - - if (vkCreateImageView(device_, &createInfo, nullptr, &swapchainImageViews_[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views!"); - } - } -} - -void VulkanGraphicsBackend::CreateRenderPass() { - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapchainImageFormat_; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - - VkSubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - VkRenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - - if (vkCreateRenderPass(device_, &renderPassInfo, nullptr, &renderPass_) != VK_SUCCESS) { - throw std::runtime_error("failed to create render pass!"); - } -} - -void VulkanGraphicsBackend::CreateFramebuffers() { - framebuffers_.resize(swapchainImageViews_.size()); - - for (size_t i = 0; i < swapchainImageViews_.size(); i++) { - VkImageView attachments[] = { - swapchainImageViews_[i] - }; - - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass_; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = swapchainExtent_.width; - framebufferInfo.height = swapchainExtent_.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device_, &framebufferInfo, nullptr, &framebuffers_[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create framebuffer!"); - } - } -} - -void VulkanGraphicsBackend::CreateCommandPool() { - QueueFamilyIndices queueFamilyIndices = FindQueueFamilies(physicalDevice_); - - VkCommandPoolCreateInfo poolInfo{}; - poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - - if (vkCreateCommandPool(device_, &poolInfo, nullptr, &commandPool_) != VK_SUCCESS) { - throw std::runtime_error("failed to create command pool!"); - } -} - -void VulkanGraphicsBackend::CreateCommandBuffers() { - commandBuffers_.resize(MAX_FRAMES_IN_FLIGHT); - - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool_; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = (uint32_t) commandBuffers_.size(); - - if (vkAllocateCommandBuffers(device_, &allocInfo, commandBuffers_.data()) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate command buffers!"); - } -} - -void VulkanGraphicsBackend::CreateSyncObjects() { - imageAvailableSemaphores_.resize(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores_.resize(MAX_FRAMES_IN_FLIGHT); - inFlightFences_.resize(MAX_FRAMES_IN_FLIGHT); - - VkSemaphoreCreateInfo semaphoreInfo{}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - VkFenceCreateInfo fenceInfo{}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - if (vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &imageAvailableSemaphores_[i]) != VK_SUCCESS || - vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &renderFinishedSemaphores_[i]) != VK_SUCCESS || - vkCreateFence(device_, &fenceInfo, nullptr, &inFlightFences_[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to create synchronization objects for a frame!"); - } - } -} - -void VulkanGraphicsBackend::CleanupSwapChain() { - for (auto framebuffer : framebuffers_) { - vkDestroyFramebuffer(device_, framebuffer, nullptr); - } - - for (auto imageView : swapchainImageViews_) { - vkDestroyImageView(device_, imageView, nullptr); - } - - vkDestroySwapchainKHR(device_, swapchain_, nullptr); -} - -std::vector VulkanGraphicsBackend::GetRequiredExtensions(SDL_Window* window) { - uint32_t sdlExtensionCount = 0; - const char** sdlExtensions = SDL_Vulkan_GetInstanceExtensions(&sdlExtensionCount); - - std::vector extensions(sdlExtensions, sdlExtensions + sdlExtensionCount); - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; -} - -bool VulkanGraphicsBackend::CheckValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - for (const char* layerName : validationLayers) { - bool layerFound = false; - - for (const auto& layerProperties : availableLayers) { - if (strcmp(layerName, layerProperties.layerName) == 0) { - layerFound = true; - break; - } - } - - if (!layerFound) { - return false; - } - } - - return true; -} - -bool VulkanGraphicsBackend::IsDeviceSuitable(VkPhysicalDevice device) { - QueueFamilyIndices indices = FindQueueFamilies(device); - - bool extensionsSupported = CheckDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = QuerySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return indices.isComplete() && extensionsSupported && swapChainAdequate; -} - -bool VulkanGraphicsBackend::CheckDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); - - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); -} - -QueueFamilyIndices VulkanGraphicsBackend::FindQueueFamilies(VkPhysicalDevice device) { - QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, &presentSupport); - - if (presentSupport) { - indices.presentFamily = i; - } - - if (indices.isComplete()) { - break; - } - - i++; - } - - return indices; -} - -SwapChainSupportDetails VulkanGraphicsBackend::QuerySwapChainSupport(VkPhysicalDevice device) { - SwapChainSupportDetails details; - - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, &details.capabilities); - - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, nullptr); - - if (formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, nullptr); - - if (presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, details.presentModes.data()); - } - - return details; -} - -VkSurfaceFormatKHR VulkanGraphicsBackend::ChooseSwapSurfaceFormat(const std::vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - return availableFormats[0]; -} - -VkPresentModeKHR VulkanGraphicsBackend::ChooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - - return VK_PRESENT_MODE_FIFO_KHR; -} - -VkExtent2D VulkanGraphicsBackend::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, SDL_Window* window) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - SDL_Vulkan_GetDrawableSize(window, &width, &height); - - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - - return actualExtent; - } -} - -} // namespace sdl3cpp::services::impl \ No newline at end of file diff --git a/src/services/impl/vulkan_gui_service.cpp b/src/services/impl/vulkan_gui_service.cpp index 090711f..f378959 100644 --- a/src/services/impl/vulkan_gui_service.cpp +++ b/src/services/impl/vulkan_gui_service.cpp @@ -74,14 +74,15 @@ void VulkanGuiService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage rendererService_->RenderToSwapchain(commandBuffer, image); } -void VulkanGuiService::Resize(uint32_t width, uint32_t height, VkFormat format) { +void VulkanGuiService::Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) { logger_->Trace("VulkanGuiService", "Resize", "width=" + std::to_string(width) + ", height=" + std::to_string(height) + - ", format=" + std::to_string(static_cast(format))); + ", format=" + std::to_string(static_cast(format)) + + ", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false")); if (rendererService_) { - rendererService_->Resize(width, height, format); + rendererService_->Resize(width, height, format, renderPass); } } diff --git a/src/services/impl/vulkan_gui_service.hpp b/src/services/impl/vulkan_gui_service.hpp index c17b1b9..b4c8883 100644 --- a/src/services/impl/vulkan_gui_service.hpp +++ b/src/services/impl/vulkan_gui_service.hpp @@ -34,7 +34,7 @@ public: void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) override; - void Resize(uint32_t width, uint32_t height, VkFormat format) override; + void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) override; void Shutdown() noexcept override; diff --git a/src/services/interfaces/i_graphics_backend.hpp b/src/services/interfaces/i_graphics_backend.hpp index 1ae126f..58ccbeb 100644 --- a/src/services/interfaces/i_graphics_backend.hpp +++ b/src/services/interfaces/i_graphics_backend.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "graphics_types.hpp" namespace sdl3cpp::services { @@ -51,6 +52,19 @@ public: */ virtual void Shutdown() = 0; + /** + * @brief Recreate the swapchain for a new window size. + * + * @param width New width in pixels + * @param height New height in pixels + */ + virtual void RecreateSwapchain(uint32_t width, uint32_t height) = 0; + + /** + * @brief Wait for GPU operations to complete. + */ + virtual void WaitIdle() = 0; + /** * @brief Create a graphics device. * @@ -145,6 +159,41 @@ public: virtual void Draw(GraphicsDeviceHandle device, GraphicsPipelineHandle pipeline, GraphicsBufferHandle vertexBuffer, GraphicsBufferHandle indexBuffer, uint32_t indexCount, const std::array& modelMatrix) = 0; + + /** + * @brief Get the physical device handle. + * + * @return Opaque physical device handle + */ + virtual GraphicsDeviceHandle GetPhysicalDevice() const = 0; + + /** + * @brief Get the swapchain extent. + * + * @return Width and height in pixels + */ + virtual std::pair GetSwapchainExtent() const = 0; + + /** + * @brief Get the swapchain image format. + * + * @return Format identifier + */ + virtual uint32_t GetSwapchainFormat() const = 0; + + /** + * @brief Get the current command buffer. + * + * @return Opaque command buffer handle + */ + virtual void* GetCurrentCommandBuffer() const = 0; + + /** + * @brief Get the graphics queue handle. + * + * @return Opaque queue handle + */ + virtual void* GetGraphicsQueue() const = 0; }; -} // namespace sdl3cpp::services \ No newline at end of file +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_gui_renderer_service.hpp b/src/services/interfaces/i_gui_renderer_service.hpp index 8b78c4a..c356ed2 100644 --- a/src/services/interfaces/i_gui_renderer_service.hpp +++ b/src/services/interfaces/i_gui_renderer_service.hpp @@ -23,7 +23,7 @@ public: virtual void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) = 0; - virtual void Resize(uint32_t width, uint32_t height, VkFormat format) = 0; + virtual void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) = 0; virtual void Shutdown() noexcept = 0; diff --git a/src/services/interfaces/i_gui_service.hpp b/src/services/interfaces/i_gui_service.hpp index fa76e64..3876d6e 100644 --- a/src/services/interfaces/i_gui_service.hpp +++ b/src/services/interfaces/i_gui_service.hpp @@ -68,7 +68,7 @@ public: * @param height New height in pixels * @param format Swapchain image format */ - virtual void Resize(uint32_t width, uint32_t height, VkFormat format) = 0; + virtual void Resize(uint32_t width, uint32_t height, VkFormat format, VkRenderPass renderPass) = 0; /** * @brief Shutdown and release GPU resources. diff --git a/src/services/interfaces/i_render_command_service.hpp b/src/services/interfaces/i_render_command_service.hpp index 339469d..76f4271 100644 --- a/src/services/interfaces/i_render_command_service.hpp +++ b/src/services/interfaces/i_render_command_service.hpp @@ -75,6 +75,13 @@ public: * @return Max concurrent frames */ virtual uint32_t GetMaxFramesInFlight() const = 0; + + /** + * @brief Handle swapchain recreation. + * + * Resets command buffers/synchronization and updates GUI renderer state. + */ + virtual void OnSwapchainRecreated() = 0; }; } // namespace sdl3cpp::services