mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
[Feat] (Remote): migrate f1 clear copy and mipmap verbs under inproc
This commit is contained in:
@@ -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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user