diff --git a/src/app/sdl3_app_buffers.cpp b/src/app/sdl3_app_buffers.cpp index 2fb914d..1c45dbf 100644 --- a/src/app/sdl3_app_buffers.cpp +++ b/src/app/sdl3_app_buffers.cpp @@ -55,8 +55,15 @@ void Sdl3App::LoadSceneData() { void Sdl3App::CreateVertexBuffer() { TRACE_FUNCTION(); + if (vertices_.empty()) { + throw std::runtime_error("Cannot create vertex buffer: no vertices loaded"); + } VkDeviceSize bufferSize = sizeof(vertices_[0]) * vertices_.size(); TRACE_VAR(bufferSize); + std::cout << "Creating vertex buffer: " << vertices_.size() << " vertices (" + << (bufferSize / 1024) << " KB)\n"; + std::cout.flush(); + vulkan::CreateBuffer(device_, physicalDevice_, bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer_, vertexBufferMemory_); @@ -69,8 +76,15 @@ void Sdl3App::CreateVertexBuffer() { void Sdl3App::CreateIndexBuffer() { TRACE_FUNCTION(); + if (indices_.empty()) { + throw std::runtime_error("Cannot create index buffer: no indices loaded"); + } VkDeviceSize bufferSize = sizeof(indices_[0]) * indices_.size(); TRACE_VAR(bufferSize); + std::cout << "Creating index buffer: " << indices_.size() << " indices (" + << (bufferSize / 1024) << " KB)\n"; + std::cout.flush(); + vulkan::CreateBuffer(device_, physicalDevice_, bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer_, indexBufferMemory_); diff --git a/src/app/sdl3_app_device.cpp b/src/app/sdl3_app_device.cpp index 526c2d3..2200556 100644 --- a/src/app/sdl3_app_device.cpp +++ b/src/app/sdl3_app_device.cpp @@ -9,6 +9,36 @@ namespace sdl3cpp::app { void Sdl3App::CreateInstance() { TRACE_FUNCTION(); + + // Early validation: Check if Vulkan is available + uint32_t apiVersion = 0; + VkResult enumResult = vkEnumerateInstanceVersion(&apiVersion); + if (enumResult != VK_SUCCESS) { + std::string errorMsg = "Vulkan is not available on this system.\n\n"; + errorMsg += "Please install Vulkan drivers:\n"; + errorMsg += "- Ubuntu/Debian: sudo apt install vulkan-tools libvulkan1\n"; + errorMsg += "- Fedora: sudo dnf install vulkan-tools vulkan-loader\n"; + errorMsg += "- Arch: sudo pacman -S vulkan-tools vulkan-icd-loader\n"; + errorMsg += "\nFor NVIDIA GPUs, install: nvidia-vulkan-icd\n"; + errorMsg += "For AMD GPUs, install: mesa-vulkan-drivers\n"; + throw std::runtime_error(errorMsg); + } + + uint32_t major = VK_API_VERSION_MAJOR(apiVersion); + uint32_t minor = VK_API_VERSION_MINOR(apiVersion); + uint32_t patch = VK_API_VERSION_PATCH(apiVersion); + std::cout << "Vulkan Version: " << major << "." << minor << "." << patch << "\n"; + std::cout.flush(); + + if (apiVersion < VK_API_VERSION_1_2) { + std::string errorMsg = "Vulkan version is too old.\n"; + errorMsg += "Required: 1.2 or higher\n"; + errorMsg += "Found: " + std::to_string(major) + "." + std::to_string(minor); + errorMsg += "." + std::to_string(patch) + "\n\n"; + errorMsg += "Please update your GPU drivers."; + throw std::runtime_error(errorMsg); + } + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "SDL3 Vulkan"; @@ -58,7 +88,11 @@ void Sdl3App::CreateSurface() { void Sdl3App::PickPhysicalDevice() { TRACE_FUNCTION(); uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr); + VkResult enumResult = vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr); + if (enumResult != VK_SUCCESS) { + throw std::runtime_error("Failed to enumerate physical devices (error code: " + + std::to_string(enumResult) + ")"); + } if (deviceCount == 0) { throw std::runtime_error("Failed to find GPUs with Vulkan support.\n\nPlease ensure:\n- You have a compatible GPU\n- Vulkan drivers are properly installed\n- Your GPU supports Vulkan 1.2 or higher"); } @@ -66,18 +100,38 @@ void Sdl3App::PickPhysicalDevice() { vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data()); std::string deviceInfo; + std::cout << "\n=== GPU Detection ===\n"; for (size_t i = 0; i < devices.size(); ++i) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(devices[i], &props); + + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(devices[i], &memProps); + + uint64_t totalMemory = 0; + for (uint32_t j = 0; j < memProps.memoryHeapCount; ++j) { + if (memProps.memoryHeaps[j].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + totalMemory += memProps.memoryHeaps[j].size; + } + } + deviceInfo += "\nGPU " + std::to_string(i) + ": " + props.deviceName; + deviceInfo += " (VRAM: " + std::to_string(totalMemory / (1024 * 1024)) + " MB)"; + std::cout << "GPU " << i << ": " << props.deviceName + << " (VRAM: " << (totalMemory / (1024 * 1024)) << " MB)\n"; + if (IsDeviceSuitable(devices[i])) { physicalDevice_ = devices[i]; deviceInfo += " [SELECTED]"; + std::cout << " -> SELECTED\n"; break; } else { deviceInfo += " [UNSUITABLE]"; + std::cout << " -> UNSUITABLE (missing required features)\n"; } } + std::cout << "==================\n\n"; + std::cout.flush(); if (physicalDevice_ == VK_NULL_HANDLE) { std::string errorMsg = "Failed to find a suitable GPU.\n\n"; diff --git a/src/app/sdl3_app_swapchain.cpp b/src/app/sdl3_app_swapchain.cpp index 8bf1e8b..9bebeaa 100644 --- a/src/app/sdl3_app_swapchain.cpp +++ b/src/app/sdl3_app_swapchain.cpp @@ -9,6 +9,36 @@ namespace sdl3cpp::app { void Sdl3App::CreateSwapChain() { TRACE_FUNCTION(); SwapChainSupportDetails support = QuerySwapChainSupport(physicalDevice_); + + // Validate swap chain support + if (support.formats.empty()) { + throw std::runtime_error("No surface formats available for swap chain.\n" + "This may indicate GPU driver issues or incompatible surface."); + } + if (support.presentModes.empty()) { + throw std::runtime_error("No present modes available for swap chain.\n" + "This may indicate GPU driver issues or incompatible surface."); + } + + // Validate window dimensions + int windowWidth = 0, windowHeight = 0; + SDL_GetWindowSize(window_, &windowWidth, &windowHeight); + std::cout << "Window size: " << windowWidth << "x" << windowHeight << "\n"; + + if (windowWidth == 0 || windowHeight == 0) { + throw std::runtime_error("Invalid window dimensions (" + + std::to_string(windowWidth) + "x" + std::to_string(windowHeight) + ").\n" + + "Window may be minimized or invalid."); + } + + std::cout << "Surface capabilities:\n"; + std::cout << " Min extent: " << support.capabilities.minImageExtent.width + << "x" << support.capabilities.minImageExtent.height << "\n"; + std::cout << " Max extent: " << support.capabilities.maxImageExtent.width + << "x" << support.capabilities.maxImageExtent.height << "\n"; + std::cout << " Min images: " << support.capabilities.minImageCount << "\n"; + std::cout << " Max images: " << support.capabilities.maxImageCount << "\n"; + std::cout.flush(); VkSurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(support.formats); VkPresentModeKHR presentMode = ChooseSwapPresentMode(support.presentModes); diff --git a/src/app/vulkan_api.cpp b/src/app/vulkan_api.cpp index c2d689f..535e346 100644 --- a/src/app/vulkan_api.cpp +++ b/src/app/vulkan_api.cpp @@ -38,14 +38,39 @@ VkExtent2D ChooseSwapExtent(VkSurfaceCapabilitiesKHR capabilities, SDL_Window* w void CreateBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + // Validate buffer size + if (size == 0) { + throw std::runtime_error("Cannot create buffer with size 0"); + } + + // Check available memory before allocating + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); + + uint64_t totalAvailable = 0; + for (uint32_t i = 0; i < memProps.memoryHeapCount; ++i) { + if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + totalAvailable += memProps.memoryHeaps[i].size; + } + } + + if (size > totalAvailable) { + throw std::runtime_error("Requested buffer size (" + + std::to_string(size / (1024 * 1024)) + " MB) exceeds available GPU memory (" + + std::to_string(totalAvailable / (1024 * 1024)) + " MB)"); + } + VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { - throw std::runtime_error("Failed to create buffer"); + VkResult createResult = vkCreateBuffer(device, &bufferInfo, nullptr, &buffer); + if (createResult != VK_SUCCESS) { + throw std::runtime_error("Failed to create buffer (error code: " + + std::to_string(createResult) + ", size: " + + std::to_string(size / 1024) + " KB)"); } VkMemoryRequirements memRequirements; @@ -54,11 +79,32 @@ void CreateBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = - FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties); + + try { + allocInfo.memoryTypeIndex = + FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties); + } catch (const std::exception& e) { + vkDestroyBuffer(device, buffer, nullptr); + throw std::runtime_error(std::string("Failed to find suitable memory type: ") + e.what()); + } - if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { - throw std::runtime_error("Failed to allocate buffer memory"); + VkResult allocResult = vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory); + if (allocResult != VK_SUCCESS) { + vkDestroyBuffer(device, buffer, nullptr); + std::string errorMsg = "Failed to allocate buffer memory.\n"; + errorMsg += "Requested: " + std::to_string(memRequirements.size / (1024 * 1024)) + " MB\n"; + errorMsg += "Error code: " + std::to_string(allocResult) + "\n"; + if (allocResult == VK_ERROR_OUT_OF_DEVICE_MEMORY) { + errorMsg += "\nOut of GPU memory. Try:\n"; + errorMsg += "- Closing other GPU-intensive applications\n"; + errorMsg += "- Reducing window resolution\n"; + errorMsg += "- Upgrading GPU or system memory"; + } else if (allocResult == VK_ERROR_OUT_OF_HOST_MEMORY) { + errorMsg += "\nOut of system memory. Try:\n"; + errorMsg += "- Closing other applications\n"; + errorMsg += "- Adding more RAM to your system"; + } + throw std::runtime_error(errorMsg); } vkBindBufferMemory(device, buffer, bufferMemory, 0); diff --git a/tests/VALIDATION_IMPROVEMENTS.md b/tests/VALIDATION_IMPROVEMENTS.md new file mode 100644 index 0000000..4d9ac25 --- /dev/null +++ b/tests/VALIDATION_IMPROVEMENTS.md @@ -0,0 +1,243 @@ +# Validation and Error Handling Improvements + +## Overview +This document describes the comprehensive validation and error handling improvements made to the SDL3CPlusPlus application to catch errors early and provide better user feedback before crashes occur. + +## Problem +The application was crashing with exit code 137 (SIGKILL), showing only a see-through window with no feedback on what went wrong. This made it difficult to diagnose issues. + +## Solution +Added extensive validation at every critical initialization stage with clear, actionable error messages. + +--- + +## Changes Made + +### 1. **Early Vulkan Validation** ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)) + +#### Before: +- Directly attempted to create Vulkan instance without checking if Vulkan is available +- Generic error messages + +#### After: +- **Version Check**: Validates Vulkan is installed and checks API version before proceeding + ```cpp + VkResult enumResult = vkEnumerateInstanceVersion(&apiVersion); + if (enumResult != VK_SUCCESS) { + // Provides OS-specific installation instructions + } + ``` +- **Version Requirements**: Verifies Vulkan 1.2 or higher is available +- **Console Output**: Displays detected Vulkan version + ``` + Vulkan Version: 1.3.xxx + ``` +- **Detailed Error Messages**: Includes OS-specific installation commands for Ubuntu/Debian, Fedora, Arch +- **GPU-specific Guidance**: Separate instructions for NVIDIA and AMD GPUs + +### 2. **GPU Detection and Selection** ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)) + +#### Before: +- Limited information about available GPUs +- Simple unsuitable device messages + +#### After: +- **Comprehensive GPU Enumeration**: Lists all detected GPUs with details + ``` + === GPU Detection === + GPU 0: NVIDIA GeForce RTX 3080 (VRAM: 10240 MB) + -> SELECTED + GPU 1: Intel UHD Graphics (VRAM: 2048 MB) + -> UNSUITABLE (missing required features) + ================== + ``` +- **Memory Information**: Shows VRAM for each GPU +- **Error Result Codes**: Returns Vulkan error codes for debugging +- **Clear Selection Feedback**: Shows which GPU was selected and why others were rejected +- **Detailed Requirements**: Lists specific missing features: + - Graphics queue support + - Present queue support + - Swapchain extension support + - Adequate swapchain formats and present modes + +### 3. **Swap Chain Validation** ([sdl3_app_swapchain.cpp](src/app/sdl3_app_swapchain.cpp)) + +#### Before: +- Assumed swap chain support was available +- No validation of window dimensions + +#### After: +- **Surface Format Validation**: Checks if surface formats are available + ```cpp + if (support.formats.empty()) { + throw std::runtime_error("No surface formats available..."); + } + ``` +- **Present Mode Validation**: Verifies present modes exist +- **Window Dimension Check**: Validates window is not minimized or has invalid size + ```cpp + if (windowWidth == 0 || windowHeight == 0) { + throw std::runtime_error("Invalid window dimensions..."); + } + ``` +- **Capability Reporting**: Displays surface capabilities: + ``` + Window size: 1024x768 + Surface capabilities: + Min extent: 1x1 + Max extent: 4096x4096 + Min images: 2 + Max images: 8 + ``` + +### 4. **Memory Allocation Validation** ([vulkan_api.cpp](src/app/vulkan_api.cpp)) + +#### Before: +- Created buffers without checking available memory +- Generic "failed to allocate" errors + +#### After: +- **Size Validation**: Checks buffer size is non-zero +- **Available Memory Check**: Compares requested size against total GPU memory + ```cpp + if (size > totalAvailable) { + throw std::runtime_error("Requested buffer size exceeds available GPU memory"); + } + ``` +- **Error Code Reporting**: Returns specific Vulkan error codes +- **Detailed Error Messages**: Provides size information and troubleshooting steps + - For `VK_ERROR_OUT_OF_DEVICE_MEMORY`: + - Close GPU-intensive applications + - Reduce window resolution + - Upgrade GPU or system memory + - For `VK_ERROR_OUT_OF_HOST_MEMORY`: + - Close other applications + - Add more RAM +- **Cleanup on Failure**: Properly destroys partially created resources + ```cpp + if (allocResult != VK_SUCCESS) { + vkDestroyBuffer(device, buffer, nullptr); + // throw detailed error... + } + ``` + +### 5. **Buffer Creation Validation** ([sdl3_app_buffers.cpp](src/app/sdl3_app_buffers.cpp)) + +#### Before: +- Attempted to create buffers even if data was empty + +#### After: +- **Data Validation**: Checks vertices/indices are loaded before buffer creation + ```cpp + if (vertices_.empty()) { + throw std::runtime_error("Cannot create vertex buffer: no vertices loaded"); + } + ``` +- **Resource Reporting**: Displays buffer sizes being allocated + ``` + Creating vertex buffer: 24 vertices (1 KB) + Creating index buffer: 36 indices (0 KB) + ``` +- **Immediate Feedback**: Uses `std::cout.flush()` to ensure messages appear immediately + +### 6. **Enhanced Error Dialog Support** + +All errors now: +- Show in console with detailed information +- Display in SDL message boxes (when available) +- Include actionable troubleshooting steps +- Reference specific error codes for debugging + +--- + +## Benefits + +1. **Early Detection**: Problems are caught before they cause crashes +2. **Clear Feedback**: Users see exactly what went wrong and where +3. **Actionable Guidance**: Error messages include specific steps to resolve issues +4. **Better Diagnostics**: Includes error codes, sizes, and system capabilities +5. **Progress Visibility**: Console output shows what the app is doing during initialization +6. **Resource Awareness**: Validates resources before attempting allocation + +--- + +## Testing + +To test the improvements: + +```bash +cd /home/rewrich/Documents/GitHub/SDL3CPlusPlus/build/Release +make -j$(nproc) +./sdl3_app --json-file-in ./config/seed_runtime.json +``` + +You should now see: +- Vulkan version detection +- GPU enumeration with memory information +- Window and swap chain validation +- Buffer creation progress +- Clear error messages if any step fails + +--- + +## Example Error Messages + +### Missing Vulkan Drivers +``` +ERROR: Vulkan is not available on this system. + +Please install Vulkan drivers: +- Ubuntu/Debian: sudo apt install vulkan-tools libvulkan1 +- Fedora: sudo dnf install vulkan-tools vulkan-loader +- Arch: sudo pacman -S vulkan-tools vulkan-icd-loader + +For NVIDIA GPUs, install: nvidia-vulkan-icd +For AMD GPUs, install: mesa-vulkan-drivers +``` + +### No Suitable GPU +``` +ERROR: Failed to find a suitable GPU. + +Found 2 GPU(s), but none meet the requirements. +GPU 0: Intel UHD Graphics (VRAM: 2048 MB) [UNSUITABLE] +GPU 1: AMD Radeon (VRAM: 4096 MB) [UNSUITABLE] + +Required features: +- Graphics queue support +- Present queue support +- Swapchain extension support (VK_KHR_swapchain) +- Adequate swapchain formats and present modes +``` + +### Out of Memory +``` +ERROR: Failed to allocate buffer memory. +Requested: 512 MB +Error code: -2 + +Out of GPU memory. Try: +- Closing other GPU-intensive applications +- Reducing window resolution +- Upgrading GPU or system memory +``` + +--- + +## Files Modified + +1. [src/app/sdl3_app_device.cpp](src/app/sdl3_app_device.cpp) - Vulkan and GPU validation +2. [src/app/sdl3_app_swapchain.cpp](src/app/sdl3_app_swapchain.cpp) - Swap chain validation +3. [src/app/vulkan_api.cpp](src/app/vulkan_api.cpp) - Memory allocation validation +4. [src/app/sdl3_app_buffers.cpp](src/app/sdl3_app_buffers.cpp) - Buffer creation validation + +--- + +## Future Improvements + +Consider adding: +- Memory usage tracking throughout app lifecycle +- Swap chain recreation validation +- Shader loading validation with detailed error messages +- Configuration file validation before initialization +- Performance monitoring and warnings for low-memory conditions diff --git a/tests/rebuild_and_test.sh b/tests/rebuild_and_test.sh new file mode 100644 index 0000000..8193422 --- /dev/null +++ b/tests/rebuild_and_test.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Quick rebuild and test script + +set -e + +echo "===================================" +echo " SDL3CPlusPlus - Rebuild & Test" +echo "===================================" +echo "" + +cd "$(dirname "$0")" +BUILD_DIR="build/Release" + +# Build +echo "Building application..." +cd "$BUILD_DIR" +make -j$(nproc) +echo "✓ Build complete" +echo "" + +# Test with config +echo "Starting application with seed_runtime.json..." +echo "Press Ctrl+C to exit, or wait for error messages" +echo "---" +./sdl3_app --json-file-in ./config/seed_runtime.json diff --git a/tests/test_validation.sh b/tests/test_validation.sh new file mode 100644 index 0000000..ccd79ee --- /dev/null +++ b/tests/test_validation.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Test script for validation improvements + +cd /home/rewrich/Documents/GitHub/SDL3CPlusPlus/build/Release + +echo "=== Testing application startup with validation ===" +echo "" + +# Test 1: Check --help output +echo "Test 1: Running with --help" +timeout 2 ./sdl3_app --help 2>&1 || echo "(timed out or exited with code $?)" +echo "" + +# Test 2: Try with config file +echo "Test 2: Running with config file (5 second timeout)" +timeout 5 ./sdl3_app --json-file-in ./config/seed_runtime.json 2>&1 & +PID=$! +sleep 2 + +# Check if process is still running +if ps -p $PID > /dev/null 2>&1; then + echo "Process started successfully (PID: $PID)" + kill -9 $PID 2>/dev/null + wait $PID 2>/dev/null +else + echo "Process exited early" + wait $PID 2>/dev/null + echo "Exit code: $?" +fi +echo "" + +# Test 3: Check for error in a non-existent config +echo "Test 3: Testing with non-existent config" +./sdl3_app --json-file-in ./config/nonexistent.json 2>&1 +echo "" + +echo "=== Tests complete ==="