[MG_Backend, MG_Impl, MG_Remote, CI] (Disaggregated): P5f fm - connect family records and preserve GPU texture writes

This commit is contained in:
2026-09-19 22:18:24 -04:00
parent 20bbf9647e
commit 911cc74a57
11 changed files with 310 additions and 55 deletions
+6 -31
View File
@@ -1391,7 +1391,7 @@ jobs:
tail -5 "${RUNNER_TEMP}/dualblock-split.txt"
echo "dual-block census run: rc=${rc}"
rows=$(grep -cvE '^\s*(#|$)' "${expected}")
rows=$(awk '!/^[[:space:]]*(#|$)/ { n++ } END { print n+0 }' "${expected}")
if [ "${rc}" -eq 0 ] && [ "${rows}" -gt 0 ]; then
echo "::error::the dual-block census lane went GREEN with ${rows} expected pair(s) outstanding - the knob stopped arming, or the fields retired and dualblock-expected-fatals.txt was not emptied in the same commit"
exit 1
@@ -1407,42 +1407,17 @@ jobs:
# ---- P5e (gl), CONTRACT-P5E §7: MAGMA'S OWN LANE, EXPECTED RED -----------------------
#
# The two DirectVulkan.Split.NamedBlit entries abort on GetFramebufferBindingSlot@Clear
# (VulkanRenderer.cpp:7648). That field retires in P5e ON ESPRYT and in P7 ON MAGMA, and
# FieldOwnership.def carries ONE retiring-phase string for both - so admitting the pair to
# keep them in the Espryt lane would forgive an Espryt Clear reading the frontend's binding
# slot again, which is exactly the regression fb's handle arm exists to prevent. Splitting
# the lane costs two entries; admitting the row would cost the gate.
#
# THE LABEL IS `integration-magma-split` AND THE SPELLING MATTERS: `ctest -L` is a REGEX, so
# a label named `integration-split-magma` is still matched by `-L integration-split` and the
# entries would never have left. Measured before the rename.
#
# IT IS ASSERTED RED, WITH ITS MARKER. "Magma is red" must not be allowed to decay into
# "Magma did not run": the step fails both when the run goes green (P7 landed early, or the
# entries stopped drawing) and when it goes red without the named abort in a private log.
- name: Integration-magma-split under strict - expected red until P7 (CONTRACT-P5E section 7)
# P5f fm: Magma's framebuffer, copy and mipmap verbs consume server records.
# The label remains separate so both backends' coverage stays explicit.
- name: Integration-magma-split under strict and dual-block state (P5f fm)
working-directory: build-split
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
MOBILEGL_IPC_STRICT_ERRORS: "1"
MOBILEGL_IPC_ROLE_SPLIT_STATE: "1"
MOBILEGL_TRANSPORT: "inproc"
run: |
ulimit -c unlimited
rc=0
ctest -L integration-magma-split --no-tests=error -j 2 > "${RUNNER_TEMP}/strict-magma.txt" 2>&1 || rc=$?
tail -5 "${RUNNER_TEMP}/strict-magma.txt"
if [ "${rc}" -eq 0 ]; then
echo "::error::integration-magma-split went GREEN under strict. Magma is in lockstep for the whole of P5e (ID-90) and GetFramebufferBindingSlot retires there in P7, so these entries are expected to abort. If P7 has landed, delete this step and fold the entries back into integration-split."
exit 1
fi
marker='Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"}'
if ! grep -rqF "${marker}" MobileGL/MG_IntegrationTest/p5b-DirectVulkan-*.log 2>/dev/null; then
echo "::error::integration-magma-split is red but no private log carries ${marker} - the lane failed for some OTHER reason, which is a defect rather than the expected-red control"
grep -rhoE '(Fatal|Admitted)\{[A-Za-z]*, "[^"]*"\}' MobileGL/MG_IntegrationTest/p5b-DirectVulkan-*.log 2>/dev/null | sort | uniq -c | sort -rn | head
exit 1
fi
echo "integration-magma-split is red by design on ${marker}"
ctest -L integration-magma-split --no-tests=error -j 2 --output-on-failure
# ---- P5e (ra2), ID-134 / ID-82: THE CLIENT-ARRAY LANE, EXPECTED RED UNDER RUN-AHEAD ----
#
+6
View File
@@ -9,6 +9,9 @@
#pragma once
#include <Includes.h>
#include "MG_State/GLState/TextureState/TextureEnum.h"
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Pipe/MGPipeTypes.h>
#endif
namespace MobileGL {
namespace MG_State::GLState {
@@ -33,6 +36,9 @@ namespace MobileGL {
struct CopyImageEndpoint {
SharedPtr<MG_State::GLState::ITextureObject> Texture;
SharedPtr<MG_State::GLState::RenderbufferObject> Renderbuffer;
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeHandle TextureHandle = MG_Pipe::kMGPipeNullHandle;
#endif
Bool IsRenderbuffer() const { return Renderbuffer != nullptr; }
Bool Exists() const { return Texture != nullptr || Renderbuffer != nullptr; }
@@ -12297,8 +12297,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// An endpoint that named nothing is the frontend validator's INVALID_VALUE and never
// reaches here - but the assertion that says so is compiled out of a release build, and
// SyncTextureObjectToBackend would register a null state object.
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle);
} else
#endif
{
if (!endpoint.Texture) return false;
out.texture = TextureImpl::SyncTextureObjectToBackend(endpoint.Texture);
}
if (!out.texture) return false;
const TextureTarget stateTarget = MG_Util::ConvertGLEnumToTextureTarget(appTarget);
out.target = TextureImpl::ConvertTextureTargetToBackendGLEnum(stateTarget);
@@ -12316,6 +12323,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
const auto* record = PipeTextureRecordForHandle(endpoint.TextureHandle);
return record ? static_cast<TextureInternalFormat>(record->Desc.InternalFormat)
: TextureInternalFormat::Unknown;
}
#endif
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetInternalFormat();
return endpoint.Texture ? endpoint.Texture->GetFormat() : TextureInternalFormat::Unknown;
}
@@ -20,6 +20,7 @@
#if MOBILEGL_BUILD_DISAGGREGATED
#include <Config.h>
#include <MG_Remote/Server/ServerLoop.h>
#include <MG_Pipe/PipeApply.h>
#endif
#include <atomic>
#include <bit>
@@ -712,6 +713,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
auto& state = MG_Pipe::MGPipeApplier();
const auto handle = state.VerbStorageBlockProgram;
if (handle.Slot >= state.ShaderCsos.size() || !state.ShaderCsos[handle.Slot].Live ||
state.ShaderCsos[handle.Slot].Gen != handle.Gen || !storageBlockName) {
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:storage-block-program-record\"}");
std::abort();
}
auto& record = state.ShaderCsos[handle.Slot];
auto found = std::find_if(record.StorageOverrides.begin(), record.StorageOverrides.end(),
[&](const auto& entry) { return entry.Name == storageBlockName; });
if (found == record.StorageOverrides.end())
record.StorageOverrides.push_back({storageBlockName, static_cast<Int32>(storageBlockBinding)});
else found->Binding = static_cast<Int32>(storageBlockBinding);
++record.BindingsSerial;
return;
}
#endif
auto* programObject = TryGetDirectVulkanProgram(program);
if (!programObject || storageBlockName == nullptr) return;
const Int maxBindings =
@@ -1806,8 +1806,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the bytes were actually staged).
//
// What this arm deliberately does NOT have, and the loud answer each gets:
// * GL texture VIEWS (desc.ViewOf non-null): no view chain crosses the wire yet -
// declined, the texture stays unbacked;
// * buffer textures (StorageKind == Buffer): the buffer twin is P7's - declined;
// * sampled/storage VIEW construction: clear/blit/readback/mipmap name images and
// subresources, never descriptors, so no VkImageView is built here at all;
@@ -2017,11 +2015,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
if (!MG_Pipe::MGPipeHandleIsNull(desc.ViewOf)) {
MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: GL texture views do not cross the wire yet; declined",
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
// ResolveWireTextureStorage has already followed ViewOf. Views share the storage
// owner's image and layout; allocating a second image would break aliasing.
if (!MG_Pipe::MGPipeHandleIsNull(desc.ViewOf)) return false;
if (desc.Levels == 0) {
// A create before the first respecify: no storage, nothing to back.
return false;
@@ -2086,7 +2082,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_2dArrayCompatibleUnsupported.find(format) == m_2dArrayCompatibleUnsupported.end()) {
imageCreateFlags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
}
if (storageImageCapable && IsMutableStorageImageFormat(format) &&
// A view can be created after its storage was first used. Give wire color storage
// its legal format-compatibility class from birth, so that view creation needs no
// frontend identity lookup and cannot silently use an immutable-format VkImage.
if (IsMutableStorageImageFormat(format) &&
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
}
@@ -2303,6 +2302,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint16 level = 0;
Uint32 baseArrayLayer = 0;
IntVec3 texelSize = {0, 0, 0};
VkOffset3D imageOffset{};
const void* source = nullptr;
SizeT byteSize = 0;
Vector<Uint8> owned;
@@ -2353,13 +2353,75 @@ namespace MobileGL::MG_Backend::DirectVulkan {
incomplete = true;
continue;
}
// Each VkBufferImageCopy offset must satisfy the format's texel-block alignment
// as well as Vulkan's four-byte rule. All supported blocks divide 16 bytes;
// AcquireUploadStagingSpace aligns the batch base to at least this boundary.
stagingSize = (stagingSize + 15u) & ~VkDeviceSize{15u};
item.offset = stagingSize;
stagingSize += static_cast<VkDeviceSize>(item.byteSize);
items.push_back(Move(item));
// Adoption covers the complete CPU shadow, but only the emitted regions are
// authoritative after a GPU write. Uploading the whole run here overwrites
// untouched texels of a prior clear/blit/mipmap with stale client bytes.
const SizeT levelTexels = static_cast<SizeT>(texelSize.x()) * texelSize.y() *
std::max(texelSize.z(), 1);
const Bool combined = (resource.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) &&
(resource.aspect & VK_IMAGE_ASPECT_STENCIL_BIT);
if (vkuFormatIsCompressed(resource.format) || !levelTexels ||
item.byteSize % levelTexels != 0 || item.byteSize / levelTexels == 0) {
incomplete = true;
continue;
}
const SizeT texelBytes = combined ? 4u : item.byteSize / levelTexels;
const Bool arrayLayers = resource.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
resource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
const auto addRegion = [&](const MG_Pipe::MGPBox& glBox) {
IntVec3 offset{glBox.X, glBox.Y, glBox.Z};
IntVec3 extent{static_cast<Int>(glBox.W), static_cast<Int>(glBox.H),
static_cast<Int>(glBox.D)};
if (target == TextureTarget::Texture1DArray) {
offset = {glBox.X, 0, glBox.Y};
extent = {static_cast<Int>(glBox.W), 1, static_cast<Int>(glBox.H)};
}
for (Int axis = 0; axis < 3; ++axis) {
if (offset[axis] < 0 || extent[axis] <= 0 ||
static_cast<Uint64>(offset[axis]) + static_cast<Uint64>(extent[axis]) >
static_cast<Uint64>(texelSize[axis])) {
incomplete = true;
return;
}
}
WireUploadItem region{};
region.uploadTarget = uploadTarget;
region.level = level;
region.baseArrayLayer = item.baseArrayLayer + (arrayLayers ? offset.z() : 0);
region.texelSize = extent;
region.imageOffset = {offset.x(), offset.y(), arrayLayers ? 0 : offset.z()};
const SizeT regionTexels = static_cast<SizeT>(extent.x()) * extent.y() * extent.z();
region.owned.resize(regionTexels * (combined ? 5u : texelBytes));
const auto* source = static_cast<const Uint8*>(item.source);
for (Int z = 0; z < extent.z(); ++z) {
for (Int y = 0; y < extent.y(); ++y) {
const SizeT sourceTexel =
(static_cast<SizeT>(offset.z() + z) * texelSize.y() + offset.y() + y) *
texelSize.x() + offset.x();
const SizeT destinationTexel =
(static_cast<SizeT>(z) * extent.y() + y) * extent.x();
std::memcpy(region.owned.data() + destinationTexel * texelBytes,
source + sourceTexel * texelBytes, extent.x() * texelBytes);
if (combined) {
std::memcpy(region.owned.data() + regionTexels * 4 + destinationTexel,
source + levelTexels * 4 + sourceTexel, extent.x());
}
}
}
region.source = region.owned.data();
region.byteSize = region.owned.size();
stagingSize = (stagingSize + 15u) & ~VkDeviceSize{15u};
region.offset = stagingSize;
stagingSize += static_cast<VkDeviceSize>(region.byteSize);
items.push_back(Move(region));
};
if (pending.Regions.empty()) {
addRegion(pending.UnionBox);
} else {
for (const auto& region : pending.Regions)
addRegion({region.X, region.Y, region.Z, region.W, region.H, region.D});
}
}
if (incomplete) return false;
if (items.empty()) {
@@ -2413,7 +2475,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
copy.imageSubresource.mipLevel = item.level;
copy.imageSubresource.baseArrayLayer = item.baseArrayLayer;
copy.imageSubresource.layerCount = depthSelectsArrayLayer ? depthOrLayers : 1;
copy.imageOffset = {0, 0, 0};
copy.imageOffset = item.imageOffset;
copy.imageExtent = {static_cast<Uint32>(item.texelSize.x()),
static_cast<Uint32>(item.texelSize.y()),
depthSelectsArrayLayer ? 1u : depthOrLayers};
@@ -2459,6 +2521,54 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
MG_Pipe::MGPipeHandle VkTextureManager::ResolveWireTextureStorage(
MG_Pipe::MGPipeHandle handle, Uint32& level, Uint32& layer,
VkFormat* viewFormat, Uint32* layerCount) {
const auto& state = MG_Pipe::MGPipeApplier();
if (viewFormat) *viewFormat = VK_FORMAT_UNDEFINED;
Uint32 mappedLevel = level, mappedLayer = layer;
Uint32 remainingLayers = ~Uint32{0};
VkFormat outerFormat = VK_FORMAT_UNDEFINED;
// Production views point directly to their root. The bound also makes a corrupt
// or future multi-hop record graph terminate without ever following client data.
for (SizeT hop = 0; hop <= state.TextureResources.size(); ++hop) {
if (MG_Pipe::MGPipeHandleIsNull(handle) || handle.Slot >= state.TextureResources.size())
return MG_Pipe::kMGPipeNullHandle;
const auto& record = state.TextureResources[handle.Slot];
if (!record.Live || record.Gen != handle.Gen) return MG_Pipe::kMGPipeNullHandle;
if (MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) {
if (mappedLevel >= record.Desc.Levels) return MG_Pipe::kMGPipeNullHandle;
const Uint32 layers = record.Desc.Target ==
static_cast<Uint8>(MG_Pipe::MGPipeResourceTarget::Tex3D)
? std::max(record.Desc.Depth >> mappedLevel, 1u)
: std::max<Uint32>(record.Desc.ArrayLayers, 1u);
if (mappedLayer >= layers) return MG_Pipe::kMGPipeNullHandle;
level = mappedLevel;
layer = mappedLayer;
if (viewFormat) *viewFormat = outerFormat;
if (layerCount) *layerCount = std::min(remainingLayers, layers - mappedLayer);
return handle;
}
const auto viewHandle = record.ViewCso;
if (MG_Pipe::MGPipeHandleIsNull(viewHandle) || viewHandle.Slot >= state.SamplerViewCsos.size())
return MG_Pipe::kMGPipeNullHandle;
const auto& viewRecord = state.SamplerViewCsos[viewHandle.Slot];
const auto& view = viewRecord.View;
if (!viewRecord.Live || viewRecord.Gen != viewHandle.Gen || view.Texture != handle ||
mappedLevel >= view.NumLevels || mappedLayer >= view.NumLayers)
return MG_Pipe::kMGPipeNullHandle;
if (outerFormat == VK_FORMAT_UNDEFINED) {
outerFormat = ResolveTextureFormatInfo(static_cast<TextureInternalFormat>(view.InternalFormat)).format;
if (outerFormat == VK_FORMAT_UNDEFINED) return MG_Pipe::kMGPipeNullHandle;
}
remainingLayers = std::min(remainingLayers, static_cast<Uint32>(view.NumLayers) - mappedLayer);
mappedLevel += view.MinLevel;
mappedLayer += view.MinLayer;
handle = record.Desc.ViewOf;
}
return MG_Pipe::kMGPipeNullHandle;
}
VkTextureManager::TextureResource* VkTextureManager::SyncTextureResourceByHandle(
MG_Pipe::MGPipeHandle handle, Bool renderbuffer) {
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureResourceByHandle: m_device == VK_NULL_HANDLE");
@@ -2466,6 +2576,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return nullptr;
}
auto& applier = MG_Pipe::MGPipeApplier();
if (!renderbuffer) {
Uint32 level = 0, layer = 0;
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat);
if (MG_Pipe::MGPipeHandleIsNull(storage)) return nullptr;
if (storage != handle) {
auto* resource = SyncTextureResourceByHandle(storage);
if (!resource) return nullptr;
if (viewFormat != resource->format &&
((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0 ||
vkuFormatCompatibilityClass(viewFormat) != vkuFormatCompatibilityClass(resource->format))) {
MGLOG_E_ONCE("Magma wire view: format %d cannot reinterpret storage format %d",
static_cast<Int>(viewFormat), static_cast<Int>(resource->format));
return nullptr;
}
return resource;
}
}
auto& records = renderbuffer ? applier.RenderbufferResources : applier.TextureResources;
auto& resources = renderbuffer ? m_wireRenderbufferResources : m_wireTextureResources;
if (handle.Slot >= records.size()) {
@@ -2500,6 +2628,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return &resource;
}
void VkTextureManager::MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel,
Uint32 baseArrayLayer, Uint32 layerCount) {
Uint32 availableLayers = 0;
handle = ResolveWireTextureStorage(handle, mipLevel, baseArrayLayer, nullptr, &availableLayers);
layerCount = std::min(layerCount, availableLayers);
const auto& records = MG_Pipe::MGPipeApplier().TextureResources;
if (MG_Pipe::MGPipeHandleIsNull(handle) || handle.Slot >= records.size()) return;
const auto& record = records[handle.Slot];
if (!record.Live || record.Gen != handle.Gen) return;
using MG_Pipe::MGPipeResourceTarget;
TextureUploadTarget target;
switch (static_cast<MGPipeResourceTarget>(record.Desc.Target)) {
case MGPipeResourceTarget::Tex1D: target = TextureUploadTarget::Texture1D; break;
case MGPipeResourceTarget::Tex1DArray: target = TextureUploadTarget::Texture1DArray; break;
case MGPipeResourceTarget::Tex3D: target = TextureUploadTarget::Texture3D; break;
case MGPipeResourceTarget::TexRect: target = TextureUploadTarget::TextureRectangle; break;
case MGPipeResourceTarget::Tex2DMS: target = TextureUploadTarget::Texture2DMultisample; break;
case MGPipeResourceTarget::Tex2DMSArray: target = TextureUploadTarget::Texture2DMultisampleArray; break;
case MGPipeResourceTarget::Tex2DArray: target = TextureUploadTarget::Texture2DArray; break;
case MGPipeResourceTarget::TexCubeArray: target = TextureUploadTarget::CubeMapArray; break;
case MGPipeResourceTarget::TexCube: {
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(handle);
for (Uint32 face = baseArrayLayer; face < 6 && face - baseArrayLayer < layerCount; ++face)
MG_Remote::Server::ServerStagedTexture().MarkLevelGpuDirty(key,
static_cast<Uint16>(TextureUploadTarget::CubeMapPositiveX) + face,
static_cast<Uint16>(mipLevel), true);
return;
}
default: target = TextureUploadTarget::Texture2D; break;
}
MG_Remote::Server::ServerStagedTexture().MarkLevelGpuDirty(
MG_Remote::Server::StagedTextureStore::KeyForHandle(handle),
static_cast<Uint16>(target), static_cast<Uint16>(mipLevel), true);
}
Bool VkTextureManager::GrowWireTextureMipChain(MG_Pipe::MGPipeHandle handle, Uint32 requiredMipLevels,
VkCommandBuffer commandBuffer) {
if (MG_Pipe::MGPipeHandleIsNull(handle)) {
@@ -551,9 +551,14 @@ public:
//
// The resource lives in m_wireTextureResources keyed by StagedTextureStore::KeyForHandle -
// a NAMESPACE THE FRONTEND-KEYED m_textureResources NEVER MEETS, so a recycled {slot, gen}
// can never inherit its predecessor's image. Textures whose GL object is a VIEW of another
// (desc.ViewOf) or whose storage is a buffer are declined here: no view chain crosses today.
// can never inherit its predecessor's image. Texture views resolve the storage owner from
// Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm.
TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false);
// Map a texture/view-relative subresource to its live storage owner. layerCount, when
// supplied, receives the number of accessible layers starting at the input layer.
// viewFormat is UNDEFINED for an ordinary texture, or the outer view's format.
MG_Pipe::MGPipeHandle ResolveWireTextureStorage(MG_Pipe::MGPipeHandle handle, Uint32& level,
Uint32& layer, VkFormat* viewFormat = nullptr, Uint32* layerCount = nullptr);
// Grows the live image of a handle-keyed resource to `requiredMipLevels`, carrying the
// existing levels' content across with an in-command-buffer copy. The caller
// (GenerateMipmap's record arm) has flushed every pending submission first, so the old
@@ -562,6 +567,10 @@ public:
// stays as it was).
Bool GrowWireTextureMipChain(MG_Pipe::MGPipeHandle handle, Uint32 requiredMipLevels,
VkCommandBuffer commandBuffer);
// Records that GPU writes superseded the staged CPU snapshot of this level. Layer
// ranges select cube faces; array and 3D levels share one staged level shadow.
void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel,
Uint32 baseArrayLayer = 0, Uint32 layerCount = 1);
#endif
VkImageView GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel);
VkImageView GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel,
@@ -9784,6 +9784,10 @@ void main() {
srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &copyRegion);
#if MOBILEGL_BUILD_DISAGGREGATED
if (wire) m_textureManager->MarkWireTextureGpuWritten(dstEndpoint.TextureHandle,
dstMipLevel, dstSlices.BaseArrayLayer(), dstSlices.slicesAreDepth ? 1u : copySliceCount);
#endif
VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcRestoreAccessMask = 0;
@@ -9937,6 +9941,10 @@ void main() {
const CopyImageEndpoint& dstEndpoint,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
#if MOBILEGL_BUILD_DISAGGREGATED
const Bool wire = MG_Config::Transport != MG_Config::TransportMode::Monolith;
if (wire && HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("copy-image-flush");
#endif
MOBILEGL_ASSERT(srcEndpoint.Exists() && dstEndpoint.Exists(),
"CopyImageSubData requires valid source and destination images.");
// The frontend already declines a zero or negative extent, so anything else here is a
@@ -9963,7 +9971,11 @@ void main() {
srcEndpoint.Texture ? &VkTextureManager::StorageTextureOf(*srcEndpoint.Texture) : nullptr;
const auto* dstStorageTexture =
dstEndpoint.Texture ? &VkTextureManager::StorageTextureOf(*dstEndpoint.Texture) : nullptr;
if (srcStorageTexture == dstStorageTexture && srcEndpoint.Renderbuffer == dstEndpoint.Renderbuffer) {
if (
#if MOBILEGL_BUILD_DISAGGREGATED
!wire &&
#endif
srcStorageTexture == dstStorageTexture && srcEndpoint.Renderbuffer == dstEndpoint.Renderbuffer) {
MGLOG_E_ONCE("%s: in-place copy on objectId=%u is not supported; declining the copy", __func__,
CopyImageEndpointName(srcEndpoint));
return;
@@ -9982,6 +9994,21 @@ void main() {
// SyncTextureAndGetDescriptor the copy always used; the renderbuffer arm goes through the
// render-pass manager, which is where a renderbuffer's VkImage lives.
const auto resolveImage = [this](const CopyImageEndpoint& endpoint, CopyImageVkImage& out) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
auto* resource = m_textureManager->SyncTextureResourceByHandle(endpoint.TextureHandle);
if (!resource) MagmaWireFatal("copy-image-resource");
out.image = resource->image;
out.trackedLayout = &resource->layout;
out.aspect = resource->aspect;
out.mipLevels = resource->mipLevels;
out.extent = resource->extent;
out.depth = resource->depth;
out.arrayLayers = resource->arrayLayers;
out.format = resource->format;
return out.image != VK_NULL_HANDLE;
}
#endif
if (endpoint.IsRenderbuffer()) {
auto* resource = m_renderPassManager->GetOrCreateRenderbufferResource(endpoint.Renderbuffer);
if (resource == nullptr) return false;
@@ -10016,6 +10043,12 @@ void main() {
CopyImageVkImage dstImage{};
const Bool srcResolved = resolveImage(srcEndpoint, srcImage);
const Bool dstResolved = resolveImage(dstEndpoint, dstImage);
#if MOBILEGL_BUILD_DISAGGREGATED
if (wire) {
if (srcImage.image == dstImage.image) MagmaWireFatal("copy-image-in-place@P7");
m_textureManager->FlushPendingUploads();
}
#endif
// Real checks, not MOBILEGL_ASSERT: the assertions this replaces compile to nothing in
// a release build, which is where both observed failures happened - a null resource
// dereferenced right below (lavapipe) and a mip level the VkImage does not have handed
@@ -10144,6 +10177,9 @@ void main() {
}
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
#endif
if (endpoint.IsRenderbuffer()) {
return MaterializePendingClearForRenderbuffer(frame.commandBuffer, endpoint.Renderbuffer);
}
@@ -10255,6 +10291,10 @@ void main() {
srcImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dstImage.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &copyRegion);
#if MOBILEGL_BUILD_DISAGGREGATED
if (wire) m_textureManager->MarkWireTextureGpuWritten(dstEndpoint.TextureHandle,
dstMipLevel, dstSlices.BaseArrayLayer(), dstSlices.slicesAreDepth ? 1u : copySliceCount);
#endif
VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcRestoreAccessMask = 0;
@@ -70,6 +70,7 @@
void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
const ClearAttachmentPayload& payload, GLint drawbuffer) {
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return;
const auto clearSurface = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect,
Uint32 colorIndex) {
@@ -165,6 +166,8 @@
vkDestroyFramebuffer(m_device, fb, nullptr);
vkDestroyRenderPass(m_device, pass, nullptr);
vkDestroyImageView(m_device, view, nullptr);
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
m_textureManager->MarkWireTextureGpuWritten(surface.Res, image.level, image.layer, image.layers);
if (image.isDefault) {
if (color) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired, true);
else m_swapchainObject.SetDepthStencilContentDefined(m_imageIndexAcquired, true);
@@ -185,6 +188,7 @@
void VulkanRenderer::ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, void* pixels) {
if (width <= 0 || height <= 0) return;
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
const auto* fbo = MG_Pipe::MGPipeApplier().ReadFramebuffer();
if (!fbo) MagmaWireFatal("read-framebuffer-record");
VkImageAspectFlags aspect = format == GL_DEPTH_COMPONENT ? VK_IMAGE_ASPECT_DEPTH_BIT :
@@ -238,6 +242,7 @@
void VulkanRenderer::BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) {
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
auto& state = MG_Pipe::MGPipeApplier();
const Bool named = !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitReadFbo) ||
!MG_Pipe::MGPipeHandleIsNull(state.VerbBlitDrawFbo);
@@ -270,6 +275,8 @@
vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
destination.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&region,
filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
if (!destination.isDefault && dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
m_textureManager->MarkWireTextureGpuWritten(dst.Res, destination.level, destination.layer, destination.layers);
if (destination.isDefault) m_swapchainObject.SetImageContentDefined(m_imageIndexAcquired,true);
};
if (mask & GL_COLOR_BUFFER_BIT) {
@@ -290,6 +297,7 @@
"CopyTexSubImage2D currently only supports GL_TEXTURE_2D destinations.");
return;
}
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
auto& state = MG_Pipe::MGPipeApplier();
const auto* read = state.ReadFramebuffer();
if (!read) MagmaWireFatal("copy-framebuffer-record");
@@ -319,9 +327,11 @@
}
vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
destination.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&region,VK_FILTER_NEAREST);
m_textureManager->MarkWireTextureGpuWritten(state.VerbCopyTexDst,destination.level,destination.layer);
}
void VulkanRenderer::GenerateWireMipmap() {
if (HasPendingRecordedWork() && !FlushPendingCommands()) MagmaWireFatal("wire-verb-flush");
auto& state = MG_Pipe::MGPipeApplier();
const auto handle = state.VerbMipRes;
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle);
+16 -2
View File
@@ -1392,8 +1392,10 @@ namespace MobileGL::MG_Pipe {
// family here would be a NEW rule, and a client that withheld more than the server
// refuses leaves the server's handle arm live with no records to read.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
// P5f fm: texture/framebuffer records have their own server consumers;
// Magma consumes them without claiming the still-unmigrated buffer family.
return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(
kMGPipeSubsystemResources);
subsystem & kMGPipeP4aFamilySubsystems);
}
#endif
return MGPipeGetResourceOps() != nullptr;
@@ -2814,6 +2816,9 @@ namespace MobileGL::MG_Pipe {
Bool ApplierDerives = false;
Bool ContextValuesWireLive = false;
Bool P4aConsumer = false;
#if MOBILEGL_BUILD_DISAGGREGATED
Uint64 CapsGeneration = 0;
#endif
// One bit per FIELD - not per verb class. The class mask is applied at the walk
// exactly as it always was, so "does this verb read this field" stays the walk's
// business and this stays a statement about EMISSION alone.
@@ -2839,7 +2844,12 @@ namespace MobileGL::MG_Pipe {
if (g_fillPlan.Valid && g_fillPlan.PushMask == pushMask &&
g_fillPlan.ApplierDerives == applierDerives &&
g_fillPlan.ContextValuesWireLive == contextValuesWireLive &&
g_fillPlan.P4aConsumer == p4aConsumer) {
g_fillPlan.P4aConsumer == p4aConsumer
#if MOBILEGL_BUILD_DISAGGREGATED
&& g_fillPlan.CapsGeneration == (MG_Config::Transport != MG_Config::TransportMode::Monolith ?
MG_Remote::Client::CapsMirrorInstance().Generation() : 0)
#endif
) {
return g_fillPlan.Supplied;
}
MGPipeFieldMask built{};
@@ -2880,6 +2890,10 @@ namespace MobileGL::MG_Pipe {
g_fillPlan.ApplierDerives = applierDerives;
g_fillPlan.ContextValuesWireLive = contextValuesWireLive;
g_fillPlan.P4aConsumer = p4aConsumer;
#if MOBILEGL_BUILD_DISAGGREGATED
g_fillPlan.CapsGeneration = MG_Config::Transport != MG_Config::TransportMode::Monolith ?
MG_Remote::Client::CapsMirrorInstance().Generation() : 0;
#endif
g_fillPlan.Supplied = built;
return built;
}
+2
View File
@@ -846,6 +846,7 @@ namespace MobileGL::MG_Pipe {
Bool VerbBlitNamedConsumed = false;
// copy_framebuffer_to_texture's destination texture (MGPCopyFromFramebuffer::Dst).
MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle;
MGPipeHandle VerbStorageBlockProgram = kMGPipeNullHandle;
// generate_mipmap's texture (MGPMipPlan::Res).
MGPipeHandle VerbMipRes = kMGPipeNullHandle;
Uint16 VerbMipBaseLevel = 0;
@@ -872,6 +873,7 @@ namespace MobileGL::MG_Pipe {
VerbBlitDrawFbo = kMGPipeNullHandle;
VerbBlitNamedConsumed = false;
VerbCopyTexDst = kMGPipeNullHandle;
VerbStorageBlockProgram = kMGPipeNullHandle;
VerbMipRes = kMGPipeNullHandle;
VerbMipBaseLevel = VerbMipLevelCount = 0;
VerbIndirectBuffer = kMGPipeNullHandle;
+5 -3
View File
@@ -855,9 +855,9 @@ namespace MobileGL::MG_Remote::Server {
// retires them by making the backend take the handles that travel beside the names.
MG_Backend::CopyImageEndpoint src{};
MG_Backend::CopyImageEndpoint dst{};
src.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast<Uint>(copy.SrcGlName));
dst.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast<Uint>(copy.DstGlName));
if (!src.Exists() || !dst.Exists()) {
src.TextureHandle = copy.Src;
dst.TextureHandle = copy.Dst;
if (MG_Pipe::MGPipeHandleIsNull(copy.Src) || MG_Pipe::MGPipeHandleIsNull(copy.Dst)) {
// The monolith's own answer to this, in its own words (DirectGLES.cpp:9067
// "source or destination image failed to sync; declining the copy"): the frontend
// validator is what keeps it unreachable and what reports the INVALID_VALUE the
@@ -921,6 +921,8 @@ namespace MobileGL::MG_Remote::Server {
// Both backends resolve the PROGRAM through the barrier-pulled GetProgramObject(GlName)
// / TryGetDirectVulkanProgram - `rsp` again, retired by P9. ShaderCso travels beside the
// name for the phase that dispatches on it.
MG_Pipe::MGPipeApplier().ClearVerbHandles();
MG_Pipe::MGPipeApplier().VerbStorageBlockProgram = binding.ShaderCso;
table->GL.ShaderStorageBlockBinding(static_cast<GLuint>(binding.GlName), name,
static_cast<GLuint>(binding.Binding));
++m_storageBlockBindings;