Enhance validation and error handling across the application

- Implement early Vulkan validation to check availability and version.
- Add comprehensive GPU detection and selection feedback.
- Validate swap chain support and window dimensions before creation.
- Introduce buffer size validation and memory checks during buffer creation.
- Improve error messages with actionable troubleshooting steps.
- Create scripts for rebuilding and testing the application with validation improvements.
This commit is contained in:
2026-01-03 22:56:37 +00:00
parent b39eec6885
commit 3a4fc2ea8b
7 changed files with 456 additions and 7 deletions

View File

@@ -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_);

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

25
tests/rebuild_and_test.sh Normal file
View File

@@ -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

37
tests/test_validation.sh Normal file
View File

@@ -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 ==="