Skip to content

Commit 1ee46ba

Browse files
committed
Improve Watchdog functionality and mesh resource handling
- Added progress tracking to Watchdog for better diagnostics during long operations. - Enhanced Vulkan extension support checks with reusable helper. - Optimized fencing and timeline semaphore handling for resource uploads. - Improved staging buffer usage and batch processing for entity resources. The windows debug now loads in under a minute and displays ray query as default and at 80fps. Forward+ causes raster pass to average around 30fps while normal PBR raster, phong and no culling are all around 50 fps.
1 parent 41365a0 commit 1ee46ba

File tree

11 files changed

+1401
-417
lines changed

11 files changed

+1401
-417
lines changed

attachments/simple_engine/crash_reporter.h

Lines changed: 133 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
#pragma once
1818

19+
#include <atomic>
1920
#include <chrono>
2021
#include <cstring>
2122
#include <ctime>
@@ -25,10 +26,11 @@
2526
#include <mutex>
2627
#include <string>
2728
#include <thread>
29+
#include <unordered_map>
2830

2931
#ifdef _WIN32
30-
# include <dbghelp.h>
3132
# include <windows.h>
33+
# include <dbghelp.h>
3234
# pragma comment(lib, "dbghelp.lib")
3335
#elif defined(__APPLE__) || defined(__linux__)
3436
# include <execinfo.h>
@@ -118,18 +120,7 @@ class CrashReporter
118120
*/
119121
void HandleCrash(const std::string &message)
120122
{
121-
std::lock_guard<std::mutex> lock(mutex);
122-
123-
LOG_FATAL("CrashReporter", "Crash detected: " + message);
124-
125-
// Generate minidump
126-
GenerateMinidump(message);
127-
128-
// Call registered callbacks
129-
for (const auto &callback : crashCallbacks)
130-
{
131-
callback(message);
132-
}
123+
HandleCrashInternal(message, nullptr);
133124
}
134125

135126
/**
@@ -161,7 +152,7 @@ class CrashReporter
161152
* @brief Generate a minidump.
162153
* @param message The crash message.
163154
*/
164-
void GenerateMinidump(const std::string &message)
155+
void GenerateMinidump(const std::string &message, void *platformExceptionPointers = nullptr)
165156
{
166157
// Get current time for filename
167158
auto now = std::chrono::system_clock::now();
@@ -171,12 +162,37 @@ class CrashReporter
171162

172163
// Create minidump filename
173164
std::string filename = minidumpDir + "/" + appName + "_" + timeStr + ".dmp";
165+
std::string report = minidumpDir + "/" + appName + "_" + timeStr + ".txt";
174166

175-
LOG_INFO("CrashReporter", "Generating minidump: " + filename);
167+
// Also write a small sidecar text file so users can quickly see the exception code/address
168+
// without needing a debugger.
169+
try
170+
{
171+
std::ofstream rep(report, std::ios::out | std::ios::trunc);
172+
rep << "Crash Report for " << appName << " " << appVersion << "\n";
173+
rep << "Timestamp: " << timeStr << "\n";
174+
rep << "Message: " << message << "\n";
175+
#ifdef _WIN32
176+
if (platformExceptionPointers)
177+
{
178+
auto *exPtrs = reinterpret_cast<EXCEPTION_POINTERS *>(platformExceptionPointers);
179+
if (exPtrs && exPtrs->ExceptionRecord)
180+
{
181+
rep << "ExceptionCode: 0x" << std::hex << exPtrs->ExceptionRecord->ExceptionCode << std::dec << "\n";
182+
rep << "ExceptionAddress: " << exPtrs->ExceptionRecord->ExceptionAddress << "\n";
183+
rep << "ExceptionFlags: 0x" << std::hex << exPtrs->ExceptionRecord->ExceptionFlags << std::dec << "\n";
184+
}
185+
}
186+
#endif
187+
}
188+
catch (...)
189+
{
190+
}
176191

177192
// Generate minidump based on platform
178193
#ifdef _WIN32
179194
// Windows implementation
195+
EXCEPTION_POINTERS *exPtrs = reinterpret_cast<EXCEPTION_POINTERS *>(platformExceptionPointers);
180196
HANDLE hFile = CreateFileA(
181197
filename.c_str(),
182198
GENERIC_WRITE,
@@ -188,19 +204,19 @@ class CrashReporter
188204

189205
if (hFile != INVALID_HANDLE_VALUE)
190206
{
191-
MINIDUMP_EXCEPTION_INFORMATION exInfo;
207+
MINIDUMP_EXCEPTION_INFORMATION exInfo{};
192208
exInfo.ThreadId = GetCurrentThreadId();
193-
exInfo.ExceptionPointers = NULL; // Would be set in a real exception handler
209+
exInfo.ExceptionPointers = exPtrs;
194210
exInfo.ClientPointers = FALSE;
195211

196-
MiniDumpWriteDump(
197-
GetCurrentProcess(),
198-
GetCurrentProcessId(),
199-
hFile,
200-
MiniDumpNormal,
201-
&exInfo,
202-
NULL,
203-
NULL);
212+
MINIDUMP_EXCEPTION_INFORMATION *exInfoPtr = exPtrs ? &exInfo : nullptr;
213+
MiniDumpWriteDump(GetCurrentProcess(),
214+
GetCurrentProcessId(),
215+
hFile,
216+
MiniDumpNormal,
217+
exInfoPtr,
218+
NULL,
219+
NULL);
204220

205221
CloseHandle(hFile);
206222
}
@@ -232,7 +248,10 @@ class CrashReporter
232248
}
233249
#endif
234250

235-
LOG_INFO("CrashReporter", "Minidump generated: " + filename);
251+
// Best-effort stderr note (stdout/stderr redirection will capture this even if DebugSystem isn't initialized)
252+
std::fprintf(stderr, "[CrashReporter] Wrote minidump: %s\n", filename.c_str());
253+
std::fprintf(stderr, "[CrashReporter] Wrote report: %s\n", report.c_str());
254+
std::fflush(stderr);
236255
}
237256

238257
private:
@@ -259,6 +278,80 @@ class CrashReporter
259278
// Crash callbacks
260279
std::unordered_map<int, std::function<void(const std::string &)>> crashCallbacks;
261280
int nextCallbackId = 0;
281+
std::atomic<bool> handlingCrash{false};
282+
283+
#ifdef _WIN32
284+
static bool ShouldCaptureException(EXCEPTION_POINTERS *exInfo, bool unhandled)
285+
{
286+
if (unhandled)
287+
return true;
288+
if (!exInfo || !exInfo->ExceptionRecord)
289+
return false;
290+
const DWORD code = exInfo->ExceptionRecord->ExceptionCode;
291+
const DWORD flags = exInfo->ExceptionRecord->ExceptionFlags;
292+
// Ignore common first-chance C++ exceptions and breakpoint exceptions.
293+
if (code == 0xE06D7363u /* MSVC C++ EH */ || code == 0x80000003u /* breakpoint */)
294+
return false;
295+
// Capture likely-fatal errors and non-continuable exceptions.
296+
if ((flags & EXCEPTION_NONCONTINUABLE) != 0)
297+
return true;
298+
switch (code)
299+
{
300+
case 0xC0000409u: // STATUS_STACK_BUFFER_OVERRUN
301+
case 0xC0000005u: // STATUS_ACCESS_VIOLATION
302+
case 0xC000001Du: // STATUS_ILLEGAL_INSTRUCTION
303+
case 0xC00000FDu: // STATUS_STACK_OVERFLOW
304+
case 0xC0000374u: // STATUS_HEAP_CORRUPTION
305+
return true;
306+
default:
307+
return false;
308+
}
309+
}
310+
#endif
311+
312+
#ifdef _WIN32
313+
void *vectoredHandlerHandle = nullptr;
314+
#endif
315+
316+
void HandleCrashInternal(const std::string &message, void *platformExceptionPointers)
317+
{
318+
bool expected = false;
319+
if (!handlingCrash.compare_exchange_strong(expected, true))
320+
{
321+
// Already handling a crash; avoid recursion.
322+
return;
323+
}
324+
std::lock_guard<std::mutex> lock(mutex);
325+
326+
std::string msg = message;
327+
(void) platformExceptionPointers;
328+
329+
#ifdef _WIN32
330+
if (platformExceptionPointers)
331+
{
332+
auto *exPtrs = reinterpret_cast<EXCEPTION_POINTERS *>(platformExceptionPointers);
333+
if (exPtrs && exPtrs->ExceptionRecord)
334+
{
335+
const DWORD code = exPtrs->ExceptionRecord->ExceptionCode;
336+
void *addr = exPtrs->ExceptionRecord->ExceptionAddress;
337+
char buf[128];
338+
std::snprintf(buf, sizeof(buf), " (code=0x%08lX, addr=%p)", static_cast<unsigned long>(code), addr);
339+
msg += buf;
340+
}
341+
}
342+
#endif
343+
344+
LOG_FATAL("CrashReporter", "Crash detected: " + msg);
345+
346+
// Generate minidump
347+
GenerateMinidump(msg, platformExceptionPointers);
348+
349+
// Call registered callbacks
350+
for (const auto &callback : crashCallbacks)
351+
{
352+
callback.second(msg);
353+
}
354+
}
262355

263356
/**
264357
* @brief Install platform-specific crash handlers.
@@ -267,8 +360,16 @@ class CrashReporter
267360
{
268361
#ifdef _WIN32
269362
// Windows implementation
363+
// Vectored handler runs before SEH/unhandled filters and is more likely to fire for fast-fail style crashes.
364+
vectoredHandlerHandle = AddVectoredExceptionHandler(1, [](EXCEPTION_POINTERS *exInfo) -> LONG {
365+
if (CrashReporter::ShouldCaptureException(exInfo, /*unhandled=*/false))
366+
{
367+
CrashReporter::GetInstance().HandleCrashInternal("Vectored exception", exInfo);
368+
}
369+
return EXCEPTION_CONTINUE_SEARCH;
370+
});
270371
SetUnhandledExceptionFilter([](EXCEPTION_POINTERS *exInfo) -> LONG {
271-
CrashReporter::GetInstance().HandleCrash("Unhandled exception");
372+
CrashReporter::GetInstance().HandleCrashInternal("Unhandled exception", exInfo);
272373
return EXCEPTION_EXECUTE_HANDLER;
273374
});
274375
#else
@@ -303,6 +404,11 @@ class CrashReporter
303404
#ifdef _WIN32
304405
// Windows implementation
305406
SetUnhandledExceptionFilter(NULL);
407+
if (vectoredHandlerHandle)
408+
{
409+
RemoveVectoredExceptionHandler(vectoredHandlerHandle);
410+
vectoredHandlerHandle = nullptr;
411+
}
306412
#else
307413
// Unix implementation
308414
signal(SIGSEGV, SIG_DFL);

attachments/simple_engine/imgui_system.cpp

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,11 @@ void ImGuiSystem::NewFrame()
165165

166166
ImGui::NewFrame();
167167

168-
// Loading overlay: show only a fullscreen progress bar while the model
169-
// itself is loading. Once the scene is ready and geometry is visible,
170-
// we no longer block the view with a full-screen progress bar.
168+
// Loading overlay: show a fullscreen progress bar while the initial scene is loading.
169+
// The bar resets between phases (Textures -> Physics -> AS -> Finalizing) so users
170+
// don't stare at a 100% bar while the engine is still doing work.
171171
if (renderer)
172172
{
173-
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
174-
const uint32_t completed = renderer->GetTextureTasksCompleted();
175173
const bool modelLoading = renderer->IsLoading();
176174
if (modelLoading)
177175
{
@@ -202,11 +200,40 @@ void ImGuiSystem::NewFrame()
202200
const float barY = dispSize.y * 0.45f;
203201
ImGui::SetCursorPos(ImVec2(barX, barY));
204202
ImGui::BeginGroup();
205-
float frac = (scheduled > 0) ? (float) completed / (float) scheduled : 0.0f;
203+
204+
// Phase-aware progress (resets between phases).
205+
float frac = 0.0f;
206+
auto phase = renderer->GetLoadingPhase();
207+
if (phase == Renderer::LoadingPhase::Textures)
208+
{
209+
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
210+
const uint32_t completed = renderer->GetTextureTasksCompleted();
211+
frac = (scheduled > 0) ? (static_cast<float>(completed) / static_cast<float>(scheduled)) : 0.0f;
212+
}
213+
else if (phase == Renderer::LoadingPhase::AccelerationStructures)
214+
{
215+
frac = renderer->GetASBuildProgress();
216+
}
217+
else
218+
{
219+
frac = renderer->GetLoadingPhaseProgress();
220+
}
206221
ImGui::ProgressBar(frac, ImVec2(barWidth, 0.0f));
207222
ImGui::Dummy(ImVec2(0.0f, 10.0f));
208223
ImGui::SetCursorPosX(barX);
209-
ImGui::Text("Loading scene...");
224+
ImGui::Text("Loading: %s", renderer->GetLoadingPhaseName());
225+
if (phase == Renderer::LoadingPhase::Textures)
226+
{
227+
const uint32_t scheduled = renderer->GetTextureTasksScheduled();
228+
const uint32_t completed = renderer->GetTextureTasksCompleted();
229+
ImGui::Text("Textures: %u/%u", completed, scheduled);
230+
}
231+
else if (phase == Renderer::LoadingPhase::AccelerationStructures)
232+
{
233+
const uint32_t done = renderer->GetASBuildItemsDone();
234+
const uint32_t total = renderer->GetASBuildItemsTotal();
235+
ImGui::Text("%s (%u/%u, %.1fs)", renderer->GetASBuildStage(), done, total, renderer->GetASBuildElapsedSeconds());
236+
}
210237
ImGui::EndGroup();
211238
ImGui::PopStyleVar();
212239
}
@@ -226,6 +253,41 @@ void ImGuiSystem::NewFrame()
226253
const uint32_t uploadTotal = renderer->GetUploadJobsTotal();
227254
const uint32_t uploadDone = renderer->GetUploadJobsCompleted();
228255
const bool modelLoading = renderer->IsLoading();
256+
const bool showASBuild = renderer->ShouldShowASBuildProgressInUI();
257+
258+
// Acceleration structure build can happen after initial load completes.
259+
// If it takes a long time, show a compact progress window.
260+
if (!modelLoading && showASBuild)
261+
{
262+
ImGuiIO &io = ImGui::GetIO();
263+
const ImVec2 dispSize = io.DisplaySize;
264+
265+
const float windowWidth = std::min(320.0f, dispSize.x * 0.42f);
266+
const float windowHeight = 90.0f;
267+
const ImVec2 winPos(dispSize.x - windowWidth - 10.0f, 10.0f);
268+
269+
ImGui::SetNextWindowPos(winPos, ImGuiCond_Always);
270+
ImGui::SetNextWindowSize(ImVec2(windowWidth, windowHeight));
271+
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize |
272+
ImGuiWindowFlags_NoMove |
273+
ImGuiWindowFlags_NoCollapse |
274+
ImGuiWindowFlags_NoSavedSettings;
275+
276+
if (ImGui::Begin("##ASBuildStatus", nullptr, flags))
277+
{
278+
ImGui::Text("Building acceleration structures...");
279+
const float asFrac = renderer->GetASBuildProgress();
280+
ImGui::ProgressBar(asFrac, ImVec2(-1.0f, 0.0f));
281+
const uint32_t done = renderer->GetASBuildItemsDone();
282+
const uint32_t total = renderer->GetASBuildItemsTotal();
283+
ImGui::Text("%s (%u/%u, %.1fs)",
284+
renderer->GetASBuildStage(),
285+
done,
286+
total,
287+
renderer->GetASBuildElapsedSeconds());
288+
}
289+
ImGui::End();
290+
}
229291

230292
if (!modelLoading && uploadTotal > 0 && uploadDone < uploadTotal)
231293
{
@@ -234,7 +296,9 @@ void ImGuiSystem::NewFrame()
234296

235297
const float windowWidth = std::min(260.0f, dispSize.x * 0.35f);
236298
const float windowHeight = 120.0f;
237-
const ImVec2 winPos(dispSize.x - windowWidth - 10.0f, 10.0f);
299+
// If the AS build status window is visible, offset streaming window below it.
300+
const float yBase = 10.0f + (showASBuild ? (90.0f + 10.0f) : 0.0f);
301+
const ImVec2 winPos(dispSize.x - windowWidth - 10.0f, yBase);
238302

239303
ImGui::SetNextWindowPos(winPos, ImGuiCond_Always);
240304
ImGui::SetNextWindowSize(ImVec2(windowWidth, windowHeight));

attachments/simple_engine/main.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717
#include "camera_component.h"
18+
#include "crash_reporter.h"
1819
#include "engine.h"
1920
#include "scene_loading.h"
2021
#include "transform_component.h"
@@ -63,6 +64,7 @@ void SetupScene(Engine *engine)
6364
if (auto *renderer = engine->GetRenderer())
6465
{
6566
renderer->SetLoading(true);
67+
renderer->SetLoadingPhase(Renderer::LoadingPhase::Textures);
6668
}
6769
std::thread([engine] {
6870
LoadGLTFModel(engine, "../Assets/bistro/bistro.gltf");
@@ -107,6 +109,10 @@ int main(int, char *[])
107109
{
108110
try
109111
{
112+
// Enable minidump generation for Release-only crashes (e.g., stack cookie failures / fast-fail).
113+
// Writes dumps under the current working directory (the build/run directory).
114+
CrashReporter::GetInstance().Initialize("crashes", "SimpleEngine", "1.0.0");
115+
110116
// Create the engine
111117
Engine engine;
112118

@@ -122,11 +128,14 @@ int main(int, char *[])
122128
// Run the engine
123129
engine.Run();
124130

131+
CrashReporter::GetInstance().Cleanup();
132+
125133
return 0;
126134
}
127135
catch (const std::exception &e)
128136
{
129137
std::cerr << "Exception: " << e.what() << std::endl;
138+
CrashReporter::GetInstance().Cleanup();
130139
return 1;
131140
}
132141
}

0 commit comments

Comments
 (0)