fix(vk): keep a copy into BC3 to whole blocks or the level's edge #672
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!672
Loading…
Reference in a new issue
No description provided.
Delete branch "jcabarca/Longshot:fix/vk-bc-copy-extent"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: fix · Risk: low · Tested on: Windows, AMD Radeon RX 9070 XT · Needs: none
Why
A copy from an ASTC 6x6 or 10x10 texture into a BC3 texture copied the encoded blocks in with the region's own size. When that size is not whole 4x4 blocks and the region stops before the level's edge, the copy breaks VUID-vkCmdCopyBufferToImage-dstImage-00207 / 00208, and what a driver does with it is undefined (blocks written in the wrong place, or the copy dropped).
How to reproduce
Seen on: not seen in the game: found by reading
texture_copy_into_bc()ingpu/vk/vk_texture.cpp; it can happen whenever a game copies a region of an ASTC 6x6 or 10x10 texture whose size is not a multiple of 4 into the middle of a BC3 textureSettings and switches: defaults
Steps: game-free:
python -I -B tests/test_mem_trim.pyon main (copy the test from this PR); it readstexture_copy_into_bc()invk_texture.cppExpected: a region the BC3 copy cannot write as whole blocks is not copied with an invalid extent
Actual: on main it is encoded and copied in with its own extent; the test fails
What changed
texture_copy_into_bc()now returnsfalse, before it takes the BC scratch, for a region into a BC3 texture that is not resized when its width is not a multiple of 4 and does not reach the level's right edge, or its height is not a multiple of 4 and does not reach the level's bottom edge. Afalsethere already means "not done": the caller (vk_xfer.cpp, the copy into a BC3-kept texture) then skips the copy with the warning "texture copy into or out of a BC3-kept ASTC texture it cannot make: skipped", as it does for every other case this function refuses (offsets not on a block, a region bigger than the scratch, a resized destination that is not a whole level). The code comment's "left to the copy's other paths" means that caller.texture_copy_into_bc()ingpu/vk/vk_texture.cpp:if (!dst->bc_resized && (((w & 3) && dx + w != ow) || ((h & 3) && dy + h != oh))) return false;beforebc_scratch_layout(). Resized BC3 destinations already take only whole levels (checked just above) and are unchanged.tests/test_mem_trim.py(extended): the check oftexture_copy_into_bc()now also needs this guard, beforebc_scratch_layout(.The bug, step by step:
texture_copy_into_bc()copies awxhregion of an ASTC texture into a BC3 texture atdx,dy: it copies the region into the scratch, encodes it to BC3 blocks in a buffer (bc_encode_into), then copies the buffer into the BC3 image.dxanddyon a 4x4 block ((dx & 3) || (dy & 3)returnsfalse), but notwandh.bc_encode_intocopies the blocks withVkBufferImageCopy{..., {dx, dy, 0}, {w, h, 1}}. Withwnot a multiple of 4 anddx + wshort of the level's width (or the same forh), that copy breaks the two rules below.The rules, from Khronos
validusage.json:The copy that breaks them, in
bc_encode_into, unchanged:Before:
After:
Start reading at
bool texture_copy_into_bc(invk_texture.cpp, thenbc_encode_intonear the top of the file.Measurements
Verdict: it fixes the bug:
tests/test_mem_trim.pyfails on main (a BC3 copy region that is not whole blocks and does not reach the level's edge is copied in) and passes with this PR, in both interleaved runs. No speed change is expected: one comparison before the copy; a refused region is skipped, as other refused regions already are.Hardware: AMD Radeon RX 9070 XT, Windows; the test itself is game-free (it reads source, it does not use the GPU); Python 3.12
Scene: none, game-free
Method:
tests/test_mem_trim.pyfrom this PR against each side's source (a checkout of each commit, the PR's test copied into main's), 2 runs each, interleaved (main, PR, main, PR); the result is pass/fail, so the median is the result both runs gaveHow to reproduce:
python -I -B tests/test_mem_trim.pyin a checkout of each side (copy the test into main's)82886e895ed443b9tests/test_mem_trim.py, run 1tests/test_mem_trim.py, run 2What the test checks (the new part):
bool texture_copy_into_bc(, the guardif (!dst->bc_resized && (((w & 3) && dx + w != ow) || ((h & 3) && dy + h != oh))) return false;must be present.bc_scratch_layout(, so a refused region takes no scratch and records no copy.vk_memory.cppare unchanged and still pass.Flags and settings
None.
Testing
Tested: on this branch, Windows, Python 3.12:
test_mem_trimpasses (fails on main), and the other checks that read the BC3 copy and upload pass:test_bc_upload_retry,test_astc_bc_policy,test_parallel_init_order.Built and played: this commit builds on Windows (llvm-mingw back end) and on Linux (
longshot_vk.so, without and with AMD FSR 4). Played on Windows, AMD Radeon RX 9070 XT: the game runs normally.Not tested: such a copy in the game: not checked in the game (it needs an ASTC 6x6 or 10x10 region whose size is not a multiple of 4 copied into the middle of a BC3 texture). Linux was built, not run.
Must be tested: None: the change is the same on every GPU and OS, and the game-free test covers it.
Risks
What could break: a region refused here is no longer copied at all (the caller skips it with its warning), so those texels of the BC3 texture keep what they had. Before, the same region was copied with an extent the rules forbid, so what landed there was undefined anyway. Copies whose size is whole blocks or that reach the level's edge, and copies into resized BC3 textures, are unchanged.
Turning it off: revert the commit.
Compatibility: Nothing changes.
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (the checks that read the changed code, on this PR, Windows: all pass)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.mainand passing here. (tests/test_mem_trim.pyextended)tests/that reads changed source updated in this PR, not weakened. (tests/test_mem_trim.py: one check added, none removed)longshot_vk.sowithout and with AMD FSR 4; no OS-specific code or paths)WIP: fix(vk): keep a copy into BC3 to whole blocks or the level's edgeto fix(vk): keep a copy into BC3 to whole blocks or the level's edgeView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.