mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, MG_Impl, MG_Remote, CI] (Disaggregated): P5f fm - connect family records and preserve GPU texture writes
This commit is contained in:
@@ -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 ----
|
||||
#
|
||||
|
||||
@@ -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, ©Region);
|
||||
#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, ©Region);
|
||||
#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,®ion,
|
||||
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,®ion,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user