Repository navigation
fix(agc): keep texture images inside their extent's mip chain - #6280
fdfawef324 wants to merge 6 commits into
Conversation
|
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
Two things from Demon's Souls (PPSA01341 v1.07, Linux, RTX 5070 Ti, NVIDIA 595.91.07). It fixes our Xid 31. We'd hit The conflict with main is #6054. It added
Lavapipe full suite on that tree: 1460 of 1461 pass. The one failure, Resolution hunks (b9badf4 replayed on main 5057673)diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
index 82f733941..a16eb56fe 100644
--- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
+++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
@@ -244,14 +257,14 @@ struct ExpandImage {
VkDeviceSize bytes = 0;
};
-ExpandImage CreateExpandImage(const Context& context, const GuestTextureResource& descriptor, std::uint32_t depth, std::uint32_t layers) {
+ExpandImage CreateExpandImage(const Context& context, const GuestTextureResource& descriptor, std::uint32_t depth, std::uint32_t levels, std::uint32_t layers) {
ExpandImage result;
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
imageInfo.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u);
imageInfo.imageType = ImageTypeFor(descriptor.dimension);
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageInfo.extent = {descriptor.width, descriptor.height, depth};
- imageInfo.mipLevels = descriptor.mipCount;
+ imageInfo.mipLevels = levels;
imageInfo.arrayLayers = layers;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
@@ -271,9 +284,9 @@ ExpandImage CreateExpandImage(const Context& context, const GuestTextureResource
return result;
}
-void RecordExpandBlit(const Context& context, VkCommandBuffer commands, VkImage source, VkImageLayout sourceLayout, VkImage target, const GuestTextureResource& descriptor, std::uint32_t depth, std::uint32_t layers) {
+void RecordExpandBlit(const Context& context, VkCommandBuffer commands, VkImage source, VkImageLayout sourceLayout, VkImage target, const GuestTextureResource& descriptor, std::uint32_t depth, std::uint32_t levels, std::uint32_t layers) {
std::vector<VkImageBlit> regions;
- for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
+ for (std::uint32_t level = 0; level < levels; ++level) {
const VkOffset3D end{static_cast<std::int32_t>(std::max(descriptor.width >> level, 1u)), static_cast<std::int32_t>(std::max(descriptor.height >> level, 1u)), static_cast<std::int32_t>(descriptor.dimension == TextureDimension::k3D ? std::max(depth >> level, 1u) : 1u)};
VkImageBlit region{};
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, layers};
@@ -285,6 +298,11 @@ void RecordExpandBlit(const Context& context, VkCommandBuffer commands, VkImage
context.Function<PFN_vkCmdBlitImage>("vkCmdBlitImage")(commands, source, sourceLayout, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<std::uint32_t>(regions.size()), regions.data(), VK_FILTER_NEAREST);
}
+void RequireImageLevel(const GuestTextureResource& descriptor, const SurfaceGeometry& geometry, std::uint32_t level, const char* access) {
+ if (level < geometry.imageLevels) return;
+ throw std::runtime_error("AGC graphics: " + std::string(access) + " of mip level " + std::to_string(level) + " of a " + std::to_string(descriptor.width) + "x" + std::to_string(descriptor.height) + "x" + std::to_string(geometry.imageDepth) + " surface of " + std::to_string(descriptor.mipCount) + " levels is not implemented: a Vulkan image of that extent holds " + std::to_string(geometry.imageLevels) + " levels, and the 1x1 levels past floor(log2(max extent)) + 1 are not represented");
+}
+
std::atomic<std::uint64_t>& SampledMemoryCounter() {
static std::atomic<std::uint64_t> bytes{0};
return bytes;
@@ -353,7 +375,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
const VkImage uploadImage = image;
std::shared_ptr<OwnedImage> uploadOwned;
if (srgbExpand) {
- auto expanded = CreateExpandImage(context, descriptor, geometry.imageDepth, geometry.imageLayers);
+ auto expanded = CreateExpandImage(context, descriptor, geometry.imageDepth, geometry.imageLevels, geometry.imageLayers);
uploadOwned = std::move(owned);
owned = std::move(expanded.owned);
image = expanded.image;
@@ -445,14 +467,14 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.image = uploadImage;
- toTransferDst.subresourceRange = {aspect, 0, descriptor.mipCount, 0, geometry.imageLayers};
+ toTransferDst.subresourceRange = {aspect, 0, geometry.imageLevels, 0, geometry.imageLayers};
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearReadBarrier, 1, &toTransferDst);
std::vector<VkBufferImageCopy> regions;
regions.reserve(static_cast<std::size_t>(arrayLayers) * mips.size());
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
const auto linearLayerOffset = static_cast<std::uint64_t>(layer) * sliceLinearBytes;
- for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) {
+ for (std::uint32_t level = 0; level < geometry.imageLevels; ++level) {
if (!geometry.HasLayer(level, layer)) continue;
const auto& mip = mips[level];
VkBufferImageCopy region{};
@@ -476,7 +498,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
toBlit[1] = toTransferDst;
toBlit[1].image = image;
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 2, toBlit.data());
- RecordExpandBlit(context, commands, uploadImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, descriptor, geometry.imageDepth, geometry.imageLayers);
+ RecordExpandBlit(context, commands, uploadImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, descriptor, geometry.imageDepth, geometry.imageLevels, geometry.imageLayers);
}
std::unique_ptr<Buffer> dump;
if (dumpWanted) {
@@ -531,7 +553,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
viewInfo.format = srgbExpand ? VK_FORMAT_R8G8B8A8_SRGB : vkFormat;
viewFormat = viewInfo.format;
viewInfo.components = depthCompare ? VkComponentMapping{} : components;
- viewInfo.subresourceRange = {aspect, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
+ viewInfo.subresourceRange = {aspect, viewBaseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
VkImageViewMinLodCreateInfoEXT minLod{VK_STRUCTURE_TYPE_IMAGE_VIEW_MIN_LOD_CREATE_INFO_EXT};
ChainMinLod(context, descriptor, viewInfo, minLod);
@@ -777,15 +799,15 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
const auto geometry = DescribeSurface(descriptor);
copyWidth = descriptor.width;
copyHeight = descriptor.height;
- copyLevels = descriptor.mipCount;
+ copyLevels = geometry.imageLevels;
copyLayers = geometry.imageLayers;
APS5_LOG_OUT("Texture address=0x%llx %ux%u mips=%u layers=%u sRGB copy of storage image (guest format %u)", static_cast<unsigned long long>(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, descriptor.format);
- auto expanded = CreateExpandImage(context, descriptor, 1u, copyLayers);
+ auto expanded = CreateExpandImage(context, descriptor, 1u, copyLevels, copyLayers);
image = expanded.image;
owned = std::move(expanded.owned);
allocationBytes = expanded.bytes;
- const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
+ const auto [viewBaseLevel, viewLevelCount] = ViewLevels(descriptor, geometry.imageLevels);
const auto viewLayerCount = geometry.imageLayers - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
@@ -796,7 +818,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
viewFormat = viewInfo.format;
viewInfo.components = components;
- viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
+ viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, viewBaseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
VkImageViewMinLodCreateInfoEXT minLod{VK_STRUCTURE_TYPE_IMAGE_VIEW_MIN_LOD_CREATE_INFO_EXT};
ChainMinLod(context, descriptor, viewInfo, minLod);
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView sRGB copy");
@@ -836,7 +858,7 @@ bool Texture::refreshSrgbExpand() {
toTransferDst.image = image;
toTransferDst.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, copyLevels, 0, copyLayers};
barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &produced, 0, nullptr, 1, &toTransferDst);
- RecordExpandBlit(context, commands, storageSource->Image(), VK_IMAGE_LAYOUT_GENERAL, image, sourceDescriptor, 1u, copyLayers);
+ RecordExpandBlit(context, commands, storageSource->Image(), VK_IMAGE_LAYOUT_GENERAL, image, sourceDescriptor, 1u, copyLevels, copyLayers);
VkImageMemoryBarrier toShaderRead = toTransferDst;
toShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toShaderRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;AI-assisted: yes |
|
Hardware data for the part the description says is not measured on AMD. RX 6700 XT (gfx1031, RDNA 2), run through HIP:
So the second commit's values hold on RDNA 2: the level count is
This is desktop RDNA 2, not a PS5, and it covers only the queries and loads above, not samples or gathers past the chain. |
|
Two more conflicts with current
With those resolved, the stack ( AI-assisted: yes |
A T# or a color target may name more mip levels than its extent halves into. On RDNA a level's extent is max(1, extent >> level), so the levels past floor(log2(max(width, height, depth))) + 1 are more 1x1 levels with texels of their own in guest memory. A Vulkan image cannot have them (VUID-VkImageCreateInfo-mipLevels-00947), but the sampled texture upload, its block and depth copies and the storage image created their images with the descriptor's level count and copied every guest level into them. Demon's Souls (PPSA01342) samples 28x1 and 4x1 BC3 and BC7 arrays through T#s of 8 to 11 levels, among them a 28x1 BC3 array of 256 layers and 10 levels whose extent holds 5. NVIDIA (RTX 3060, 591.86) sizes a 28x1 BC3 image of 10 levels like one of 5 (0xc00 bytes a layer), so the copies of levels 5-9 wrote past the image's memory. Each of the title's seven device losses followed the creation of such a texture; in the run with the address log, VK_EXT_device_fault reported a write fault at the first byte past that image. SurfaceGeometry::imageLevels is the descriptor's level count, at most FullMipChainLevels of the extent. Sampled textures and their block and depth copies create, transition and copy only those levels, and their views keep the base level, the level count and the minimum LOD inside them: a view of levels past the chain reads the image's last level. Storage images hold the same levels. Their uploads, write-backs, clears, barriers and copy regions cover only those levels, so the guest bytes of the levels past the chain are neither read into the image nor written back. A storage view or a color attachment of a level past the chain throws, and so does creating a storage image for such a level. A fill or a buffer copy over such a surface is no longer turned into a clear or an image copy (FillClear and CopyFrom refuse it), since that would leave the levels past the chain unchanged, and the surface is tracked per layer: a 64 KiB unit of it may hold only those levels, which no slice window moves. docs/dev/debt/libSceAgcDriver.md records the levels that are not represented. Test: agc_driver_graphics (tests/TextureMipChain.cpp) runs the sampled upload of the 28x1 BC3 10-level array, a 4x1 11-level one and two full chains, and a 28x1 RGBA8 storage surface of 10 levels (upload, views, a color target, a fill, a buffer copy and a write-back) and a full 64x32 storage chain, through a recording mock device. It checks that every image, copy, barrier, clear and view stays inside the levels the extent can hold and that the accesses past them throw or are refused. On main it fails with "28x1 BC3 with 10 levels: the image was created with 10 mip levels, more than its 28x1 extent can hold (5)"; with only the sampled texture part it fails with "a 28x1 storage image of 10 guest levels must keep the 5 levels its extent has, not 10". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit gives a sampled view of a T# whose levels reach past its extent's full mip chain only the levels of that chain. Shader accesses that see the level count then diverged from the T#: image_get_resinfo returned the view's count (5 for the 28x1 T# of levels 0-9 that Demon's Souls samples, 1 for its levels 7-9) instead of LAST_LEVEL - BASE_LEVEL + 1, and its sizes at those LODs came from an out-of-range OpImageQuerySizeLod. image_get_resinfo adds the levels the view lacks to OpImageQueryLevels and returns 1 for the width, height and depth at a LOD the view does not hold. Both follow query_levels and lower_query_size in Mesa's src/amd/common/nir/ac_nir_lower_resinfo.c, which derive them from the descriptor: LAST_LEVEL - BASE_LEVEL + 1 and max(1, extent >> (BASE_LEVEL + lod)); every level past the chain is 1x1. The count comes from ResourceMaterializer::LevelsPastChain, which repeats the driver's view rule, as a pipeline specialization constant (LevelsPastViewBase) or, for registration modules, in bits 22-25 of the runtime image metadata flags. The tracker records which image accesses observe levels (ImageResource::levelUses), which moves the shader disk cache format to 37. MIMG BY2/BY4 and PCK2/PCK4 loads stay as they are: on hardware they return zero at every level narrower than their N texels, so a level the view lacks reads zero either way. image_load_mip and image_get_lod, an image_load through a T# whose base level lies past the chain, and image_get_resinfo through an image table cannot be exact (the texels of those levels are not represented, and a table does not carry the missing count), so such a descriptor is refused: the draw or dispatch throws, an image table entry faults. docs/dev/debt/shader.md records it. Test: agc_driver_image_levels_past_chain (tests/execution/ ImageLevelsPastChain.cpp) runs on the device: image_get_resinfo of a 28x1 2D and 2D-array T# of levels 0-9 at LODs 0-9 and of levels 7-9, image_load_mip and image_load at base level 7 refused, image_sample_l of levels 0-4 and image_load at base level 0 reading the uploaded texels, and an image_store to level 0 of a 28x1 R32_UINT surface of 10 levels (linear and R64KBX) changing only that level's 28 texels in guest memory. Without this commit it fails with "image_get_resinfo of a 28x1 2D T# of levels 0-9 at LOD 0: the level count is 5, expected 10" and the two loads run. agc_driver_graphics (tests/TextureMipChain.cpp) checks that LevelsPastChain matches the driver's view for seven T#s, the specialization constant and the metadata bits, and which accesses are refused; its range check also covers array layers now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agc_driver_image_levels_past_chain sent each of its 32 invocations another LOD (tid % levels) in one image_get_resinfo dispatch. On lavapipe the 28x1 2D and 2D-array T#s of levels 0-9 failed at LOD 1 with a width of 28 instead of 14 (boykopovar#6280), although the module is right: OpImageQuerySizeLod takes any integer LOD per invocation. Mesa's llvmpipe up to 25.2 answers the size query of a vector of invocations at the LOD of its first one (lp_build_size_query_soa in src/gallium/auxiliary/gallivm/lp_bld_sample_soa.c, "FIXME: this needs to honor per-element lod"), which bc56ec7ce032 (gallivm: Implement txs with divergent explicit lod) removed in 25.3.0. Applied to the emitted instructions, that behaviour gives exactly these two failures and passes the levels 7-9 case, where every width is 1. Every LOD of the T# now gets a dispatch of its own, with the same expected width, height, layer count and level count. The dispatch with a different LOD per invocation still runs on every device but llvmpipe, which prints that it skips it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Keep out-of-range queries on a valid Vulkan LOD, zero their extent and layer results, and retain the guest level count. Cover shortened and ordinary views, a single unrepresented base level, and capped sRGB copy paths. Preserve the active refusal for loads whose single base mip is not represented. Co-Authored-By: Codex <noreply@openai.com>
Keep the repacked image, its view and refresh regions inside the levels allocated for the source extent. Exercise ordinary and unrepresented guest base levels through the recording mock device. Co-Authored-By: Codex <noreply@openai.com>
Set the recording device's compute group-count limits so the repack refresh control reaches its recorded copy bounds. Qualify the existing raw LAST_LEVEL/MAX_MIP query discrepancy without changing decode behavior. Co-Authored-By: Codex <noreply@openai.com>
c80ba9e to
0e1f2ab
Compare
|
Thanks for the reports. The #6280 recovery preserves #6054's physical allocation/blit/view/refresh bounds, including uploadImage, plus the repacked-copy cap. The missing LOD>=count case is now covered: a valid physical LOD is selected before the Vulkan query, every extent/layer word is zeroed beyond the decoded count, and the count is retained. Tests include ordinary and shortened views, LOD=count/count+1/31/UINT_MAX and mixed lanes. A single base 7 / last 7 missing level now refuses loads/LOD queries while pure size queries remain valid. Frozen a599 passed four CPU/mock groups and its two selective CPU controls failed as intended. Final rebased/device evidence: The new query fixtures have MAX_MIP=LAST_LEVEL. Raw LAST_LEVEL=9 / MAX_MIP=0 still follows the inherited decoder clamp and remains a discrepancy against thuanquang's report. The debt credits that RX 6700 XT/HIP evidence as desktop RDNA2 queries/loads, not PS5 or sampling/gather proof. Uncharted's eight clean ten-minute composed runs, fisherjoey's driver-commit 1500 s run and later 1535/1536 lavapipe stack remain attributed external results, not our own reruns. Extra-level backing and the composed-title results remain #7085's scope. The shared codec/eligibility order with #7550/#7579 is being coordinated in the linked description; this verified update stays in draft pending agreement, with no invented dependency or composed execution result. AI-assisted: yes (Codex) |
|
On the remaining
So Desktop RDNA 2 again, not a PS5. |
|
Thanks for keeping this current. Before a full review, could you rebase it onto current
#7085 composes these commits, so once this is rebased it can be restacked on the new head. |
What
A guest texture descriptor can name more mip levels than Vulkan permits for its extent. Cap sampled, storage and expanded-copy images at the physical chain; bound their allocations, views, uploads, blits, barriers, clears and write-back. Extra-level storage views and attachments remain refused; #7085 supplies separate backing in a follow-up.
Demon's Souls PPSA01342 originally lost the device after a 28x1 BC3 array declared ten levels while the extent held five. The private integration fault pointed to the first byte beyond that image. This is historical diagnosis; it is not a fresh title run on the final rebase.
For the decoded views exercised here (
MAX_MIP = LAST_LEVEL),image_get_resinforetains the guest level count and returns 1x1 spatial extents for extra levels within that count. AtLOD >= count, it zeros every extent/layer component and retains the count. The emitter selects a valid physical query LOD before asking Vulkan, including for LOD 31 / UINT_MAX. Single-level descriptors with an unrepresented base refuse texel loads and LOD queries; pure size queries remain valid. Repacked storage-to-sampled copies use the same allocation/view cap.The rebase preserves #6054's uploadImage path and passes physical
geometry.imageLevels/copyLevelsthrough sRGB image creation, blits, views and refresh. It retains main'sConvertedSampleRequiredalongsideViewLevelsPastChain/LevelsPastChain, and keepsconvertedSamplesandlevelUsesin the cache encoder, decoder and nonzero round-trip fixture. Final cache format:41;ImageResourceguard:40.The existing decoder still clamps raw
LAST_LEVELtoMAX_MIPwhenBASE_LEVEL <= MAX_MIP. The reported 28x1 LAST_LEVEL=9 / MAX_MIP=0 count of ten remains a discrepancy; this change does not fix it. A CPU LAST9/MAX6 control pins that unchanged decode. Samples/gathers past the chain still clamp to represented data, with console behavior unverified. There is no local PS5 or 3D/cube resinfo hardware claim.External evidence is attributed, not counted as our own testing:
Tested
Windows Server 2025 (10.0.26100), WinLibs MinGW-w64 GCC 15.2.0, Release. Final publication candidate
0e1f2ab53b596a4d33ec8426097bda2c05b82068on main676d423968a1a8fcc79c6247020ce22090a8e0d4.Seven selected Release/O3 targets complete; the final restored incremental build took 14.06 seconds. Four CPU/mock CTests passed without skips in 14.91 seconds: agc_shader_bindless, agc_shader_memory, agc_shader_disk_cache and agc_driver_graphics. Two precise current-source controls failed at the intended single-unrepresented-base admission and repacked mip-5-of-5 copy assertions. Source was restored, rebuilt and retested. Conventions pass against the pinned base and later main
72a3d9f2a49d; its clean merge was checked separately from this 676-based execution.The recording mock checks ordinary and beyond-chain sampled/upload/storage sRGB expansion and repacked-copy allocation, view and copy bounds. Device query fixtures cover shortened and ordinary five-level views, single-level base 7 / last 7, 2D/2D-array layouts, LOD=count/count+1/31/UINT_MAX, and uniform/mixed lanes. The inherited llvmpipe mixed-LOD exclusion remains; no Mesa-version cutoff is newly claimed.
Current-head device checks passed 5/5 registrations / four executables without skips in 4.35 seconds on the RTX 3060, sequentially in the managed slot. Required cases: image_levels_past_chain, image_sample_lod_clamp, image_srgb_load, image_srgb_sample_copy and forced sRGB-copy. The frozen naturally dirty query control at this same source head exited 1 in 0.90 seconds and reproduced both expected errors (LOD10 width1 instead of0; LOD5 width28 instead of0), with actual RTX/Vulkan initialization. The checker requires both diagnostics; this is an expected regression failure, separate from the five passing production cases. Device skips/exit77 are not passes. The selective control removes only the zero-extent selection, retaining the safe physical query LOD and guest count; count-boundary mismatches must be observed to count it as a meaningful red.
Runtime hashes were verified before and after; ANYPS5_REQUIRE_VULKAN=1 was set. No fresh title, FPS, console-equivalence or playability result is claimed by this update.
Shared-source coordination is pending: #7550 codec order and #7579 eligibility-line order propose this PR first, with both changes retained by the later rebase. Those proposals are not agreements, so the verified update remains a draft. #7085 already declares this PR as a prerequisite; its depth/layer/level argument order must be reconciled by meaning. There is no additional standalone build dependency or combined-runtime claim.
Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)Co-Authored-By: Codex noreply@openai.com