mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 22:13:24 +09:00
Merge commit '552ade5b' into codex/p5f-wave2
# Conflicts: # MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt # MobileGL/MG_IntegrationTest/Harness/strict-expected-markers.txt
This commit is contained in:
@@ -95,21 +95,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (handle.Slot >= state.TextureResources.size()) WireDescriptorFatal("image-record");
|
||||
const auto& record = state.TextureResources[handle.Slot];
|
||||
if (!record.Live || record.Gen != handle.Gen) WireDescriptorFatal("image-record-generation");
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle);
|
||||
auto* resource = m_textureManager->SyncTextureResourceByHandle(handle, false, storage);
|
||||
if (!resource) WireDescriptorFatal("image-resource");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
||||
using Target = MG_Pipe::MGPipeResourceTarget;
|
||||
const Bool layerable = target == Target::Tex1DArray || target == Target::Tex2DArray ||
|
||||
target == Target::Tex2DMSArray || target == Target::Tex3D ||
|
||||
target == Target::TexCube || target == Target::TexCubeArray;
|
||||
Uint32 level = storage ? state.BoundShaderImages[unit].Level : record.Params.BaseLevel;
|
||||
Uint32 layer = storage && !state.BoundShaderImages[unit].Layered ? state.BoundShaderImages[unit].Layer : 0;
|
||||
// GL ignores Layer/Layered for non-layerable targets. In particular image1D
|
||||
// stays a 1D view, and an ignored nonzero Layer must not address another slice.
|
||||
Uint32 layer = storage && layerable && !state.BoundShaderImages[unit].Layered
|
||||
? state.BoundShaderImages[unit].Layer : 0;
|
||||
const Uint32 localLevel = level;
|
||||
const Uint32 localLayer = layer;
|
||||
Uint32 layers = 0;
|
||||
VkFormat aliasFormat = VK_FORMAT_UNDEFINED;
|
||||
const auto root = m_textureManager->ResolveWireTextureStorage(handle, level, layer, &aliasFormat, &layers);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(root)) WireDescriptorFatal("image-view-window");
|
||||
const auto target = static_cast<MG_Pipe::MGPipeResourceTarget>(record.Desc.Target);
|
||||
VkImageViewType type = WireViewType(target);
|
||||
if (storage && !state.BoundShaderImages[unit].Layered) {
|
||||
if (storage && layerable && !state.BoundShaderImages[unit].Layered) {
|
||||
layers = 1;
|
||||
type = target == MG_Pipe::MGPipeResourceTarget::Tex1DArray ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D;
|
||||
}
|
||||
@@ -122,9 +129,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const auto viewHandle = state.BoundSamplerViews[unit].View;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(viewHandle) && viewHandle.Slot < state.SamplerViewCsos.size()) {
|
||||
const auto& view = state.SamplerViewCsos[viewHandle.Slot];
|
||||
if (!view.Live || view.Gen != viewHandle.Gen || view.View.NumLevels <= localLevel)
|
||||
if (!view.Live || view.Gen != viewHandle.Gen || view.View.Texture != handle)
|
||||
WireDescriptorFatal("sampler-view-record");
|
||||
levels = std::min<Uint32>(levels, view.View.NumLevels - localLevel);
|
||||
// Ordinary texture views can encode an unrestricted window with zero
|
||||
// counts; only an actual restriction narrows the resource/parameter range.
|
||||
if (view.View.NumLevels != 0) {
|
||||
if (view.View.NumLevels <= localLevel) WireDescriptorFatal("sampler-view-level");
|
||||
levels = std::min<Uint32>(levels, view.View.NumLevels - localLevel);
|
||||
}
|
||||
} else if (!MG_Pipe::MGPipeHandleIsNull(viewHandle)) {
|
||||
WireDescriptorFatal("sampler-view-record");
|
||||
}
|
||||
levels = std::min(levels, resource->mipLevels - level);
|
||||
}
|
||||
|
||||
@@ -2008,7 +2008,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
Bool VkTextureManager::SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record,
|
||||
TextureResource& resource) {
|
||||
TextureResource& resource, Bool requireStorage) {
|
||||
const MG_Pipe::MGPResourceDesc& desc = record.Desc;
|
||||
if (desc.StorageKind != static_cast<Uint8>(TextureStorageType::Mipmap)) {
|
||||
MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: buffer-backed textures are not migrated; declined",
|
||||
@@ -2072,7 +2072,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
||||
// The descriptor's sticky bind mask carries the image-unit hint, so the image is born
|
||||
// with STORAGE usage and no re-mint ever pulls texels back (TextureEmit.h D-A3).
|
||||
const Bool markedAsStorageImage = (desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0;
|
||||
const Bool markedAsStorageImage = requireStorage || resource.storageUsageResolved ||
|
||||
(desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0;
|
||||
const Bool storageImageCapable =
|
||||
!isMultisample && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||
@@ -2570,7 +2571,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
VkTextureManager::TextureResource* VkTextureManager::SyncTextureResourceByHandle(
|
||||
MG_Pipe::MGPipeHandle handle, Bool renderbuffer) {
|
||||
MG_Pipe::MGPipeHandle handle, Bool renderbuffer, Bool requireStorage) {
|
||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureResourceByHandle: m_device == VK_NULL_HANDLE");
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
return nullptr;
|
||||
@@ -2582,7 +2583,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat);
|
||||
if (MG_Pipe::MGPipeHandleIsNull(storage)) return nullptr;
|
||||
if (storage != handle) {
|
||||
auto* resource = SyncTextureResourceByHandle(storage);
|
||||
auto* resource = SyncTextureResourceByHandle(storage, false, requireStorage);
|
||||
if (!resource) return nullptr;
|
||||
if (viewFormat != resource->format &&
|
||||
((resource->imageCreateFlags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0 ||
|
||||
@@ -2615,15 +2616,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
(void)inserted;
|
||||
TextureResource& resource = it->second;
|
||||
if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial &&
|
||||
record.PendingUploads.empty()) {
|
||||
record.PendingUploads.empty() && (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) {
|
||||
return &resource;
|
||||
}
|
||||
if (!SyncWireTextureShape(record, resource)) {
|
||||
if (!SyncWireTextureShape(record, resource, requireStorage)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!renderbuffer && !UploadPendingWireLevels(handle, record, resource)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (requireStorage && (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) == 0) return nullptr;
|
||||
resource.syncedWireSerial = record.Serial;
|
||||
return &resource;
|
||||
}
|
||||
|
||||
@@ -553,7 +553,8 @@ public:
|
||||
// a NAMESPACE THE FRONTEND-KEYED m_textureResources NEVER MEETS, so a recycled {slot, gen}
|
||||
// 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);
|
||||
TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false,
|
||||
Bool requireStorage = 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.
|
||||
@@ -733,7 +734,8 @@ private:
|
||||
// descriptor (create / recreate / compatibility), then the pending-upload walk against the
|
||||
// server's staged store. UploadPendingWireLevels mutates the applier's record (consumed
|
||||
// entries are dropped), which is why the record is a non-const reference.
|
||||
Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource);
|
||||
Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource,
|
||||
Bool requireStorage);
|
||||
Bool UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record,
|
||||
TextureResource& resource);
|
||||
// The handle-keyed twin of m_textureResources, keyed by StagedTextureStore::KeyForHandle.
|
||||
|
||||
@@ -57,6 +57,13 @@
|
||||
WireBufferLegacyFatal();
|
||||
}
|
||||
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
// Descriptor resolution can upgrade a view's root image to STORAGE usage.
|
||||
// Resolve it before constructing framebuffer views so both roles of that image
|
||||
// name the same allocation even on its first storage-image use.
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false;
|
||||
|
||||
Vector<VkAttachmentDescription> attachments;
|
||||
Vector<VkImageView> views;
|
||||
Vector<VkAttachmentReference> colors;
|
||||
@@ -146,9 +153,6 @@
|
||||
VertexInputStateFactory::BackendVertexInputState input{};
|
||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass, false);
|
||||
if (pipeline == VK_NULL_HANDLE) return false;
|
||||
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
|
||||
if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
|
||||
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) return false;
|
||||
Uint32 binding = 0;
|
||||
for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) {
|
||||
if (!(programObj.activeVertexInputLocationMask & (1u << location))) continue;
|
||||
|
||||
@@ -2383,7 +2383,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_TRANSPORT=inproc"
|
||||
"MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
|
||||
foreach(fmSlot VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels)
|
||||
foreach(fmSlot ComputeImageStoreThroughViewPreservesOtherRootLayer EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels)
|
||||
set(MGL_ITEST_FM_LABEL "integration-magma-split")
|
||||
if (fmSlot STREQUAL "GenerateMipmapPackedFloatPixels")
|
||||
# R11F_G11F_B10F mip blits already fail on the baseline Magma lane.
|
||||
|
||||
@@ -34,10 +34,12 @@
|
||||
# a row advances those entries to their next one. See docs/Disaggregated/notes/p5f/f1-report.md.
|
||||
# ---------------------------------------------------------------------------------------------
|
||||
#
|
||||
# P5f fe measured at 881e050e: 181 entries, 175 green, 4 skipped, 2 red.
|
||||
# Espryt retired all five previous first-blocker pairs, and their downstream reads:
|
||||
# READ FBO surface classification + reply-only PixelPack; copy destination arm ordering;
|
||||
# copy-image endpoint handles; capture archive/ranges; storage-block program handle.
|
||||
# The complete lane reached no new first-blocker pair (see fe-report.md).
|
||||
# Magma's Clear path remains fm's independently owned pair (two NamedBlit entries).
|
||||
GetFramebufferBindingSlot@Clear
|
||||
# P5f wave 2: the expected set is empty after fc -> fe -> fm integration.
|
||||
# fe retired five original first-blockers: ReadPixels framebuffer/pack readers,
|
||||
# capture program/ranges, copy-image texture handles, copy destination unit,
|
||||
# and storage-block program-name validation. fm retired Clear on Magma through
|
||||
# server framebuffer/resource records. The shared copy-image seam also retires
|
||||
# GetTextureObject in either package in isolation; it is one removal at integration.
|
||||
# The expanded Magma lane includes real bufferless shader/image pixel scenarios.
|
||||
# Its named P7 native-format probe has a separate, enforced known-red CI lane.
|
||||
# Every new Fatal or Admitted in this census remains a regression.
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <initializer_list>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#if !defined(_WIN32) && GTEST_HAS_DEATH_TEST
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
@@ -375,7 +381,7 @@ void main() {
|
||||
)";
|
||||
const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}});
|
||||
ASSERT_NE(program, 0u);
|
||||
glBindImageTexture(2, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
|
||||
glBindImageTexture(2, texture, 0, GL_FALSE, 7, GL_WRITE_ONLY, GL_RGBA8); // 2D ignores the layer.
|
||||
glUseProgram(program);
|
||||
glDispatchCompute(8, 8, 1);
|
||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
@@ -397,6 +403,186 @@ void main() {
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, ComputeImageStoreThroughViewPreservesOtherRootLayer) {
|
||||
if (!Ready()) return;
|
||||
GLuint root = 0, view = 0;
|
||||
glGenTextures(1, &root);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, root);
|
||||
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 8, 8, 2);
|
||||
std::array<GLubyte, 8 * 8 * 2 * 4> red{};
|
||||
for (size_t i = 0; i < red.size(); i += 4) {
|
||||
red[i] = 255;
|
||||
red[i + 3] = 255;
|
||||
}
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, red.data());
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE));
|
||||
// Materialize the root's Vulkan image before any storage-image binding. The
|
||||
// subsequent view-only bind must upgrade the root and preserve its other layer.
|
||||
std::array<GLubyte, 4> initial{};
|
||||
glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, initial.data());
|
||||
ASSERT_EQ(initial, (std::array<GLubyte, 4>{255, 0, 0, 255}));
|
||||
glGenTextures(1, &view);
|
||||
glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 1, 1);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
const char* compute = R"(#version 430 core
|
||||
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
|
||||
layout(rgba8, binding=2) writeonly uniform image2D destination;
|
||||
void main() { imageStore(destination, ivec2(gl_GlobalInvocationID.xy), vec4(0.0, 1.0, 0.0, 1.0)); }
|
||||
)";
|
||||
const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}});
|
||||
ASSERT_NE(program, 0u);
|
||||
glBindImageTexture(2, view, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
|
||||
glUseProgram(program);
|
||||
glDispatchCompute(8, 8, 1);
|
||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
for (const int layer : {0, 1}) {
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, layer);
|
||||
std::array<GLubyte, 8 * 8 * 4> pixels{};
|
||||
glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
const std::array<GLubyte, 4> expected = layer == 1
|
||||
? std::array<GLubyte, 4>{0, 255, 0, 255} : std::array<GLubyte, 4>{255, 0, 0, 255};
|
||||
for (size_t at = 0; at < pixels.size(); at += 4) {
|
||||
const std::array<GLubyte, 4> actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]};
|
||||
EXPECT_EQ(actual, expected) << "root layer " << layer << ", texel " << at / 4;
|
||||
}
|
||||
}
|
||||
glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
|
||||
glUseProgram(0);
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
glDeleteProgram(program);
|
||||
glDeleteTextures(1, &view);
|
||||
glDeleteTextures(1, &root);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, EnabledVertexBufferDrawKeepsNamedP7Fatal) {
|
||||
if (!Ready()) return;
|
||||
#if !defined(_WIN32) && GTEST_HAS_DEATH_TEST
|
||||
Attach(GL_RGBA8);
|
||||
const char* vertex = R"(#version 430 core
|
||||
layout(location=0) in vec2 position;
|
||||
void main() { gl_Position = vec4(position, 0.0, 1.0); }
|
||||
)";
|
||||
const char* fragment = R"(#version 430 core
|
||||
layout(location=0) out vec4 color;
|
||||
void main() { color = vec4(0.0, 1.0, 0.0, 1.0); }
|
||||
)";
|
||||
const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}});
|
||||
ASSERT_NE(program, 0u);
|
||||
GLuint vao = 0, vbo = 0;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
glGenBuffers(1, &vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
const GLfloat positions[] = {-1, -1, 3, -1, -1, 3};
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
glEnableVertexAttribArray(0);
|
||||
glUseProgram(program);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
|
||||
// A fresh exec reconstructs the fixture and apply thread; no child executes GL
|
||||
// against a forked mutex/thread state. Capture the named log because shipped
|
||||
// builds may disable console logging, which makes a stderr regex insufficient.
|
||||
const char* oldLogEnv = std::getenv("MOBILEGL_LOG_FILE_PATH");
|
||||
const bool hadLogEnv = oldLogEnv != nullptr;
|
||||
const std::string oldLogPath = hadLogEnv ? oldLogEnv : "";
|
||||
const std::string childLog = oldLogPath.empty()
|
||||
? "/tmp/mobilegl-vbo-death-" + std::to_string(static_cast<long long>(::getpid())) + ".log"
|
||||
: oldLogPath + ".vbo-death";
|
||||
std::remove(childLog.c_str());
|
||||
ASSERT_EQ(::setenv("MOBILEGL_LOG_FILE_PATH", childLog.c_str(), 1), 0);
|
||||
const std::string oldStyle = ::testing::FLAGS_gtest_death_test_style;
|
||||
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||||
EXPECT_DEATH({
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glFinish();
|
||||
}, "");
|
||||
::testing::FLAGS_gtest_death_test_style = oldStyle;
|
||||
if (hadLogEnv) ::setenv("MOBILEGL_LOG_FILE_PATH", oldLogPath.c_str(), 1);
|
||||
else ::unsetenv("MOBILEGL_LOG_FILE_PATH");
|
||||
std::ifstream log(childLog, std::ios::binary);
|
||||
std::ostringstream contents;
|
||||
contents << log.rdbuf();
|
||||
log.close();
|
||||
EXPECT_NE(contents.str().find("buffer-legacy-arm"), std::string::npos)
|
||||
<< "draw must keep the existing named P7 refusal, not die at a new object/field access\n"
|
||||
<< contents.str();
|
||||
std::remove(childLog.c_str());
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glDeleteBuffers(1, &vbo);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
glDeleteProgram(program);
|
||||
#else
|
||||
GTEST_SKIP() << "requires gtest thread-safe exec death tests and POSIX environment handling";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, SrgbDrawTracksFramebufferConversion) {
|
||||
if (!Ready()) return;
|
||||
Attach(GL_SRGB8_ALPHA8);
|
||||
const char* fragment = R"(#version 430 core
|
||||
layout(location=0) out vec4 color;
|
||||
void main() { color = vec4(0.5, 0.5, 0.5, 1.0); }
|
||||
)";
|
||||
const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle},
|
||||
{GL_FRAGMENT_SHADER, fragment}});
|
||||
ASSERT_NE(program, 0u);
|
||||
GLuint vao = 0;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
glUseProgram(program);
|
||||
glViewport(0, 0, 8, 8);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_RASTERIZER_DISCARD);
|
||||
for (const bool conversion : {false, true}) {
|
||||
if (conversion) glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
std::array<GLubyte, 4> pixel{};
|
||||
glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
for (int channel = 0; channel < 3; ++channel)
|
||||
EXPECT_NEAR(pixel[channel], conversion ? 188 : 128, 1) << "FRAMEBUFFER_SRGB=" << conversion;
|
||||
EXPECT_EQ(pixel[3], 255);
|
||||
}
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, Texture1DFramebufferClearPixels) {
|
||||
if (!Ready()) return;
|
||||
GLuint line = 0;
|
||||
glGenTextures(1, &line);
|
||||
glBindTexture(GL_TEXTURE_1D, line);
|
||||
glTexStorage1D(GL_TEXTURE_1D, 1, GL_RGBA8, 8);
|
||||
glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, line, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE));
|
||||
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
std::array<GLubyte, 8 * 4> pixels{};
|
||||
glReadPixels(0, 0, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
const int expected[] = {64, 128, 191, 255};
|
||||
for (int x = 0; x < 8; ++x)
|
||||
for (int channel = 0; channel < 4; ++channel)
|
||||
EXPECT_NEAR(pixels[x * 4 + channel], expected[channel], 1) << "1D texel " << x;
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
glBindTexture(GL_TEXTURE_1D, 0);
|
||||
glDeleteTextures(1, &line);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) {
|
||||
if (!Ready()) return;
|
||||
Attach(GL_RGBA8);
|
||||
|
||||
@@ -50,6 +50,17 @@
|
||||
// RetiringPhase is `-` for every class but BARRIER_PULLED, where it is the ROADMAP phase
|
||||
// whose row stops the pull. It is a string, not an enum, because two of them name two
|
||||
// phases for the two backends and flattening that would lose the half that matters.
|
||||
//
|
||||
// P5f fm: the Magma transport consumers of the nine object rows below have retired.
|
||||
// WireDraw/WireFramebuffer and MagmaProgramSource/UniformManager read server records;
|
||||
// enabled vertex arrays, EBO/indirect/named UBO/SSBO/texel-buffer uses meet the existing
|
||||
// named P7 buffer refusal before any frontend read. Texture sync uses handle-keyed images
|
||||
// and staged bytes, never GetTextureObject. The old getters remain reachable only through
|
||||
// the monolith implementation (or behind the named P7/class-C boundary). Keep these global
|
||||
// classifications until fe + fm are integrated and the residual-fill/generator invariants
|
||||
// are retired together; a missing wire branch must still poison, never read a default.
|
||||
// RecordError belongs to fv. Details and the no-buffer draw counterexample are in
|
||||
// docs/Disaggregated/notes/p5f/fm-report.md.
|
||||
#define MGP_FIELD_OWNERSHIP_LIST(X) \
|
||||
/* ---- BARRIER_PULLED: the 9 non-sticky rows the reduced path actually reads ---- */ \
|
||||
/* scout-unmigrated-census section 3 intersects each verb class's may-read mask with "no */ \
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# P5f fm — Magma 跨角色读写退役
|
||||
|
||||
> 分支 `p5f/fm`;Windows worktree `MobileGL-p5f-fm`;WSL gate tree `~/w7/p5f-fm`。
|
||||
> 代码 checkpoint:`b394a09f`。实现与范围结论已核对;§5 的最后一轮数字由门禁执行者补齐。
|
||||
> 本报告只裁定 fm;不宣布 P5f 总出口完成。
|
||||
|
||||
## 1. 真实可达性修正
|
||||
|
||||
f0 的“带 buffer 的 draw 最终到 buffer-legacy-arm”不能推出整个 draw 子树不可达。
|
||||
`ClipDistanceScenario.AnEnabledClipDistanceRemovesTheNegativeHalf` 使用 `gl_VertexID`,没有 VBO;在 f1 基线
|
||||
`~/w7/p5f-f1` 的真实 Magma/inproc、role-split=0、strict=0 下像素测试通过(`/tmp/p5f-fm-baseline-clip.log`)。
|
||||
因此本包保留无 buffer draw/dispatch,新增 server program source、descriptor 和 render-pass 路径;没有把所有 draw 拒绝。
|
||||
迁移后同一 case 在 role-split=1、strict=1 下通过(`fm-clip.log`;集成者独立复核 `int-wire-clip.log`)。
|
||||
|
||||
记录能被消费也必须能被发射。client 的 P4a family gate 和 server 的 `NoP4aConsumer`
|
||||
原来都绑在 buffer resource-ops 上,而 Magma 没有该 ops。本包改为 client 查询该 family
|
||||
的已发布 consumer bit、server 接受其 object records;残余 fill memo 加 caps generation。
|
||||
Magma 仍不发布 buffer resources bit 7;发布 indexed-binding bit 13,用记录辨别未绑定与 P7 buffer 债。
|
||||
texture staged bytes 原本也只由 GLES 的 resource-ops hook 采纳,本包补无 hook 时的 server-owned
|
||||
adoption、respecify/defined-ness、destroy/release,保留有 hook 的 Espryt 路径避免双采纳。
|
||||
|
||||
## 2. 九字段与邻接字段
|
||||
|
||||
| 字段 | Magma transport 的现在来源 / 边界 |
|
||||
|---|---|
|
||||
| GetBoundVertexArray | `SetupWireDraw` 读 `BoundVertexElements` / `VertexElementsCsos`;无数组与 current generic attribute 正常画;实际读取 enabled array 时在 client 读前具名 `buffer-legacy-arm`(P7)。|
|
||||
| GetBufferBindingPoint | UniformManager 读 `BoundShaderBuffers` 判断绑定;已存在的无绑定 native buffer placeholder 保留,有真实 SSBO/UBO buffer 时具名 `buffer-legacy-arm`。XFB capture 同样在读取前端绑定前拒绝。|
|
||||
| GetFramebufferBindingSlot | `WireFramebuffer.inc` 的 clear/blit/read/copy 与 `WireDraw.inc` / pipeline builder 全读 framebuffer record / `MGPSurface`。|
|
||||
| GetImageTextureBinding | UniformManager 读 `BoundShaderImages`;native image view 与 sampler 使用 server resource record。|
|
||||
| GetTextureUnitObject | sampler 读 `BoundSamplerViews` / `BoundSamplerStates`;mipmap 与 framebuffer-copy 读各 verb handle。|
|
||||
| GetProgramForDraw | `MagmaProgramSource(handle, ShaderCsoRecord)`;SPIR-V / reflection 来自 `Archive`,可变绑定和 global UBO 来自记录尾部。|
|
||||
| GetProgramForDispatch | 同一 source,以 `DispatchProgram` 句柄选择 compute program。|
|
||||
| GetTransformFeedbackProgram | 无 XFB draw 不进该读点;active XFB capture 在 wire setup 的 P7 buffer 边界具名拒绝。本包没有宣称 Magma 的 XFB capture 已可用。|
|
||||
| GetTextureObject | handle-keyed VkTextureManager 完成 sync,无 GL-name / client weak_ptr 查询。|
|
||||
|
||||
邻接项:`GetBufferBindingPointCount` 不再被 wire uniform 路径读取;`HasOpenTransformFeedbackSpan`
|
||||
只留在 class-C query / P7 capture 子树。`GetBufferBindingSlot` 的 indirect wrappers 在第一处前端读取前
|
||||
保持具名 P7 拒绝;server 生成的 LINE_LOOP indices 用 transient server bytes,普通无 buffer draw 不受影响。
|
||||
`ShaderStorageBlockBinding` 用 `VerbStorageBlockProgram` 更新 server `StorageOverrides`,不再经
|
||||
`TryGetDirectVulkanProgram` / `ValidateProgramName` / `GetProgramObject` 探 client 名表。
|
||||
|
||||
独立 fm 的全局 FieldOwnership 分类仍保留 15 个 BARRIER_PULLED 字段;fe 集成后两个 scalar sticky
|
||||
改为 APPLIER_DERIVED,仍余 **13 个字段**。其余对象行需在收口时与 residual-fill /
|
||||
generator 的静态不变量一起归零,不是本包门绿后自动翻成 RECORD_SUPPLIED。
|
||||
这里没有把“没有调用旧对象 getter”冒充“旧 SharedPtr 字段已由记录填充”。`FieldOwnership.def` 已加该说明。
|
||||
`RecordError` 的反向通道归 fv;`PipeInputs::IsLive()` 仍 forward 到 `LiveContext()`,也是
|
||||
未退役的 client liveness 读取,留给 fv / 阶段收口核对;均未在本包领取完成。
|
||||
|
||||
### Program source、descriptor 与发射约束
|
||||
|
||||
`MagmaProgramSource` 是仅在 disaggregated 构建存在的只读视图:monolith/internal-program
|
||||
构造接 `const ProgramObject&`,wire 构造接 handle 和 server `MGPipeShaderCsoRecord`。
|
||||
非 disaggregated 构建是原 `ProgramObject` 的类型别名,保持 pull 代码形态。
|
||||
SPIR-V、linked stages、uniform/block/XFB reflection 来自 ProgramArchive;`BlockBindings`、
|
||||
`SamplerUnits`、`StorageOverrides`、`GlobalConstants` 来自当前记录,后者覆盖 archive 的旧值。
|
||||
全局 UBO 字节与版本不再问 client,SSBO 索引沿用旧 SPIR-V reflection 的排序规则。
|
||||
|
||||
server 视图不使用或写回 `ProgramObject` 的 hash memo:每次按当前 archive/绑定尾部算 hash,
|
||||
编译产物仍由 ProgramFactory 的 server cache 复用。按内容进一步缓存 hash 属性能工作,
|
||||
本包没有以共享 client memo 换取“快”。draw 和 dispatch 各自从记录选 program;普通
|
||||
无 buffer draw 支持 current generic attributes,`gl_InstanceID` 的既有 SPIR-V rebase 保留。
|
||||
|
||||
sampler/image descriptor 消费 `BoundSamplerViews` / `BoundSamplerStates` / `BoundShaderImages`,
|
||||
以 server texture record 创建原生 image view、按 record sampler 参数创建 sampler;view 的
|
||||
mip/layer window、format alias、swizzle 与隐式 alpha 按记录恢复。真实 buffer/texel-buffer
|
||||
消费者仍在前端解引用前具名拒绝。无绑定 image 所需的 frontend placeholder 则明确报
|
||||
`Fatal{UnmigratedVerb, "Magma:unbound-image-placeholder@P7"}`,不是构造 client 对象来兜底。
|
||||
|
||||
vertex-input 的发射不受 Magma 缺 bit 7 的牵连:`NewVertexElements` 属 bit 8,P4a 的
|
||||
consumer/dependency gate 只管该阶段的四个 object families。server 因而能区分 enabled array
|
||||
与 current attributes;wire 缺少 vertex-elements record 会具名拒绝,不能静默当空 VAO。
|
||||
|
||||
`CapsMirrorTest.ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBufferOps` 在同一
|
||||
子进程中采用 Texture-only → Framebuffer-only → Texture-only 三个
|
||||
snapshot;buffer bit 始终为 0,逐次断言真实 `MGPipeP4aFamilyEmits` 的正反变化。
|
||||
当前对象行仍被 BARRIER_PULLED 分类支配,测试没有假造 `SuppliedFieldMask` 的值变化;
|
||||
caps generation 是该环境缓存的失效键,也是后续归属表收口必须保留的条件。
|
||||
|
||||
## 3. T5 与三类结构耦合
|
||||
|
||||
- T5:mipmap 使用 `VerbMipRes/BaseLevel/LevelCount`,只改 server image / staged shadow;upload-target 集合由 resource descriptor 推导,view window 从 `ViewOf` / sampler-view record 解析。无 client texture unit、upload-target vector 或 mip-level storage 写入。
|
||||
- texture twin / memo:独立 `std::unordered_map` 以 `{slot, gen}` 为键;dead-generation prune、shutdown 清理;shape 重建保留旧 GPU 内容。pending-upload 只上传指定 regions,避免 CPU 旧影子覆盖 GPU clear/blit/mipmap 后未改区域。失败上传不推进 synced serial。
|
||||
- program twin:`MagmaProgramSource` 在非 disaggregated 编译下是 `ProgramObject` 别名,在 split 下按值引用 server archive/record;`GetBackendHashMemo/SetBackendHashMemo` 不再写 client 对象。
|
||||
- server 构造 client 类型:wire clear/blit/mipmap/draw 使用 native image/view/framebuffer/render-pass,sampler 从 `SamplerParameters` 构造;不走隐藏 ShaderObject/ProgramObject/SamplerObject 或前端 texture placeholder 构造。原 frontend placeholder 的 allocator 边界显式改写为上节的具名 P7 拒绝,未装成新可用功能。
|
||||
- buffer twin / 直写:真实前端 BufferObject 的 backing resource、mapped bytes、EnsureGpuResident 等仅留 monolith 或既有 P7 拒绝之后;`ResourceTracker.h` 的 GPU-written fallback 归 fv,没有改。
|
||||
|
||||
`CopyImageEndpoint.TextureHandle` / `VerbStorageBlockProgram` 与 fe 采用相同 seam。
|
||||
为独立 fm 门可运行,同时带入 fe 的 GLES CopyImage endpoint 接线和 shared readback helper 的 server reply-scratch
|
||||
分支;集成保留同一份,不重复计算为两份迁移。
|
||||
|
||||
新 draw render-pass 仍沿用既有正确性规则:sRGB attachment 随 `GL_FRAMEBUFFER_SRGB`
|
||||
选择 sRGB/UNORM 写 view;1D/1D-array storage 保留相应 view 类型;各 attachment 使用
|
||||
GENERAL layout 与 descriptor 一致。旧 pass/framebuffer/view 退休前等待已提交 GPU 工作完成。
|
||||
每次 command buffer 重开均经 FrameContext observer 重置 dynamic-state shadow,viewport、
|
||||
scissor、blend/stencil 等不会因连续 draw 的相同值而漏发。此处选择正确性优先的提交/等待,
|
||||
不声称完成 P7 的批处理性能设计。
|
||||
|
||||
staged store 的 hookless 路径与 GLES hook 二选一,绝不双 Adopt。named respecify 定义对应
|
||||
mip(包含 null-data),whole immutable respecify 重定义完整链,destroy 删除 handle key,
|
||||
object-record release 清空;make-current reset 保留仍活对象的数据。
|
||||
|
||||
## 4. 出口门 4 的回答
|
||||
|
||||
**Magma 不发布 `kCapRunAheadApply`。** 无 buffer draw / compute、framebuffer、texture 诸实际可达路径
|
||||
以与 Espryt 相同的 role-split + strict 机制验证;分块时旧 client pointers 根本不在 server 块里,lockstep 不能
|
||||
掩盖这些读取。真实 buffer consumers、host-texel placeholders 和少量 native format/function shapes 仍是 P7 功能债。
|
||||
新 wire render-pass 为保证对象退休正确,提交后等待 queue 完成才销毁,尚未建立 run-ahead 的资源版本和性能契约。
|
||||
此外 `IsLive` forward、fv 剩余反向写入与完整字段归属收口仍未完成,不能把当前已测工作集
|
||||
扩大成“全 server apply 路径已满足 run-ahead”的能力承诺。保留 lockstep 是能力声明;
|
||||
不把它当跨角色读写的豁免。`MagmaPipeIdentityTest.AMagmaServerNeverPublishesTheRunAheadCapBit`
|
||||
分别对 ready=false/true 固定 Magma 的回答为不发布,避免随 Espryt readiness 翻转而误开启。
|
||||
|
||||
R11F_G11F_B10F 的 mip blit 是已实测的原有功能债:f1 基线真实 inproc 下
|
||||
`F1WireScenario.GenerateMipmapPackedFloatPixels` 像素失败,RGB 都为 0(`fm-base-packed.log`)。
|
||||
本包对应失败为 `Fatal{UnmigratedVerb, "Magma:mipmap-native-format@P7"}`;新增 Magma 同名测试留在
|
||||
`integration-magma-p7-known-red`,CI 强制恰一条实际执行、failure 非 skip、私有日志精确含该 marker。
|
||||
它不加入双块 field-fatal 棘轮;原 Espryt case 与所有既有测试名保留。
|
||||
|
||||
## 5. 门与 red-once
|
||||
|
||||
验证数字将在最后一轮运行后补齐。已完成的独立证据:
|
||||
|
||||
- 无 buffer ClipDistance:baseline 共享块绿 / fm 双块 strict 绿。
|
||||
- Program source 单测 3/3;临时退回 archive 旧 binding 值,`MagmaProgramSourceTest.ServerBindingTailsReplaceLinkTimeDefaults` 真红(旧值 9 分别不等于记录值 3/6);健康源码 3/3 绿(`fm-program-source-tests.log` / `fm-program-source-red-once.log`)。这份证据为独立临时 object/executable 执行;最终集成 unit 门仍单列核对。
|
||||
- `StagedTextureProductionTest.HooklessTextureConsumerOwnsBytesAndScopedStorageLifetime`:旧 metadata-only 条件吞 named null-data mip,level-1 extent 为 0 导致真红;修复后 1/1 绿(集成者 `int-fm-staging-red-once.log` / `int-fm-staging-green.log`)。
|
||||
- G1 pull 已测 checkpoint:`.text` 10806051 → 10806051;defined symbols 27815 → 27815;0 added / 0 removed / 0 resized / 0 renamed(`fm-g1-final.log`)。最后新增代码后的确认由下表更新。
|
||||
- flavours 已测 checkpoint:pull / push 生效宏与构建通过;独立 unit 首轮 2266/2266、0 failed(10 个宏/平台基线 skip;该次尚不含后来加入的 caps case)。
|
||||
- generator check/self-test、include closure 已绿;doc citations 只有与 f1 相同的两处既有 basename 歧义。
|
||||
|
||||
最终门记录(门禁执行者填入最终头与对应日志,不从上述 checkpoint 推算):
|
||||
|
||||
| 门 | 最终结果 |
|
||||
|---|---|
|
||||
| 最终代码头 / build | 待最后一轮结果 |
|
||||
| unit / integration-split | 待最后一轮结果 |
|
||||
| Magma 双块 + strict | 待最后一轮结果;新 sampler/uniform、compute image-store 场景也必须实际执行 |
|
||||
| 双块 Fatal 棘轮 | 待集成复测:fm 要退役 `GetFramebufferBindingSlot@Clear`;独立 fm 的其它 Espryt 首阻塞仍归 fe,不追加新预期对 |
|
||||
| integration-gpu | 待集成门结果 |
|
||||
| pull / push / G1 / G2 / G14 | 待最后一轮确认 |
|
||||
| packed-format known-red | 待最后一轮精确 marker 核对;不是 skip,也不是双块字段 Fatal |
|
||||
|
||||
strict、双块与 gpu 会覆盖同一测试的私有日志,串行运行并保存各自摘要;后跑车道的日志
|
||||
不能拿来替前一车道补证。新增 scenario 名集合保留既有测试,known-red 专门车道也必须实际
|
||||
执行并验证自己的失败原因,避免通过移标签隐藏回归。
|
||||
|
||||
设备门未执行:当前没有 Redmi `2f7cbe2e`;该项由阶段收口记录,不能由 host lavapipe 结果代替。
|
||||
Reference in New Issue
Block a user