[MG_Backend, MG_Test, MG_IntegrationTest, scripts] (P7): a private image placeholder is keyed by (shape, unit) and capped, and the depth-resolve identity is built by a tested helper

This commit is contained in:
2026-09-23 17:45:57 -04:00
parent 8f3d179ced
commit 9eb798a60e
10 changed files with 233 additions and 78 deletions
@@ -113,7 +113,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// checks the level's and the layer's signs) nor at the draw. It is the observable an empty
// unit has, so the answer is the empty unit's (ResolveWirePlaceholderImage): a NULL storage
// descriptor where the device enables VK_EXT_robustness2 nullDescriptor, else a storage
// placeholder private to the (binding, unit), cleared before each use, of the dimensionality
// placeholder private to the unit (per shape), cleared before each use, of the dimensionality
// the SHADER declared.
//
// KHR-GL46.shader_image_load_store.incomplete_textures is the shape that found it: level 2
@@ -169,7 +169,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto handle = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
// An EMPTY image unit is the same 8.26 invalid access as the named-but-missing texel
// below, so it takes the same answer: a null storage descriptor where the device has
// one, else the (binding, unit)-private placeholder (codex closeout finding 2).
// one, else the unit-private placeholder (codex closeout finding 2).
if (MG_Pipe::MGPipeHandleIsNull(handle))
return ResolveWirePlaceholderImage(commandBuffer, program, programObj, binding, storage, out,
VK_FORMAT_UNDEFINED, static_cast<Uint32>(unit));
@@ -659,6 +659,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_DISAGGREGATED
for (auto& entry : m_wirePlaceholderImages) DestroyWirePlaceholderImage(entry.second);
m_wirePlaceholderImages.clear();
m_wirePrivateStoragePlaceholders = 0;
m_wireInvalidStorageImagesBindNull = false;
#endif
@@ -13,6 +13,7 @@
#include "VkBufferManager.h"
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
#include "WirePlaceholderKey.h"
#include "../VkIncludes.h"
#include <Includes.h>
@@ -67,9 +68,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VK_EXT_robustness2's nullDescriptor at device creation. With it the unit binds a NULL
// storage-image descriptor, which is exactly that rule. Without it (or under
// MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER=1, the host lanes' way onto this arm) the
// unit binds a placeholder PRIVATE to its (binding, unit), cleared before each use, so no
// unit binds a placeholder PRIVATE to its unit (per shape), cleared before each use, so no
// two units ever alias; what that cannot give is a load of the SAME unit after its own
// store inside one pass reading zero (CONTRACT-P7 §12). Called after Initialize.
// store inside one pass reading zero, nor privacy past kWirePrivateStoragePlaceholderCap
// placeholders (CONTRACT-P7 §12). Called after Initialize.
void SetWireInvalidStorageImageArm(Bool deviceNullDescriptor);
#endif
// A command buffer (re)began recording: descriptor bindings recorded into
@@ -220,31 +222,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
Uint32 layers = 1;
};
// The placeholder's shape (format, target, storage, shadow) and, for a STORAGE placeholder
// on the arm without a null descriptor, the (binding, unit) it is private to. A sampled
// placeholder is never written, so it stays shared: its binding and unit are ~0u.
struct WirePlaceholderKey {
Uint64 shape = 0;
Uint32 binding = ~0u;
Uint32 unit = ~0u;
Bool operator==(const WirePlaceholderKey& other) const {
return shape == other.shape && binding == other.binding && unit == other.unit;
}
};
struct WirePlaceholderKeyHash {
SizeT operator()(const WirePlaceholderKey& key) const {
Uint64 h = key.shape * 0x9E3779B97F4A7C15ull;
h ^= (static_cast<Uint64>(key.binding) << 32 | key.unit) + 0x9E3779B97F4A7C15ull + (h << 6) + (h >> 2);
return static_cast<SizeT>(h);
}
};
// Keyed by shape, and a storage placeholder on the arm without a null descriptor by its
// image unit too (WirePlaceholderKey.h: ChooseWirePlaceholderKey, and its cap).
mutable UnorderedMap<WirePlaceholderKey, WirePlaceholderImage, WirePlaceholderKeyHash> m_wirePlaceholderImages;
// How many of those are private to a unit (against kWirePrivateStoragePlaceholderCap).
mutable SizeT m_wirePrivateStoragePlaceholders = 0;
// See SetWireInvalidStorageImageArm.
Bool m_wireInvalidStorageImagesBindNull = false;
// boundStorageFormat: for a storage binding with no reflected format, the format the
// unit's glBindImageTexture named, when the unit holds a texture it cannot address
// (GL 4.6 core 8.26); UNDEFINED keeps the numeric-domain R32 default. `unit` is the image
// unit of a storage binding (the key of its private placeholder); unused for a sampler.
// unit of a storage binding (what its private placeholder is private to); unused for a sampler.
Bool ResolveWirePlaceholderImage(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Bool storage,
VkDescriptorImageInfo& out, VkFormat boundStorageFormat = VK_FORMAT_UNDEFINED, Uint32 unit = ~0u) const;
@@ -15880,14 +15880,8 @@ void main() {
const WireDepthResolveProbeKnob knob = ParseWireDepthResolveProbeKnob(knobValue);
// Memoized per DEVICE IDENTITY, not per process (codex closeout finding 7; see
// WireDepthResolveArmCache): a second renderer on another device or driver probes for itself.
WireDepthResolveDeviceIdentity identity;
identity.vendorID = m_physicalDevice.properties.vendorID;
identity.deviceID = m_physicalDevice.properties.deviceID;
identity.driverVersion = m_physicalDevice.properties.driverVersion;
std::memcpy(identity.pipelineCacheUUID, m_physicalDevice.properties.pipelineCacheUUID,
sizeof(identity.pipelineCacheUUID));
identity.renderPassArmAvailable = renderPassArmAvailable;
identity.knob = knob;
const WireDepthResolveDeviceIdentity identity =
MakeWireDepthResolveDeviceIdentity(m_physicalDevice.properties, renderPassArmAvailable, knob);
static WireDepthResolveArmCache s_choices;
const WireDepthResolveArmChoice resolved = s_choices.Resolve(identity, [&]() {
if (knob == WireDepthResolveProbeKnob::Unrecognised)
@@ -241,6 +241,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
};
// The renderer's identity from its device's VkPhysicalDeviceProperties - the ONE place the fields
// are filled (VulkanRenderer::ArmWireDepthResolveOrder calls it), so the unit test proves that each
// property reaches the key: with only the cache tested, a field left out here stayed green.
inline WireDepthResolveDeviceIdentity MakeWireDepthResolveDeviceIdentity(const VkPhysicalDeviceProperties& properties,
Bool renderPassArmAvailable,
WireDepthResolveProbeKnob knob) {
WireDepthResolveDeviceIdentity identity;
identity.vendorID = properties.vendorID;
identity.deviceID = properties.deviceID;
identity.driverVersion = properties.driverVersion;
static_assert(sizeof(identity.pipelineCacheUUID) == sizeof(properties.pipelineCacheUUID));
std::memcpy(identity.pipelineCacheUUID, properties.pipelineCacheUUID, sizeof(identity.pipelineCacheUUID));
identity.renderPassArmAvailable = renderPassArmAvailable;
identity.knob = knob;
return identity;
}
class WireDepthResolveArmCache {
public:
// The identity's memoized choice, or `decide()`'s - called at most once per identity, under
@@ -19,7 +19,7 @@
m_wireInvalidStorageImagesBindNull = deviceNullDescriptor && !forcePrivate;
MGLOG_I("Magma wire: an invalid or unbound image unit binds %s (VK_EXT_robustness2 nullDescriptor %s%s)",
m_wireInvalidStorageImagesBindNull ? "a NULL storage descriptor"
: "a placeholder private to its (binding, unit)",
: "a placeholder private to its unit",
deviceNullDescriptor ? "enabled" : "unavailable",
deviceNullDescriptor && forcePrivate ? "; MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER=1" : "");
}
@@ -93,17 +93,21 @@
break;
default: return false;
}
// A STORAGE placeholder is private to its (binding, unit). Shared by shape alone, a store
// A STORAGE placeholder is private to its unit (per shape). Shared by shape alone, a store
// through invalid unit A was visible, after a barrier, to a load through invalid unit B of
// the same shape - one Vulkan image behind both (codex closeout finding 2). The clear
// below still runs before each use; what a private image cannot hide is the SAME unit's
// own store to its own later load inside one pass (CONTRACT-P7 §12).
WirePlaceholderKey key;
key.shape = static_cast<Uint32>(format) | (static_cast<Uint64>(target) << 32) |
(static_cast<Uint64>(storage) << 48) | (static_cast<Uint64>(shadow) << 49);
if (storage) {
key.binding = binding;
key.unit = unit;
// own store to its own later load inside one pass (CONTRACT-P7 §12). Not per binding: GL
// aliases per unit, and a (binding, unit) key only multiplied allocations. Past
// kWirePrivateStoragePlaceholderCap a new (shape, unit) shares its shape's (WirePlaceholderKey.h).
const Uint64 shape = static_cast<Uint32>(format) | (static_cast<Uint64>(target) << 32) |
(static_cast<Uint64>(storage) << 48) | (static_cast<Uint64>(shadow) << 49);
const WirePlaceholderKey key = ChooseWirePlaceholderKey(shape, storage, unit, m_wirePrivateStoragePlaceholders,
[this](const WirePlaceholderKey& candidate) { return m_wirePlaceholderImages.count(candidate) != 0; });
if (storage && unit != WirePlaceholderKey::kShared && key.unit == WirePlaceholderKey::kShared) {
MGLOG_W_ONCE("Magma wire: %zu image-unit-private placeholders exist (the cap); a further invalid or "
"unbound unit shares its shape's placeholder, so two such units of one shape can alias",
static_cast<size_t>(m_wirePrivateStoragePlaceholders));
}
auto found = m_wirePlaceholderImages.find(key);
if (found == m_wirePlaceholderImages.end()) {
@@ -194,11 +198,12 @@
if (vkCreateFramebuffer(m_device, &framebuffer, nullptr, &image.clearFramebuffer) != VK_SUCCESS) return fail();
}
found = m_wirePlaceholderImages.emplace(key, image).first;
if (key.unit != WirePlaceholderKey::kShared) ++m_wirePrivateStoragePlaceholders;
}
auto& image = found->second;
// Sampled incomplete textures remain immutable opaque black. A storage
// placeholder is reset before each use, so a prior discarded store cannot
// leak into a later pass's loads through the same (binding, unit).
// leak into a later pass's loads through the same unit.
if (storage || image.layout == VK_IMAGE_LAYOUT_UNDEFINED) {
VkClearValue clear{};
if (!storage) {
@@ -0,0 +1,62 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/WirePlaceholderKey.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
// The pure half of UniformManager's wire placeholder cache (WirePlaceholderImages.inc): WHICH
// placeholder a binding with no usable image takes. A unit test pins it
// (MG_Test/Pipeline/PipelineQuirkTest.cpp); the Vulkan half that makes the image stays in the .inc.
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Pipe/MGPipeTypes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
// The placeholder's SHAPE (format, target, storage, shadow) and, for a STORAGE placeholder on
// the arm without a null descriptor, the image UNIT it is private to. GL aliases per UNIT, so the
// unit is the whole of the no-alias guarantee (codex closeout finding 2): two bindings that name
// one unit are one GL image, and keying them apart as well only multiplied allocations (cf-magma
// critic). A sampled placeholder is never written, so it stays shared: its unit is kShared.
struct WirePlaceholderKey {
static constexpr Uint32 kShared = ~0u;
Uint64 shape = 0;
Uint32 unit = kShared;
Bool operator==(const WirePlaceholderKey& other) const { return shape == other.shape && unit == other.unit; }
};
struct WirePlaceholderKeyHash {
SizeT operator()(const WirePlaceholderKey& key) const {
Uint64 h = key.shape * 0x9E3779B97F4A7C15ull;
h ^= static_cast<Uint64>(key.unit) + 0x9E3779B97F4A7C15ull + (h << 6) + (h >> 2);
return static_cast<SizeT>(h);
}
};
// Every private placeholder is its own vkCreateImage + vkAllocateMemory, freed only at Shutdown.
// (shape, unit) bounds them by shapes x kMGPipeMaxImageUnits, and a peer rotating formats and
// targets across the units could still walk that towards maxMemoryAllocationCount (4096 is the
// spec minimum). So past this many, a NEW (shape, unit) takes its shape's SHARED placeholder - the
// pre-fix aliasing, logged once by the caller - instead of another allocation; a unit that already
// has its private placeholder keeps it. Two per unit is far past any real program.
inline constexpr SizeT kWirePrivateStoragePlaceholderCap = 2 * static_cast<SizeT>(MG_Pipe::kMGPipeMaxImageUnits);
// The key a placeholder of `shape` is found or made under. `privateStorage` = a storage binding on
// the arm without a null descriptor (with one, the caller never gets here); `unit` = its image
// unit; `privateCount` = how many private placeholders exist; `known(key)` = the cache holds `key`.
template <typename KnownFn>
WirePlaceholderKey ChooseWirePlaceholderKey(Uint64 shape, Bool privateStorage, Uint32 unit, SizeT privateCount,
KnownFn&& known) {
WirePlaceholderKey key;
key.shape = shape;
if (!privateStorage || unit == WirePlaceholderKey::kShared) return key;
key.unit = unit;
if (privateCount < kWirePrivateStoragePlaceholderCap || known(key)) return key;
key.unit = WirePlaceholderKey::kShared;
return key;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
#endif
+5 -3
View File
@@ -3495,8 +3495,10 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# KHR-GL46.shader_image_load_store.incomplete_textures ended the inproc Redmi run at
# Fatal{UnmigratedVerb, "Magma:image-view-window"}: the unit named level 2 of a texture
# with only level 0, a monolith Fail turned into a SIGABRT. GL 4.6 core 8.26 makes that
# access invalid - zero loads, dropped stores - and the wire descriptor resolve now binds
# the storage placeholder for it. One entry per case for the private log path; NO KNOB,
# access invalid - zero loads, dropped stores - and the wire descriptor resolve now binds a
# null storage descriptor for it where the device enables VK_EXT_robustness2 nullDescriptor,
# else a placeholder private to the unit (UniformManager::ResolveWireImageDescriptor and
# ResolveWirePlaceholderImage). One entry per case for the private log path; NO KNOB,
# so all three arms. The monolith `DirectVulkan.` entry of the same scenario skips by
# name (it drops the draw: the gate 5 BASE, and a pull-build .text change to move).
# ALevelPastATextureViewsWindowReadsZero joined once the F2 repair retired the transport
@@ -3521,7 +3523,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
#
# The two AStoreThrough... cases above run the null-descriptor arm on this host: lavapipe
# enables VK_EXT_robustness2 nullDescriptor, so an invalid or empty storage unit binds a
# null descriptor. A device without it binds a placeholder PRIVATE to the (binding, unit)
# null descriptor. A device without it binds a placeholder PRIVATE to the unit (per shape)
# instead; MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER=1 puts the server on that arm
# (UniformManager::SetWireInvalidStorageImageArm), and the client reads the same marker to
# expect it: B's load zero (no alias), A's own load of its store = the recorded CONTRACT-P7
@@ -487,7 +487,7 @@ void main() {
}
// The Magma wire arm without a null storage descriptor (a device lacking
// VK_EXT_robustness2 nullDescriptor; forced on the host by the knob): each invalid
// unit gets a placeholder private to its (binding, unit). B's load is still zero -
// unit gets a placeholder private to its unit. B's load is still zero -
// the units no longer alias - but A's load of A's own store in the same pass is
// not: that is the CONTRACT-P7 §12 residual, and here it is what PROVES the knob
// reached the server (the null-descriptor arm would read zero there too).
+118 -32
View File
@@ -14,6 +14,7 @@
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h>
#include <MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h>
#include <MG_Backend/DirectVulkan/Renderer/WirePlaceholderKey.h>
#endif
using namespace MobileGL;
@@ -730,9 +731,12 @@ TEST(PipelineQuirkTest, WireDepthResolveProbeKnobReplacesTheMeasurementNotTheVer
// second physical device, or under a changed driver, in the same process took the first device's
// order without probing. ArmWireDepthResolveOrder now asks WireDepthResolveArmCache, keyed by
// (vendor, device, driver version, pipeline-cache UUID) plus the choice's own inputs; this pins that
// two identities are decided independently and that one identity is still decided once.
// two identities are decided independently and that one identity is still decided once. Every
// identity is built by MakeWireDepthResolveDeviceIdentity - the helper ArmWireDepthResolveOrder
// fills its key with - from VkPhysicalDeviceProperties, so a property the helper drops goes red too.
TEST(PipelineQuirkTest, WireDepthResolveVerdictIsMemoizedPerDeviceIdentityNotPerProcess) {
#if MOBILEGL_BUILD_DISAGGREGATED
using MobileGL::MG_Backend::DirectVulkan::MakeWireDepthResolveDeviceIdentity;
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveArmCache;
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveArmChoice;
using MobileGL::MG_Backend::DirectVulkan::WireDepthResolveDeviceIdentity;
@@ -747,19 +751,33 @@ TEST(PipelineQuirkTest, WireDepthResolveVerdictIsMemoizedPerDeviceIdentityNotPer
};
};
// The Redmi's device, whose render pass writes nothing ...
WireDepthResolveDeviceIdentity adreno;
adreno.vendorID = 0x5143;
adreno.deviceID = 0x44050001;
adreno.driverVersion = 0x80000000u | 512;
adreno.pipelineCacheUUID[0] = 0xA8;
adreno.renderPassArmAvailable = true;
VkPhysicalDeviceProperties adrenoDevice{};
adrenoDevice.vendorID = 0x5143;
adrenoDevice.deviceID = 0x44050001;
adrenoDevice.driverVersion = 0x80000000u | 512;
adrenoDevice.pipelineCacheUUID[0] = 0xA8;
const WireDepthResolveDeviceIdentity adreno =
MakeWireDepthResolveDeviceIdentity(adrenoDevice, true, WireDepthResolveProbeKnob::Measure);
// ... and, later in the same process, a device whose render pass resolves.
WireDepthResolveDeviceIdentity lavapipe;
lavapipe.vendorID = 0x10005;
lavapipe.deviceID = 0;
lavapipe.driverVersion = 1;
lavapipe.pipelineCacheUUID[0] = 0x3E;
lavapipe.renderPassArmAvailable = true;
VkPhysicalDeviceProperties lavapipeDevice{};
lavapipeDevice.vendorID = 0x10005;
lavapipeDevice.deviceID = 0;
lavapipeDevice.driverVersion = 1;
lavapipeDevice.pipelineCacheUUID[0] = 0x3E;
const WireDepthResolveDeviceIdentity lavapipe =
MakeWireDepthResolveDeviceIdentity(lavapipeDevice, true, WireDepthResolveProbeKnob::Measure);
// The helper copies every property it is given, the UUID's last byte included.
adrenoDevice.pipelineCacheUUID[VK_UUID_SIZE - 1] = 0x5A;
const WireDepthResolveDeviceIdentity copied =
MakeWireDepthResolveDeviceIdentity(adrenoDevice, false, WireDepthResolveProbeKnob::ForceBug);
EXPECT_EQ(copied.vendorID, 0x5143u);
EXPECT_EQ(copied.deviceID, 0x44050001u);
EXPECT_EQ(copied.driverVersion, 0x80000000u | 512);
EXPECT_EQ(copied.pipelineCacheUUID[0], 0xA8);
EXPECT_EQ(copied.pipelineCacheUUID[VK_UUID_SIZE - 1], 0x5A);
EXPECT_FALSE(copied.renderPassArmAvailable);
EXPECT_EQ(copied.knob, WireDepthResolveProbeKnob::ForceBug);
adrenoDevice.pipelineCacheUUID[VK_UUID_SIZE - 1] = 0;
Bool decided = false;
const WireDepthResolveArmChoice first = cache.Resolve(adreno, probeReading(kRenderPassWroteNothing), &decided);
@@ -777,35 +795,103 @@ TEST(PipelineQuirkTest, WireDepthResolveVerdictIsMemoizedPerDeviceIdentityNotPer
EXPECT_TRUE(again.preferShader);
EXPECT_EQ(probes, 2);
// Each field of the identity separates: a driver update (version or pipeline-cache UUID alone),
// another device of the vendor, and the choice's own inputs.
const auto separates = [&](WireDepthResolveDeviceIdentity other, const char* what) {
// Each property separates, through the helper: a driver update (version or pipeline-cache UUID
// alone), another device of the vendor, and the choice's own inputs.
const auto separates = [&](const VkPhysicalDeviceProperties& other, Bool renderPassArm,
WireDepthResolveProbeKnob knob, const char* what) {
const Int before = probes;
const auto choice = cache.Resolve(other, probeReading(kBothArmsResolve), &decided);
const auto choice = cache.Resolve(MakeWireDepthResolveDeviceIdentity(other, renderPassArm, knob),
probeReading(kBothArmsResolve), &decided);
EXPECT_TRUE(decided) << what << " reused another identity's verdict";
EXPECT_EQ(probes, before + 1) << what;
EXPECT_FALSE(choice.preferShader) << what;
};
auto updatedDriver = adreno;
const auto measured = WireDepthResolveProbeKnob::Measure;
auto updatedDriver = adrenoDevice;
updatedDriver.driverVersion += 1;
separates(updatedDriver, "a changed driver version");
auto rebuiltDriver = adreno;
rebuiltDriver.pipelineCacheUUID[15] = 0x01;
separates(rebuiltDriver, "a changed pipeline-cache UUID");
auto sibling = adreno;
separates(updatedDriver, true, measured, "a changed driver version");
auto rebuiltDriver = adrenoDevice;
rebuiltDriver.pipelineCacheUUID[VK_UUID_SIZE - 1] = 0x01;
separates(rebuiltDriver, true, measured, "a changed pipeline-cache UUID (its last byte)");
auto sibling = adrenoDevice;
sibling.deviceID += 1;
separates(sibling, "another device of the same vendor");
auto otherVendor = adreno;
separates(sibling, true, measured, "another device of the same vendor");
auto otherVendor = adrenoDevice;
otherVendor.vendorID = 0x13B5;
separates(otherVendor, "another vendor with the same device id");
auto noRenderPassArm = adreno;
noRenderPassArm.renderPassArmAvailable = false;
separates(noRenderPassArm, "a renderer without the render-pass arm");
auto forcedClean = adreno;
forcedClean.knob = WireDepthResolveProbeKnob::ForceClean;
separates(forcedClean, "a different probe knob");
separates(otherVendor, true, measured, "another vendor with the same device id");
separates(adrenoDevice, false, measured, "a renderer without the render-pass arm");
separates(adrenoDevice, true, WireDepthResolveProbeKnob::ForceClean, "a different probe knob");
EXPECT_EQ(cache.Size(), 8u);
// A property the identity does not hold does not split it: the device NAME is not the device.
auto renamed = adrenoDevice;
std::snprintf(renamed.deviceName, sizeof(renamed.deviceName), "%s", "Adreno (TM) 830 renamed");
(void)cache.Resolve(MakeWireDepthResolveDeviceIdentity(renamed, true, measured), probeReading(kBothArmsResolve),
&decided);
EXPECT_FALSE(decided) << "the device name is not part of the identity";
EXPECT_EQ(cache.Size(), 8u);
#else
GTEST_SKIP() << "the wire resolve arms exist only in the disaggregated build";
#endif
}
// Codex closeout finding 2, the cf-magma critic's follow-up: WHICH PLACEHOLDER AN INVALID OR EMPTY
// STORAGE UNIT TAKES on the arm without a null descriptor. Private per (shape, UNIT) - GL aliases per
// unit, so the unit is the no-alias guarantee (the ImageUnitPrivate integration entries go red when it
// leaves the key) and the binding is not in the key at all - and bounded: every private placeholder
// is an allocation freed only at Shutdown, so past kWirePrivateStoragePlaceholderCap a NEW (shape,
// unit) shares its shape's placeholder while the units that have one keep it. A sampled placeholder
// is never written and stays shared.
TEST(PipelineQuirkTest, WirePlaceholderKeysArePrivatePerShapeAndUnitAndBounded) {
#if MOBILEGL_BUILD_DISAGGREGATED
using MobileGL::MG_Backend::DirectVulkan::ChooseWirePlaceholderKey;
using MobileGL::MG_Backend::DirectVulkan::kWirePrivateStoragePlaceholderCap;
using MobileGL::MG_Backend::DirectVulkan::WirePlaceholderKey;
using MobileGL::MG_Backend::DirectVulkan::WirePlaceholderKeyHash;
constexpr Uint32 kUnits = MobileGL::MG_Pipe::kMGPipeMaxImageUnits;
static_assert(kWirePrivateStoragePlaceholderCap >= kUnits,
"one shape on every image unit must stay private");
UnorderedMap<WirePlaceholderKey, Int, WirePlaceholderKeyHash> cache;
SizeT privateCount = 0;
// What ResolveWirePlaceholderImage does with the key: find it, or make (and count) it.
const auto take = [&](Uint64 shape, Bool storage, Uint32 unit) {
const WirePlaceholderKey key = ChooseWirePlaceholderKey(shape, storage, unit, privateCount,
[&](const WirePlaceholderKey& candidate) { return cache.count(candidate) != 0; });
if (cache.emplace(key, 0).second && key.unit != WirePlaceholderKey::kShared) ++privateCount;
return key;
};
const Uint64 rgba8Storage2D = 37u | (1ull << 48);
const Uint64 r32uiStorage3D = 98u | (4ull << 32) | (1ull << 48);
const Uint64 rgba8Sampled2D = 37u;
// Units A and B of one shape: two placeholders (the finding) ...
const WirePlaceholderKey a = take(rgba8Storage2D, true, 2);
const WirePlaceholderKey b = take(rgba8Storage2D, true, 3);
EXPECT_FALSE(a == b) << "two invalid units of one shape share a placeholder";
EXPECT_EQ(a.unit, 2u);
// ... and unit A through a second binding is A's own placeholder, not a third allocation.
EXPECT_TRUE(take(rgba8Storage2D, true, 2) == a) << "a second binding on unit A made another placeholder";
EXPECT_EQ(privateCount, 2u);
// A sampled placeholder is shared by shape, whatever unit it came from.
EXPECT_EQ(take(rgba8Sampled2D, false, 2).unit, WirePlaceholderKey::kShared);
EXPECT_TRUE(take(rgba8Sampled2D, false, 7) == take(rgba8Sampled2D, false, 2));
EXPECT_EQ(privateCount, 2u);
// A peer rotating two shapes across every unit, then a third: the map stops at the cap (plus
// the shared placeholders), every unit that got a private placeholder keeps it, and the ones
// past the cap share their shape's.
for (Uint32 unit = 0; unit < kUnits; ++unit) {
(void)take(rgba8Storage2D, true, unit);
(void)take(r32uiStorage3D, true, unit);
}
for (Uint32 unit = 0; unit < kUnits; ++unit) {
const WirePlaceholderKey rotated = take(37u | (2ull << 32) | (1ull << 48), true, unit);
EXPECT_EQ(rotated.unit, WirePlaceholderKey::kShared) << "unit " << unit << " past the cap still allocated";
}
EXPECT_EQ(privateCount, kWirePrivateStoragePlaceholderCap);
EXPECT_LE(cache.size(), kWirePrivateStoragePlaceholderCap + 3u);
EXPECT_TRUE(take(rgba8Storage2D, true, 3) == b) << "a unit that had its private placeholder lost it at the cap";
EXPECT_EQ(take(r32uiStorage3D, true, kUnits - 1).unit, kUnits - 1);
#else
GTEST_SKIP() << "the wire placeholders exist only in the disaggregated build";
#endif
}
+1 -1
View File
@@ -94,7 +94,7 @@ MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.")
# the same server-side arm choice reads to run the REAL probe with its render-pass resolve left out.
# Codex closeout finding 2 (cf-magma): the `.ImageUnitPrivate.` entries set
# MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER=1, which the SERVER's UniformManager reads to bind a
# (binding, unit)-private placeholder for an invalid image unit instead of a null descriptor - the
# unit-private placeholder for an invalid image unit instead of a null descriptor - the
# knob-free `.ImageUnitWindow.` copies of the same two cases keep their tcp entries. Two cases, so two
# tails (one case each), as for `.Reclaim.`.
MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",