Skip to content

Commit fe96695

Browse files
bghgaryCopilot
andcommitted
Merge master into update-device-test, align with Helpers.* refactor
PR BabylonJS#1712 landed master's Helpers.* split (Helpers.h, Helpers.D3D11.cpp, etc.) which overlaps with this branch's earlier Utils.* helpers (Utils.h, Utils.D3D11.cpp, etc.). This merge resolves by deleting the Utils.* files, folding CreateTestGraphicsDevice / DestroyTestGraphicsDevice into the Helpers namespace, and updating Tests.Device.cpp to use Helpers:: prefix. Tests.Device.D3D11.cpp auto-merged onto master's #include "Helpers.h" preserving this branch's UpdateDeviceThrowsWhenRenderingEnabled test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 583bd40 + c56f12a commit fe96695

61 files changed

Lines changed: 28441 additions & 23363 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Babylon Native -- Debuggability Reference
2+
3+
Read this **before** debugging Babylon Native crashes, hangs, asserts, or visual
4+
test failures. The Playground app has dedicated diagnostic infrastructure
5+
that turns common silent failures into searchable banners with stack traces.
6+
Don't add new ad-hoc logging or `MessageBox` prompts when the existing facilities
7+
already cover the case.
8+
9+
## When to Use
10+
11+
Read this when the user mentions: Playground, validation tests, BabylonNative
12+
crash, headless tests, `BX_ASSERT`, "MS C++ runtime dialog", call stack,
13+
`config.json` test, `excludeFromAutomaticTesting`, pixel diff failure,
14+
`captureNextFrame`, RenderDoc capture from Playground, or asks to debug a
15+
specific BN test by name or index.
16+
17+
For pure RenderDoc CLI usage (any app, not BN-specific), see
18+
`renderdoc/renderdoc-gpu-debug.instructions.md` and
19+
`renderdoc/rdc-commands-reference.instructions.md`.
20+
21+
## Layout
22+
23+
| File / Dir | Role |
24+
|---|---|
25+
| `Apps/Playground/Shared/CommandLine.{h,cpp}` | Argument parser. Single source of truth for supported flags. |
26+
| `Apps/Playground/Shared/Diagnostics.{h,cpp}` | Crash handler, `DumpFailure`, finish-line, exit-code tracking. |
27+
| `Apps/Playground/Shared/AppContext.cpp` | Wires `UnhandledExceptionHandler` + `console.error` into `DumpFailure`; injects `_playgroundOptions` into JS. |
28+
| `Apps/Playground/Scripts/validation_native.js` | Test runner. Reads `_playgroundOptions`, picks tests, calls `TestUtils.captureNextFrame()`. Reference-image load failures arrive via `BABYLON.Tools.LoadFile`'s `onLoadFileError` and are tagged with `MISSING_REFERENCE_IMAGE:`. |
29+
| `Apps/Playground/Scripts/config.json` | Test catalog. Each entry has `title`, `playgroundId`/`scriptToRun`, `referenceImage`, optional `excludeFromAutomaticTesting`/`reason`/`onlyVisual`/`renderCount`/`capture`/`threshold`/`errorRatio`. |
30+
| `Plugins/TestUtils/Source/TestUtils.cpp` | Native side of `TestUtils.captureNextFrame()` -- calls `m_deviceContext.RequestCaptureNextFrame()`. |
31+
| `Core/Graphics/Source/BgfxCallback.cpp` | bgfx trace/fatal sink. Routes bgfx output to stdout in headless mode and to `OutputDebugString` always. |
32+
33+
## Built-in features -- do NOT re-implement
34+
35+
These are already wired up. Use them; don't add parallel mechanisms.
36+
37+
### 1. Crash + assert handler with native call stacks
38+
`Diagnostics::InstallCrashHandler()` runs from `Diagnostics::Initialize()`,
39+
which is called at the **top** of `wWinMain` / `main`. It:
40+
- installs `bx::installExceptionHandler()` (SEH on Windows, signals elsewhere)
41+
so unhandled exceptions print a banner + native callstack to **stderr**
42+
before terminating with exit code **3**;
43+
- on Windows: suppresses the modal "Microsoft Visual C++ Runtime Library"
44+
assertion dialog, the `abort()` retry dialog, and the invalid-parameter
45+
dialog -- all are routed through `DumpFailure` instead;
46+
- only calls `bx::debugBreak()` when a debugger is attached, so the breakpoint
47+
doesn't escape and corrupt the exit code in CI;
48+
- maps `BX_ASSERT` / `BX_CHECK` failures into the same banner path (the bx
49+
default assert handler delegates to `DumpFailure`).
50+
51+
### 2. `DumpFailure(category, file, line, skipFrames, fmt, ...)`
52+
Reusable, formatted banner emitter. Output looks like:
53+
```
54+
--- <CATEGORY> ---
55+
<file>(<line>): <printf-formatted message body>
56+
57+
Callstack (N):
58+
#: File --- Line PC --- Function ---
59+
0: foo.cpp 123 0x00007ff6... foo::bar
60+
...
61+
62+
Build info:
63+
Compiler: ..., CPU: ..., Arch: ..., OS: ..., CRT: ..., C++: ..., Date: ..., Time: ...
64+
65+
--- END ---
66+
```
67+
Every line is grep-able and includes a callstack down to the failure site.
68+
69+
Pre-wired call sites:
70+
| Category | Triggered by |
71+
|---|---|
72+
| `ASSERT` | `BX_ASSERT` failures |
73+
| `ABORT` | `SIGABRT` / runtime invalid-parameter / `_CRT_ERROR` |
74+
| `CRASH` | unhandled SEH / `bx::installExceptionHandler` path |
75+
| `UNCAUGHT JS ERROR` | `Babylon::AppRuntime::UnhandledExceptionHandler` |
76+
| `JS CONSOLE ERROR` | every `console.error(...)` from JS (Babylon.js routes recoverable errors through this) |
77+
78+
When you see `[Error] Error: Cannot load X` in stdout, **scroll up** --
79+
`DumpFailure` prints the full banner + JS + native stacks **before** the
80+
short error line. The short line is kept so legacy log scrapers still match.
81+
82+
### 3. JS stack on every `console.error`
83+
`AppContext.cpp`'s `Console::Initialize` callback calls
84+
`Babylon::Polyfills::Console::CaptureCurrentJsStack(env)` on every
85+
`LogLevel::Error` message and appends the captured stack to the
86+
`DumpFailure` banner body. The capture is best-effort -- if the JS engine
87+
can't produce a stack (no JS context active, etc.) the helper returns an
88+
empty string and the banner just shows the message.
89+
Do not add per-callsite stack capture -- it's automatic.
90+
91+
### 4. Colored finish line
92+
`Playground: Finished in <time>. (exit <code>)` is printed once at every exit
93+
path (atexit + at_quick_exit + explicit calls). Green on success, red on
94+
failure. Use `Diagnostics::SetExitCode(N)` before any `std::quick_exit`/`_Exit`
95+
so the line colorizes correctly.
96+
97+
### 5. Headless mode + stdout routing
98+
`--headless` runs without a visible window (still creates an HWND for input
99+
plumbing) and **routes all `console.log`/`console.error`, bgfx trace, and
100+
diagnostic banners to stdout** -- without it, output goes to OutputDebugString
101+
only and is invisible from a console run. Use `--headless --once` for any
102+
scripted/CI run.
103+
104+
### 6. RenderDoc capture trigger (already integrated)
105+
`TestUtils.captureNextFrame()` JS API -> `DeviceContext::RequestCaptureNextFrame()`
106+
-> on next bgfx frame, `BGFX_FRAME_DEBUG_CAPTURE` flag -> bgfx's
107+
`renderDocTriggerCapture()` -> `s_renderDoc->TriggerCapture()`. bgfx loads
108+
`renderdoc.dll` automatically if it's reachable via PATH/LoadLibrary at
109+
init. **Don't add a parallel RenderDoc integration.** Use the `--capture=N`
110+
CLI flag (see below) for ad-hoc capture; see
111+
`playground/playground-renderdoc-capture.instructions.md` for the
112+
test-capture recipe and gotchas.
113+
114+
## Command-line flags (Playground)
115+
116+
Reference, not a guess -- `--help` prints the same. **Only one short alias
117+
per flag** (intentional design -- long-name aliases were removed).
118+
119+
```
120+
-h, --help Show help and exit (0)
121+
-l, --list List configured tests as TSV and exit (0)
122+
--headless Don't show window; route all logs to stdout
123+
--break-on-fail Trigger debugger break on a failing test
124+
--generate-references Save rendered images as new reference PNGs
125+
--once Run only the first matching test, then exit
126+
--include-excluded Force-run tests with excludeFromAutomaticTesting /
127+
onlyVisual / excludedGraphicsApis set in config.json
128+
--save-results=BOOL Override saving of result PNGs (default true)
129+
--debug-trace=BOOL Enable/disable Babylon::DebugTrace
130+
--perf-trace=LEVEL Set Babylon::PerfTrace level (None / Log)
131+
--capture=N Trigger RenderDoc capture on the Nth rendered frame
132+
of every executed test. Auto-extends each test's
133+
render budget so the .rdc finalizes; pixel compare
134+
still runs on the test's original renderCount, so
135+
pass/fail is unaffected. Output: <cwd>/temp/
136+
bgfx_frame<N>.rdc. Combine with --once / --test /
137+
--test-index for a single capture. Requires
138+
renderdoc.dll to be loaded into the process; easiest
139+
is to launch via `renderdoccmd capture -w` or
140+
`rdc capture --trigger -w`, both of which inject
141+
the paired DLL before main.
142+
-t, --test=PATTERN Run tests whose title contains PATTERN (substring,
143+
case-insensitive). Repeatable; multiple = OR.
144+
--test-index=LIST Run only the listed indices.
145+
LIST: '3' or '3,5,7' or '3-6' or '3,5-7,9'.
146+
-- End of options; everything after is a script path.
147+
```
148+
149+
## Exit codes
150+
151+
| Code | Meaning |
152+
|---:|---|
153+
| `0` | success / `--help` / `--list` |
154+
| `1` | uncaught JS exception |
155+
| `2` | command-line parse error |
156+
| `3` | hard crash (assert / SIGABRT / unhandled exception) |
157+
| `-1` | pixel-diff comparison failure |
158+
159+
PowerShell shows `-1` as `4294967295` after unsigned cast in some contexts.
160+
161+
## Common debugging scenarios
162+
163+
### Run one test by name
164+
```powershell
165+
.\Playground.exe --headless --once --test "MultiRenderTarget" `
166+
app:///Scripts/validation_native.js
167+
```
168+
Substring match, case-insensitive, against `test.title`.
169+
170+
### Run one test by index (when titles collide or you only have a survey row)
171+
```powershell
172+
.\Playground.exe --headless --once --test-index=N `
173+
app:///Scripts/validation_native.js
174+
```
175+
Use `--list` to find the index for a title. Indices are not stable across
176+
config.json edits; always re-resolve when the test list changes.
177+
178+
### Run a quarantined test without editing config.json
179+
```powershell
180+
.\Playground.exe --headless --once --include-excluded --test "Decal Map" `
181+
app:///Scripts/validation_native.js
182+
```
183+
Bypasses `excludeFromAutomaticTesting`, `onlyVisual`, and
184+
`excludedGraphicsApis` filters. Use this whenever you need to *debug* a
185+
quarantined test -- never edit config.json's exclusion flags as part of
186+
debugging.
187+
188+
### Discover what tests exist
189+
```powershell
190+
.\Playground.exe --list
191+
```
192+
Prints TSV with index, title, referenceImage, exclusionReason. Pipe to
193+
`findstr` / `Select-String` for filtering.
194+
195+
### Pixel-diff investigation
196+
1. Run the failing test with `--headless --once --include-excluded`.
197+
2. Stdout will contain `First pixel off at <byteOffset>: Value: (R,G,B) - Expected: (R,G,B)` for the first divergent pixel.
198+
3. Result PNG is at `<exe-parent>/Results/<referenceImage>` (e.g.
199+
`Apps/Playground/Results/instancecolors.png`).
200+
4. Diff overlay (red where pixels differ) is at `<exe-parent>/Errors/<referenceImage>`.
201+
5. Reference is `<exe-dir>/ReferenceImages/<referenceImage>`.
202+
6. Compare visually first. If geometry/transforms look right but colors are
203+
off, the bug is shader/texture/uniform; if geometry is wrong, it's
204+
vertex/index/transform. Then capture with RenderDoc -- see the dedicated
205+
recipe.
206+
207+
### Reference image missing
208+
If the test runner emits a line containing `MISSING_REFERENCE_IMAGE:` then the
209+
reference asset for that test wasn't usable. Two distinct sub-cases:
210+
211+
```
212+
MISSING_REFERENCE_IMAGE: Test 'X' has no 'referenceImage' field in config.json - cannot run pixel comparison.
213+
MISSING_REFERENCE_IMAGE: Test 'X' failed to load reference at app:///ReferenceImages/<name>.png. <exception>
214+
```
215+
216+
The first is a catalog/config error (fixed by editing `config.json`). The
217+
second is a runtime asset-missing case -- the XHR for the reference PNG fired
218+
its `error` event (e.g. the file isn't on disk, or the local-file load failed
219+
otherwise), `BABYLON.Tools.LoadFile`'s `onLoadFileError` ran, the runner
220+
tagged the message with the grep-able token and `failTest`ed. Exit code is
221+
**-1** (test failure); the run summary increments a dedicated counter
222+
(`missingRef=N`). RenderDoc capture is meaningless for these tests -- there
223+
are zero draws because the runner never reaches `loadPlayground`. The check
224+
is a no-op for `--generate-references` runs (refs don't exist by design) and
225+
for `onlyVisual` tests (which don't compare pixels).
226+
227+
### RenderDoc capture (one-liner)
228+
```powershell
229+
# Launch under renderdoccmd: it injects the paired renderdoc.dll into the
230+
# Playground process before main, so bgfx::findModule adopts it. Version
231+
# always matches what `rdc open` accepts -- no PATH-ordering bugs.
232+
& "<renderdoc-py-dir>\renderdoccmd.exe" capture -w .\Playground.exe `
233+
--headless --once --test "<test title or substring>" --include-excluded --capture=5 `
234+
app:///Scripts/validation_native.js
235+
# .rdc lives at <cwd>\temp\bgfx_frame5.rdc
236+
```
237+
Equivalent with the rdc-cli wrapper (inject-only, no auto-capture handshake):
238+
```powershell
239+
& rdc capture --trigger --wait-for-exit -- .\Playground.exe `
240+
--headless --once --test "<test title or substring>" --include-excluded --capture=5 `
241+
app:///Scripts/validation_native.js
242+
```
243+
Verify `renderdoc.dll` is loaded:
244+
```powershell
245+
(Get-Process Playground).Modules | Where-Object ModuleName -eq 'renderdoc.dll'
246+
```
247+
Pixel pass/fail is unchanged by `--capture`. See
248+
`playground/playground-renderdoc-capture.instructions.md` for the full recipe
249+
(launcher selection, multiple captures, `rdc` CLI inspection).
250+
251+
### "App crashed but I only see one short line"
252+
Scroll **up** from the error line. The full `--- CRASH ---` /
253+
`--- ASSERT ---` / `--- UNCAUGHT JS ERROR ---` banner with both JS and
254+
native stacks is printed *before* the legacy one-liner. If that banner is
255+
missing, the process probably terminated through a path that bypasses
256+
`Diagnostics::Initialize()` (e.g. a static-init crash before
257+
`InstallCrashHandler()` ran).
258+
259+
### bgfx assert during test (`bgfx.cpp:NNNN: ...`)
260+
The bgfx callback in `Core/Graphics/Source/BgfxCallback.cpp` mirrors trace
261+
output to `Diagnostics`'s sink. `BX_ASSERT` failures show up as `--- ASSERT ---`
262+
banners. The actual assert message is in the second-to-last line of the
263+
banner body; bx's "STR=" prefix indicates the underlying string passed to
264+
the assert.
265+
266+
### Config-driven knobs per test
267+
| Field | Effect |
268+
|---|---|
269+
| `excludeFromAutomaticTesting: true` | Skipped unless `--include-excluded` |
270+
| `onlyVisual: true` | Render but skip pixel comparison (use `--include-excluded` to force pixel compare path; result is still saved) |
271+
| `excludedGraphicsApis: ["D3D11"]` | Skipped on listed APIs unless `--include-excluded` |
272+
| `renderCount: N` | Run scene N frames before pixel comparison (default 1) |
273+
| `capture: true` | Call `TestUtils.captureNextFrame()` once (when `renderCount === 1`) -- triggers bgfx's RenderDoc capture. **Prefer the `--capture=N` CLI flag** for ad-hoc capture; this config knob is kept for legacy tests that hard-pin a per-test capture. |
274+
| `threshold: N` | Per-channel tolerance for pixel comparison (default 25) |
275+
| `errorRatio: F` | % of pixels allowed to differ (default 2.5) |
276+
| `replace: "src,dst,..."` | String-replace pairs applied to the playground source before `eval` |
277+
| `specificRoot: "..."` | Override `BABYLON.Tools.BaseUrl` for asset loads |
278+
279+
## Adding new diagnostic output -- guidelines
280+
281+
- Use `Diagnostics::DumpFailure` for any *failure* that would otherwise be a
282+
one-line log entry hard to track down. It gives a searchable banner +
283+
callstack + build info for free.
284+
- Use `Babylon::Polyfills::Console::Initialize`'s `debugLog` callback for
285+
general informational logs (it's already wired to stdout in headless
286+
mode and OutputDebugString always).
287+
- For new command-line flags, add them only to `CommandLine.cpp`, follow the
288+
`match("--long", "-short", FlagKind::...)` pattern, and update `PrintUsage`
289+
in the same file. **No long-name aliases**: pick one canonical long name.
290+
- Update this instructions file when adding flags or new diagnostic
291+
categories so future agents find them.
292+
293+
## Building
294+
295+
The branch builds via the standard CMake setup. After source changes that
296+
touch `Apps/Playground/Scripts/*.js` or `Apps/Playground/Scripts/config.json`,
297+
the CMake `Apps/Playground/CMakeLists.txt` copies them to
298+
`<build>/Apps/Playground/<config>/Scripts/`. For *transient* runtime tweaks
299+
(e.g. flipping `capture: true` for one debug run), edit the build-dir copy
300+
directly and revert when done -- that avoids accidental commits to
301+
`config.json` and avoids a rebuild round-trip.

0 commit comments

Comments
 (0)