Merge commit 'b394a09f' into codex/p5f-wave2

This commit is contained in:
2026-09-19 23:00:39 -04:00
3 changed files with 182 additions and 1 deletions
+1 -1
View File
@@ -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 PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels)
foreach(fmSlot 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.
@@ -2,6 +2,9 @@
#include "../Harness/ScenarioFixture.h"
#include "../Harness/SplitRuntimePeek.h"
#include <array>
#include <initializer_list>
#include <utility>
#include <vector>
#ifdef GLAPI
#undef GLAPI
#endif
@@ -12,6 +15,45 @@
namespace MGITest {
namespace {
constexpr const char* kWireVertexIdTriangle = R"(#version 430 core
void main() {
vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}
)";
GLuint BuildWireProgram(std::initializer_list<std::pair<GLenum, const char*>> sources) {
const GLuint program = glCreateProgram();
for (const auto& stage : sources) {
const GLuint shader = glCreateShader(stage.first);
glShaderSource(shader, 1, &stage.second, nullptr);
glCompileShader(shader);
GLint compiled = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
char log[4096]{};
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
ADD_FAILURE() << "wire pixel shader compilation: " << log;
glDeleteShader(shader);
glDeleteProgram(program);
return 0;
}
glAttachShader(program, shader);
glDeleteShader(shader);
}
glLinkProgram(program);
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
char log[4096]{};
glGetProgramInfoLog(program, sizeof(log), nullptr, log);
ADD_FAILURE() << "wire pixel program link: " << log;
glDeleteProgram(program);
return 0;
}
return program;
}
class F1WireScenario : public ScenarioTest {
protected:
GLuint fbo = 0, texture = 0;
@@ -259,6 +301,102 @@ TEST_F(F1WireScenario, GenerateMipmapDepthPixels) {
EXPECT_NEAR(pixel, 0.375f, 0.00001f);
}
TEST_F(F1WireScenario, VertexIdSamplerAndScalarUniformPixels) {
if (!Ready()) return;
Attach(GL_RGBA8);
const char* fragment = R"(#version 430 core
uniform sampler2D sourceTexture;
uniform float scale;
layout(location=0) out vec4 color;
void main() { color = vec4(texture(sourceTexture, vec2(0.5)).rgb * scale, 1.0); }
)";
const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle},
{GL_FRAGMENT_SHADER, fragment}});
ASSERT_NE(program, 0u);
GLuint vao = 0, source = 0, sampler = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao); // No attributes, VBO, UBO or SSBO participates in either draw.
glGenTextures(1, &source);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, source);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
const std::array<GLubyte, 4> texel{64, 128, 192, 255};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texel.data());
glGenSamplers(1, &sampler);
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindSampler(3, sampler);
glUseProgram(program);
const GLint sourceLocation = glGetUniformLocation(program, "sourceTexture");
const GLint scaleLocation = glGetUniformLocation(program, "scale");
ASSERT_GE(sourceLocation, 0);
ASSERT_GE(scaleLocation, 0);
glUniform1i(sourceLocation, 3);
glViewport(0, 0, 8, 8);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_RASTERIZER_DISCARD);
glDisable(GL_FRAMEBUFFER_SRGB);
for (const GLfloat scale : {0.5f, 0.25f}) {
glUniform1f(scaleLocation, scale); // Post-link changes must come from GlobalConstants.
glDrawArrays(GL_TRIANGLES, 0, 3);
std::array<GLubyte, 4> pixel{};
glReadPixels(2, 3, 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], texel[channel] * scale, 1) << "scalar uniform " << scale;
EXPECT_EQ(pixel[3], 255);
}
glUseProgram(0);
glBindVertexArray(0);
glBindSampler(3, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glDeleteSamplers(1, &sampler);
glDeleteTextures(1, &source);
glDeleteVertexArrays(1, &vao);
glDeleteProgram(program);
}
TEST_F(F1WireScenario, ComputeImageStoreFramebufferPixels) {
if (!Ready()) return;
Attach(GL_RGBA8);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
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() {
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
imageStore(destination, p, vec4(vec2(p + ivec2(1)) / 8.0, 0.5, 1.0));
}
)";
const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}});
ASSERT_NE(program, 0u);
glBindImageTexture(2, texture, 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);
// Read through an FBO so the assertion does not depend on the class-C GetTexImage path.
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));
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
const size_t at = static_cast<size_t>(y * 8 + x) * 4;
EXPECT_NEAR(pixels[at], (x + 1) * 255.0 / 8.0, 1) << x << ", " << y;
EXPECT_NEAR(pixels[at + 1], (y + 1) * 255.0 / 8.0, 1) << x << ", " << y;
EXPECT_NEAR(pixels[at + 2], 128, 1);
EXPECT_EQ(pixels[at + 3], 255);
}
}
glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
glUseProgram(0);
glDeleteProgram(program);
}
TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) {
if (!Ready()) return;
Attach(GL_RGBA8);
@@ -589,6 +589,49 @@ TEST(CapsMirrorTest, APlaceholderMirrorConsumesNothing) {
(void)empty;
}
TEST(CapsMirrorTest, ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBufferOps) {
#if MGTEST_HAVE_FORK
// Isolate every process global: transport, push mask, caps generation, refusal
// counters and the cached supply environment must not escape into another case.
const ChildResult result = RunInChild([] {
MG_Config::Transport = MG_Config::TransportMode::InProcess;
MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP5e;
MG_Pipe::MGPipeSetResourceOps(nullptr);
const auto emits = [](Uint64 family) { return MGPipeP4aFamilyEmits(family, family); };
Snapshot snapshot = MakeSnapshot(kMGPipeSubsystemTextureResources, 0);
snapshot.Backend = BackendType::DirectVulkan;
AdoptSnapshot(snapshot);
const Uint64 firstGeneration = CapsMirrorInstance().Generation();
EXPECT_FALSE(CapsMirrorInstance().ServerConsumes(kMGPipeSubsystemResources));
EXPECT_TRUE(emits(kMGPipeSubsystemTextureResources));
EXPECT_FALSE(emits(kMGPipeSubsystemFramebuffer));
// The former shared buffer-consumer gate cannot distinguish these two
// snapshots: both withhold bit 7. The production per-family gate must.
snapshot.Caps.CallMask = MGCapsConsumerBits(kMGPipeSubsystemFramebuffer);
AdoptSnapshot(snapshot);
EXPECT_GT(CapsMirrorInstance().Generation(), firstGeneration);
EXPECT_FALSE(CapsMirrorInstance().ServerConsumes(kMGPipeSubsystemResources));
EXPECT_FALSE(emits(kMGPipeSubsystemTextureResources));
EXPECT_TRUE(emits(kMGPipeSubsystemFramebuffer));
snapshot.Caps.CallMask = MGCapsConsumerBits(kMGPipeSubsystemTextureResources);
AdoptSnapshot(snapshot);
EXPECT_TRUE(emits(kMGPipeSubsystemTextureResources));
EXPECT_FALSE(emits(kMGPipeSubsystemFramebuffer));
// SuppliedFieldMask's object rows remain BARRIER_PULLED until the final
// ownership retirement. They cannot honestly expose a mask change yet;
// this checks the actual emitter gate, not a synthetic memo-only answer.
std::fflush(nullptr);
::_exit(::testing::Test::HasFailure() ? 1 : 0);
});
ASSERT_GE(result.Status, 0) << "fork/waitpid failed";
ASSERT_TRUE(WIFEXITED(result.Status)) << DescribeStatus(result) << result.Log;
EXPECT_EQ(WEXITSTATUS(result.Status), 0) << result.Log;
#else
GTEST_SKIP() << "process-global consumer isolation requires fork";
#endif
}
TEST(CapsMirrorTest, TheConsumerBlockDoesNotCollideWithTheFeatureBits) {
// The two halves of CallMask, asserted against each other rather than against a constant:
// bits 0..8 are MGPCapBit and bits 32..47 are the consumer mask, and the whole reason R-8