fix(vk): the tree-shadow mask blend off by default (a GPU hang / device lost near Hyrule Castle) #2374

Merged
NotRexed merged 1 commit from fix/mask-blend-off-by-default into main 2026-11-11 16:57:41 +00:00
NotRexed commented 2026-11-11 16:30:49 +00:00 (Migrated from github.com)

Release blocker for 2.1.1. The owner's test of main crashed near Hyrule Castle: device lost after a ~2.5 s GPU hang on the mesh draw b0b3b40244c71ec7. In the checkpoints, the draw that follows it is OoT's tree-shadow pass 3427a1dbefd5e02c, the one #1581's mask blend (mask_stabilize) runs after.

build, the owner's castle save, native mesh result
v2.1.0 (before #1581) 48,960 presents, no crash
main 49f75dc8 device lost at ~30,000, twice
main, mesh through the compute path 49,560, no crash (flashes)
main, RX_VK_MASK_BLEND=0 51,600, no crash

Two of last night's route runs (sun_nodefer, c8_on) died the same way, on the same program.

This makes the blend opt-in (RX_VK_MASK_BLEND=<weight>; the default was 0.85). Without the TAAU network, the tree-shadow streaks it evened out come back until the hang is found. tests/test_tree_shadow_mask.py is updated (passes).

cc the author of #1581: the hang is on the draw before your pass, so it may be the mask image's history or barrier rather than the blend itself. Logs are available.

🤖 Generated with Claude Code

**Release blocker for 2.1.1.** The owner's test of main crashed near Hyrule Castle: device lost after a ~2.5 s GPU hang on the mesh draw `b0b3b40244c71ec7`. In the checkpoints, the draw that follows it is OoT's tree-shadow pass `3427a1dbefd5e02c`, the one #1581's mask blend (`mask_stabilize`) runs after. | build, the owner's castle save, native mesh | result | |---|---| | v2.1.0 (before #1581) | 48,960 presents, no crash | | main `49f75dc8` | device lost at ~30,000, **twice** | | main, mesh through the compute path | 49,560, no crash (flashes) | | main, `RX_VK_MASK_BLEND=0` | **51,600, no crash** | Two of last night's route runs (`sun_nodefer`, `c8_on`) died the same way, on the same program. This makes the blend opt-in (`RX_VK_MASK_BLEND=<weight>`; the default was 0.85). Without the TAAU network, the tree-shadow streaks it evened out come back until the hang is found. `tests/test_tree_shadow_mask.py` is updated (passes). cc the author of #1581: the hang is on the draw before your pass, so it may be the mask image's history or barrier rather than the blend itself. Logs are available. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) approved these changes 2026-11-11 16:50:05 +00:00
hckrman101 (Migrated from github.com) left a comment

Right call for 2.1.1: with the default at 0 the MaskGuard in draw() is never armed, so mask_stabilize (its history images, barriers and dispatches) never runs, and the frame is what v2.1.0 drew. RX_VK_MASK_BLEND=0.85 still brings it back for testing, and the updated check pins the 0.0f default and the 0 to 0.95 clamp.

On the hang itself, #2171 (mikaba-eu) looks like the likely cause and fits your checkpoints. mask_guard = {cmd, at.color[0], ...} builds a temporary MaskGuard and copies it in; the temporary's destructor runs at the end of that statement, so mask_stabilize fires once before the tree-shadow draw (right after b0b3b40244c71ec7 in GPU order, on the mask before it is drawn), and again when the real guard leaves scope. That first call can also record the history images' UNDEFINED->GENERAL barriers before end_pass, i.e. inside an open dynamic-rendering pass. #2171 arms the guard field by field and moves end_pass to the top. Worth running the castle save with #2171 and RX_VK_MASK_BLEND=0.85 before turning the default back on. The two PRs touch neighbouring lines in vk_draw.cpp and should merge cleanly in either order (this one changes only the default and the doc comment).

CI: the Linux Python checks part 2/2 and appimage failures are runner DNS/apt errors (Temporary failure resolving 'azure.archive.ubuntu.com'), not this change; Windows source checks and the Windows Vulkan build pass. A re-run should clear them.

Right call for 2.1.1: with the default at 0 the `MaskGuard` in `draw()` is never armed, so `mask_stabilize` (its history images, barriers and dispatches) never runs, and the frame is what v2.1.0 drew. `RX_VK_MASK_BLEND=0.85` still brings it back for testing, and the updated check pins the 0.0f default and the 0 to 0.95 clamp. On the hang itself, #2171 (mikaba-eu) looks like the likely cause and fits your checkpoints. `mask_guard = {cmd, at.color[0], ...}` builds a temporary `MaskGuard` and copies it in; the temporary's destructor runs at the end of that statement, so `mask_stabilize` fires once *before* the tree-shadow draw (right after `b0b3b40244c71ec7` in GPU order, on the mask before it is drawn), and again when the real guard leaves scope. That first call can also record the history images' UNDEFINED->GENERAL barriers before `end_pass`, i.e. inside an open dynamic-rendering pass. #2171 arms the guard field by field and moves `end_pass` to the top. Worth running the castle save with #2171 and `RX_VK_MASK_BLEND=0.85` before turning the default back on. The two PRs touch neighbouring lines in `vk_draw.cpp` and should merge cleanly in either order (this one changes only the default and the doc comment). CI: the Linux Python checks part 2/2 and appimage failures are runner DNS/apt errors (`Temporary failure resolving 'azure.archive.ubuntu.com'`), not this change; Windows source checks and the Windows Vulkan build pass. A re-run should clear them.
@ -5984,2 +5986,3 @@
/// RX_VK_MASK_BLEND=0 ran 14 minutes there clean, as v2.1.0 (before this pass) did. Opt-in until the hang is found
float mask_weight() {
static const float w = std::clamp(getenv("RX_VK_MASK_BLEND") ? (float)atof(getenv("RX_VK_MASK_BLEND")) : 0.85f, 0.0f, 0.95f);
static const float w = std::clamp(getenv("RX_VK_MASK_BLEND") ? (float)atof(getenv("RX_VK_MASK_BLEND")) : 0.0f, 0.0f, 0.95f);
hckrman101 (Migrated from github.com) commented 2026-11-11 16:50:05 +00:00

Nit: when this goes back on, the comment could name the likely cause (the temporary guard's early dispatch, #2171) rather than only "until the hang is found". Also missing a final period.

Nit: when this goes back on, the comment could name the likely cause (the temporary guard's early dispatch, #2171) rather than only "until the hang is found". Also missing a final period.
Commenting is not possible because the repository is archived.
No description provided.