mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Compare commits
4
Commits
7dbe0e5c78
...
ad7340921e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad7340921e | ||
|
|
095cf2643b | ||
|
|
230a90a439 | ||
|
|
28bf0c1653 |
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
{
|
||||
"file_excess_lines": 22926,
|
||||
"file_excess_lines": 21854,
|
||||
"function_excess_lines": 15516,
|
||||
"layering_violations": 63
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user