mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Backend, Test] isolate native mip completeness during GLES texture readback
This commit is contained in:
@@ -1,6 +1,60 @@
|
||||
// Included in DirectGLES.cpp after the native packed-word readback helper.
|
||||
// The server needs owned tight rows for a storage promotion, not client PACK
|
||||
// layout or a fallback into a frontend texture's potentially stale shadow.
|
||||
class ScopedWireTextureReadLevel {
|
||||
public:
|
||||
ScopedWireTextureReadLevel(GLuint texture, GLenum target, GLint level)
|
||||
: m_texture(texture), m_target(target) {
|
||||
switch (target) {
|
||||
case GL_TEXTURE_2D: m_binding = GL_TEXTURE_BINDING_2D; break;
|
||||
case GL_TEXTURE_3D: m_binding = GL_TEXTURE_BINDING_3D; break;
|
||||
case GL_TEXTURE_2D_ARRAY: m_binding = GL_TEXTURE_BINDING_2D_ARRAY; break;
|
||||
case GL_TEXTURE_CUBE_MAP: m_binding = GL_TEXTURE_BINDING_CUBE_MAP; break;
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY: m_binding = GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; break;
|
||||
default: return;
|
||||
}
|
||||
GLint previous = 0;
|
||||
DrainESErrors();
|
||||
g_GLESFuncs.glGetIntegerv(m_binding, &previous);
|
||||
g_GLESFuncs.glBindTexture(m_target, m_texture);
|
||||
g_GLESFuncs.glGetTexParameteriv(m_target, GL_TEXTURE_BASE_LEVEL, &m_baseLevel);
|
||||
g_GLESFuncs.glGetTexParameteriv(m_target, GL_TEXTURE_MAX_LEVEL, &m_maxLevel);
|
||||
m_saved = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
if (m_saved) {
|
||||
// ES framebuffer completeness can reject a defined level whose
|
||||
// base level or intervening mip chain is missing/inconsistent.
|
||||
// Read this existing native image as the sole base level. The
|
||||
// logical GL parameters and every cached copy remain unchanged.
|
||||
g_GLESFuncs.glTexParameteri(m_target, GL_TEXTURE_BASE_LEVEL, level);
|
||||
g_GLESFuncs.glTexParameteri(m_target, GL_TEXTURE_MAX_LEVEL, level);
|
||||
m_valid = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
}
|
||||
g_GLESFuncs.glBindTexture(m_target, static_cast<GLuint>(previous));
|
||||
}
|
||||
|
||||
~ScopedWireTextureReadLevel() {
|
||||
if (!m_saved) return;
|
||||
// The packed-word path can use its own texture scratch binding.
|
||||
// Save the binding at restoration time too; do not assume an active
|
||||
// texture unit or make the backend's binding cache forget a bind.
|
||||
GLint previous = 0;
|
||||
g_GLESFuncs.glGetIntegerv(m_binding, &previous);
|
||||
g_GLESFuncs.glBindTexture(m_target, m_texture);
|
||||
g_GLESFuncs.glTexParameteri(m_target, GL_TEXTURE_BASE_LEVEL, m_baseLevel);
|
||||
g_GLESFuncs.glTexParameteri(m_target, GL_TEXTURE_MAX_LEVEL, m_maxLevel);
|
||||
g_GLESFuncs.glBindTexture(m_target, static_cast<GLuint>(previous));
|
||||
}
|
||||
|
||||
Bool Valid() const { return m_valid; }
|
||||
|
||||
private:
|
||||
GLuint m_texture;
|
||||
GLenum m_target;
|
||||
GLenum m_binding = GL_NONE;
|
||||
GLint m_baseLevel = 0, m_maxLevel = 0;
|
||||
Bool m_saved = false, m_valid = false;
|
||||
};
|
||||
|
||||
static Bool ReadWireColorRowsTight(GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
Bool forceOpaqueAlpha, const NormalizedImageCarrierRead& carrier,
|
||||
Vector<Uint8>& bytes) {
|
||||
@@ -164,13 +218,15 @@
|
||||
Bool sourceUsesImageCarrier, GLenum format, GLenum type, Vector<Uint8>& bytes) {
|
||||
if (!texture || level < 0 || TextureImpl::IsMultisampleTextureTarget(target)) return false;
|
||||
const IntVec3 extent = TextureImpl::GetBackendUploadSize(target, logicalExtent);
|
||||
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
|
||||
ScopedWireTextureReadLevel readLevel(texture, nativeTarget, level);
|
||||
if (!readLevel.Valid()) return false;
|
||||
if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL || format == GL_STENCIL_INDEX)
|
||||
return ReadWireDepthStencilLevelTight(texture, target, uploadTarget, level, extent, format, type, bytes);
|
||||
ReadbackChannelMapping mapping{};
|
||||
if (!GetReadbackChannelMapping(format, mapping)) return false;
|
||||
const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, type);
|
||||
if (!pixelBytes || extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return false;
|
||||
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
|
||||
const GLenum faceTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
|
||||
const auto widening = sourceUsesImageCarrier
|
||||
? TextureImpl::GetImageBindableStorageWidening(logicalFormat) : TextureImpl::ImageBindableStorageWidening{};
|
||||
@@ -208,7 +264,12 @@
|
||||
else
|
||||
ScratchFBOImpl::EnsureColorAttachment2D(framebuffer, GL_READ_FRAMEBUFFER, texture, faceTarget, level);
|
||||
ScratchFBOImpl::EnsureReadBuffer(framebuffer, GL_COLOR_ATTACHMENT0);
|
||||
if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false;
|
||||
const GLenum status = g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
MGLOG_E_ONCE("Espryt wire texture readback: framebuffer status=0x%x texture=%u level=%d target=0x%x",
|
||||
status, texture, level, nativeTarget);
|
||||
return false;
|
||||
}
|
||||
Vector<Uint8> slice;
|
||||
if (!ReadWireColorRowsTight(extent.x(), extent.y(), format, type,
|
||||
TextureImpl::BackendTextureFormatAddsAlpha(logicalFormat, target), carrier, slice) ||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
@@ -247,4 +248,60 @@ namespace MGITest {
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// A native readback may temporarily isolate a mip for ES completeness. The
|
||||
// following draws observe the actual sampling state, not a frontend getter
|
||||
// that would hide a leaked native BASE_LEVEL/MAX_LEVEL change.
|
||||
TEST_F(ClearTexImageUndefinedLevelZeroScenario, ReadingMipLevelsPreservesSubsequentSamplingLodRange) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
const char* vertex = R"(#version 330 core
|
||||
void main() { vec2 p[3]=vec2[3](vec2(-1,-1),vec2(3,-1),vec2(-1,3));
|
||||
gl_Position=vec4(p[gl_VertexID],0,1); })";
|
||||
const char* fragment = R"(#version 330 core
|
||||
uniform sampler2D source; uniform float lod; out vec4 color;
|
||||
void main() { color=textureLod(source,vec2(0.5),lod); })";
|
||||
std::string error;
|
||||
const GLuint program = CompileProgram(vertex, fragment, &error);
|
||||
ASSERT_NE(program, 0u) << error;
|
||||
struct Objects {
|
||||
GLuint program, vao = 0, fbo = 0, output = 0;
|
||||
~Objects() {
|
||||
glUseProgram(0); glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &output);
|
||||
glDeleteVertexArrays(1, &vao); glDeleteProgram(program);
|
||||
}
|
||||
} objects{program};
|
||||
glGenVertexArrays(1, &objects.vao); glBindVertexArray(objects.vao);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &objects.output); glBindTexture(GL_TEXTURE_2D, objects.output);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glGenFramebuffers(1, &objects.fbo); glBindFramebuffer(GL_FRAMEBUFFER, objects.fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, objects.output, 0);
|
||||
ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE));
|
||||
MakeTextureWithOnlyLevel(0);
|
||||
const std::vector<Texel8> upper(4 * 4, kClearValue);
|
||||
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, upper.data());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_STENCIL_TEST); glDisable(GL_RASTERIZER_DISCARD); glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glUseProgram(program);
|
||||
const GLint lod = glGetUniformLocation(program, "lod");
|
||||
ASSERT_GE(lod, 0);
|
||||
std::vector<Texel8> readUpper(4 * 4);
|
||||
glGetTexImage(GL_TEXTURE_2D, 1, GL_RGBA, GL_UNSIGNED_BYTE, readUpper.data());
|
||||
ExpectAllTexels("upper level readback", readUpper, kClearValue);
|
||||
glViewport(0, 0, 4, 4); glUniform1f(lod, 0); glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
ExpectAllTexels("base level readback", ReadLevel(0), kInitialValue);
|
||||
glViewport(4, 0, 4, 4); glUniform1f(lod, 1); glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
Texel8 baseSample{}, upperSample{};
|
||||
glReadPixels(2, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &baseSample);
|
||||
glReadPixels(6, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &upperSample);
|
||||
EXPECT_EQ(baseSample, kInitialValue) << "readback must restore native BASE_LEVEL";
|
||||
EXPECT_EQ(upperSample, kClearValue) << "readback must restore native MAX_LEVEL";
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
Reference in New Issue
Block a user