mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 21:55:09 +00:00
Implement comprehensive error handling and feedback for SDL and Vulkan initialization
This commit is contained in:
229
docs/test_error_handling.md
Normal file
229
docs/test_error_handling.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Error Handling Improvements - Summary
|
||||
|
||||
## Problem Statement
|
||||
The application was crashing with a "see-through window" and no user feedback about what went wrong. The process would be killed (exit code 137 - SIGKILL) without any diagnostic information.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. SDL Initialization Error Dialogs ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp))
|
||||
- Added `ShowErrorDialog()` helper function to display visual error messages via `SDL_ShowSimpleMessageBox`
|
||||
- Wrapped SDL initialization calls with try-catch blocks that show error dialogs:
|
||||
- **SDL_Init**: Shows which subsystem failed to initialize
|
||||
- **SDL_Vulkan_LoadLibrary**: Warns user to check Vulkan driver installation
|
||||
- **SDL_CreateWindow**: Provides window creation error details
|
||||
|
||||
**Example error message**:
|
||||
```
|
||||
Title: "Vulkan Library Load Failed"
|
||||
Message: "Failed to load Vulkan library. Make sure Vulkan drivers are installed.
|
||||
|
||||
Error: SDL_Vulkan_LoadLibrary failed: ..."
|
||||
```
|
||||
|
||||
### 2. Vulkan Validation with Detailed Feedback ([sdl3_app_device.cpp](src/app/sdl3_app_device.cpp))
|
||||
|
||||
#### Instance Creation
|
||||
- Lists all required Vulkan extensions when creation fails
|
||||
- Provides hints about common causes (missing drivers, incompatible GPU)
|
||||
|
||||
#### Physical Device Selection
|
||||
- Enumerates all detected GPUs with their names
|
||||
- Marks each as [SELECTED] or [UNSUITABLE]
|
||||
- Explains required features:
|
||||
- Graphics queue support
|
||||
- Present queue support
|
||||
- Swapchain extension (VK_KHR_swapchain)
|
||||
- Adequate swapchain formats and present modes
|
||||
|
||||
**Example error message**:
|
||||
```
|
||||
Failed to find a suitable GPU.
|
||||
|
||||
Found 2 GPU(s), but none meet the requirements.
|
||||
GPU 0: Intel HD Graphics 620 [UNSUITABLE]
|
||||
GPU 1: NVIDIA GeForce GTX 1050 [UNSUITABLE]
|
||||
|
||||
Required features:
|
||||
- Graphics queue support
|
||||
- Present queue support
|
||||
- Swapchain extension support (VK_KHR_swapchain)
|
||||
- Adequate swapchain formats and present modes
|
||||
```
|
||||
|
||||
#### Device Extension Support
|
||||
- Logs all missing required extensions to stderr
|
||||
- Helps identify driver or GPU capability issues
|
||||
|
||||
### 3. Resource Existence Validation ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp), [sdl3_app_pipeline.cpp](src/app/sdl3_app_pipeline.cpp))
|
||||
|
||||
#### Enhanced ReadFile() Function
|
||||
Now validates before attempting to read:
|
||||
- ✅ File exists at the specified path
|
||||
- ✅ Path points to a regular file (not a directory)
|
||||
- ✅ File is not empty
|
||||
- ✅ File has read permissions
|
||||
|
||||
**Example error messages**:
|
||||
```
|
||||
File not found: /path/to/shader.spv
|
||||
|
||||
Please ensure the file exists at this location.
|
||||
```
|
||||
|
||||
```
|
||||
Failed to open file: /path/to/file.txt
|
||||
|
||||
The file exists but cannot be opened. Check file permissions.
|
||||
```
|
||||
|
||||
#### Shader File Validation
|
||||
- Checks both vertex and fragment shader files exist before loading
|
||||
- Provides the shader key to help identify which pipeline failed
|
||||
- Suggests checking that shaders are compiled and in the correct directory
|
||||
|
||||
**Example error message**:
|
||||
```
|
||||
Vertex shader not found: shaders/cube.vert.spv
|
||||
|
||||
Shader key: default
|
||||
|
||||
Please ensure shader files are compiled and present in the shaders directory.
|
||||
```
|
||||
|
||||
### 4. Structured Vulkan Initialization ([sdl3_app_core.cpp](src/app/sdl3_app_core.cpp))
|
||||
|
||||
The `InitVulkan()` function now wraps each stage in try-catch blocks with specific error dialogs:
|
||||
|
||||
1. **Instance Creation** → "Vulkan Instance Creation Failed"
|
||||
2. **Surface Creation** → "Vulkan Surface Creation Failed"
|
||||
3. **GPU Selection** → "GPU Selection Failed"
|
||||
4. **Logical Device** → "Logical Device Creation Failed"
|
||||
5. **Resource Setup** → "Vulkan Resource Setup Failed"
|
||||
- Swapchain, GUI renderer, image views, render pass
|
||||
- Scene data, graphics pipeline, framebuffers
|
||||
- Command pool, vertex/index buffers, command buffers, sync objects
|
||||
|
||||
Each failure shows a message box with the error details before rethrowing.
|
||||
|
||||
### 5. Main Function Error Handling ([main.cpp](src/main.cpp))
|
||||
|
||||
- Catches all exceptions at the top level
|
||||
- Displays error via `SDL_ShowSimpleMessageBox` for visual feedback
|
||||
- Also logs to stderr for console/log file capture
|
||||
- Returns EXIT_FAILURE with clear error indication
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. Visual Feedback ✨
|
||||
**Before**: Silent crash with transparent window
|
||||
**After**: Clear error dialog explaining what went wrong
|
||||
|
||||
### 2. Actionable Messages 🔧
|
||||
Errors now include:
|
||||
- What failed (e.g., "GPU Selection Failed")
|
||||
- Why it might have failed (e.g., "Missing Vulkan drivers")
|
||||
- What to check (e.g., "Ensure shader files are compiled")
|
||||
|
||||
### 3. Debugging Aid 🐛
|
||||
Detailed information helps diagnose issues:
|
||||
- List of detected GPUs and their suitability
|
||||
- Required vs available extensions
|
||||
- Full file paths for missing resources
|
||||
|
||||
### 4. Early Validation ⚡
|
||||
Resources are validated before use:
|
||||
- No more cryptic segfaults from missing files
|
||||
- Fails fast with clear error messages
|
||||
- Reduces time to identify and fix issues
|
||||
|
||||
### 5. No Silent Failures 📢
|
||||
All error paths now provide feedback:
|
||||
- Console output (stderr)
|
||||
- Visual message boxes (SDL)
|
||||
- Proper exit codes
|
||||
|
||||
## Testing the Improvements
|
||||
|
||||
### Test Case 1: Missing Lua Script
|
||||
```bash
|
||||
cd build/Release
|
||||
sed -i 's/cube_logic.lua/nonexistent_file.lua/' config/test_bad_config.json
|
||||
./sdl3_app --json-file-in ./config/test_bad_config.json
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
```
|
||||
ERROR: Lua script not found at /path/to/nonexistent_file.lua
|
||||
```
|
||||
Plus a message box with the same error.
|
||||
|
||||
### Test Case 2: Missing Shader File
|
||||
```bash
|
||||
cd build/Release
|
||||
mv shaders/cube.vert.spv shaders/cube.vert.spv.backup
|
||||
./sdl3_app --json-file-in ./config/seed_runtime.json
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
Message box showing:
|
||||
```
|
||||
Vertex shader not found: shaders/cube.vert.spv
|
||||
|
||||
Shader key: default
|
||||
|
||||
Please ensure shader files are compiled and present in the shaders directory.
|
||||
```
|
||||
|
||||
### Test Case 3: Normal Operation
|
||||
```bash
|
||||
cd build/Release
|
||||
./sdl3_app --json-file-in ./config/seed_runtime.json
|
||||
```
|
||||
|
||||
**Expected Result**:
|
||||
Application runs normally. If any errors occur during initialization, they will be caught and displayed with helpful context instead of causing a silent crash.
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **[src/app/sdl3_app_core.cpp](src/app/sdl3_app_core.cpp)**
|
||||
- Added `ShowErrorDialog()` function
|
||||
- Enhanced `ReadFile()` with validation
|
||||
- Added error handling to `InitSDL()` and `InitVulkan()`
|
||||
|
||||
2. **[src/app/sdl3_app_device.cpp](src/app/sdl3_app_device.cpp)**
|
||||
- Enhanced error messages in `CreateInstance()`
|
||||
- Improved GPU enumeration in `PickPhysicalDevice()`
|
||||
- Added missing extension logging in `CheckDeviceExtensionSupport()`
|
||||
|
||||
3. **[src/app/sdl3_app_pipeline.cpp](src/app/sdl3_app_pipeline.cpp)**
|
||||
- Added shader file existence validation in `CreateGraphicsPipeline()`
|
||||
|
||||
4. **[src/main.cpp](src/main.cpp)**
|
||||
- Added SDL message box display for all caught exceptions
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Enable Tracing**: Use the `--trace` flag to get detailed execution logs:
|
||||
```bash
|
||||
./sdl3_app --json-file-in config/seed_runtime.json --trace
|
||||
```
|
||||
|
||||
2. **Check Logs**: If the app crashes, check:
|
||||
- Console output (stderr)
|
||||
- System logs: `journalctl -xe` or `dmesg` (on Linux)
|
||||
- Application return code: `echo $?`
|
||||
|
||||
3. **Verify Environment**:
|
||||
- Vulkan drivers installed: `vulkaninfo`
|
||||
- GPU compatibility: `vkcube` (if available)
|
||||
- Required extensions available
|
||||
|
||||
4. **Config Validation**: The app now catches config errors early. Use `--dump-json` to verify your configuration:
|
||||
```bash
|
||||
./sdl3_app --json-file-in config/seed_runtime.json --dump-json
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The application now provides comprehensive error feedback instead of failing silently with a "see-through window". Every potential failure point has been wrapped with validation and clear error messages, making it much easier to diagnose and fix issues.
|
||||
|
||||
@@ -16,14 +16,32 @@ namespace sdl3cpp::app {
|
||||
|
||||
std::vector<char> ReadFile(const std::string& path) {
|
||||
TRACE_FUNCTION();
|
||||
|
||||
// Validate file exists before attempting to open
|
||||
if (!std::filesystem::exists(path)) {
|
||||
throw std::runtime_error("File not found: " + path +
|
||||
"\n\nPlease ensure the file exists at this location.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
throw std::runtime_error("Path is not a regular file: " + path);
|
||||
}
|
||||
|
||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||
if (!file) {
|
||||
throw std::runtime_error("failed to open file: " + path);
|
||||
throw std::runtime_error("Failed to open file: " + path +
|
||||
"\n\nThe file exists but cannot be opened. Check file permissions.");
|
||||
}
|
||||
size_t size = static_cast<size_t>(file.tellg());
|
||||
if (size == 0) {
|
||||
throw std::runtime_error("File is empty: " + path);
|
||||
}
|
||||
std::vector<char> buffer(size);
|
||||
file.seekg(0);
|
||||
file.read(buffer.data(), buffer.size());
|
||||
if (!file) {
|
||||
throw std::runtime_error("Failed to read file contents: " + path);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -81,6 +99,14 @@ void ThrowSdlErrorIfFailed(bool success, const char* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void ShowErrorDialog(const char* title, const std::string& message) {
|
||||
SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_ERROR,
|
||||
title,
|
||||
message.c_str(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Sdl3App::Sdl3App(const std::filesystem::path& scriptPath, bool luaDebug)
|
||||
@@ -102,11 +128,29 @@ void Sdl3App::InitSDL() {
|
||||
TRACE_FUNCTION();
|
||||
TRACE_VAR(kWidth);
|
||||
TRACE_VAR(kHeight);
|
||||
ThrowSdlErrorIfFailed(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO), "SDL_Init failed");
|
||||
ThrowSdlErrorIfFailed(SDL_Vulkan_LoadLibrary(nullptr), "SDL_Vulkan_LoadLibrary failed");
|
||||
|
||||
try {
|
||||
ThrowSdlErrorIfFailed(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO), "SDL_Init failed");
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("SDL Initialization Failed",
|
||||
std::string("Failed to initialize SDL subsystems.\n\nError: ") + e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
ThrowSdlErrorIfFailed(SDL_Vulkan_LoadLibrary(nullptr), "SDL_Vulkan_LoadLibrary failed");
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("Vulkan Library Load Failed",
|
||||
std::string("Failed to load Vulkan library. Make sure Vulkan drivers are installed.\n\nError: ") + e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
window_ = SDL_CreateWindow("SDL3 Vulkan Demo", kWidth, kHeight, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||
if (!window_) {
|
||||
throw std::runtime_error(BuildSdlErrorMessage("SDL_CreateWindow failed"));
|
||||
std::string errorMsg = BuildSdlErrorMessage("SDL_CreateWindow failed");
|
||||
ShowErrorDialog("Window Creation Failed",
|
||||
std::string("Failed to create application window.\n\nError: ") + errorMsg);
|
||||
throw std::runtime_error(errorMsg);
|
||||
}
|
||||
TRACE_VAR(window_);
|
||||
SDL_StartTextInput(window_);
|
||||
@@ -120,22 +164,53 @@ void Sdl3App::InitSDL() {
|
||||
|
||||
void Sdl3App::InitVulkan() {
|
||||
TRACE_FUNCTION();
|
||||
CreateInstance();
|
||||
CreateSurface();
|
||||
PickPhysicalDevice();
|
||||
CreateLogicalDevice();
|
||||
CreateSwapChain();
|
||||
SetupGuiRenderer();
|
||||
CreateImageViews();
|
||||
CreateRenderPass();
|
||||
LoadSceneData();
|
||||
CreateGraphicsPipeline();
|
||||
CreateFramebuffers();
|
||||
CreateCommandPool();
|
||||
CreateVertexBuffer();
|
||||
CreateIndexBuffer();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
try {
|
||||
CreateInstance();
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("Vulkan Instance Creation Failed", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
CreateSurface();
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("Vulkan Surface Creation Failed", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
PickPhysicalDevice();
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("GPU Selection Failed", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
CreateLogicalDevice();
|
||||
} catch (const std::exception& e) {
|
||||
ShowErrorDialog("Logical Device Creation Failed", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
CreateSwapChain();
|
||||
SetupGuiRenderer();
|
||||
CreateImageViews();
|
||||
CreateRenderPass();
|
||||
LoadSceneData();
|
||||
CreateGraphicsPipeline();
|
||||
CreateFramebuffers();
|
||||
CreateCommandPool();
|
||||
CreateVertexBuffer();
|
||||
CreateIndexBuffer();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
} catch (const std::exception& e) {
|
||||
std::string errorMsg = "Vulkan initialization failed during resource setup:\n\n";
|
||||
errorMsg += e.what();
|
||||
ShowErrorDialog("Vulkan Resource Setup Failed", errorMsg);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Sdl3App::MainLoop() {
|
||||
|
||||
@@ -34,7 +34,17 @@ void Sdl3App::CreateInstance() {
|
||||
createInfo.ppEnabledExtensionNames = extensionList.data();
|
||||
|
||||
if (vkCreateInstance(&createInfo, nullptr, &instance_) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create Vulkan instance");
|
||||
std::string errorMsg = "Failed to create Vulkan instance. This may be due to:\n";
|
||||
errorMsg += "- Missing or outdated Vulkan drivers\n";
|
||||
errorMsg += "- Incompatible GPU\n";
|
||||
errorMsg += "- Missing required Vulkan extensions\n\n";
|
||||
errorMsg += "Required extensions (" + std::to_string(extensionList.size()) + "):\n";
|
||||
for (const auto* ext : extensionList) {
|
||||
errorMsg += " - ";
|
||||
errorMsg += ext;
|
||||
errorMsg += "\n";
|
||||
}
|
||||
throw std::runtime_error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,20 +60,35 @@ void Sdl3App::PickPhysicalDevice() {
|
||||
uint32_t deviceCount = 0;
|
||||
vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr);
|
||||
if (deviceCount == 0) {
|
||||
throw std::runtime_error("Failed to find GPUs with Vulkan support");
|
||||
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");
|
||||
}
|
||||
std::vector<VkPhysicalDevice> devices(deviceCount);
|
||||
vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data());
|
||||
|
||||
for (const auto& device : devices) {
|
||||
if (IsDeviceSuitable(device)) {
|
||||
physicalDevice_ = device;
|
||||
std::string deviceInfo;
|
||||
for (size_t i = 0; i < devices.size(); ++i) {
|
||||
VkPhysicalDeviceProperties props;
|
||||
vkGetPhysicalDeviceProperties(devices[i], &props);
|
||||
deviceInfo += "\nGPU " + std::to_string(i) + ": " + props.deviceName;
|
||||
if (IsDeviceSuitable(devices[i])) {
|
||||
physicalDevice_ = devices[i];
|
||||
deviceInfo += " [SELECTED]";
|
||||
break;
|
||||
} else {
|
||||
deviceInfo += " [UNSUITABLE]";
|
||||
}
|
||||
}
|
||||
|
||||
if (physicalDevice_ == VK_NULL_HANDLE) {
|
||||
throw std::runtime_error("Failed to find a suitable GPU");
|
||||
std::string errorMsg = "Failed to find a suitable GPU.\n\n";
|
||||
errorMsg += "Found " + std::to_string(deviceCount) + " GPU(s), but none meet the requirements.";
|
||||
errorMsg += deviceInfo;
|
||||
errorMsg += "\n\nRequired features:\n";
|
||||
errorMsg += "- Graphics queue support\n";
|
||||
errorMsg += "- Present queue support\n";
|
||||
errorMsg += "- Swapchain extension support (VK_KHR_swapchain)\n";
|
||||
errorMsg += "- Adequate swapchain formats and present modes\n";
|
||||
throw std::runtime_error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,9 +168,17 @@ bool Sdl3App::CheckDeviceExtensionSupport(VkPhysicalDevice device) {
|
||||
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
|
||||
|
||||
std::set<std::string> requiredExtensions(kDeviceExtensions.begin(), kDeviceExtensions.end());
|
||||
|
||||
for (const auto& extension : availableExtensions) {
|
||||
requiredExtensions.erase(extension.extensionName);
|
||||
}
|
||||
|
||||
if (!requiredExtensions.empty()) {
|
||||
std::cerr << "Missing required device extensions:\n";
|
||||
for (const auto& missing : requiredExtensions) {
|
||||
std::cerr << " - " << missing << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return requiredExtensions.empty();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,20 @@ void Sdl3App::CreateGraphicsPipeline() {
|
||||
pipelineInfo.subpass = 0;
|
||||
|
||||
for (const auto& [key, paths] : shaderPathMap_) {
|
||||
// Validate shader files exist before attempting to load
|
||||
if (!std::filesystem::exists(paths.vertex)) {
|
||||
throw std::runtime_error(
|
||||
"Vertex shader not found: " + paths.vertex +
|
||||
"\n\nShader key: " + key +
|
||||
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
|
||||
}
|
||||
if (!std::filesystem::exists(paths.fragment)) {
|
||||
throw std::runtime_error(
|
||||
"Fragment shader not found: " + paths.fragment +
|
||||
"\n\nShader key: " + key +
|
||||
"\n\nPlease ensure shader files are compiled and present in the shaders directory.");
|
||||
}
|
||||
|
||||
auto vertShaderCode = ReadFile(paths.vertex);
|
||||
auto fragShaderCode = ReadFile(paths.fragment);
|
||||
|
||||
|
||||
@@ -312,6 +312,12 @@ int main(int argc, char** argv) {
|
||||
app.Run();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "ERROR: " << e.what() << '\n';
|
||||
// Show error dialog if SDL is available
|
||||
SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_ERROR,
|
||||
"Application Error",
|
||||
e.what(),
|
||||
nullptr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
Reference in New Issue
Block a user