Compare commits

...
4 Commits
Author SHA1 Message Date
swung0x48 ad7340921e [Fix] (MG_Impl/GLImpl, MG_State/GLState, MG_Test/Texture, scripts/data): keep GENERATE_MIPMAP on the texture, not keyed by GL name
- A process-wide map keyed by GL name gave a deleted texture's flag to the
  next texture created under the recycled name.
- The flag is now texture object state, so it dies with its texture.
2026-10-16 16:52:59 -04:00
swung0x48 095cf2643b [CI] (scripts/data): expect the new F1 scenarios to skip on the monolith lanes 2026-10-16 16:04:48 -04:00
swung0x48 230a90a439 [Fix] (MG_Impl/GLImpl, MG_IntegrationTest, MG_IntegrationTest/Scenarios, MG_Remote/Client, MG_Test/Texture, scripts/data): make by-name texture calls act on the named texture
- The 19 calls that bound their texture first now share object cores with the target forms
- GL 4.6 rules the binding hid: cube maps in TextureSubImage3D and level queries, named target errors
2026-10-16 14:47:50 -04:00
swung0x48 28bf0c1653 [Refactor] (MG_Backend, MG_Impl/Pipe, MG_IntegrationTest/Harness, MG_Pipe, MG_Pipe/generated, MG_Remote/Client, MG_Test/Pipe, MG_Test/Wire, scripts/data): add by-name mipmap and copy slots that name their texture
- GenerateTextureMipmap and CopyTextureSubImage2D emit the records their target slots emit
- RemoteEmitTable.TheThreeClassesPartitionAllSeventyOneSlots is renamed ...PartitionEverySlot
2026-10-16 14:47:40 -04:00
25 changed files with 1669 additions and 2192 deletions
+7
View File
@@ -111,11 +111,18 @@ namespace MobileGL {
GLsizei height, GLint border);
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
// The by-name form: the destination is `texture`, whatever the units hold. `target` is
// the one the record carries (the texture's own 2D-class target).
void (*CopyTextureSubImage2D)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target,
GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height);
void (*CopyImageSubData)(const CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX,
GLint srcY, GLint srcZ, const CopyImageEndpoint& dst, GLenum dstTarget,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth,
GLsizei srcHeight, GLsizei srcDepth);
void (*GenerateMipmap)(GLenum target);
// The by-name form, also used once the bound-target form has resolved its texture.
void (*GenerateTextureMipmap)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target);
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void* pixels);
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
File diff suppressed because it is too large Load Diff
+27 -8
View File
@@ -471,18 +471,21 @@ namespace MobileGL::MG_Record {
EmitF1Clear("ClearNamedFramebufferfi", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), buffer,
drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil);
}
// The target-taking slots name the texture through the active unit; the by-name slots pass
// it in. Both build the same record, so the server cannot tell the two apart.
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,
void EmitF1Copy(const char* slot, const SharedPtr<MG_State::GLState::ITextureObject>& destination,
GLenum target, GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height,
GLint xoffset, GLint yoffset, Bool subImage) {
VerbChannel session = ChannelFor(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
VerbChannel session = ChannelFor(slot);
BeforeReadOnlyVerb();
MG_Pipe::MGPCopyFromFramebuffer record{};
record.Dst = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*F1BoundTexture(target));
record.Dst = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*destination);
record.Target = static_cast<Uint32>(target);
record.Level = level;
record.InternalFormat = format;
@@ -498,16 +501,22 @@ namespace MobileGL::MG_Record {
}
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);
EmitF1Copy("CopyTexImage2D", F1BoundTexture(target), 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);
EmitF1Copy("CopyTexSubImage2D", F1BoundTexture(target), target, level, 0, x, y, width, height, xoffset,
yoffset, true);
}
void EmitGenerateMipmap(GLenum target) {
VerbChannel session = ChannelFor("GenerateMipmap");
void EmitCopyTextureSubImage2D(const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target,
GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height) {
EmitF1Copy("CopyTextureSubImage2D", texture, target, level, 0, x, y, width, height, xoffset, yoffset, true);
}
void EmitMipPlan(const char* slot, const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target) {
VerbChannel session = ChannelFor(slot);
BeforeReadOnlyVerb();
const auto& texture = F1BoundTexture(target);
MG_Pipe::MGPMipPlan record{};
record.Res = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*texture);
record.Target = static_cast<Uint16>(target);
@@ -523,6 +532,12 @@ namespace MobileGL::MG_Record {
session.EmitAndWait(MG_Pipe::MGPWireOp::GenerateMipmap, &record, sizeof(record), nullptr, 0, nullptr, 0,
nullptr);
}
void EmitGenerateMipmap(GLenum target) {
EmitMipPlan("GenerateMipmap", F1BoundTexture(target), target);
}
void EmitGenerateTextureMipmap(const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target) {
EmitMipPlan("GenerateTextureMipmap", texture, target);
}
// All draw entry points share the owned-input preparation and emission below.
[[noreturn]] void RefuseDrawByName(const char* slot, const char* qualifier);
@@ -1639,7 +1654,9 @@ namespace MobileGL::MG_Record {
table.GL.ClearNamedFramebufferuiv = &EmitClearNamedFramebufferuiv;
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
table.GL.CopyTextureSubImage2D = &EmitCopyTextureSubImage2D;
table.GL.GenerateMipmap = &EmitGenerateMipmap;
table.GL.GenerateTextureMipmap = &EmitGenerateTextureMipmap;
table.GL.GetTexImage = &EmitGetTexImage;
table.GL.GetTextureImage = &EmitGetTextureImage;
table.GL.BindImageTexture = &EmitBindImageTexture;
@@ -1680,8 +1697,10 @@ namespace MobileGL::MG_Record {
// P13 W4b: the texture family - its record arm resolves the texture from the verb's own
// handle (VerbMipRes, VerbCopyTexDst, the copy's two endpoints).
table.GL.GenerateMipmap = &EmitGenerateMipmap;
table.GL.GenerateTextureMipmap = &EmitGenerateTextureMipmap;
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
table.GL.CopyTextureSubImage2D = &EmitCopyTextureSubImage2D;
table.GL.CopyImageSubData = &EmitCopyImageSubData;
table.GL.ReadPixels = &EmitReadPixels;
table.GL.GetTexImage = &EmitGetTexImage;
+2 -2
View File
@@ -2438,7 +2438,7 @@ 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 GenerateMipmapPackedFloat GenerateMipmapDepth)
foreach(f1Slot ClearBufferfi ClearBufferfv ClearBufferiv ClearBufferuiv ClearNamedFramebufferfi ClearNamedFramebufferfv ClearNamedFramebufferiv ClearNamedFramebufferuiv CopyTexImage2D CopyTexSubImage2D CopyTextureSubImage2D GenerateMipmap GenerateTextureMipmap GenerateMipmapPackedFloat GenerateMipmapDepth)
mgl_itest_register_split_arms("F1WireScenario.${f1Slot}Pixels" "F1.")
endforeach()
# THE TWO PACK-STATE CASES OF F1, which are not ${slot}Pixels-shaped (P3b/P4b espryt D1
@@ -2467,7 +2467,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
mgl_itest_magma_arm_shape(${mglItestArm})
mgl_itest_join_environment(mglItestMagmaWireEnvironment
${MGL_ITEST_MAGMA_WIRE_FLAVOUR_ENV} ${mglItestArmTransportEnv})
foreach(fmSlot ComputeImageStoreThroughViewPreservesOtherRootLayer EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels GenerateMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels)
foreach(fmSlot ComputeImageStoreThroughViewPreservesOtherRootLayer EnabledVertexBufferDrawKeepsNamedP7Fatal SrgbDrawTracksFramebufferConversion Texture1DFramebufferClearPixels VertexIdSamplerAndScalarUniformPixels ComputeImageStoreFramebufferPixels PartialTextureUploadPreservesGpuClearPixels TextureViewClearTargetsItsRootMipAndLayer ClearBufferfiPixels ClearBufferfvPixels ClearBufferivPixels ClearBufferuivPixels ClearNamedFramebufferfiPixels ClearNamedFramebufferfvPixels ClearNamedFramebufferivPixels ClearNamedFramebufferuivPixels CopyTexImage2DPixels CopyTexSubImage2DPixels CopyTextureSubImage2DPixels GenerateMipmapPixels GenerateTextureMipmapPixels GenerateMipmapPackedFloatPixels GenerateMipmapDepthPixels)
mgl_itest_lane("DirectVulkan.${mglItestArm}.Fm." "F1WireScenario.${fmSlot}"
LABELS "${mglItestMagmaLabels}" PROPERTIES ${mglItestArmProperties} ENV "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-fm${mglItestArmLogTag}-${fmSlot}.log")
endforeach()
@@ -69,7 +69,7 @@ namespace MGITest {
bool dataArmIsRecord = false;
// ClientSession::Active() != nullptr.
bool sessionActive = false;
// ImplementedVerbCount(), out of kRemoteEmitSlotCount (71).
// ImplementedVerbCount(), out of kRemoteEmitSlotCount (73).
unsigned int implementedVerbs = 0;
unsigned int totalVerbSlots = 0;
// The encoder's highest produced record ordinal, or 0 when there is no session.
@@ -468,6 +468,64 @@ void main() { color = vec4(value, 0.25, 0.75, 1.0); }
for (int i = 0; i < 4; ++i)
EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateMipmap.pixels";
}
// The by-name copy and mipmap verbs name their texture in the record. Another texture stays
// bound on the unit throughout: it is neither written nor unbound, and the named one is.
TEST_F(F1WireScenario, CopyTextureSubImage2DPixels) {
if (!Ready()) return;
Attach(GL_RGBA8);
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GLuint textures[2] = {}; // destination, decoy
glCreateTextures(GL_TEXTURE_2D, 2, textures);
const std::array<GLubyte, 4 * 4 * 4> zeros{};
for (GLuint name : textures) {
glTextureStorage2D(name, 1, GL_RGBA8, 4, 4);
glTextureSubImage2D(name, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, zeros.data());
}
glBindTexture(GL_TEXTURE_2D, textures[1]);
const auto before = PeekSplitRuntime().emitSeq;
glCopyTextureSubImage2D(textures[0], 0, 1, 1, 2, 3, 2, 2);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTextureSubImage2D.wire";
GLint bound = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &bound);
EXPECT_EQ(GLuint(bound), textures[1]) << "F1.CopyTextureSubImage2D.binding";
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
const auto copied = ReadOnePixel(1, 1);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[1], 0);
const auto decoy = ReadOnePixel(1, 1);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTextureSubImage2D.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i) {
EXPECT_NEAR(copied[i], expected[i], 1) << "F1.CopyTextureSubImage2D.pixels";
EXPECT_EQ(decoy[i], 0) << "F1.CopyTextureSubImage2D.decoy";
}
glDeleteTextures(2, textures);
}
TEST_F(F1WireScenario, GenerateTextureMipmapPixels) {
if (!Ready()) return;
Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4);
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GLuint decoy = 0;
glGenTextures(1, &decoy);
glBindTexture(GL_TEXTURE_2D, decoy);
glTexStorage2D(GL_TEXTURE_2D, 4, GL_RGBA8, 8, 8);
const auto before = PeekSplitRuntime().emitSeq;
glGenerateTextureMipmap(texture);
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.GenerateTextureMipmap.wire";
GLint bound = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &bound);
EXPECT_EQ(GLuint(bound), decoy) << "F1.GenerateTextureMipmap.binding";
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2);
const auto pixel = ReadOnePixel(1, 1);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.GenerateTextureMipmap.error";
const int expected[4] = {64, 128, 191, 255};
for (int i = 0; i < 4; ++i)
EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateTextureMipmap.pixels";
glDeleteTextures(1, &decoy);
}
TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) {
if (!Ready()) return;
// Only level zero exists initially. Generating the special-format chain must use the
+2
View File
@@ -51,8 +51,10 @@
X(BlitNamedFramebuffer, kBlitOrCopy) \
X(CopyTexImage2D, kBlitOrCopy) \
X(CopyTexSubImage2D, kBlitOrCopy) \
X(CopyTextureSubImage2D, kBlitOrCopy) \
X(CopyImageSubData, kBlitOrCopy) \
X(GenerateMipmap, kTextureOp) \
X(GenerateTextureMipmap, kTextureOp) \
X(ReadPixels, kReadback) \
X(GetTexImage, kReadback) \
X(GetTextureImage, kReadback) \
+2
View File
@@ -56,8 +56,10 @@
X(BlitNamedFramebuffer, Blit) \
X(CopyTexImage2D, CopyFramebufferToTexture) \
X(CopyTexSubImage2D, CopyFramebufferToTexture) \
X(CopyTextureSubImage2D, CopyFramebufferToTexture) \
X(CopyImageSubData, ResourceCopyRegion) \
X(GenerateMipmap, GenerateMipmap) \
X(GenerateTextureMipmap, GenerateMipmap) \
/* read_pixels (reply) or read_pixels_to_buffer (pack buffer): the reply wins. */ \
X(ReadPixels, ReadPixels) \
/* get_texture_image (reply) or its pack-buffer half: the reply wins. */ \
@@ -366,8 +366,10 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = {
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BlitNamedFramebuffer
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTexImage2D
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTexSubImage2D
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTextureSubImage2D
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyImageSubData
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GenerateMipmap
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GenerateTextureMipmap
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ReadPixels
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetTexImage
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetTextureImage
@@ -52,8 +52,10 @@ enum class MGPipeVerb : Uint8 {
BlitNamedFramebuffer,
CopyTexImage2D,
CopyTexSubImage2D,
CopyTextureSubImage2D,
CopyImageSubData,
GenerateMipmap,
GenerateTextureMipmap,
ReadPixels,
GetTexImage,
GetTextureImage,
@@ -92,7 +94,7 @@ enum class MGPipeVerb : Uint8 {
};
inline constexpr SizeT kMGPipeVerbCount = static_cast<SizeT>(MGPipeVerb::kVerbCount);
static_assert(kMGPipeVerbCount == 69, "the GLFunctionsTable verb set moved");
static_assert(kMGPipeVerbCount == 71, "the GLFunctionsTable verb set moved");
inline constexpr const char* kMGPipeVerbNames[kMGPipeVerbCount] = {
"DrawArrays",
@@ -128,8 +130,10 @@ inline constexpr const char* kMGPipeVerbNames[kMGPipeVerbCount] = {
"BlitNamedFramebuffer",
"CopyTexImage2D",
"CopyTexSubImage2D",
"CopyTextureSubImage2D",
"CopyImageSubData",
"GenerateMipmap",
"GenerateTextureMipmap",
"ReadPixels",
"GetTexImage",
"GetTextureImage",
@@ -228,8 +232,10 @@ inline constexpr MGPipeVerbClass kMGPipeVerbClass[kMGPipeVerbCount] = {
MGPipeVerbClass::kBlitOrCopy, // BlitNamedFramebuffer
MGPipeVerbClass::kBlitOrCopy, // CopyTexImage2D
MGPipeVerbClass::kBlitOrCopy, // CopyTexSubImage2D
MGPipeVerbClass::kBlitOrCopy, // CopyTextureSubImage2D
MGPipeVerbClass::kBlitOrCopy, // CopyImageSubData
MGPipeVerbClass::kTextureOp, // GenerateMipmap
MGPipeVerbClass::kTextureOp, // GenerateTextureMipmap
MGPipeVerbClass::kReadback, // ReadPixels
MGPipeVerbClass::kReadback, // GetTexImage
MGPipeVerbClass::kReadback, // GetTextureImage
+2 -2
View File
@@ -43,8 +43,8 @@ namespace MobileGL::MG_Remote::Client {
const MG_Backend::FormatCapabilityCache& Formats() const;
const String& ApiVersion() const;
// The SERVER's backend type, never a new "Remote" enumerator: frontend branches
// switch on this (GL_Framebuffer.cpp:47, GL_Texture.cpp:6536, CompileEnv.cpp:122) and
// a value they do not know silently takes the wrong arm.
// switch on this (ActiveBackendRejectsDistinctDepthStencil, CaptureCompileEnv) and a
// value they do not know silently takes the wrong arm.
BackendType Backend() const;
Uint64 CallMask() const;
+2 -3
View File
@@ -632,9 +632,8 @@ namespace MobileGL::MG_Remote::Client {
snapshot->apiVersion() == nullptr ? String{} : String{snapshot->apiVersion()->c_str()};
// THE SERVER'S BACKEND TYPE, NEVER A NEW "Remote" ENUMERATOR and never guessed from
// the renderer string: GL_Framebuffer.cpp:47, GL_Texture.cpp:6536 and
// CompileEnv.cpp:122 SWITCH on it, and a value they do not know takes a wrong arm
// rather than failing.
// the renderer string: ActiveBackendRejectsDistinctDepthStencil and CaptureCompileEnv
// switch on it, and a value they do not know takes a wrong arm rather than failing.
const Uint32 rawBackend = snapshot->backendType();
if (rawBackend >= static_cast<Uint32>(BackendType::BackendTypeCount)) {
MGLOG_E("MG_Remote client: CapsSnapshot names backend type %u, which this build "
+6 -6
View File
@@ -67,7 +67,7 @@ namespace MobileGL::MG_Remote::Client {
using MG_Record::LogE2ControlLine;
// The slot arithmetic, asserted rather than commented. GlobalBackendFunctionsTable is
// GLFunctionsTable plus Present plus SetSwapInterval; GLFunctionsTable is 69 function
// GLFunctionsTable plus Present plus SetSwapInterval; GLFunctionsTable is 71 function
// pointers plus one Bool (PrefersCpuXfbPrimitiveAccounting). A slot
// added to either without a decision here is a build break, which is the point: R-4 forbids
// a null slot, so a new slot needs an owner on the day it appears.
@@ -76,7 +76,7 @@ namespace MobileGL::MG_Remote::Client {
"GlobalBackendFunctionsTable is no longer GLFunctionsTable + Present + SetSwapInterval");
static_assert(sizeof(MG_Backend::GlobalBackendFunctionsTable) ==
kRemoteEmitSlotCount * sizeof(void (*)()) + sizeof(void (*)()),
"the emit table's 71 slots plus the packed Bool no longer describe the table");
"the emit table's 73 slots plus the packed Bool no longer describe the table");
namespace {
@@ -558,7 +558,7 @@ namespace MobileGL::MG_Remote::Client {
constexpr Uint32 kEmittedSlotsD1 = 19;
constexpr Uint32 kEmittedSlotsI1 = 7;
constexpr Uint32 kEmittedSlotsT2 = 7;
constexpr Uint32 kEmittedSlotsF1 = 11;
constexpr Uint32 kEmittedSlotsF1 = 13;
// BlitNamedFramebuffer, both texture readbacks, and SetSwapInterval (P10 B) - the one
// class-B slot that crosses as a surface control frame rather than a SEG_CMD record.
constexpr Uint32 kEmittedSlotsTail = 4;
@@ -575,12 +575,12 @@ namespace MobileGL::MG_Remote::Client {
static_assert(kUnmigratedD1 + kEmittedSlotsD1 == 19, "d1 owns the 19 draw slots");
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO 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 == 13, "f1 owns the 13 clear/copy/mip slots");
static_assert(kUnmigratedTail + kEmittedSlotsTail + kEmittedSlotsSync + kEmittedSlotsQueries == 20,
"the original wave-3 tail owns 20 slots");
static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
static_assert(kUnmigratedSlots + kEmittedSlots == 71, "class B and C own 71 slots");
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
"the three classes no longer partition the 71 slots");
"the three classes no longer partition the 73 slots");
MG_Backend::GlobalBackendFunctionsTable BuildRemoteEmitTable() {
MG_Record::ArmVerbControlKnobs();
+2 -2
View File
@@ -69,14 +69,14 @@ namespace MobileGL::MG_Remote::Client {
using MG_Record::TightReadbackByteCount;
using MG_Record::UnmigratedVerbFatal;
// How many of the 71 slots have a real emitter. Reported at bring-up and asserted by the
// How many of the 73 slots have a real emitter. Reported at bring-up and asserted by the
// gate: a table that silently loses an emitter should not be able to look the same as one
// that never had it.
Uint32 ImplementedVerbCount();
// The total the count above is out of. Asserted against the struct in EmitTables.cpp, so
// a slot added to GLFunctionsTable without a decision here is a build break.
inline constexpr Uint32 kRemoteEmitSlotCount = 71;
inline constexpr Uint32 kRemoteEmitSlotCount = 73;
// The other two thirds of the census, so a case can assert the WHOLE partition rather than
// only the half that emits. CONTRACT-P5.md §7's three classes are 2 + 5 + 64, and
@@ -99,6 +99,9 @@ namespace MobileGL::MG_State::GLState {
// DEPTH_COMPONENT until set, and meaningless for every other format.
virtual GLenum GetDepthStencilTextureMode() const = 0;
virtual void SetDepthStencilTextureMode(GLenum mode) = 0;
// GENERATE_MIPMAP: level 0 uploads regenerate the chain. Frontend-only, so no version moves.
virtual Bool GetAutoGenerateMipmap() const = 0;
virtual void SetAutoGenerateMipmap(Bool enabled) = 0;
// ---- Texture views (ARB_texture_view / GL 4.6 core 8.18) ----
// The texture object whose immutable storage this one's texels actually live in, or
@@ -206,6 +209,8 @@ namespace MobileGL::MG_State::GLState {
// set_texture_params is addressed by resource and is independent of every binding.
PipePublishParams();
}
Bool GetAutoGenerateMipmap() const override { return m_autoGenerateMipmap; }
void SetAutoGenerateMipmap(Bool enabled) override { m_autoGenerateMipmap = enabled; }
protected:
static Uint64 AllocateLifetimeId();
@@ -290,6 +295,7 @@ namespace MobileGL::MG_State::GLState {
Uint m_viewNumLayers = 0;
Int m_samples = 0;
Bool m_fixedSampleLocations = true;
Bool m_autoGenerateMipmap = false;
};
class TextureObjectMipmap : public TextureObjectBase {
+3 -2
View File
@@ -938,10 +938,10 @@ TEST(PipeCatalogue, PipeInputFieldsStartUnfilled) {
EXPECT_FALSE(MGPipeInputFieldIsFresh(state, MGPipeInputField::GetRenderStateParameters));
}
// G5b: the verb enum is GLFunctionsTable's member list (69 entries), every class has verbs,
// G5b: the verb enum is GLFunctionsTable's member list (71 entries), every class has verbs,
// and the seven sticky fields ride in every class mask (P1 brief D7).
TEST(PipeCatalogue, VerbTableIsTheFunctionTable) {
EXPECT_EQ(kMGPipeVerbCount, 69u);
EXPECT_EQ(kMGPipeVerbCount, 71u);
EXPECT_EQ(kMGPipeVerbClassCount, 9u);
SizeT perClass[kMGPipeVerbClassCount] = {};
for (SizeT v = 0; v < kMGPipeVerbCount; ++v) {
@@ -964,6 +964,7 @@ TEST(PipeCatalogue, VerbTableIsTheFunctionTable) {
EXPECT_FALSE(MGPipeFieldMaskHas(query, MGPipeInputField::GetRenderStateParameters));
EXPECT_TRUE(MGPipeFieldMaskHas(query, MGPipeInputField::GetTransformFeedbackPausedPrimitiveCounter));
EXPECT_EQ(kMGPipeVerbClass[static_cast<SizeT>(MGPipeVerb::GenerateMipmap)], MGPipeVerbClass::kTextureOp);
EXPECT_EQ(kMGPipeVerbClass[static_cast<SizeT>(MGPipeVerb::GenerateTextureMipmap)], MGPipeVerbClass::kTextureOp);
EXPECT_STREQ(kMGPipeVerbNames[static_cast<SizeT>(MGPipeVerb::GetGpuTimestampNs)], "GetGpuTimestampNs");
}
+3 -3
View File
@@ -359,7 +359,7 @@ TEST_F(PipeInputsTest, MutatedFieldIsNamedAtRead) {
::_exit(3);
});
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=1"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 71 verbs, fatal=1"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify read of GetLineWidth (index 0, 0) differs from the live context"),
std::string::npos)
<< r.Log;
@@ -389,7 +389,7 @@ TEST_F(PipeInputsTest, VerifyCorruptKnobNamesTheFieldAtEntry) {
::_exit(3);
});
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=1"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 71 verbs, fatal=1"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify corruption armed - GetRenderStateParameters"), std::string::npos) << r.Log;
const std::string expected =
"Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" + std::to_string(serial) +
@@ -445,7 +445,7 @@ TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) {
std::exit(0);
});
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=0"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 71 verbs, fatal=0"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify summary - 2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0"),
std::string::npos)
<< r.Log;
+18
View File
@@ -45,3 +45,21 @@ target_link_libraries(
gtest_discover_tests(TextureViewTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
add_executable(
TextureByNameTest
TextureByNameTest.cpp
)
target_include_directories(TextureByNameTest PRIVATE
${MGL_ROOT}/include
${MGL_ROOT}/MobileGL
)
target_link_libraries(
TextureByNameTest PRIVATE
MobileGLTestMain
${LINK_LIBRARIES}
)
gtest_discover_tests(TextureByNameTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
@@ -0,0 +1,420 @@
// MobileGL - MobileGL/MG_Test/Texture/TextureByNameTest.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// The by-name (DSA) texture entry points edit the named texture directly: no unit binding and no
// bind generation moves, and each checks the texture's effective target the way GL 4.6 core asks of
// the by-name spelling - which is not always what the bound-target spelling accepts.
#include <gtest/gtest.h>
#include <cstring>
#include "Includes.h"
#include "Init.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#include <MG_State/GLState/Core.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
using namespace MobileGL;
class TextureByNameTest : public ::testing::Test {
protected:
static void DrainPendingGlErrors() {
for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {}
}
static void ExpectSingleGlError(GLenum expected) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), expected);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "the call recorded more than one error";
}
void SetUp() override {
MobileGL::Initialize();
DrainPendingGlErrors();
}
void TearDown() override {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind";
}
};
namespace {
// This binary has no GL context behind its backend table, so the two calls that reach the backend
// are stubbed for the duration of a case; what is under test is what the frontend does first.
class ScopedBackendStubs {
public:
ScopedBackendStubs() : m_snapshot(MG_Backend::gBackendFunctionsTable) {
auto& gl = MG_Backend::gBackendFunctionsTable.GL;
gl.GenerateTextureMipmap = [](const SharedPtr<MG_State::GLState::ITextureObject>&, GLenum) {};
gl.CopyTextureSubImage2D = [](const SharedPtr<MG_State::GLState::ITextureObject>&, GLenum, GLint, GLint,
GLint, GLint, GLint, GLsizei, GLsizei) {};
}
~ScopedBackendStubs() { MG_Backend::gBackendFunctionsTable = m_snapshot; }
ScopedBackendStubs(const ScopedBackendStubs&) = delete;
ScopedBackendStubs& operator=(const ScopedBackendStubs&) = delete;
private:
MG_Backend::GlobalBackendFunctionsTable m_snapshot;
};
// What a by-name call must leave as it found it: every slot of the active unit and the bind
// generation backends key their per-unit memos on.
struct UnitState {
Uint64 generation = 0;
Vector<MG_State::GLState::ITextureObject*> slots;
static UnitState Capture() {
UnitState state;
state.generation = MG_State::pGLContext->GetTextureBindGeneration();
auto& unit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
for (TextureTarget target :
{TextureTarget::Texture1D, TextureTarget::Texture2D, TextureTarget::Texture3D,
TextureTarget::Texture1DArray, TextureTarget::Texture2DArray, TextureTarget::TextureCubeMap,
TextureTarget::Texture2DMultisample, TextureTarget::Texture2DMultisampleArray}) {
state.slots.push_back(unit.GetBindingSlot(target).GetBoundObject().get());
}
return state;
}
Bool operator==(const UnitState& other) const { return generation == other.generation && slots == other.slots; }
};
GLuint CreateTexture(GLenum target) {
GLuint texture = 0;
MG_Impl::GLImpl::CreateTextures(target, 1, &texture);
return texture;
}
// The CPU texel shadow of one image of a named texture.
const Uint8* ImageBytes(GLuint texture, TextureUploadTarget image, Uint level = 0) {
auto* mipmap = MG_State::GLState::AsMipmapTexture(MG_State::pGLContext->GetTextureObject(texture).get());
return mipmap ? static_cast<const Uint8*>(mipmap->MapMipmapData(image, level)) : nullptr;
}
TextureUploadTarget CubeFace(Int face) {
return static_cast<TextureUploadTarget>(static_cast<SizeT>(TextureUploadTarget::CubeMapPositiveX) +
static_cast<SizeT>(face));
}
constexpr GLsizei kRgtc1Block = 8; // one 4x4 block of GL_COMPRESSED_RED_RGTC1
} // namespace
TEST_F(TextureByNameTest, EveryByNameCallLeavesTheUnitAndTheBindGenerationAlone) {
const ScopedBackendStubs stubs;
// Decoys on every target of the active unit, so a call that bound its texture would show.
for (GLenum target : {GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP}) {
GLuint decoy = 0;
MG_Impl::GLImpl::GenTextures(1, &decoy);
MG_Impl::GLImpl::BindTexture(target, decoy);
}
const GLuint tex1D = CreateTexture(GL_TEXTURE_1D);
MG_Impl::GLImpl::TextureStorage1D(tex1D, 1, GL_RGBA8, 4);
const GLuint tex2D = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureStorage2D(tex2D, 3, GL_RGBA8, 4, 4);
const GLuint texArray = CreateTexture(GL_TEXTURE_2D_ARRAY);
MG_Impl::GLImpl::TextureStorage3D(texArray, 1, GL_RGBA8, 4, 4, 2);
const GLuint texMs = CreateTexture(GL_TEXTURE_2D_MULTISAMPLE);
const GLuint texMsArray = CreateTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY);
GLuint readFramebuffer = 0;
MG_Impl::GLImpl::CreateFramebuffers(1, &readFramebuffer);
const GLuint readTexture = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureStorage2D(readTexture, 1, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::NamedFramebufferTexture(readFramebuffer, GL_COLOR_ATTACHMENT0, readTexture, 0);
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint8 pixels[4 * 4 * 4 * 2] = {};
const GLfloat border[4] = {0, 0, 0, 1};
const GLint swizzle[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
const GLuint swizzleU[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
GLint ints[4] = {};
GLuint uints[4] = {};
GLfloat floats[4] = {};
const auto expectUntouched = [](const char* name, const auto& call) {
const UnitState before = UnitState::Capture();
call();
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << name;
EXPECT_TRUE(UnitState::Capture() == before) << name << " moved a binding or the bind generation";
};
expectUntouched("TextureParameterfv",
[&] { MG_Impl::GLImpl::TextureParameterfv(tex2D, GL_TEXTURE_BORDER_COLOR, border); });
expectUntouched("TextureParameteriv",
[&] { MG_Impl::GLImpl::TextureParameteriv(tex2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle); });
expectUntouched("TextureParameterIiv",
[&] { MG_Impl::GLImpl::TextureParameterIiv(tex2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle); });
expectUntouched("TextureParameterIuiv",
[&] { MG_Impl::GLImpl::TextureParameterIuiv(tex2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleU); });
expectUntouched("GetTextureParameteriv",
[&] { MG_Impl::GLImpl::GetTextureParameteriv(tex2D, GL_TEXTURE_MAX_LEVEL, ints); });
expectUntouched("GetTextureParameterfv",
[&] { MG_Impl::GLImpl::GetTextureParameterfv(tex2D, GL_TEXTURE_MIN_LOD, floats); });
expectUntouched("GetTextureParameterIiv",
[&] { MG_Impl::GLImpl::GetTextureParameterIiv(tex2D, GL_TEXTURE_BORDER_COLOR, ints); });
expectUntouched("GetTextureParameterIuiv",
[&] { MG_Impl::GLImpl::GetTextureParameterIuiv(tex2D, GL_TEXTURE_BORDER_COLOR, uints); });
expectUntouched("GetTextureLevelParameteriv",
[&] { MG_Impl::GLImpl::GetTextureLevelParameteriv(tex2D, 1, GL_TEXTURE_WIDTH, ints); });
expectUntouched("GetTextureLevelParameterfv",
[&] { MG_Impl::GLImpl::GetTextureLevelParameterfv(tex2D, 1, GL_TEXTURE_HEIGHT, floats); });
expectUntouched("TextureSubImage1D",
[&] { MG_Impl::GLImpl::TextureSubImage1D(tex1D, 0, 0, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels); });
expectUntouched("TextureSubImage2D", [&] {
MG_Impl::GLImpl::TextureSubImage2D(tex2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
});
expectUntouched("TextureSubImage3D", [&] {
MG_Impl::GLImpl::TextureSubImage3D(texArray, 0, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
});
expectUntouched("TextureStorage2DMultisample",
[&] { MG_Impl::GLImpl::TextureStorage2DMultisample(texMs, 1, GL_RGBA8, 4, 4, GL_TRUE); });
expectUntouched("TextureStorage3DMultisample",
[&] { MG_Impl::GLImpl::TextureStorage3DMultisample(texMsArray, 1, GL_RGBA8, 4, 4, 2, GL_TRUE); });
expectUntouched("GenerateTextureMipmap", [&] { MG_Impl::GLImpl::GenerateTextureMipmap(tex2D); });
expectUntouched("CopyTextureSubImage2D",
[&] { MG_Impl::GLImpl::CopyTextureSubImage2D(tex2D, 0, 0, 0, 0, 0, 2, 2); });
MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
DrainPendingGlErrors();
}
// The compressed by-name uploads, with textures whose images are stored compressed.
TEST_F(TextureByNameTest, CompressedByNameUploadsLeaveTheUnitAndTheBindGenerationAlone) {
const GLuint tex2D = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, tex2D);
const Uint8 blocks[kRgtc1Block * 2] = {};
MG_Impl::GLImpl::CompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RED_RGTC1, 4, 4, 0, kRgtc1Block, blocks);
const GLuint texArray = CreateTexture(GL_TEXTURE_2D_ARRAY);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, texArray);
MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RED_RGTC1, 4, 4, 2, 0, kRgtc1Block * 2,
blocks);
GLuint decoys[2] = {};
MG_Impl::GLImpl::GenTextures(2, decoys);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, decoys[0]);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, decoys[1]);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
UnitState before = UnitState::Capture();
MG_Impl::GLImpl::CompressedTextureSubImage2D(tex2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RED_RGTC1, kRgtc1Block, blocks);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_TRUE(UnitState::Capture() == before);
before = UnitState::Capture();
MG_Impl::GLImpl::CompressedTextureSubImage3D(texArray, 0, 0, 0, 0, 4, 4, 2, GL_COMPRESSED_RED_RGTC1,
kRgtc1Block * 2, blocks);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_TRUE(UnitState::Capture() == before);
}
// GL 4.6 core 8.6: the by-name 3D upload takes a cube map, zoffset naming the first face and depth
// the number of faces. The bound-target path it used to borrow has no target for that.
TEST_F(TextureByNameTest, TextureSubImage3DWritesTheCubeFacesZOffsetSelects) {
const GLuint cube = CreateTexture(GL_TEXTURE_CUBE_MAP);
MG_Impl::GLImpl::TextureStorage2D(cube, 1, GL_RGBA8, 2, 2);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 faces[2][2 * 2 * 4];
std::memset(faces[0], 0x40, sizeof(faces[0]));
std::memset(faces[1], 0x80, sizeof(faces[1]));
MG_Impl::GLImpl::TextureSubImage3D(cube, 0, 0, 0, 2, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, faces);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
for (Int face = 0; face < 6; ++face) {
const Uint8* bytes = ImageBytes(cube, CubeFace(face));
ASSERT_NE(bytes, nullptr) << "face " << face;
const Uint8 expected = face == 2 ? 0x40 : (face == 3 ? 0x80 : 0);
for (SizeT i = 0; i < sizeof(faces[0]); ++i) {
ASSERT_EQ(bytes[i], expected) << "face " << face << " byte " << i;
}
}
// Faces past the sixth do not exist.
MG_Impl::GLImpl::TextureSubImage3D(cube, 0, 0, 0, 5, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, faces);
ExpectSingleGlError(GL_INVALID_VALUE);
}
TEST_F(TextureByNameTest, TextureSubImage3DIntoACubeMapThatIsNotCubeCompleteIsInvalidOperation) {
const GLuint cube = CreateTexture(GL_TEXTURE_CUBE_MAP);
const Uint8 pixels[2 * 2 * 4] = {};
MG_Impl::GLImpl::TextureSubImage3D(cube, 0, 0, 0, 0, 2, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_OPERATION);
}
TEST_F(TextureByNameTest, CompressedTextureSubImage3DWritesTheCubeFacesZOffsetSelects) {
const GLuint cube = CreateTexture(GL_TEXTURE_CUBE_MAP);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP, cube);
const Uint8 zero[kRgtc1Block] = {};
for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++face) {
MG_Impl::GLImpl::CompressedTexImage2D(face, 0, GL_COMPRESSED_RED_RGTC1, 4, 4, 0, kRgtc1Block, zero);
}
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
Uint8 blocks[2][kRgtc1Block];
std::memset(blocks[0], 0x11, sizeof(blocks[0]));
std::memset(blocks[1], 0x22, sizeof(blocks[1]));
MG_Impl::GLImpl::CompressedTextureSubImage3D(cube, 0, 0, 0, 1, 4, 4, 2, GL_COMPRESSED_RED_RGTC1, kRgtc1Block * 2,
blocks);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
for (Int face = 0; face < 6; ++face) {
Uint8 stored[kRgtc1Block] = {};
MG_Impl::GLImpl::GetCompressedTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, stored);
const Uint8 expected = face == 1 ? 0x11 : (face == 2 ? 0x22 : 0);
for (SizeT i = 0; i < sizeof(stored); ++i) {
ASSERT_EQ(stored[i], expected) << "face " << face << " byte " << i;
}
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL 4.6 core table 8.15. A target the command does not take is INVALID_ENUM through a target
// token and INVALID_OPERATION through a named texture's effective target.
TEST_F(TextureByNameTest, SubImageCommandsCheckTheirTargetTable) {
const Uint8 pixels[4 * 4 * 4] = {};
const GLuint cube = CreateTexture(GL_TEXTURE_CUBE_MAP);
MG_Impl::GLImpl::TextureStorage2D(cube, 1, GL_RGBA8, 4, 4);
const GLuint tex2D = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureStorage2D(tex2D, 1, GL_RGBA8, 4, 4);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::TextureSubImage2D(cube, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::TextureSubImage3D(tex2D, 0, 0, 0, 0, 4, 4, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::TextureSubImage1D(tex2D, 0, 0, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, tex2D);
MG_Impl::GLImpl::TexSubImage3D(GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP, cube);
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_CUBE_MAP, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_ENUM);
MG_Impl::GLImpl::TexSubImage2D(GL_PROXY_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_ENUM);
// A face takes any rectangle inside it: the square-face rule is image specification's.
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// The box is bounded by the level it writes, not by level 0: at level 1 of a 4x4 texture a 2x2
// box at (1, 1) runs off the image, which is INVALID_VALUE rather than a silently dropped upload.
TEST_F(TextureByNameTest, SubImageOutsideTheLevelItWritesIsInvalidValue) {
const Uint8 pixels[2 * 2 * 4] = {};
const GLuint texture = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureStorage2D(texture, 2, GL_RGBA8, 4, 4);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::TextureSubImage2D(texture, 1, 1, 1, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_VALUE);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 1, 1, 1, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ExpectSingleGlError(GL_INVALID_VALUE);
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL 4.6 core 8.11.3: by name a cube map answers level queries for face zero; through a target
// token GL_TEXTURE_CUBE_MAP is not a level-query target at all.
TEST_F(TextureByNameTest, LevelQueriesOfACubeMapByNameAnswerForFaceZero) {
const GLuint cube = CreateTexture(GL_TEXTURE_CUBE_MAP);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP, cube);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint width = 0;
MG_Impl::GLImpl::GetTextureLevelParameteriv(cube, 0, GL_TEXTURE_WIDTH, &width);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(width, 8);
GLfloat height = 0;
MG_Impl::GLImpl::GetTextureLevelParameterfv(cube, 0, GL_TEXTURE_HEIGHT, &height);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(height, 8.0f);
MG_Impl::GLImpl::GetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP, 0, GL_TEXTURE_WIDTH, &width);
ExpectSingleGlError(GL_INVALID_ENUM);
}
// GL 4.6 core 8.11.2: TEXTURE_TARGET answers the effective target by name and the target itself
// through a token.
TEST_F(TextureByNameTest, ParameterQueriesAnswerTheTextureTarget) {
const GLuint array = CreateTexture(GL_TEXTURE_2D_ARRAY);
GLint target = 0;
MG_Impl::GLImpl::GetTextureParameteriv(array, GL_TEXTURE_TARGET, &target);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(target, GL_TEXTURE_2D_ARRAY);
GLuint bound = 0;
MG_Impl::GLImpl::GenTextures(1, &bound);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, bound);
GLfloat floatTarget = 0;
MG_Impl::GLImpl::GetTexParameterfv(GL_TEXTURE_3D, GL_TEXTURE_TARGET, &floatTarget);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(floatTarget, static_cast<GLfloat>(GL_TEXTURE_3D));
}
// The float setters of both spellings take every scalar pname the integer ones take: the bound
// form once refused DEPTH_STENCIL_TEXTURE_MODE and the by-name form the single-channel swizzles.
TEST_F(TextureByNameTest, FloatSettersTakeEveryScalarPnameOfBothForms) {
const GLuint texture = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureParameterf(texture, GL_TEXTURE_SWIZZLE_G, static_cast<GLfloat>(GL_RED));
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint swizzle = 0;
MG_Impl::GLImpl::GetTextureParameteriv(texture, GL_TEXTURE_SWIZZLE_G, &swizzle);
EXPECT_EQ(swizzle, GL_RED);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
MG_Impl::GLImpl::TexParameterf(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE,
static_cast<GLfloat>(GL_STENCIL_INDEX));
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint mode = 0;
MG_Impl::GLImpl::GetTexParameteriv(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, &mode);
EXPECT_EQ(mode, GL_STENCIL_INDEX);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL 4.6 core 8.14.4: mipmaps are generated for seven targets; a rectangle texture is not one.
TEST_F(TextureByNameTest, MipmapGenerationChecksTheTargetOfBothForms) {
const ScopedBackendStubs stubs;
const GLuint rectangle = CreateTexture(GL_TEXTURE_RECTANGLE);
MG_Impl::GLImpl::GenerateTextureMipmap(rectangle);
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_RECTANGLE, rectangle);
MG_Impl::GLImpl::GenerateMipmap(GL_TEXTURE_RECTANGLE);
ExpectSingleGlError(GL_INVALID_ENUM);
}
// GENERATE_MIPMAP belongs to the texture it was set on. GL names are recycled, so the next texture
// created under a deleted texture's name must start without the flag.
TEST_F(TextureByNameTest, AutoMipmapFlagDoesNotOutliveItsTexture) {
const ScopedBackendStubs stubs;
static Int generated;
generated = 0;
MG_Backend::gBackendFunctionsTable.GL.GenerateTextureMipmap =
[](const SharedPtr<MG_State::GLState::ITextureObject>&, GLenum) { ++generated; };
const Uint8 pixels[4 * 4 * 4] = {};
const GLuint deleted = CreateTexture(GL_TEXTURE_2D);
MG_Impl::GLImpl::TextureParameteri(deleted, GL_GENERATE_MIPMAP, GL_TRUE);
MG_Impl::GLImpl::TextureStorage2D(deleted, 3, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::TextureSubImage2D(deleted, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
ASSERT_EQ(generated, 1) << "the flag did not generate mipmaps on its own texture";
MG_Impl::GLImpl::DeleteTextures(1, &deleted);
const GLuint recycled = CreateTexture(GL_TEXTURE_2D);
ASSERT_EQ(recycled, deleted) << "the name was not recycled, so nothing here is tested";
MG_Impl::GLImpl::TextureStorage2D(recycled, 3, GL_RGBA8, 4, 4);
MG_Impl::GLImpl::TextureSubImage2D(recycled, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(generated, 1) << "the new texture inherited the deleted texture's GENERATE_MIPMAP";
}
+20 -48
View File
@@ -181,23 +181,11 @@ namespace {
GLuint BoundTexture = 0;
} g_copyTexSubImage2DCall;
void RecordCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height) {
g_copyTexSubImage2DCall = {
true,
target,
level,
xoffset,
yoffset,
x,
y,
width,
height,
MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit())
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject()
->GetExternalIndex(),
};
void RecordCopyTextureSubImage2D(const SharedPtr<MG_State::GLState::ITextureObject>& texture, GLenum target,
GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height) {
g_copyTexSubImage2DCall = {true, target, level, xoffset, yoffset,
x, y, width, height, texture->GetExternalIndex()};
}
} // namespace
@@ -285,7 +273,7 @@ TEST_F(TextureTest, ClearTexSubImageClearsOnlyRequestedRectangle) {
TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) {
const ScopedTextureBackendFunctionsOverride backendGuard;
MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D = RecordCopyTexSubImage2D;
MG_Backend::gBackendFunctionsTable.GL.CopyTextureSubImage2D = RecordCopyTextureSubImage2D;
g_copyTexSubImage2DCall = {};
GLuint namedTexture = 0;
@@ -319,6 +307,7 @@ TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) {
EXPECT_EQ(g_copyTexSubImage2DCall.Y, 6);
EXPECT_EQ(g_copyTexSubImage2DCall.Width, 7);
EXPECT_EQ(g_copyTexSubImage2DCall.Height, 8);
// The backend is handed the named texture itself; nothing was bound to reach it.
EXPECT_EQ(g_copyTexSubImage2DCall.BoundTexture, namedTexture);
EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(),
boundBefore);
@@ -327,7 +316,7 @@ TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) {
TEST_F(TextureTest, CopyTextureSubImage2DRejectsCubeMapTargets) {
const ScopedTextureBackendFunctionsOverride backendGuard;
MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D = RecordCopyTexSubImage2D;
MG_Backend::gBackendFunctionsTable.GL.CopyTextureSubImage2D = RecordCopyTextureSubImage2D;
g_copyTexSubImage2DCall = {};
GLuint cubeTexture = 0;
@@ -1125,8 +1114,7 @@ TEST_F(TextureTest, EverySamplerScalarPnameConvertsToTheQueriedType) {
}
// glTextureParameter* has no target token, so GL 4.6 core 8.10 applies the ten-target list to the
// texture's EFFECTIVE target. The four vector DSA forms reached that gate for free by re-entering
// through the bound-target path; the two scalar forms called the per-object setter directly and
// texture's EFFECTIVE target, and a texture outside it is INVALID_OPERATION. The scalar forms once
// reached no gate at all, so one DSA family disagreed with itself about the same texture.
TEST_F(TextureTest, ScalarDsaTextureParameterAppliesTheSameTargetRuleAsItsVectorTwins) {
GLuint bufferTexture = 0;
@@ -1134,16 +1122,13 @@ TEST_F(TextureTest, ScalarDsaTextureParameterAppliesTheSameTargetRuleAsItsVector
ASSERT_NE(bufferTexture, 0u);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// The vector form has rejected this since the target gate landed...
const GLint baseLevel = 1;
MG_Impl::GLImpl::TextureParameteriv(bufferTexture, GL_TEXTURE_BASE_LEVEL, &baseLevel);
ExpectSingleGlError(GL_INVALID_ENUM);
// ...and the scalar forms must agree rather than silently applying the parameter.
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::TextureParameteri(bufferTexture, GL_TEXTURE_BASE_LEVEL, 1);
ExpectSingleGlError(GL_INVALID_ENUM);
ExpectSingleGlError(GL_INVALID_OPERATION);
MG_Impl::GLImpl::TextureParameterf(bufferTexture, GL_TEXTURE_MIN_LOD, 1.0f);
ExpectSingleGlError(GL_INVALID_ENUM);
ExpectSingleGlError(GL_INVALID_OPERATION);
// A texture whose target IS legal still goes through, so the gate is about the target and not
// about the by-name spelling.
@@ -4875,19 +4860,10 @@ TEST_F(TextureTest, BindSamplerRejectsUnitsBeyondMaxCombinedTextureImageUnits) {
DrainPendingGlErrors();
}
// The DSA by-name entry points are emulated by temporarily binding the named texture onto the
// active unit's slot for its target, running the classic bound-texture code, then putting the
// previous binding back. For as long as the emulated call runs, that swap is a REAL change to
// which texture is bound at that unit, so both transitions have to move the texture bind
// generation.
//
// They used to move nothing. Backends memoise per-unit work keyed on the bind generation and
// BORROW the binding slot (they hold a pointer to the slot's shared_ptr, not a copy), so a memo
// built while texture A sat in the slot stayed "valid" while B was temporarily in it - and the
// backend then drove A's backend twin from B's frontend state, re-specifying A's backend storage
// with B's shape. Any content A only ever had on the GPU was gone. That is what blanked
// Minecraft's lightmap when Iris uploaded to a BSL shadow map: the text shader multiplies by the
// lightmap, so `if (color.a < 0.1) discard` then threw away every glyph in the process.
// The by-name entry points edit the named texture directly. They used to be emulated by binding it
// onto the active unit and running the bound-texture code, and a swap the bind generation did not
// see let a backend memo drive one texture's twin from another's state - which blanked Minecraft's
// lightmap under Iris. Neither the binding nor the bind generation may move now.
TEST_F(TextureTest, NamedTextureCallKeepsUnitBindingAccountingCoherent) {
GLuint names[2] = {};
MG_Impl::GLImpl::GenTextures(2, names);
@@ -4905,19 +4881,13 @@ TEST_F(TextureTest, NamedTextureCallKeepsUnitBindingAccountingCoherent) {
ASSERT_NE(boundObject, nullptr);
ASSERT_EQ(boundObject->GetExternalIndex(), boundName);
// TextureParameteriv is one of the by-name calls that is emulated by binding: it reaches
// WithTemporarilyBoundNamedTexture, unlike the scalar TextureParameteri, which edits the
// object directly and never touches a unit.
const Uint64 base = MG_State::pGLContext->GetTextureBindGeneration();
const GLint maxLevel = 0;
MG_Impl::GLImpl::TextureParameteriv(namedName, GL_TEXTURE_MAX_LEVEL, &maxLevel);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
// The emulation put `namedName` on the unit and took it off again. A generation-keyed memo
// must be able to see that the slot it borrows was not stable across the call.
EXPECT_GT(MG_State::pGLContext->GetTextureBindGeneration(), base)
<< "a by-name texture call swapped a live unit binding without moving the bind generation";
// ...and the application-visible binding is exactly what it was before the call.
EXPECT_EQ(MG_State::pGLContext->GetTextureBindGeneration(), base)
<< "a by-name texture call moved the bind generation";
EXPECT_EQ(slot.GetBoundObject(), boundObject);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
@@ -6839,6 +6809,8 @@ namespace {
struct ScopedNoOpGenerateMipmap {
ScopedNoOpGenerateMipmap() : m_snapshot(MobileGL::MG_Backend::gBackendFunctionsTable) {
MobileGL::MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap = [](GLenum) {};
MobileGL::MG_Backend::gBackendFunctionsTable.GL.GenerateTextureMipmap =
[](const SharedPtr<MG_State::GLState::ITextureObject>&, GLenum) {};
}
~ScopedNoOpGenerateMipmap() { MobileGL::MG_Backend::gBackendFunctionsTable = m_snapshot; }
ScopedNoOpGenerateMipmap(const ScopedNoOpGenerateMipmap&) = delete;
+1 -1
View File
@@ -209,7 +209,7 @@ gtest_discover_tests(PipeWireCodecTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS un
# main() of its own. All three also reach MG_Pipe, MG_Backend and MG_State, so all three carry
# those include paths.
#
# RemoteClientTest the 71-slot emit table, the caps mirror, R-8's liveness gates, R-17's
# RemoteClientTest the 73-slot emit table, the caps mirror, R-8's liveness gates, R-17's
# routing and ID-47/ID-49's readback rules (c1)
# ServerLoopTest the apply thread, the blocking control mailbox, the verb stamp and
# R-11's server-owned staging copy (v1)
+64 -5
View File
@@ -6,7 +6,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// P5 package c1's suite: the 71-slot emit table, the caps mirror and R-8's liveness gates.
// P5 package c1's suite: the 73-slot emit table, the caps mirror and R-8's liveness gates.
// The helper cases below are supplemented by RemoteClientControls.inc: installed producers
// over a live session, with adversarial peer replies and a repeated-make-current control.
//
@@ -192,11 +192,11 @@ namespace {
// The emit table: the partition, and that no slot is null
// =====================================================================================
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
// P5 baseline five + f1 eleven + i1 seven + t2 six emitted slots, ... and since P10 B the
TEST(RemoteEmitTable, TheThreeClassesPartitionEverySlot) {
// P5 baseline five + f1 thirteen + i1 seven + t2 six emitted slots, ... and since P10 B the
// swap interval: class C is EMPTY, every slot but the two local answers reaches the server.
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
EXPECT_EQ(ImplementedVerbCount(), 69u);
EXPECT_EQ(ImplementedVerbCount(), 71u);
EXPECT_EQ(UnmigratedSlotCount(), 0u);
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(), kRemoteEmitSlotCount);
}
@@ -298,7 +298,7 @@ TEST(RemoteEmitTable, NoSlotIsNull) {
const MG_Backend::GlobalBackendFunctionsTable& table = RemoteEmitTable();
const void* const* cells = reinterpret_cast<const void* const*>(&table);
const SizeT cellCount = sizeof(table) / sizeof(void*);
// The struct is 71 function pointers plus ONE cell holding the packed Bool
// The struct is 73 function pointers plus ONE cell holding the packed Bool
// PrefersCpuXfbPrimitiveAccounting and its padding (EmitTables.cpp static_asserts exactly
// that shape). That Bool is legitimately zero when the server did not publish
// kCapCpuXfbPrimitiveAccounting, so at most one cell may read null - and this is stated as
@@ -1981,6 +1981,65 @@ TEST(RemoteF1, GenerateMipmapFieldsCross) {
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.GenerateMipmap.fields: " << DescribeStatus(child) << child.Log;
}
namespace {
// A second texture for the by-name slots, acquired but NOT bound: F1Texture's stays on the unit.
SharedPtr<MG_State::GLState::ITextureObject> F1NamedTexture() {
auto tex = MakeShared<MG_State::GLState::TextureObject2D>(92);
tex->SetInternalFormat(TextureInternalFormat::RGBA8);
for (Uint level = 0; level != 2; ++level) {
const Int size = 4 >> level;
tex->AllocateStorage(
TextureUploadTarget::Texture2D, level,
MG_State::GLState::MipmapInput{IntVec3{size, size, 1}, static_cast<SizeT>(size * size * 4)});
}
MGPipeTextureEmitterInstance().AcquireTexture(tex->GetLifetimeId(), tex.get());
return tex;
}
} // namespace
// The by-name slots name their texture themselves: the record carries the named texture's handle
// while another texture stays bound on the active unit.
TEST(RemoteF1, CopyTextureSubImage2DNamesItsTextureNotTheBoundOne) {
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer;
peer.Install();
const auto bound = F1Texture();
const auto named = F1NamedTexture();
RemoteEmitTable().GL.CopyTextureSubImage2D(named, GL_TEXTURE_2D, 1, 1, 0, -3, 4, 1, 2);
const auto& r = peer.copy;
if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*named) ||
MGPipeHandleIsNull(r.Dst) || r.Dst == MGPipeTextureEmitterInstance().FindTexture(*bound) ||
r.Target != GL_TEXTURE_2D || r.Level != 1 || r.XOffset != 1 || r.YOffset != 0 || r.X != -3 || r.Y != 4 ||
r.Width != 1 || r.Height != 2 || r.SubImage != 1)
::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.CopyTextureSubImage2D.fields: " << DescribeStatus(child) << child.Log;
}
TEST(RemoteF1, GenerateTextureMipmapNamesItsTextureNotTheBoundOne) {
const auto child = RunInChild([] {
StartControlSession();
F1Peer peer;
peer.Install();
const auto bound = F1Texture();
const auto named = F1NamedTexture();
RemoteEmitTable().GL.GenerateTextureMipmap(named, GL_TEXTURE_2D);
const GLfloat fence[4] = {0, 0, 0, 0}; // see GenerateMipmapFieldsCross
RemoteEmitTable().GL.ClearBufferfv(GL_COLOR, 0, fence);
const auto& r = peer.mip;
if (peer.calls != 2 || r.Res != MGPipeTextureEmitterInstance().FindTexture(*named) ||
MGPipeHandleIsNull(r.Res) || r.Res == MGPipeTextureEmitterInstance().FindTexture(*bound) ||
r.Target != GL_TEXTURE_2D || r.BaseLevel != 0 || r.LevelCount != 2)
::_exit(101);
ClientSessionInstance().Stop();
});
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
<< "F1.GenerateTextureMipmap.fields: " << DescribeStatus(child) << child.Log;
}
#endif
#if MGTEST_HAVE_FORK
+9 -1
View File
@@ -4903,6 +4903,7 @@
"DirectGLES.F1WireScenario.CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone",
"DirectGLES.F1WireScenario.CopyTexImage2DPixels",
"DirectGLES.F1WireScenario.CopyTexSubImage2DPixels",
"DirectGLES.F1WireScenario.CopyTextureSubImage2DPixels",
"DirectGLES.F1WireScenario.EachWireFrameHasZeroResidualPulls",
"DirectGLES.F1WireScenario.EnabledVertexBufferDrawKeepsNamedP7Fatal",
"DirectGLES.F1WireScenario.GenerateMipmapDepthPixels",
@@ -4914,6 +4915,7 @@
"DirectGLES.F1WireScenario.GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow",
"DirectGLES.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.GenerateMipmapWithoutNativeBlitPixels",
"DirectGLES.F1WireScenario.GenerateTextureMipmapPixels",
"DirectGLES.F1WireScenario.MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch",
@@ -5132,6 +5134,7 @@
"DirectVulkan.F1WireScenario.CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone",
"DirectVulkan.F1WireScenario.CopyTexImage2DPixels",
"DirectVulkan.F1WireScenario.CopyTexSubImage2DPixels",
"DirectVulkan.F1WireScenario.CopyTextureSubImage2DPixels",
"DirectVulkan.F1WireScenario.EachWireFrameHasZeroResidualPulls",
"DirectVulkan.F1WireScenario.EnabledVertexBufferDrawKeepsNamedP7Fatal",
"DirectVulkan.F1WireScenario.GenerateMipmapDepthPixels",
@@ -5143,6 +5146,7 @@
"DirectVulkan.F1WireScenario.GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow",
"DirectVulkan.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.GenerateMipmapWithoutNativeBlitPixels",
"DirectVulkan.F1WireScenario.GenerateTextureMipmapPixels",
"DirectVulkan.F1WireScenario.MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch",
@@ -9121,7 +9125,7 @@
"RemoteEmitTable.TheNineteenDrawSlotsAreEmittersDistinctFromEveryStub",
"RemoteEmitTable.TheSevenI1SlotsAreClassBAndAreNotTheFatalThunk",
"RemoteEmitTable.TheSixXfbAndPatchSlotsAreNonNullAndDistinct",
"RemoteEmitTable.TheThreeClassesPartitionAllSeventyOneSlots",
"RemoteEmitTable.TheThreeClassesPartitionEverySlot",
"RemoteF1.AnIndirectDrawStashesTheCommandAndParameterBuffers",
"RemoteF1.BlitNamedCarriesHandlesAndLeavesTheClientBindingsAlone",
"RemoteF1.BoundBlitCarriesNoHandlesAndIsAppliedAsBefore",
@@ -10578,6 +10582,7 @@
"DirectGLES.F1WireScenario.CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone",
"DirectGLES.F1WireScenario.CopyTexImage2DPixels",
"DirectGLES.F1WireScenario.CopyTexSubImage2DPixels",
"DirectGLES.F1WireScenario.CopyTextureSubImage2DPixels",
"DirectGLES.F1WireScenario.EachWireFrameHasZeroResidualPulls",
"DirectGLES.F1WireScenario.EnabledVertexBufferDrawKeepsNamedP7Fatal",
"DirectGLES.F1WireScenario.GenerateMipmapDepthPixels",
@@ -10589,6 +10594,7 @@
"DirectGLES.F1WireScenario.GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow",
"DirectGLES.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.GenerateMipmapWithoutNativeBlitPixels",
"DirectGLES.F1WireScenario.GenerateTextureMipmapPixels",
"DirectGLES.F1WireScenario.MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession",
"DirectGLES.F1WireScenario.MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch",
@@ -10807,6 +10813,7 @@
"DirectVulkan.F1WireScenario.CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone",
"DirectVulkan.F1WireScenario.CopyTexImage2DPixels",
"DirectVulkan.F1WireScenario.CopyTexSubImage2DPixels",
"DirectVulkan.F1WireScenario.CopyTextureSubImage2DPixels",
"DirectVulkan.F1WireScenario.EachWireFrameHasZeroResidualPulls",
"DirectVulkan.F1WireScenario.EnabledVertexBufferDrawKeepsNamedP7Fatal",
"DirectVulkan.F1WireScenario.GenerateMipmapDepthPixels",
@@ -10818,6 +10825,7 @@
"DirectVulkan.F1WireScenario.GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow",
"DirectVulkan.F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.GenerateMipmapWithoutNativeBlitPixels",
"DirectVulkan.F1WireScenario.GenerateTextureMipmapPixels",
"DirectVulkan.F1WireScenario.MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession",
"DirectVulkan.F1WireScenario.MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"file_excess_lines": 22926,
"file_excess_lines": 21854,
"function_excess_lines": 15516,
"layering_violations": 63
}
+4 -4
View File
@@ -1,7 +1,7 @@
{
"allcaps_comment_lines": 2256,
"line_citations": 165,
"long_comment_excess_lines": 8911,
"allcaps_comment_lines": 2253,
"line_citations": 159,
"long_comment_excess_lines": 8895,
"non_english_comment_lines": 20,
"planning_code_comment_lines": 3190
"planning_code_comment_lines": 3188
}