[Feat] (Remote): migrate f1 clear copy and mipmap verbs under inproc

This commit is contained in:
2026-09-16 11:49:41 -04:00
parent f567610339
commit 7ffa4998ab
5 changed files with 697 additions and 33 deletions
@@ -2010,3 +2010,19 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
check "${CMAKE_CTEST_COMMAND}" "${CMAKE_BINARY_DIR}") check "${CMAKE_CTEST_COMMAND}" "${CMAKE_BINARY_DIR}")
set_tests_properties(SplitLogPaths.PrivateAndDistinct PROPERTIES LABELS "integration-split") set_tests_properties(SplitLogPaths.PrivateAndDistinct PROPERTIES LABELS "integration-split")
endif() endif()
# f1: split-only source keeps the monolith registry unchanged.
if (MOBILEGL_BUILD_DISAGGREGATED)
target_sources(MobileGLIntegrationTest PRIVATE Scenarios/F1WireScenario.cpp)
foreach(f1Slot ClearBufferfi ClearBufferfv ClearBufferiv ClearBufferuiv ClearNamedFramebufferfi ClearNamedFramebufferfv ClearNamedFramebufferiv ClearNamedFramebufferuiv CopyTexImage2D CopyTexSubImage2D GenerateMipmap)
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectGLES.Split.F1."
TEST_FILTER "F1WireScenario.${f1Slot}Pixels"
DISCOVERY_TIMEOUT 30
PROPERTIES
LABELS "integration-split"
TIMEOUT ${MGL_ITEST_TIMEOUT}
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5b-f1-${f1Slot}.log"
)
endforeach()
endif()
@@ -0,0 +1,222 @@
// f1 split-only pixel controls: every result depends on the migrated verb.
#include "../Harness/ScenarioFixture.h"
#include "../Harness/SplitRuntimePeek.h"
#include <array>
#ifdef GLAPI
#undef GLAPI
#endif
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glcorearb.h>
#undef GL_GLEXT_PROTOTYPES
namespace MGITest {
namespace {
class F1WireScenario : public ScenarioTest {
protected:
GLuint fbo = 0, texture = 0;
void SetUp() override {
ScenarioTest::SetUp();
if (!Ready()) return;
const auto why = SplitRuntimeSkipReason();
if (!why.empty()) GTEST_SKIP() << why;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glDisable(GL_SCISSOR_TEST);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glStencilMask(~0u);
}
void TearDown() override {
if (!Ready()) return;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (fbo) glDeleteFramebuffers(1, &fbo);
if (texture) glDeleteTextures(1, &texture);
ScenarioTest::TearDown();
}
void Attach(GLenum format, GLenum attachment = GL_COLOR_ATTACHMENT0, int levels = 1) {
glTexStorage2D(GL_TEXTURE_2D, levels, format, 8, 8);
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture, 0);
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); }
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) << "F1.setup.framebuffer";
}
};
}
TEST_F(F1WireScenario, ClearBufferfvPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearBufferfv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA8);
const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f};
const auto before = PeekSplitRuntime().emitSeq;
glClearBufferfv(GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfv.wire";
GLubyte pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfv.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearBufferfv.pixels";
}
TEST_F(F1WireScenario, ClearNamedFramebufferfvPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA8);
const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f};
const auto before = PeekSplitRuntime().emitSeq;
glClearNamedFramebufferfv(fbo, GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfv.wire";
GLubyte pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfv.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearNamedFramebufferfv.pixels";
}
TEST_F(F1WireScenario, ClearBufferivPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearBufferiv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA32I);
const GLint value[4] = {-37, 19, -11, 5};
const auto before = PeekSplitRuntime().emitSeq;
glClearBufferiv(GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferiv.wire";
GLint pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferiv.error";
for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferiv.pixels";
}
TEST_F(F1WireScenario, ClearNamedFramebufferivPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferiv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA32I);
const GLint value[4] = {-37, 19, -11, 5};
const auto before = PeekSplitRuntime().emitSeq;
glClearNamedFramebufferiv(fbo, GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferiv.wire";
GLint pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferiv.error";
for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferiv.pixels";
}
TEST_F(F1WireScenario, ClearBufferuivPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearBufferuiv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA32UI);
const GLuint value[4] = {37, 19, 11, 5};
const auto before = PeekSplitRuntime().emitSeq;
glClearBufferuiv(GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferuiv.wire";
GLuint pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferuiv.error";
for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferuiv.pixels";
}
TEST_F(F1WireScenario, ClearNamedFramebufferuivPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferuiv.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA32UI);
const GLuint value[4] = {37, 19, 11, 5};
const auto before = PeekSplitRuntime().emitSeq;
glClearNamedFramebufferuiv(fbo, GL_COLOR, 0, value);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferuiv.wire";
GLuint pixel[4]{};
glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferuiv.error";
for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferuiv.pixels";
}
TEST_F(F1WireScenario, ClearBufferfiPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearBufferfi.pixels fails.
if (!Ready()) return;
Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT);
const auto before = PeekSplitRuntime().emitSeq;
glClearBufferfi(GL_DEPTH_STENCIL, 0, 0.375f, 91);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfi.wire";
GLuint pixel = 0;
glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfi.error";
EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearBufferfi.pixels";
EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearBufferfi.pixels";
}
TEST_F(F1WireScenario, ClearNamedFramebufferfiPixels) {
// Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfi.pixels fails.
if (!Ready()) return;
Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT);
const auto before = PeekSplitRuntime().emitSeq;
glClearNamedFramebufferfi(fbo, GL_DEPTH_STENCIL, 0, 0.375f, 91);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfi.wire";
GLuint pixel = 0;
glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfi.error";
EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearNamedFramebufferfi.pixels";
EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearNamedFramebufferfi.pixels";
}
TEST_F(F1WireScenario, CopyTexImage2DPixels) {
// Red once (executed, reverted): omit the copy sink call; F1.CopyTexImage2D.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA8);
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GLuint destination = 0;
glGenTextures(1, &destination);
glBindTexture(GL_TEXTURE_2D, destination);
const auto before = PeekSplitRuntime().emitSeq;
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 3, 4, 4, 0);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexImage2D.wire";
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0);
GLubyte pixel[4]{};
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexImage2D.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexImage2D.pixels";
glDeleteTextures(1, &destination);
}
TEST_F(F1WireScenario, CopyTexSubImage2DPixels) {
// Red once (executed, reverted): omit the copy sink call; F1.CopyTexSubImage2D.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA8);
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GLuint destination = 0;
glGenTextures(1, &destination);
glBindTexture(GL_TEXTURE_2D, destination);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
const auto before = PeekSplitRuntime().emitSeq;
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 2, 3, 2, 2);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexSubImage2D.wire";
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0);
GLubyte pixel[4]{};
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexSubImage2D.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexSubImage2D.pixels";
glDeleteTextures(1, &destination);
}
TEST_F(F1WireScenario, GenerateMipmapPixels) {
// Red once (executed, reverted): omit the mipmap sink call; F1.GenerateMipmap.pixels fails.
if (!Ready()) return;
Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4);
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
const auto before = PeekSplitRuntime().emitSeq;
glGenerateMipmap(GL_TEXTURE_2D);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.GenerateMipmap.wire";
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2);
GLubyte pixel[4]{};
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.GenerateMipmap.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateMipmap.pixels";
}
} // namespace MGITest
+135 -18
View File
@@ -39,6 +39,8 @@
#include <cstring> #include <cstring>
#include "WireTables.h" #include "WireTables.h"
#include <MG_Impl/Pipe/FramebufferEmit.h>
#include <MG_Impl/Pipe/TextureEmit.h>
namespace MobileGL::MG_Remote::Client { namespace MobileGL::MG_Remote::Client {
@@ -174,6 +176,123 @@ namespace MobileGL::MG_Remote::Client {
nullptr, 0, nullptr); nullptr, 0, nullptr);
} }
// ---- f1: verbatim clear/copy/mipmap records (CONTRACT-P5B §2) ----
void EmitF1Clear(const char* slot, MG_Pipe::MGPipeHandle fbo, GLenum buffer,
GLint drawbuffer, Uint8 valueClass, const void* value,
GLfloat depth = 0, GLint stencil = 0) {
auto& session = RequireSession(slot);
BeforeReadOnlyVerb();
MG_Pipe::MGPClear record{};
record.Fbo = fbo;
record.DrawBufferIndex = drawbuffer;
record.ValueClass = valueClass;
switch (buffer) {
case GL_COLOR:
record.Kind = MG_Pipe::kMGPipeClearKindColor;
std::memcpy(record.ColorValue, value, sizeof(record.ColorValue));
break;
case GL_DEPTH:
record.Kind = MG_Pipe::kMGPipeClearKindDepth;
std::memcpy(&record.DepthValue, value, sizeof(record.DepthValue));
break;
case GL_STENCIL:
record.Kind = MG_Pipe::kMGPipeClearKindStencil;
std::memcpy(&record.StencilValue, value, sizeof(record.StencilValue));
break;
case GL_DEPTH_STENCIL:
record.Kind = MG_Pipe::kMGPipeClearKindDepthStencil;
record.DepthValue = depth;
record.StencilValue = stencil;
break;
default: UnmigratedVerbFatal(slot);
}
session.EmitAndWait(MG_Pipe::MGPWireOp::Clear, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
EmitF1Clear("ClearBufferfv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer,
MG_Pipe::kMGPipeClearValueClassFloat, value);
}
void EmitClearNamedFramebufferfv(const SharedPtr<MG_State::GLState::FramebufferObject>& fbo,
GLenum buffer, GLint drawbuffer, const GLfloat* value) {
EmitF1Clear("ClearNamedFramebufferfv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo),
buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, value);
}
void EmitClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {
EmitF1Clear("ClearBufferiv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer,
MG_Pipe::kMGPipeClearValueClassInt, value);
}
void EmitClearNamedFramebufferiv(const SharedPtr<MG_State::GLState::FramebufferObject>& fbo,
GLenum buffer, GLint drawbuffer, const GLint* value) {
EmitF1Clear("ClearNamedFramebufferiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo),
buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassInt, value);
}
void EmitClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {
EmitF1Clear("ClearBufferuiv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer,
MG_Pipe::kMGPipeClearValueClassUint, value);
}
void EmitClearNamedFramebufferuiv(const SharedPtr<MG_State::GLState::FramebufferObject>& fbo,
GLenum buffer, GLint drawbuffer, const GLuint* value) {
EmitF1Clear("ClearNamedFramebufferuiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo),
buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassUint, value);
}
void EmitClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
EmitF1Clear("ClearBufferfi", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer,
MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil);
}
void EmitClearNamedFramebufferfi(const SharedPtr<MG_State::GLState::FramebufferObject>& fbo,
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
EmitF1Clear("ClearNamedFramebufferfi", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo),
buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil);
}
const SharedPtr<MG_State::GLState::ITextureObject>& F1BoundTexture(GLenum target) {
auto& ctx = *MG_State::pGLContext;
return ctx.GetTextureUnitObject(ctx.GetActiveTextureUnit())
.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)).GetBoundObject();
}
void EmitF1Copy(GLenum target, GLint level, GLenum format, GLint x, GLint y,
GLsizei width, GLsizei height, GLint xoffset, GLint yoffset, Bool subImage) {
auto& session = RequireSession(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
BeforeReadOnlyVerb();
MG_Pipe::MGPCopyFromFramebuffer record{};
record.Dst = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*F1BoundTexture(target));
record.Target = static_cast<Uint32>(target);
record.Level = level;
record.InternalFormat = format;
record.X = x; record.Y = y;
record.Width = width; record.Height = height;
record.XOffset = xoffset; record.YOffset = yoffset;
record.SubImage = subImage;
session.EmitAndWait(MG_Pipe::MGPWireOp::CopyFramebufferToTexture, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitCopyTexImage2D(GLenum target, GLint level, GLenum format, GLint x, GLint y,
GLsizei width, GLsizei height, GLint) {
EmitF1Copy(target, level, format, x, y, width, height, 0, 0, false);
}
void EmitCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height) {
EmitF1Copy(target, level, 0, x, y, width, height, xoffset, yoffset, true);
}
void EmitGenerateMipmap(GLenum target) {
auto& session = RequireSession("GenerateMipmap");
BeforeReadOnlyVerb();
const auto& texture = F1BoundTexture(target);
MG_Pipe::MGPMipPlan record{};
record.Res = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*texture);
record.Target = static_cast<Uint16>(target);
record.BaseLevel = texture->GetLevelRange().x();
const auto* mipmap = dynamic_cast<const MG_State::GLState::TextureObjectMipmap*>(texture.get());
record.LevelCount = mipmap ? mipmap->GetMipmapLevelCount() : 0;
session.EmitAndWait(MG_Pipe::MGPWireOp::GenerateMipmap, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitDrawArrays(GLenum mode, GLint first, GLsizei count) { void EmitDrawArrays(GLenum mode, GLint first, GLsizei count) {
ClientSession& session = RequireSession("DrawArrays"); ClientSession& session = RequireSession("DrawArrays");
BeforeDrawVerb(); BeforeDrawVerb();
@@ -539,22 +658,7 @@ namespace MobileGL::MG_Remote::Client {
X(BindTransformFeedback, void, (GLuint)) \ X(BindTransformFeedback, void, (GLuint)) \
X(DeleteTransformFeedback, void, (GLuint)) X(DeleteTransformFeedback, void, (GLuint))
#define MGR_UNMIGRATED_F1_SLOTS(X) \ #define MGR_UNMIGRATED_F1_SLOTS(X)
X(ClearBufferfi, void, (GLenum, GLint, GLfloat, GLint)) \
X(ClearBufferfv, void, (GLenum, GLint, const GLfloat*)) \
X(ClearBufferuiv, void, (GLenum, GLint, const GLuint*)) \
X(ClearBufferiv, void, (GLenum, GLint, const GLint*)) \
X(ClearNamedFramebufferfv, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, const GLfloat*)) \
X(ClearNamedFramebufferfi, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, GLfloat, GLint)) \
X(ClearNamedFramebufferiv, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, const GLint*)) \
X(ClearNamedFramebufferuiv, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, const GLuint*)) \
X(CopyTexImage2D, void, (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint)) \
X(CopyTexSubImage2D, void, (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei)) \
X(GenerateMipmap, void, (GLenum))
// The wave-3 tail. SetSwapInterval is hand-written below (it is not a GL.* slot). // The wave-3 tail. SetSwapInterval is hand-written below (it is not a GL.* slot).
#define MGR_UNMIGRATED_TAIL_SLOTS(X) \ #define MGR_UNMIGRATED_TAIL_SLOTS(X) \
@@ -646,7 +750,7 @@ namespace MobileGL::MG_Remote::Client {
constexpr Uint32 kEmittedSlotsD1 = 0; constexpr Uint32 kEmittedSlotsD1 = 0;
constexpr Uint32 kEmittedSlotsI1 = 0; constexpr Uint32 kEmittedSlotsI1 = 0;
constexpr Uint32 kEmittedSlotsT2 = 0; constexpr Uint32 kEmittedSlotsT2 = 0;
constexpr Uint32 kEmittedSlotsF1 = 0; constexpr Uint32 kEmittedSlotsF1 = 11;
constexpr Uint32 kEmittedSlots = constexpr Uint32 kEmittedSlots =
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1; kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1;
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
@@ -660,7 +764,7 @@ namespace MobileGL::MG_Remote::Client {
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots"); static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots"); static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
static_assert(kUnmigratedTail == 20, "the wave-3 tail is 20 slots and no P5b package owns one"); static_assert(kUnmigratedTail == 20, "the wave-3 tail is 20 slots and no P5b package owns one");
static_assert(kUnmigratedSlots == 64, "CONTRACT-P5.md §7 class C is 64 slots at the P5b contract commit"); static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount, static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
"the three classes no longer partition the 71 slots"); "the three classes no longer partition the 71 slots");
@@ -698,6 +802,19 @@ namespace MobileGL::MG_Remote::Client {
table.GL.BlitFramebuffer = &EmitBlitFramebuffer; table.GL.BlitFramebuffer = &EmitBlitFramebuffer;
table.Present = &EmitPresent; table.Present = &EmitPresent;
// ---- f1 ----
table.GL.ClearBufferfi = &EmitClearBufferfi;
table.GL.ClearBufferfv = &EmitClearBufferfv;
table.GL.ClearBufferiv = &EmitClearBufferiv;
table.GL.ClearBufferuiv = &EmitClearBufferuiv;
table.GL.ClearNamedFramebufferfi = &EmitClearNamedFramebufferfi;
table.GL.ClearNamedFramebufferfv = &EmitClearNamedFramebufferfv;
table.GL.ClearNamedFramebufferiv = &EmitClearNamedFramebufferiv;
table.GL.ClearNamedFramebufferuiv = &EmitClearNamedFramebufferuiv;
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
table.GL.GenerateMipmap = &EmitGenerateMipmap;
return table; return table;
} }
+26 -11
View File
@@ -73,14 +73,16 @@ namespace MobileGL::MG_Remote::Server {
if (table == nullptr) return false; if (table == nullptr) return false;
const MG_Backend::GLFunctionsTable& gl = table->GL; const MG_Backend::GLFunctionsTable& gl = table->GL;
// THE FBO HANDLE IS NOT RESOLVED HERE, AND THAT IS THE RULING RATHER THAN AN OMISSION. // Named records precede the verb; bound-form backends re-sync the live draw binding.
// MGPClear::Fbo names the framebuffer the clear belongs to, but the BINDING is already if (!MG_Pipe::MGPipeHandleIsNull(clear.Fbo) &&
// server state: set_framebuffer_state (op 33) arrives ahead of the clear and the clear.Fbo != MG_Pipe::MGPipeApplier().BoundFramebuffer[0]) {
// applier has bound it. Re-resolving the handle to a frontend FramebufferObject here const char* slot = clear.Kind == kMGPClearKindDepthStencil ? "ClearNamedFramebufferfi+UNBOUND" :
// would need the SharedPtr the four ClearNamedFramebuffer* entries take - a frontend clear.ValueClass == kMGPClearValueClassInt ? "ClearNamedFramebufferiv+UNBOUND" :
// heap reference that table 2 lists as one of the six fields with no wire carrier. So clear.ValueClass == kMGPClearValueClassUint ? "ClearNamedFramebufferuiv+UNBOUND" :
// P5 clears THE BOUND FRAMEBUFFER, which for the reduced path (default FBO) is exactly "ClearNamedFramebufferfv+UNBOUND";
// right, and the named form is P7's along with the handle it needs. MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"}", slot);
std::abort();
}
switch (clear.Kind) { switch (clear.Kind) {
case kMGPClearKindWhole: case kMGPClearKindWhole:
if (gl.Clear == nullptr) return false; if (gl.Clear == nullptr) return false;
@@ -415,12 +417,25 @@ namespace MobileGL::MG_Remote::Server {
// ---- f1 ---- // ---- f1 ----
Bool ServerVerbSink::OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) { Bool ServerVerbSink::OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
(void)plan; const auto* table = Table("GenerateMipmap");
ServerUnmigratedVerbFatal("GenerateMipmap"); if (table == nullptr || table->GL.GenerateMipmap == nullptr) return false;
table->GL.GenerateMipmap(plan.Target);
return true;
} }
Bool ServerVerbSink::OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) { Bool ServerVerbSink::OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
ServerUnmigratedVerbFatal(copy.SubImage ? "CopyTexSubImage2D" : "CopyTexImage2D"); const auto* table = Table(copy.SubImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
if (table == nullptr) return false;
if (copy.SubImage) {
if (table->GL.CopyTexSubImage2D == nullptr) return false;
table->GL.CopyTexSubImage2D(copy.Target, copy.Level, copy.XOffset, copy.YOffset,
copy.X, copy.Y, copy.Width, copy.Height);
} else {
if (table->GL.CopyTexImage2D == nullptr) return false;
table->GL.CopyTexImage2D(copy.Target, copy.Level, copy.InternalFormat,
copy.X, copy.Y, copy.Width, copy.Height, 0);
}
return true;
} }
// ----------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------
+298 -4
View File
@@ -175,8 +175,8 @@ TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
// table itself reports with - which is also what t1's arming condition reads - rather than // table itself reports with - which is also what t1's arming condition reads - rather than
// recomputed here, so a table that lost an emitter cannot look like one that never had it. // recomputed here, so a table that lost an emitter cannot look like one that never had it.
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u); EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
EXPECT_EQ(ImplementedVerbCount(), 5u); EXPECT_EQ(ImplementedVerbCount(), 16u);
EXPECT_EQ(UnmigratedSlotCount(), 64u); EXPECT_EQ(UnmigratedSlotCount(), 53u);
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(), EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
kRemoteEmitSlotCount); kRemoteEmitSlotCount);
} }
@@ -240,9 +240,9 @@ TEST(RemoteEmitTable, AnUnmigratedSlotAbortsAndNamesItself) {
TEST(RemoteEmitTable, EachUnmigratedSlotNamesItsOwnSlot) { TEST(RemoteEmitTable, EachUnmigratedSlotNamesItsOwnSlot) {
// The half the case above cannot state on its own: that the name in the message is the // The half the case above cannot state on its own: that the name in the message is the
// slot's and not a constant. Two different slots, two different names. // slot's and not a constant. Two different slots, two different names.
const ChildResult r = RunInChild([] { RemoteEmitTable().GL.GenerateMipmap(0x0DE1); }); const ChildResult r = RunInChild([] { RemoteEmitTable().GL.GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); });
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"GenerateMipmap\"}"), std::string::npos) << r.Log; EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"GetTexImage\"}"), std::string::npos) << r.Log;
EXPECT_EQ(r.Log.find("DrawElements"), std::string::npos) EXPECT_EQ(r.Log.find("DrawElements"), std::string::npos)
<< "the Fatal message names a slot other than the one that was called:\n" << "the Fatal message names a slot other than the one that was called:\n"
<< r.Log; << r.Log;
@@ -847,6 +847,300 @@ TEST(RemoteReadback, AReplyIsScatteredOnlyWhenItIsOkAndExactlyTheReadsExtent) {
} }
#include "RemoteClientControls.inc" #include "RemoteClientControls.inc"
#include <MG_Impl/Pipe/FramebufferEmit.h>
#include <MG_Impl/Pipe/TextureEmit.h>
#include <MG_State/GLState/TextureState/TextureObject2D.h>
// f1: the installed emitters are decoded by a peer on the apply thread.
#if MGTEST_HAVE_FORK
namespace {
struct F1Peer : Codec::WireVerbSink {
MGPClear clear{};
MGPCopyFromFramebuffer copy{};
MGPMipPlan mip{};
unsigned calls = 0;
Bool OnClear(const MGPClear& v) override { clear = v; ++calls; return true; }
Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer& v) override { copy = v; ++calls; return true; }
Bool OnGenerateMipmap(const MGPMipPlan& v) override { mip = v; ++calls; return true; }
void Install() {
if (Srv::ServerLoopInstance().RunOnApplyThread([](void* self) {
auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{});
decoder.SetVerbSink(static_cast<F1Peer*>(self));
return MOBILEGL_OK;
}, this) != MOBILEGL_OK) ::_exit(82);
}
};
}
TEST(RemoteF1, ClearBufferfvFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferfv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLfloat value[4] = {1, 7, 13, 23};
RemoteEmitTable().GL.ClearBufferfv(GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassFloat ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
!MGPipeHandleIsNull(r.Fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearBufferfv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearNamedFramebufferfvFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferfv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLfloat value[4] = {1, 7, 13, 23};
const auto fbo = MakeShared<MG_State::GLState::FramebufferObject>(73);
RemoteEmitTable().GL.ClearNamedFramebufferfv(fbo, GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassFloat ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearNamedFramebufferfv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearBufferivFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferiv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLint value[4] = {1, 7, 13, 23};
RemoteEmitTable().GL.ClearBufferiv(GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassInt ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
!MGPipeHandleIsNull(r.Fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearBufferiv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearNamedFramebufferivFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferiv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLint value[4] = {1, 7, 13, 23};
const auto fbo = MakeShared<MG_State::GLState::FramebufferObject>(73);
RemoteEmitTable().GL.ClearNamedFramebufferiv(fbo, GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassInt ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearNamedFramebufferiv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearBufferuivFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferuiv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLuint value[4] = {1, 7, 13, 23};
RemoteEmitTable().GL.ClearBufferuiv(GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassUint ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
!MGPipeHandleIsNull(r.Fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearBufferuiv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearNamedFramebufferuivFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferuiv.fields fails.
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer; peer.Install();
const GLuint value[4] = {1, 7, 13, 23};
const auto fbo = MakeShared<MG_State::GLState::FramebufferObject>(73);
RemoteEmitTable().GL.ClearNamedFramebufferuiv(fbo, GL_COLOR, 3, value);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassUint ||
r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 ||
r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearNamedFramebufferuiv.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearBufferfiFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferfi.fields fails.
const auto child = RunInChild([] {
StartControlSession(); F1Peer peer; peer.Install();
RemoteEmitTable().GL.ClearBufferfi(GL_DEPTH_STENCIL, 0, 0.375f, 91);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindDepthStencil || r.DrawBufferIndex != 0 ||
r.DepthValue != 0.375f || r.StencilValue != 91 ||
!MGPipeHandleIsNull(r.Fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearBufferfi.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, ClearNamedFramebufferfiFieldsCross) {
// Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferfi.fields fails.
const auto child = RunInChild([] {
StartControlSession(); F1Peer peer; peer.Install();
const auto fbo = MakeShared<MG_State::GLState::FramebufferObject>(73);
RemoteEmitTable().GL.ClearNamedFramebufferfi(fbo, GL_DEPTH_STENCIL, 0, 0.375f, 91);
const auto& r = peer.clear;
if (peer.calls != 1 || r.Kind != kMGPipeClearKindDepthStencil || r.DrawBufferIndex != 0 ||
r.DepthValue != 0.375f || r.StencilValue != 91 ||
r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.ClearNamedFramebufferfi.fields: " << DescribeStatus(child) << child.Log;
}
namespace {
SharedPtr<MG_State::GLState::TextureObject2D> F1Texture() {
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
auto tex = MakeShared<MG_State::GLState::TextureObject2D>(91);
tex->SetInternalFormat(TextureInternalFormat::RGBA8);
for (Uint level = 0; level != 3; ++level) {
const Int size = 8 >> level;
tex->AllocateStorage(TextureUploadTarget::Texture2D, level,
MG_State::GLState::MipmapInput{IntVec3{size, size, 1}, static_cast<SizeT>(size * size * 4)});
}
tex->SetBaseLevel(1);
MGPipeTextureEmitterInstance().AcquireTexture(tex->GetLifetimeId(), tex.get());
MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).Bind(tex);
return tex;
}
}
TEST(RemoteF1, CopyTexImage2DFieldsCross) {
// Red once (executed, reverted): increment the emitted copy level; F1.CopyTexImage2D.fields fails.
const auto child = RunInChild([] {
StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture();
RemoteEmitTable().GL.CopyTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, -3, 4, 11, 13, 0);
const auto& r = peer.copy;
if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) ||
MGPipeHandleIsNull(r.Dst) || r.Target != GL_TEXTURE_2D || r.Level != 2 ||
r.InternalFormat != GL_RGBA8 || r.X != -3 || r.Y != 4 || r.Width != 11 || r.Height != 13 ||
r.XOffset != 0 || r.YOffset != 0 || r.SubImage != 0) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.CopyTexImage2D.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, CopyTexSubImage2DFieldsCross) {
// Red once (executed, reverted): increment the emitted copy level; F1.CopyTexSubImage2D.fields fails.
const auto child = RunInChild([] {
StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture();
RemoteEmitTable().GL.CopyTexSubImage2D(GL_TEXTURE_2D, 2, 5, 7, -3, 4, 11, 13);
const auto& r = peer.copy;
if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) ||
MGPipeHandleIsNull(r.Dst) || r.Target != GL_TEXTURE_2D || r.Level != 2 ||
r.InternalFormat != 0 || r.X != -3 || r.Y != 4 || r.Width != 11 || r.Height != 13 ||
r.XOffset != 5 || r.YOffset != 7 || r.SubImage != 1) ::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.CopyTexSubImage2D.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, GenerateMipmapFieldsCross) {
// Red once (executed, reverted): increment the emitted base level; F1.GenerateMipmap.fields fails.
const auto child = RunInChild([] {
StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture();
RemoteEmitTable().GL.GenerateMipmap(GL_TEXTURE_2D);
const auto& r = peer.mip;
if (peer.calls != 1 || r.Res != MGPipeTextureEmitterInstance().FindTexture(*tex) ||
MGPipeHandleIsNull(r.Res) || r.Target != GL_TEXTURE_2D || r.BaseLevel != 1 || r.LevelCount != 3)
::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.GenerateMipmap.fields: " << DescribeStatus(child) << child.Log;
}
#endif
#if MGTEST_HAVE_FORK
TEST(RemoteF1, UnboundNamedfvRefusesByName) {
// Red once (executed, reverted): disable the named-FBO refusal; its exact Fatal disappears.
const auto child = RunInChild([] {
CapsPeer backend;
Srv::ServerVerbSink sink;
sink.SetBackend(&backend);
MGPClear r{};
r.Fbo = {701, 1};
r.Kind = kMGPipeClearKindColor;
r.ValueClass = kMGPipeClearValueClassFloat;
sink.OnClear(r);
});
ExpectNamedAbort(child, "Fatal{UnmigratedVerb, \"ClearNamedFramebufferfv+UNBOUND\"}");
}
TEST(RemoteF1, UnboundNamedivRefusesByName) {
// Red once (executed, reverted): disable the named-FBO refusal; its exact Fatal disappears.
const auto child = RunInChild([] {
CapsPeer backend;
Srv::ServerVerbSink sink;
sink.SetBackend(&backend);
MGPClear r{};
r.Fbo = {701, 1};
r.Kind = kMGPipeClearKindColor;
r.ValueClass = kMGPipeClearValueClassInt;
sink.OnClear(r);
});
ExpectNamedAbort(child, "Fatal{UnmigratedVerb, \"ClearNamedFramebufferiv+UNBOUND\"}");
}
TEST(RemoteF1, UnboundNameduivRefusesByName) {
// Red once (executed, reverted): disable the named-FBO refusal; its exact Fatal disappears.
const auto child = RunInChild([] {
CapsPeer backend;
Srv::ServerVerbSink sink;
sink.SetBackend(&backend);
MGPClear r{};
r.Fbo = {701, 1};
r.Kind = kMGPipeClearKindColor;
r.ValueClass = kMGPipeClearValueClassUint;
sink.OnClear(r);
});
ExpectNamedAbort(child, "Fatal{UnmigratedVerb, \"ClearNamedFramebufferuiv+UNBOUND\"}");
}
TEST(RemoteF1, UnboundNamedfiRefusesByName) {
// Red once (executed, reverted): disable the named-FBO refusal; its exact Fatal disappears.
const auto child = RunInChild([] {
CapsPeer backend;
Srv::ServerVerbSink sink;
sink.SetBackend(&backend);
MGPClear r{};
r.Fbo = {701, 1};
r.Kind = kMGPipeClearKindDepthStencil;
r.ValueClass = kMGPipeClearValueClassFloat;
sink.OnClear(r);
});
ExpectNamedAbort(child, "Fatal{UnmigratedVerb, \"ClearNamedFramebufferfi+UNBOUND\"}");
}
#endif
int main(int argc, char** argv) { int main(int argc, char** argv) {
namespace fs = std::filesystem; namespace fs = std::filesystem;