mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 21:55:09 +00:00
358 lines
16 KiB
C++
358 lines
16 KiB
C++
#include "app/sdl3_app.hpp"
|
|
#include "app/trace.hpp"
|
|
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
|
|
namespace {
|
|
const std::unordered_map<SDL_Keycode, std::string> kGuiKeyNames = {
|
|
{SDLK_BACKSPACE, "backspace"},
|
|
{SDLK_DELETE, "delete"},
|
|
{SDLK_LEFT, "left"},
|
|
{SDLK_RIGHT, "right"},
|
|
{SDLK_HOME, "home"},
|
|
{SDLK_END, "end"},
|
|
{SDLK_RETURN, "enter"},
|
|
{SDLK_UP, "up"},
|
|
{SDLK_DOWN, "down"},
|
|
};
|
|
} // namespace
|
|
|
|
namespace sdl3cpp::app {
|
|
|
|
void Sdl3App::PrintGpuDiagnostics(const std::string& errorContext) {
|
|
std::cerr << "\n========================================\n";
|
|
std::cerr << "GPU DIAGNOSTIC REPORT\n";
|
|
std::cerr << "========================================\n";
|
|
std::cerr << "Error Context: " << errorContext << "\n\n";
|
|
|
|
// Device properties
|
|
if (physicalDevice_ != VK_NULL_HANDLE) {
|
|
VkPhysicalDeviceProperties deviceProps{};
|
|
vkGetPhysicalDeviceProperties(physicalDevice_, &deviceProps);
|
|
|
|
std::cerr << "=== GPU Information ===\n";
|
|
std::cerr << "Device Name: " << deviceProps.deviceName << "\n";
|
|
std::cerr << "Driver Version: " << VK_API_VERSION_MAJOR(deviceProps.driverVersion) << "."
|
|
<< VK_API_VERSION_MINOR(deviceProps.driverVersion) << "."
|
|
<< VK_API_VERSION_PATCH(deviceProps.driverVersion) << "\n";
|
|
std::cerr << "API Version: " << VK_API_VERSION_MAJOR(deviceProps.apiVersion) << "."
|
|
<< VK_API_VERSION_MINOR(deviceProps.apiVersion) << "."
|
|
<< VK_API_VERSION_PATCH(deviceProps.apiVersion) << "\n";
|
|
std::cerr << "Vendor ID: 0x" << std::hex << deviceProps.vendorID << std::dec << "\n";
|
|
std::cerr << "Device ID: 0x" << std::hex << deviceProps.deviceID << std::dec << "\n";
|
|
|
|
VkPhysicalDeviceMemoryProperties memProps{};
|
|
vkGetPhysicalDeviceMemoryProperties(physicalDevice_, &memProps);
|
|
|
|
std::cerr << "\n=== Memory Information ===\n";
|
|
uint64_t totalVRAM = 0;
|
|
for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) {
|
|
if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
|
|
totalVRAM += memProps.memoryHeaps[i].size;
|
|
}
|
|
}
|
|
std::cerr << "Total VRAM: " << (totalVRAM / 1024 / 1024) << " MB\n";
|
|
|
|
// Memory heaps breakdown
|
|
std::cerr << "Memory Heaps (" << memProps.memoryHeapCount << "):\n";
|
|
for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) {
|
|
std::cerr << " Heap " << i << ": " << (memProps.memoryHeaps[i].size / 1024 / 1024) << " MB";
|
|
if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
|
|
std::cerr << " (Device Local)";
|
|
}
|
|
std::cerr << "\n";
|
|
}
|
|
}
|
|
|
|
// Swapchain state
|
|
std::cerr << "\n=== Swapchain State ===\n";
|
|
std::cerr << "Extent: " << swapChainExtent_.width << "x" << swapChainExtent_.height << "\n";
|
|
std::cerr << "Image Count: " << swapChainImages_.size() << "\n";
|
|
std::cerr << "Format: " << swapChainImageFormat_ << "\n";
|
|
std::cerr << "Consecutive Recreations: " << consecutiveSwapchainRecreations_ << "\n";
|
|
std::cerr << "Framebuffer Resized Flag: " << (framebufferResized_ ? "true" : "false") << "\n";
|
|
std::cerr << "First Frame Completed: " << (firstFrameCompleted_ ? "true" : "false") << "\n";
|
|
|
|
// Render objects
|
|
std::cerr << "\n=== Scene State ===\n";
|
|
std::cerr << "Render Objects: " << renderObjects_.size() << "\n";
|
|
std::cerr << "Vertices: " << vertices_.size() << "\n";
|
|
std::cerr << "Indices: " << indices_.size() << "\n";
|
|
std::cerr << "Pipelines: " << graphicsPipelines_.size() << "\n";
|
|
std::cerr << "GUI Renderer Active: " << (guiRenderer_ ? "true" : "false") << "\n";
|
|
std::cerr << "GUI Has Commands: " << (guiHasCommands_ ? "true" : "false") << "\n";
|
|
|
|
// Check device features that might be related
|
|
if (physicalDevice_ != VK_NULL_HANDLE) {
|
|
VkPhysicalDeviceFeatures deviceFeatures{};
|
|
vkGetPhysicalDeviceFeatures(physicalDevice_, &deviceFeatures);
|
|
|
|
std::cerr << "\n=== Relevant Device Features ===\n";
|
|
std::cerr << "Geometry Shader: " << (deviceFeatures.geometryShader ? "supported" : "not supported") << "\n";
|
|
std::cerr << "Tessellation Shader: " << (deviceFeatures.tessellationShader ? "supported" : "not supported") << "\n";
|
|
std::cerr << "Multi Draw Indirect: " << (deviceFeatures.multiDrawIndirect ? "supported" : "not supported") << "\n";
|
|
}
|
|
|
|
// Queue properties
|
|
if (physicalDevice_ != VK_NULL_HANDLE) {
|
|
uint32_t queueFamilyCount = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice_, &queueFamilyCount, nullptr);
|
|
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice_, &queueFamilyCount, queueFamilies.data());
|
|
|
|
std::cerr << "\n=== Queue Families ===\n";
|
|
for (uint32_t i = 0; i < queueFamilyCount; i++) {
|
|
std::cerr << "Family " << i << ": " << queueFamilies[i].queueCount << " queues, flags: 0x"
|
|
<< std::hex << queueFamilies[i].queueFlags << std::dec << "\n";
|
|
}
|
|
}
|
|
|
|
std::cerr << "\n=== Possible Causes ===\n";
|
|
std::cerr << "1. GPU driver crash or hang - Check dmesg for GPU reset messages\n";
|
|
std::cerr << "2. Infinite loop in shader code - Review vertex/fragment shaders\n";
|
|
std::cerr << "3. Command buffer submission issue - Check synchronization\n";
|
|
std::cerr << "4. GPU overheating or hardware issue - Monitor GPU temperature\n";
|
|
std::cerr << "5. Driver bug - Try updating GPU drivers to latest version\n";
|
|
std::cerr << "6. Resource exhaustion - Check system memory and VRAM usage\n";
|
|
|
|
std::cerr << "\n=== Recommended Actions ===\n";
|
|
std::cerr << "1. Check system logs: dmesg | grep -i 'gpu\\|radeon\\|amdgpu'\n";
|
|
std::cerr << "2. Update GPU drivers: sudo dnf update mesa-vulkan-drivers\n";
|
|
std::cerr << "3. Verify GPU health: radeontop or similar monitoring tool\n";
|
|
std::cerr << "4. Check for driver messages: journalctl -k | grep -i amdgpu\n";
|
|
std::cerr << "5. Try with different Vulkan settings or validation layers\n";
|
|
std::cerr << "========================================\n\n";
|
|
}
|
|
|
|
void Sdl3App::CreateCommandBuffers() {
|
|
TRACE_FUNCTION();
|
|
commandBuffers_.resize(swapChainFramebuffers_.size());
|
|
|
|
VkCommandBufferAllocateInfo allocInfo{};
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
allocInfo.commandPool = commandPool_;
|
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
allocInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers_.size());
|
|
|
|
if (vkAllocateCommandBuffers(device_, &allocInfo, commandBuffers_.data()) != VK_SUCCESS) {
|
|
throw std::runtime_error("Failed to allocate command buffers");
|
|
}
|
|
}
|
|
|
|
void Sdl3App::RecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, float time,
|
|
const std::array<float, 16>& viewProj) {
|
|
TRACE_FUNCTION();
|
|
TRACE_VAR(imageIndex);
|
|
VkCommandBufferBeginInfo beginInfo{};
|
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
|
|
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
|
|
|
VkRenderPassBeginInfo renderPassInfo{};
|
|
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
renderPassInfo.renderPass = renderPass_;
|
|
renderPassInfo.framebuffer = swapChainFramebuffers_[imageIndex];
|
|
renderPassInfo.renderArea.offset = {0, 0};
|
|
renderPassInfo.renderArea.extent = swapChainExtent_;
|
|
|
|
VkClearValue clearColor = {{{0.1f, 0.1f, 0.15f, 1.0f}}};
|
|
renderPassInfo.clearValueCount = 1;
|
|
renderPassInfo.pClearValues = &clearColor;
|
|
|
|
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
VkBuffer vertexBuffers[] = {vertexBuffer_};
|
|
VkDeviceSize offsets[] = {0};
|
|
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
|
|
vkCmdBindIndexBuffer(commandBuffer, indexBuffer_, 0, VK_INDEX_TYPE_UINT16);
|
|
core::PushConstants pushConstants{};
|
|
pushConstants.viewProj = viewProj;
|
|
for (const auto& object : renderObjects_) {
|
|
auto pipelineIt = graphicsPipelines_.find(object.shaderKey);
|
|
if (pipelineIt == graphicsPipelines_.end()) {
|
|
pipelineIt = graphicsPipelines_.find(defaultShaderKey_);
|
|
if (pipelineIt == graphicsPipelines_.end()) {
|
|
throw std::runtime_error("Missing pipeline for shader key");
|
|
}
|
|
}
|
|
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineIt->second);
|
|
pushConstants.model = cubeScript_.ComputeModelMatrix(object.computeModelMatrixRef, time);
|
|
vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(core::PushConstants),
|
|
&pushConstants);
|
|
vkCmdDrawIndexed(commandBuffer, object.indexCount, 1, object.indexOffset, object.vertexOffset, 0);
|
|
}
|
|
vkCmdEndRenderPass(commandBuffer);
|
|
if (guiRenderer_) {
|
|
guiRenderer_->BlitToSwapchain(commandBuffer, swapChainImages_[imageIndex]);
|
|
}
|
|
vkEndCommandBuffer(commandBuffer);
|
|
}
|
|
|
|
void Sdl3App::ProcessGuiEvent(const SDL_Event& event) {
|
|
TRACE_FUNCTION();
|
|
switch (event.type) {
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
guiInputSnapshot_.mouseX = static_cast<float>(event.motion.x);
|
|
guiInputSnapshot_.mouseY = static_cast<float>(event.motion.y);
|
|
break;
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
guiInputSnapshot_.mouseDown = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
|
|
}
|
|
break;
|
|
case SDL_EVENT_MOUSE_WHEEL:
|
|
guiInputSnapshot_.wheel += static_cast<float>(event.wheel.y);
|
|
break;
|
|
case SDL_EVENT_TEXT_INPUT:
|
|
guiInputSnapshot_.textInput.append(event.text.text);
|
|
break;
|
|
case SDL_EVENT_KEY_DOWN:
|
|
case SDL_EVENT_KEY_UP: {
|
|
SDL_Keycode key = event.key.key;
|
|
auto it = kGuiKeyNames.find(key);
|
|
if (it != kGuiKeyNames.end()) {
|
|
guiInputSnapshot_.keyStates[it->second] = (event.type == SDL_EVENT_KEY_DOWN);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Sdl3App::SetupGuiRenderer() {
|
|
TRACE_FUNCTION();
|
|
guiHasCommands_ = cubeScript_.HasGuiCommands();
|
|
if (!guiHasCommands_) {
|
|
guiRenderer_.reset();
|
|
return;
|
|
}
|
|
if (!guiRenderer_) {
|
|
guiRenderer_ =
|
|
std::make_unique<gui::GuiRenderer>(device_, physicalDevice_, swapChainImageFormat_,
|
|
cubeScript_.GetScriptDirectory());
|
|
}
|
|
guiRenderer_->Resize(swapChainExtent_.width, swapChainExtent_.height, swapChainImageFormat_);
|
|
}
|
|
|
|
void Sdl3App::DrawFrame(float time) {
|
|
TRACE_FUNCTION();
|
|
|
|
// Use reasonable timeout instead of infinite wait (5 seconds)
|
|
constexpr uint64_t kFenceTimeout = 5000000000ULL; // 5 seconds in nanoseconds
|
|
VkResult fenceResult = vkWaitForFences(device_, 1, &inFlightFence_, VK_TRUE, kFenceTimeout);
|
|
if (fenceResult == VK_TIMEOUT) {
|
|
std::cerr << "\nERROR: Fence wait timeout: GPU appears to be hung\n";
|
|
PrintGpuDiagnostics("Fence wait timeout after 5 seconds");
|
|
throw std::runtime_error("Fence wait timeout: GPU appears to be hung");
|
|
} else if (fenceResult != VK_SUCCESS) {
|
|
std::cerr << "\nERROR: Fence wait failed with code: " << fenceResult << "\n";
|
|
PrintGpuDiagnostics("Fence wait failed with error code " + std::to_string(fenceResult));
|
|
throw std::runtime_error("Fence wait failed");
|
|
}
|
|
vkResetFences(device_, 1, &inFlightFence_);
|
|
|
|
uint32_t imageIndex;
|
|
// Use reasonable timeout for image acquisition (5 seconds)
|
|
VkResult result = vkAcquireNextImageKHR(device_, swapChain_, kFenceTimeout,
|
|
imageAvailableSemaphore_, VK_NULL_HANDLE, &imageIndex);
|
|
|
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized_) {
|
|
consecutiveSwapchainRecreations_++;
|
|
std::cout << "Swapchain recreation triggered (attempt " << consecutiveSwapchainRecreations_ << ")";
|
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
std::cout << " - OUT_OF_DATE";
|
|
} else if (result == VK_SUBOPTIMAL_KHR) {
|
|
std::cout << " - SUBOPTIMAL";
|
|
} else if (framebufferResized_) {
|
|
std::cout << " - RESIZE_EVENT";
|
|
}
|
|
std::cout << "\n";
|
|
|
|
// Detect infinite swapchain recreation loop
|
|
constexpr int kMaxConsecutiveRecreations = 10;
|
|
if (consecutiveSwapchainRecreations_ > kMaxConsecutiveRecreations) {
|
|
throw std::runtime_error(
|
|
"Swapchain recreation loop detected: " + std::to_string(consecutiveSwapchainRecreations_) +
|
|
" consecutive recreations. This may indicate a driver issue or window manager problem.\n"
|
|
"Try running with a different window manager or updating your GPU drivers.");
|
|
}
|
|
|
|
RecreateSwapChain();
|
|
return;
|
|
} else if (result == VK_TIMEOUT) {
|
|
std::cerr << "\nERROR: Image acquisition timeout: GPU appears to be hung\n";
|
|
PrintGpuDiagnostics("Image acquisition timeout after 5 seconds");
|
|
throw std::runtime_error("Image acquisition timeout: GPU appears to be hung");
|
|
} else if (result != VK_SUCCESS) {
|
|
throw std::runtime_error("Failed to acquire swap chain image");
|
|
}
|
|
TRACE_VAR(imageIndex);
|
|
|
|
float aspect = static_cast<float>(swapChainExtent_.width) / static_cast<float>(swapChainExtent_.height);
|
|
auto viewProj = cubeScript_.GetViewProjectionMatrix(aspect);
|
|
|
|
vkResetCommandBuffer(commandBuffers_[imageIndex], 0);
|
|
RecordCommandBuffer(commandBuffers_[imageIndex], imageIndex, time, viewProj);
|
|
|
|
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore_};
|
|
VkSemaphore signalSemaphores[] = {renderFinishedSemaphore_};
|
|
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
|
|
|
VkSubmitInfo submitInfo{};
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = waitSemaphores;
|
|
submitInfo.pWaitDstStageMask = waitStages;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &commandBuffers_[imageIndex];
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = signalSemaphores;
|
|
|
|
if (vkQueueSubmit(graphicsQueue_, 1, &submitInfo, inFlightFence_) != VK_SUCCESS) {
|
|
throw std::runtime_error("Failed to submit draw command buffer");
|
|
}
|
|
|
|
VkPresentInfoKHR presentInfo{};
|
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
presentInfo.waitSemaphoreCount = 1;
|
|
presentInfo.pWaitSemaphores = signalSemaphores;
|
|
presentInfo.swapchainCount = 1;
|
|
presentInfo.pSwapchains = &swapChain_;
|
|
presentInfo.pImageIndices = &imageIndex;
|
|
|
|
result = vkQueuePresentKHR(presentQueue_, &presentInfo);
|
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized_) {
|
|
consecutiveSwapchainRecreations_++;
|
|
std::cout << "Swapchain recreation after present (attempt " << consecutiveSwapchainRecreations_ << ")\n";
|
|
|
|
constexpr int kMaxConsecutiveRecreations = 10;
|
|
if (consecutiveSwapchainRecreations_ > kMaxConsecutiveRecreations) {
|
|
throw std::runtime_error(
|
|
"Swapchain recreation loop detected after present: " + std::to_string(consecutiveSwapchainRecreations_) +
|
|
" consecutive recreations.");
|
|
}
|
|
|
|
RecreateSwapChain();
|
|
} else if (result != VK_SUCCESS) {
|
|
throw std::runtime_error("Failed to present swap chain image");
|
|
} else {
|
|
// Successfully presented a frame - reset counter
|
|
if (consecutiveSwapchainRecreations_ > 0) {
|
|
std::cout << "Frame presented successfully after " << consecutiveSwapchainRecreations_
|
|
<< " swapchain recreation(s)\n";
|
|
}
|
|
consecutiveSwapchainRecreations_ = 0;
|
|
|
|
if (!firstFrameCompleted_) {
|
|
firstFrameCompleted_ = true;
|
|
std::cout << "First frame completed successfully\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace sdl3cpp::app
|