mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-25 22:25:07 +00:00
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.
This commit is contained in:
@@ -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<uint32_t, uint32_t>{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<std::string, ShaderPaths>& 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<uint32_t, uint32_t> GraphicsService::GetSwapchainExtent() const {
|
||||
@@ -204,8 +214,7 @@ std::pair<uint32_t, uint32_t> 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
|
||||
|
||||
@@ -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<ClipPoint> ClipPolygonToRect(const std::vector<ClipPoint>& polygon,
|
||||
const GuiCommand::RectData& rect) {
|
||||
std::vector<ClipPoint> output = polygon;
|
||||
for (ClipEdge edge : {ClipEdge::Left, ClipEdge::Right, ClipEdge::Top, ClipEdge::Bottom}) {
|
||||
if (output.empty()) {
|
||||
break;
|
||||
}
|
||||
std::vector<ClipPoint> 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<uint8_t> 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<uint32_t>(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<GuiCommand>& commands, u
|
||||
};
|
||||
};
|
||||
|
||||
std::vector<GuiCommand::RectData> clipStack;
|
||||
clipStack.push_back({0.0f, 0.0f, static_cast<float>(width), static_cast<float>(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<uint32_t>(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<ClipPoint>& polygon,
|
||||
const GuiColor& color,
|
||||
const GuiCommand::RectData& clipRect) {
|
||||
std::vector<ClipPoint> clipped = ClipPolygonToRect(polygon, clipRect);
|
||||
if (clipped.size() < 3) {
|
||||
return;
|
||||
}
|
||||
uint32_t baseIndex = static_cast<uint32_t>(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<uint32_t>(i));
|
||||
indices_.push_back(baseIndex + static_cast<uint32_t>(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<uint32_t>(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<GuiCommand>& 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<float>(std::max<size_t>(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<GuiCommand>& 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<uint32_t>(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<int>(radius * 0.25f));
|
||||
const float twoPi = 6.283185307179586f;
|
||||
ClipPoint center{cx, cy};
|
||||
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
float angle0 = twoPi * static_cast<float>(i) / static_cast<float>(segments);
|
||||
float angle1 = twoPi * static_cast<float>(i + 1) / static_cast<float>(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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,596 +0,0 @@
|
||||
#include "gui_renderer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<unsigned char>(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<char>(file)), std::istreambuf_iterator<char>());
|
||||
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("<circle", search);
|
||||
if (tagStart == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
size_t tagEnd = 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<size_t>(width_) * static_cast<size_t>(height_) * 4, 0);
|
||||
clipStack_.clear();
|
||||
clipStack_.push_back({0.0f, 0.0f, static_cast<float>(width_), static_cast<float>(height_)});
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::fill(pixels_.begin(), pixels_.end(), 0);
|
||||
clipStack_.clear();
|
||||
clipStack_.push_back({0.0f, 0.0f, static_cast<float>(width_), static_cast<float>(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<float>(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<unsigned char>(text[i]);
|
||||
if (code >= 128) {
|
||||
continue;
|
||||
}
|
||||
float glyphX = x + glyphWidth * static_cast<float>(i);
|
||||
for (int row = 0; row < 8; ++row) {
|
||||
uint8_t pattern = static_cast<uint8_t>(font8x8_basic[code][row]);
|
||||
for (int col = 0; col < 8; ++col) {
|
||||
if ((pattern & (1 << col)) == 0) {
|
||||
continue;
|
||||
}
|
||||
RectData pixelRect{
|
||||
glyphX + static_cast<float>(col) * scale,
|
||||
y + static_cast<float>(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<int>(std::floor(cy - radius)), 0, static_cast<int>(height_));
|
||||
int yEnd = ClampToRange(static_cast<int>(std::ceil(cy + radius)), 0, static_cast<int>(height_));
|
||||
for (int row = yStart; row < yEnd; ++row) {
|
||||
float dy = (static_cast<float>(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<float>(row),
|
||||
2.0f * span,
|
||||
1.0f,
|
||||
};
|
||||
DrawFilledRect(slice, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& 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<int>(std::floor(clipped.x)), 0, static_cast<int>(width_));
|
||||
int startY = ClampToRange(static_cast<int>(std::floor(clipped.y)), 0, static_cast<int>(height_));
|
||||
int endX = ClampToRange(static_cast<int>(std::ceil(clipped.x + clipped.width)), 0, static_cast<int>(width_));
|
||||
int endY = ClampToRange(static_cast<int>(std::ceil(clipped.y + clipped.height)), 0, static_cast<int>(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<size_t>(y) * width_ + static_cast<size_t>(x)) * 4;
|
||||
auto clampByte = [](float value) -> uint8_t {
|
||||
return static_cast<uint8_t>(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<uint8_t> pixels_;
|
||||
std::vector<RectData> clipStack_;
|
||||
};
|
||||
|
||||
GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat,
|
||||
const std::filesystem::path& scriptDirectory,
|
||||
std::shared_ptr<IBufferService> 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<GuiCommand>& 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<float>(std::max<size_t>(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<size_t>(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<size_t>(canvasWidth_) * canvasHeight_ * 4;
|
||||
CreateStagingBuffer(bufferSize);
|
||||
}
|
||||
|
||||
void GuiRenderer::UpdateStagingBuffer() {
|
||||
if (!stagingMapped_ || !canvas_) {
|
||||
return;
|
||||
}
|
||||
const auto& pixels = canvas_->Pixels();
|
||||
size_t pixelCount = static_cast<size_t>(canvasWidth_) * canvasHeight_;
|
||||
uint8_t* dest = reinterpret_cast<uint8_t*>(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<VkDeviceSize>(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<size_t>(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
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
void Prepare(const std::vector<GuiCommand>& 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:
|
||||
|
||||
@@ -1,635 +0,0 @@
|
||||
#include "gui_renderer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<unsigned char>(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<char>(file)), std::istreambuf_iterator<char>());
|
||||
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("<circle", search);
|
||||
if (tagStart == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
size_t tagEnd = 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<size_t>(width_) * static_cast<size_t>(height_) * 4, 0);
|
||||
clipStack_.clear();
|
||||
clipStack_.push_back({0.0f, 0.0f, static_cast<float>(width_), static_cast<float>(height_)});
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::fill(pixels_.begin(), pixels_.end(), 0);
|
||||
clipStack_.clear();
|
||||
clipStack_.push_back({0.0f, 0.0f, static_cast<float>(width_), static_cast<float>(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<float>(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<unsigned char>(text[i]);
|
||||
if (code >= 128) {
|
||||
continue;
|
||||
}
|
||||
float glyphX = x + glyphWidth * static_cast<float>(i);
|
||||
for (int row = 0; row < 8; ++row) {
|
||||
uint8_t pattern = static_cast<uint8_t>(font8x8_basic[code][row]);
|
||||
for (int col = 0; col < 8; ++col) {
|
||||
if ((pattern & (1 << col)) == 0) {
|
||||
continue;
|
||||
}
|
||||
RectData pixelRect{
|
||||
glyphX + static_cast<float>(col) * scale,
|
||||
y + static_cast<float>(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<int>(std::floor(cy - radius)), 0, static_cast<int>(height_));
|
||||
int yEnd = ClampToRange(static_cast<int>(std::ceil(cy + radius)), 0, static_cast<int>(height_));
|
||||
for (int row = yStart; row < yEnd; ++row) {
|
||||
float dy = (static_cast<float>(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<float>(row),
|
||||
2.0f * span,
|
||||
1.0f,
|
||||
};
|
||||
DrawFilledRect(slice, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& 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<int>(std::floor(clipped.x)), 0, static_cast<int>(width_));
|
||||
int startY = ClampToRange(static_cast<int>(std::floor(clipped.y)), 0, static_cast<int>(height_));
|
||||
int endX = ClampToRange(static_cast<int>(std::ceil(clipped.x + clipped.width)), 0, static_cast<int>(width_));
|
||||
int endY = ClampToRange(static_cast<int>(std::ceil(clipped.y + clipped.height)), 0, static_cast<int>(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<size_t>(y) * width_ + static_cast<size_t>(x)) * 4;
|
||||
auto clampByte = [](float value) -> uint8_t {
|
||||
return static_cast<uint8_t>(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<uint8_t> pixels_;
|
||||
std::vector<RectData> clipStack_;
|
||||
};
|
||||
|
||||
GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat swapchainFormat,
|
||||
VkRenderPass renderPass, const std::filesystem::path& scriptDirectory,
|
||||
std::shared_ptr<IBufferService> 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<GuiCommand>& 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<float>(std::max<size_t>(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<size_t>(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<size_t>(canvasWidth_) * canvasHeight_ * 4;
|
||||
CreateStagingBuffer(bufferSize);
|
||||
}
|
||||
|
||||
void GuiRenderer::UpdateStagingBuffer() {
|
||||
if (!stagingMapped_ || !canvas_) {
|
||||
return;
|
||||
}
|
||||
const auto& pixels = canvas_->Pixels();
|
||||
size_t pixelCount = static_cast<size_t>(canvasWidth_) * canvasHeight_;
|
||||
uint8_t* dest = reinterpret_cast<uint8_t*>(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<VkDeviceSize>(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<size_t>(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
|
||||
@@ -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<uint32_t>(format)));
|
||||
", format=" + std::to_string(static_cast<uint32_t>(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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<GraphicsDeviceHandle>(context_);
|
||||
@@ -496,6 +508,26 @@ void GxmGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHandl
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsDeviceHandle GxmGraphicsBackend::GetPhysicalDevice() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> 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
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -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<float, 16>& modelMatrix) override;
|
||||
|
||||
GraphicsDeviceHandle GetPhysicalDevice() const override;
|
||||
std::pair<uint32_t, uint32_t> 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
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
logger_->Trace("RenderCommandService", "GetMaxFramesInFlight");
|
||||
return maxFramesInFlight_;
|
||||
}
|
||||
void OnSwapchainRecreated() override;
|
||||
|
||||
// IShutdownable interface
|
||||
void Shutdown() noexcept override;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "swapchain_service.hpp"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -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<uint32_t>::max()) {
|
||||
return capabilities.currentExtent;
|
||||
}
|
||||
|
||||
return VkExtent2D{
|
||||
std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
||||
std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
|
||||
|
||||
@@ -1,10 +1,61 @@
|
||||
#include "vulkan_device_service.hpp"
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData) {
|
||||
auto* logger = static_cast<sdl3cpp::services::ILogger*>(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<PFN_vkCreateDebugUtilsMessengerEXT>(
|
||||
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<PFN_vkDestroyDebugUtilsMessengerEXT>(
|
||||
vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
|
||||
if (func != nullptr) {
|
||||
func(instance, debugMessenger, pAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
VulkanDeviceService::VulkanDeviceService(std::shared_ptr<ILogger> logger)
|
||||
@@ -42,6 +93,7 @@ void VulkanDeviceService::Initialize(const std::vector<const char*>& 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<const char*>& deviceExten
|
||||
}
|
||||
|
||||
std::vector<const char*> 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<const char*>& require
|
||||
createInfo.ppEnabledLayerNames = layerList.data();
|
||||
createInfo.enabledExtensionCount = static_cast<uint32_t>(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;
|
||||
|
||||
@@ -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<const char*> deviceExtensions_;
|
||||
bool validationLayersEnabled_ = false;
|
||||
|
||||
// Helper methods
|
||||
void CreateInstance(const std::vector<const char*>& requiredExtensions);
|
||||
void SetupDebugMessenger();
|
||||
void PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) const;
|
||||
void PickPhysicalDevice();
|
||||
bool IsDeviceSuitable(VkPhysicalDevice device) const;
|
||||
QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device) const;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "vulkan_graphics_backend.hpp"
|
||||
|
||||
#include "../../core/vertex.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<uint32_t>(width), static_cast<uint32_t>(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<uint8_t>& 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<core::Vertex> vertices(vertexCount);
|
||||
std::memcpy(vertices.data(), data.data(), data.size());
|
||||
bufferService_->UploadVertexData(vertices);
|
||||
|
||||
// Return a dummy handle for now
|
||||
return reinterpret_cast<GraphicsBufferHandle>(bufferService_->GetVertexBuffer());
|
||||
}
|
||||
|
||||
GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector<uint8_t>& 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<uint16_t> indices(indexCount);
|
||||
std::memcpy(indices.data(), data.data(), data.size());
|
||||
bufferService_->UploadIndexData(indices);
|
||||
|
||||
return reinterpret_cast<GraphicsBufferHandle>(bufferService_->GetIndexBuffer());
|
||||
}
|
||||
|
||||
@@ -190,4 +255,30 @@ void VulkanGraphicsBackend::Draw(GraphicsDeviceHandle device, GraphicsPipelineHa
|
||||
frameCommands_.push_back(command);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
GraphicsDeviceHandle VulkanGraphicsBackend::GetPhysicalDevice() const {
|
||||
logger_->Trace("VulkanGraphicsBackend", "GetPhysicalDevice");
|
||||
return reinterpret_cast<GraphicsDeviceHandle>(deviceService_->GetPhysicalDevice());
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> 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<uint32_t>(swapchainService_->GetSwapchainImageFormat());
|
||||
}
|
||||
|
||||
void* VulkanGraphicsBackend::GetCurrentCommandBuffer() const {
|
||||
logger_->Trace("VulkanGraphicsBackend", "GetCurrentCommandBuffer");
|
||||
return reinterpret_cast<void*>(renderCommandService_->GetCurrentCommandBuffer());
|
||||
}
|
||||
|
||||
void* VulkanGraphicsBackend::GetGraphicsQueue() const {
|
||||
logger_->Trace("VulkanGraphicsBackend", "GetGraphicsQueue");
|
||||
return reinterpret_cast<void*>(deviceService_->GetGraphicsQueue());
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -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<float, 16>& modelMatrix) override;
|
||||
|
||||
GraphicsDeviceHandle GetPhysicalDevice() const override;
|
||||
std::pair<uint32_t, uint32_t> GetSwapchainExtent() const override;
|
||||
uint32_t GetSwapchainFormat() const override;
|
||||
void* GetCurrentCommandBuffer() const override;
|
||||
void* GetGraphicsQueue() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<IVulkanDeviceService> deviceService_;
|
||||
std::shared_ptr<ISwapchainService> swapchainService_;
|
||||
@@ -67,4 +75,4 @@ private:
|
||||
std::unordered_map<GraphicsPipelineHandle, std::string> pipelineToShaderKey_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -1,688 +0,0 @@
|
||||
#include "vulkan_graphics_backend.hpp"
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
const std::vector<const char*> validationLayers = {
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
};
|
||||
|
||||
const std::vector<const char*> deviceExtensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME
|
||||
};
|
||||
|
||||
struct QueueFamilyIndices {
|
||||
std::optional<uint32_t> graphicsFamily;
|
||||
std::optional<uint32_t> presentFamily;
|
||||
|
||||
bool isComplete() {
|
||||
return graphicsFamily.has_value() && presentFamily.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapChainSupportDetails {
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
std::vector<VkSurfaceFormatKHR> formats;
|
||||
std::vector<VkPresentModeKHR> 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<SDL_Window*>(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<GraphicsDeviceHandle>(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<uint8_t>& data) {
|
||||
// TODO: Create vertex buffer
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GraphicsBufferHandle VulkanGraphicsBackend::CreateIndexBuffer(GraphicsDeviceHandle device, const std::vector<uint8_t>& 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<float, 16>& 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<uint32_t>(extensions.size());
|
||||
createInfo.ppEnabledExtensionNames = extensions.data();
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
|
||||
if (enableValidationLayers) {
|
||||
createInfo.enabledLayerCount = static_cast<uint32_t>(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<VkPhysicalDevice> 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<VkDeviceQueueCreateInfo> queueCreateInfos;
|
||||
std::set<uint32_t> 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<uint32_t>(queueCreateInfos.size());
|
||||
createInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
|
||||
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
|
||||
if (enableValidationLayers) {
|
||||
createInfo.enabledLayerCount = static_cast<uint32_t>(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<const char*> VulkanGraphicsBackend::GetRequiredExtensions(SDL_Window* window) {
|
||||
uint32_t sdlExtensionCount = 0;
|
||||
const char** sdlExtensions = SDL_Vulkan_GetInstanceExtensions(&sdlExtensionCount);
|
||||
|
||||
std::vector<const char*> 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<VkLayerProperties> 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<VkExtensionProperties> availableExtensions(extensionCount);
|
||||
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
|
||||
|
||||
std::set<std::string> 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<VkQueueFamilyProperties> 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<VkSurfaceFormatKHR>& 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<VkPresentModeKHR>& 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<uint32_t>::max()) {
|
||||
return capabilities.currentExtent;
|
||||
} else {
|
||||
int width, height;
|
||||
SDL_Vulkan_GetDrawableSize(window, &width, &height);
|
||||
|
||||
VkExtent2D actualExtent = {
|
||||
static_cast<uint32_t>(width),
|
||||
static_cast<uint32_t>(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
|
||||
@@ -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<uint32_t>(format)));
|
||||
", format=" + std::to_string(static_cast<uint32_t>(format)) +
|
||||
", renderPassIsNull=" + std::string(renderPass == VK_NULL_HANDLE ? "true" : "false"));
|
||||
|
||||
if (rendererService_) {
|
||||
rendererService_->Resize(width, height, format);
|
||||
rendererService_->Resize(width, height, format, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
#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<float, 16>& 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<uint32_t, uint32_t> 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
|
||||
} // namespace sdl3cpp::services
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user