mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (DirectGLES): blit onto a non-zero array layer with glCopyImageSubData where the driver ignores the destination layer
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
|
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
|
||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
|
#include <MG_Util/SelfTest/DriverBugProbes.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||||
@@ -5141,6 +5142,150 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
DrainBlitErrors();
|
DrainBlitErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- glBlitFramebuffer onto a non-zero array layer -------------------------------------
|
||||||
|
//
|
||||||
|
// Some drivers write to layer 0 whatever layer the DRAW framebuffer's
|
||||||
|
// glFramebufferTextureLayer attachment names, and raise no error doing it (Adreno 830;
|
||||||
|
// SelfTest::ProbeBlitIgnoresDestinationArrayLayer measures it, with the destination-layer-0
|
||||||
|
// case as the control). glCopyImageSubData takes the destination layer as an argument rather
|
||||||
|
// than reading it off an attachment, and honours it on the same driver - so a blit that is a
|
||||||
|
// plain 1:1 copy is issued that way instead.
|
||||||
|
//
|
||||||
|
// ONLY a plain 1:1 copy. glCopyImageSubData cannot scale, flip, convert format or resolve
|
||||||
|
// samples, and it is not clipped by the scissor, so every one of those is a reason to hand
|
||||||
|
// the call back to the driver rather than quietly perform a different operation. Those blits
|
||||||
|
// still land on the wrong layer; a once-per-process line says so rather than leaving it to be
|
||||||
|
// rediscovered.
|
||||||
|
//
|
||||||
|
// Per aspect, not all-or-nothing: the returned mask is the bits this performed itself, and
|
||||||
|
// the caller passes the rest to the driver. A COLOR|DEPTH blit whose colour half scales and
|
||||||
|
// whose depth half does not still gets its depth half repaired.
|
||||||
|
static GLbitfield BlitLayeredDestinationAspects(
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
||||||
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer, GLint srcX0, GLint srcY0,
|
||||||
|
GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask) {
|
||||||
|
if (mask == 0 || !readFramebuffer || !drawFramebuffer) return 0;
|
||||||
|
if (!g_GLESFuncs.glCopyImageSubData) return 0;
|
||||||
|
if (!MG_Util::SelfTest::BlitIgnoresDestinationArrayLayer(g_GLESFuncs)) return 0;
|
||||||
|
|
||||||
|
// The default framebuffer has no layers to get wrong, and a blit between the two halves
|
||||||
|
// of the same framebuffer object is not a shape this substitutes for.
|
||||||
|
const Int width = srcX1 - srcX0;
|
||||||
|
const Int height = srcY1 - srcY0;
|
||||||
|
const Bool oneToOne = width > 0 && height > 0 && (dstX1 - dstX0) == width && (dstY1 - dstY0) == height;
|
||||||
|
// The scissor clips a blit and does not clip a copy, so an enabled scissor makes the two
|
||||||
|
// different operations no matter how the rectangles line up.
|
||||||
|
const Bool scissorEnabled =
|
||||||
|
(RenderStateImpl::g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0;
|
||||||
|
|
||||||
|
using MobileGL::FramebufferAttachmentType;
|
||||||
|
struct AspectPlan {
|
||||||
|
GLbitfield bit;
|
||||||
|
FramebufferAttachmentType source;
|
||||||
|
FramebufferAttachmentType destination;
|
||||||
|
};
|
||||||
|
// The colour aspect follows glReadBuffer on the read side and draw buffer 0 on the write
|
||||||
|
// side, which is the only draw buffer a blit onto a layered destination can be pinned to
|
||||||
|
// here: a blit writes EVERY enabled draw buffer, so a framebuffer with more than one is
|
||||||
|
// left to the driver rather than half-repaired.
|
||||||
|
const auto& drawBuffers = drawFramebuffer->GetDrawBuffers();
|
||||||
|
// GL_NONE is what an unwritten draw-buffer slot holds, and it is a different value from
|
||||||
|
// the "no such attachment" one - counting it as enabled made every framebuffer look like
|
||||||
|
// it had eight and sent every colour blit to the driver.
|
||||||
|
Int enabledDrawBuffers = 0;
|
||||||
|
for (const FramebufferAttachmentType buffer : drawBuffers) {
|
||||||
|
if (buffer != FramebufferAttachmentType::Unknown && buffer != FramebufferAttachmentType::None) {
|
||||||
|
++enabledDrawBuffers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const AspectPlan plans[] = {
|
||||||
|
{GL_COLOR_BUFFER_BIT, readFramebuffer->GetReadBuffer(), drawBuffers[0]},
|
||||||
|
{GL_DEPTH_BUFFER_BIT, FramebufferAttachmentType::Depth, FramebufferAttachmentType::Depth},
|
||||||
|
{GL_STENCIL_BUFFER_BIT, FramebufferAttachmentType::Stencil, FramebufferAttachmentType::Stencil},
|
||||||
|
};
|
||||||
|
|
||||||
|
GLbitfield handled = 0;
|
||||||
|
for (const AspectPlan& plan : plans) {
|
||||||
|
if ((mask & plan.bit) == 0) continue;
|
||||||
|
if (plan.source == FramebufferAttachmentType::Unknown ||
|
||||||
|
plan.destination == FramebufferAttachmentType::Unknown ||
|
||||||
|
plan.source == FramebufferAttachmentType::None ||
|
||||||
|
plan.destination == FramebufferAttachmentType::None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& sourceAttachment = readFramebuffer->GetAttachment(plan.source);
|
||||||
|
const auto& destinationAttachment = drawFramebuffer->GetAttachment(plan.destination);
|
||||||
|
// Renderbuffers have no layers, so a destination that is one cannot be hitting this.
|
||||||
|
if (!sourceAttachment.IsTexture() || !destinationAttachment.IsTexture()) continue;
|
||||||
|
// Layer 0 is the case the driver gets right, and a LAYERED attachment (glFramebufferTexture
|
||||||
|
// with no layer) blits its layer 0 by spec - neither is this defect.
|
||||||
|
if (destinationAttachment.GetTextureLayer() == 0) continue;
|
||||||
|
if (destinationAttachment.IsLayered() || sourceAttachment.IsLayered()) continue;
|
||||||
|
|
||||||
|
const auto& sourceTexture = sourceAttachment.GetTexture();
|
||||||
|
const auto& destinationTexture = destinationAttachment.GetTexture();
|
||||||
|
if (!sourceTexture || !destinationTexture) continue;
|
||||||
|
// glCopyImageSubData moves texel blocks: same format both ends, or it is a different
|
||||||
|
// operation. Multisample endpoints would additionally have to agree on sample count,
|
||||||
|
// which is a resolve the driver still owns.
|
||||||
|
if (sourceTexture->GetFormat() != destinationTexture->GetFormat()) continue;
|
||||||
|
if (sourceTexture->GetSamples() > 0 || destinationTexture->GetSamples() > 0) continue;
|
||||||
|
|
||||||
|
// A combined depth-stencil texture is ONE image to glCopyImageSubData: it carries both
|
||||||
|
// aspects across whether or not the mask asked for both. Taking only GL_DEPTH_BUFFER_BIT
|
||||||
|
// on a DEPTH24_STENCIL8 destination would overwrite a stencil the application asked to
|
||||||
|
// keep, so the copy is only allowed when the mask covers everything the format holds.
|
||||||
|
const TextureInternalFormat format = destinationTexture->GetFormat();
|
||||||
|
const Bool hasDepth = MG_Util::IsDepthFormatInternalFormat(format);
|
||||||
|
const Bool hasStencil = MG_Util::IsStencilFormatInternalFormat(format);
|
||||||
|
if (hasDepth && (mask & GL_DEPTH_BUFFER_BIT) == 0) continue;
|
||||||
|
if (hasStencil && (mask & GL_STENCIL_BUFFER_BIT) == 0) continue;
|
||||||
|
// ... and having carried both, it must be credited with both, or the caller hands the
|
||||||
|
// stencil half to the driver and it lands on layer 0 after all.
|
||||||
|
const GLbitfield aspectBits =
|
||||||
|
hasDepth || hasStencil
|
||||||
|
? static_cast<GLbitfield>((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) |
|
||||||
|
(hasStencil ? GL_STENCIL_BUFFER_BIT : 0))
|
||||||
|
: static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT);
|
||||||
|
if ((handled & aspectBits) == aspectBits) continue;
|
||||||
|
|
||||||
|
if (!oneToOne || scissorEnabled || (plan.bit == GL_COLOR_BUFFER_BIT && enabledDrawBuffers != 1)) {
|
||||||
|
MGLOG_E_ONCE("BlitFramebuffer: this driver ignores a non-zero destination array layer and this "
|
||||||
|
"blit cannot be expressed as a copy (%s), so it will land on layer 0",
|
||||||
|
!oneToOne ? "it scales or flips"
|
||||||
|
: scissorEnabled ? "the scissor test is enabled"
|
||||||
|
: "the destination has more than one draw buffer");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto backendSource = TextureImpl::SyncTextureObjectToBackend(sourceTexture);
|
||||||
|
auto backendDestination = TextureImpl::SyncTextureObjectToBackend(destinationTexture);
|
||||||
|
if (!backendSource || !backendDestination) continue;
|
||||||
|
const GLuint sourceName = backendSource->GetBackendTextureId();
|
||||||
|
const GLuint destinationName = backendDestination->GetBackendTextureId();
|
||||||
|
if (sourceName == 0 || destinationName == 0) continue;
|
||||||
|
const GLenum sourceTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(sourceTexture->GetTarget());
|
||||||
|
const GLenum destinationTarget =
|
||||||
|
TextureImpl::ConvertTextureTargetToBackendGLEnum(destinationTexture->GetTarget());
|
||||||
|
|
||||||
|
ClearGLErrors();
|
||||||
|
g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, sourceAttachment.GetTextureLevel(), srcX0, srcY0,
|
||||||
|
sourceAttachment.GetTextureLayer(), destinationName, destinationTarget,
|
||||||
|
destinationAttachment.GetTextureLevel(), dstX0, dstY0,
|
||||||
|
destinationAttachment.GetTextureLayer(), width, height, 1);
|
||||||
|
if (const GLenum error = g_GLESFuncs.glGetError(); error != GL_NO_ERROR) {
|
||||||
|
// The driver blit still runs for this aspect - onto the wrong layer, but the
|
||||||
|
// substitute has to leave the call no worse off than it found it.
|
||||||
|
MGLOG_E_ONCE("BlitFramebuffer: the layered-destination copy substitute failed with %s; the "
|
||||||
|
"driver blit will run instead and land on layer 0",
|
||||||
|
MG_Util::ConvertGLEnumToString(error).c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handled |= aspectBits;
|
||||||
|
}
|
||||||
|
return handled & mask;
|
||||||
|
}
|
||||||
|
|
||||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||||
GLint dstY1, GLbitfield mask, GLenum filter) {
|
GLint dstY1, GLbitfield mask, GLenum filter) {
|
||||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||||
@@ -5167,7 +5312,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
});
|
});
|
||||||
MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
|
MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
|
||||||
dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
|
dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
|
||||||
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
// A no-op on every driver that honours a non-zero destination array layer, which is all
|
||||||
|
// of them but the probed one. Whatever it performs itself is taken out of the mask.
|
||||||
|
mask &= ~BlitLayeredDestinationAspects(
|
||||||
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(),
|
||||||
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(), srcX0, srcY0,
|
||||||
|
srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask);
|
||||||
|
if (mask != 0) {
|
||||||
|
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||||
|
}
|
||||||
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__](auto err) {
|
||||||
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
MGLOG_D("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||||
});
|
});
|
||||||
@@ -5189,7 +5342,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1,
|
MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1,
|
||||||
dstX0, dstY0, dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
|
dstX0, dstY0, dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
|
||||||
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
// See the DSA-free entry point above: only the probed defect makes this do anything.
|
||||||
|
mask &= ~BlitLayeredDestinationAspects(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0,
|
||||||
|
dstY0, dstX1, dstY1, mask);
|
||||||
|
if (mask != 0) {
|
||||||
|
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||||
|
}
|
||||||
// Debug-only diagnostics: which GLES depth texture did this blit write?
|
// Debug-only diagnostics: which GLES depth texture did this blit write?
|
||||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||||
if (mask & GL_DEPTH_BUFFER_BIT) {
|
if (mask & GL_DEPTH_BUFFER_BIT) {
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
GLint activeTexture = GL_TEXTURE0;
|
GLint activeTexture = GL_TEXTURE0;
|
||||||
GLint texture2D = 0;
|
GLint texture2D = 0;
|
||||||
GLint texture2DMultisample = 0;
|
GLint texture2DMultisample = 0;
|
||||||
|
GLint texture2DArray = 0;
|
||||||
GLfloat clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
GLfloat clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
GLint packAlignment = 4;
|
GLint packAlignment = 4;
|
||||||
GLint packRowLength = 0;
|
GLint packRowLength = 0;
|
||||||
@@ -155,6 +156,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
gl.glGetIntegerv(GL_ACTIVE_TEXTURE, &state.activeTexture);
|
gl.glGetIntegerv(GL_ACTIVE_TEXTURE, &state.activeTexture);
|
||||||
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D, &state.texture2D);
|
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D, &state.texture2D);
|
||||||
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D_MULTISAMPLE, &state.texture2DMultisample);
|
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D_MULTISAMPLE, &state.texture2DMultisample);
|
||||||
|
gl.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &state.texture2DArray);
|
||||||
gl.glGetIntegerv(GL_PACK_ALIGNMENT, &state.packAlignment);
|
gl.glGetIntegerv(GL_PACK_ALIGNMENT, &state.packAlignment);
|
||||||
gl.glGetIntegerv(GL_PACK_ROW_LENGTH, &state.packRowLength);
|
gl.glGetIntegerv(GL_PACK_ROW_LENGTH, &state.packRowLength);
|
||||||
if (gl.glGetFloatv != nullptr) {
|
if (gl.glGetFloatv != nullptr) {
|
||||||
@@ -205,6 +207,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
gl.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(state.texture2D));
|
gl.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(state.texture2D));
|
||||||
gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE,
|
gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE,
|
||||||
static_cast<GLuint>(state.texture2DMultisample));
|
static_cast<GLuint>(state.texture2DMultisample));
|
||||||
|
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, static_cast<GLuint>(state.texture2DArray));
|
||||||
}
|
}
|
||||||
gl.glActiveTexture(static_cast<GLenum>(state.activeTexture));
|
gl.glActiveTexture(static_cast<GLenum>(state.activeTexture));
|
||||||
}
|
}
|
||||||
@@ -1348,6 +1351,174 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
// ===================== LAYERED BLIT DESTINATION =====================
|
||||||
|
|
||||||
|
constexpr const char* kLayeredBlitProbeName = "layered blit destination";
|
||||||
|
// Four texels wide: a 1x1 blit is a shape drivers special-case, and a rectangle keeps
|
||||||
|
// the probe on the ordinary path. Two layers is all the question needs.
|
||||||
|
constexpr GLsizei kLayeredBlitSize = 4;
|
||||||
|
constexpr GLsizei kLayeredBlitLayers = 2;
|
||||||
|
|
||||||
|
// One RGBA8 2D array whose every layer is filled with a distinguishable byte.
|
||||||
|
GLuint MakeLayeredBlitTexture(const GLESFunctionsTable& gl, GLubyte layer0, GLubyte layer1) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
gl.glGenTextures(1, &texture);
|
||||||
|
if (texture == 0) return 0;
|
||||||
|
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||||
|
gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kLayeredBlitSize, kLayeredBlitSize,
|
||||||
|
kLayeredBlitLayers);
|
||||||
|
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
const GLubyte fills[kLayeredBlitLayers] = {layer0, layer1};
|
||||||
|
for (GLint layer = 0; layer < kLayeredBlitLayers; ++layer) {
|
||||||
|
GLubyte texels[kLayeredBlitSize * kLayeredBlitSize * 4];
|
||||||
|
for (SizeT i = 0; i < sizeof(texels); i += 4) {
|
||||||
|
texels[i + 0] = fills[layer];
|
||||||
|
texels[i + 1] = fills[layer];
|
||||||
|
texels[i + 2] = fills[layer];
|
||||||
|
texels[i + 3] = 255;
|
||||||
|
}
|
||||||
|
gl.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kLayeredBlitSize, kLayeredBlitSize, 1,
|
||||||
|
GL_RGBA, GL_UNSIGNED_BYTE, texels);
|
||||||
|
}
|
||||||
|
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A framebuffer naming exactly one layer of one array texture.
|
||||||
|
GLuint MakeLayeredBlitFramebuffer(const GLESFunctionsTable& gl, GLuint texture, GLint layer) {
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
gl.glGenFramebuffers(1, &framebuffer);
|
||||||
|
if (framebuffer == 0) return 0;
|
||||||
|
gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||||
|
gl.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, layer);
|
||||||
|
if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
gl.glDeleteFramebuffers(1, &framebuffer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return framebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The red byte of texel (0, 0) of one layer, read through a framebuffer that names it.
|
||||||
|
// 256 is "could not read", which no fill value can be.
|
||||||
|
Int ReadLayeredBlitTexel(const GLESFunctionsTable& gl, GLuint texture, GLint layer) {
|
||||||
|
const GLuint framebuffer = MakeLayeredBlitFramebuffer(gl, texture, layer);
|
||||||
|
if (framebuffer == 0) return 256;
|
||||||
|
gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||||
|
gl.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
|
GLubyte pixel[4] = {0, 0, 0, 0};
|
||||||
|
gl.glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
|
||||||
|
gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||||
|
gl.glDeleteFramebuffers(1, &framebuffer);
|
||||||
|
Drain(gl);
|
||||||
|
return static_cast<Int>(pixel[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blits source layer 1 onto `destinationLayer` of a freshly filled destination and
|
||||||
|
// reports which layer actually received it, or -1 when the blit could not be issued.
|
||||||
|
Int LayeredBlitLandsOnLayer(const GLESFunctionsTable& gl, GLint destinationLayer, GLubyte magic) {
|
||||||
|
const GLuint source = MakeLayeredBlitTexture(gl, 0x11, magic);
|
||||||
|
const GLuint destination = MakeLayeredBlitTexture(gl, 0x33, 0x44);
|
||||||
|
const GLuint sourceFramebuffer = MakeLayeredBlitFramebuffer(gl, source, 1);
|
||||||
|
const GLuint destinationFramebuffer = MakeLayeredBlitFramebuffer(gl, destination, destinationLayer);
|
||||||
|
Int landedOn = -1;
|
||||||
|
if (source != 0 && destination != 0 && sourceFramebuffer != 0 && destinationFramebuffer != 0) {
|
||||||
|
gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
|
||||||
|
gl.glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destinationFramebuffer);
|
||||||
|
Drain(gl);
|
||||||
|
gl.glBlitFramebuffer(0, 0, kLayeredBlitSize, kLayeredBlitSize, 0, 0, kLayeredBlitSize,
|
||||||
|
kLayeredBlitSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
if (gl.glGetError() == GL_NO_ERROR) {
|
||||||
|
landedOn = -2; // issued, but seen on no layer yet
|
||||||
|
for (GLint layer = 0; layer < kLayeredBlitLayers; ++layer) {
|
||||||
|
if (ReadLayeredBlitTexel(gl, destination, layer) == static_cast<Int>(magic)) {
|
||||||
|
landedOn = layer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sourceFramebuffer != 0) gl.glDeleteFramebuffers(1, &sourceFramebuffer);
|
||||||
|
if (destinationFramebuffer != 0) gl.glDeleteFramebuffers(1, &destinationFramebuffer);
|
||||||
|
if (source != 0) gl.glDeleteTextures(1, &source);
|
||||||
|
if (destination != 0) gl.glDeleteTextures(1, &destination);
|
||||||
|
Drain(gl);
|
||||||
|
return landedOn;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Bool ProbeBlitIgnoresDestinationArrayLayer(const GLESFunctionsTable& gl) {
|
||||||
|
if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexStorage3D || !gl.glTexSubImage3D ||
|
||||||
|
!gl.glTexParameteri || !gl.glDeleteTextures || !gl.glGenFramebuffers || !gl.glBindFramebuffer ||
|
||||||
|
!gl.glFramebufferTextureLayer || !gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers ||
|
||||||
|
!gl.glBlitFramebuffer || !gl.glReadBuffer || !gl.glReadPixels || !gl.glPixelStorei || !gl.glGetError ||
|
||||||
|
!gl.glIsEnabled || !gl.glDisable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SavedState saved;
|
||||||
|
Save(gl, saved);
|
||||||
|
// A scissor left on by whoever ran before would clip the probe's own blit and make a
|
||||||
|
// working driver look broken.
|
||||||
|
gl.glDisable(GL_SCISSOR_TEST);
|
||||||
|
Drain(gl);
|
||||||
|
|
||||||
|
// THE CONTROL: the same blit onto destination layer 0, which is the case no
|
||||||
|
// implementation gets wrong. It also proves the SOURCE layer is honoured, since the
|
||||||
|
// magic byte it looks for only exists on source layer 1 - so a driver that cannot blit
|
||||||
|
// between array layers at all, or that has no working glFramebufferTextureLayer, fails
|
||||||
|
// here and reaches no verdict rather than being reported as having this bug.
|
||||||
|
const Int controlLanded = LayeredBlitLandsOnLayer(gl, 0, 0x5Au);
|
||||||
|
Bool detected = false;
|
||||||
|
if (controlLanded != 0) {
|
||||||
|
MGLOG_I("[driver-bug] %s probe reached no verdict (the destination-layer-0 control "
|
||||||
|
"landed on layer %d instead of 0)",
|
||||||
|
kLayeredBlitProbeName, controlLanded);
|
||||||
|
} else {
|
||||||
|
// THE SUBJECT: the identical blit asking for layer 1. Only the destination layer moved.
|
||||||
|
const Int subjectLanded = LayeredBlitLandsOnLayer(gl, 1, 0x5Au);
|
||||||
|
detected = subjectLanded == 0;
|
||||||
|
if (subjectLanded != 0 && subjectLanded != 1) {
|
||||||
|
MGLOG_I("[driver-bug] %s probe reached no verdict (the subject blit landed on "
|
||||||
|
"no layer at all: %d)",
|
||||||
|
kLayeredBlitProbeName, subjectLanded);
|
||||||
|
} else {
|
||||||
|
MGLOG_I("[driver-bug] %s probe: a blit asking for destination layer 1 landed on "
|
||||||
|
"layer %d%s",
|
||||||
|
kLayeredBlitProbeName, subjectLanded,
|
||||||
|
detected ? " - THE DESTINATION LAYER IS IGNORED" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Restore(gl, saved);
|
||||||
|
return detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BlitIgnoresDestinationArrayLayer(const GLESFunctionsTable& gl) {
|
||||||
|
// One driver per process, and the answer is structural rather than sampled.
|
||||||
|
static const Bool ignored = ProbeBlitIgnoresDestinationArrayLayer(gl);
|
||||||
|
return ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Optional<DriverBugFinding> ProbeLayeredBlitDestinationBug(const GLESFunctionsTable& gl) {
|
||||||
|
if (!BlitIgnoresDestinationArrayLayer(gl)) return std::nullopt;
|
||||||
|
return DriverBugFinding{
|
||||||
|
"glBlitFramebuffer ignores the destination array layer",
|
||||||
|
DriverBugVerdict::Fixed,
|
||||||
|
"a glBlitFramebuffer whose DRAW framebuffer attaches a non-zero array layer with "
|
||||||
|
"glFramebufferTextureLayer writes to layer 0 instead, for colour and depth alike, "
|
||||||
|
"and raises no error doing it. The layer is honoured everywhere else on the same "
|
||||||
|
"driver - rendering into it works, glReadPixels off it works, and the blit's own "
|
||||||
|
"SOURCE layer is read correctly - so neither layered attachments nor blitting is "
|
||||||
|
"withdrawn. MobileGL performs such a blit with glCopyImageSubData instead, which "
|
||||||
|
"takes the destination layer explicitly and honours it here; a blit that scales, "
|
||||||
|
"flips, changes format, resolves samples or is clipped by the scissor cannot be "
|
||||||
|
"expressed that way and is still handed to the driver"};
|
||||||
|
}
|
||||||
|
|
||||||
// ===================== EXPLICIT VERTEX INPUT LOCATION CEILING =====================
|
// ===================== EXPLICIT VERTEX INPUT LOCATION CEILING =====================
|
||||||
|
|
||||||
constexpr const char* kAttributeLocationProbeName = "explicit vertex input location";
|
constexpr const char* kAttributeLocationProbeName = "explicit vertex input location";
|
||||||
@@ -1639,6 +1810,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
&ProbeCrossStageImageQualifierMergeBug,
|
&ProbeCrossStageImageQualifierMergeBug,
|
||||||
&ProbeImageCoherencyResidualBug,
|
&ProbeImageCoherencyResidualBug,
|
||||||
&ProbeExplicitVertexInputLocationCeilingBug,
|
&ProbeExplicitVertexInputLocationCeilingBug,
|
||||||
|
&ProbeLayeredBlitDestinationBug,
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,27 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
String detail;
|
String detail;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Blits one layer of an RGBA8 2D array onto another array's layer 1 and reports whether the
|
||||||
|
// copy landed where it was asked to. Returns true only when the destination layer is ignored
|
||||||
|
// while the control lands correctly.
|
||||||
|
//
|
||||||
|
// Adreno 830 writes to layer 0 whatever layer the DRAW framebuffer's
|
||||||
|
// glFramebufferTextureLayer attachment names, for colour and depth alike, and raises no
|
||||||
|
// error. Everything else about the layer works on the same driver, which is what makes this
|
||||||
|
// a blit defect rather than a layered-attachment one.
|
||||||
|
//
|
||||||
|
// THE CONTROL is the same blit onto destination layer 0. It passes on every implementation
|
||||||
|
// that can blit between array layers at all, and because the value it looks for exists only
|
||||||
|
// on the SOURCE's layer 1 it also proves the source layer is honoured - so a driver with no
|
||||||
|
// working glFramebufferTextureLayer reaches no verdict instead of being reported as this.
|
||||||
|
//
|
||||||
|
// Returns false when an entry point is missing, when the probe's own framebuffers come back
|
||||||
|
// incomplete, or when the control fails. Restores every piece of GL state it touches.
|
||||||
|
Bool ProbeBlitIgnoresDestinationArrayLayer(const MG_External::GLESFunctionsTable& gl);
|
||||||
|
|
||||||
|
// ProbeBlitIgnoresDestinationArrayLayer(), evaluated at most once per process.
|
||||||
|
Bool BlitIgnoresDestinationArrayLayer(const MG_External::GLESFunctionsTable& gl);
|
||||||
|
|
||||||
// What the vertex-input location probe measured. The ceiling is reported rather than
|
// What the vertex-input location probe measured. The ceiling is reported rather than
|
||||||
// hard-coded: it is a driver property, and a clamp derived from a number measured on some
|
// hard-coded: it is a driver property, and a clamp derived from a number measured on some
|
||||||
// other device is exactly the hard-coded vendor quirk this file exists to avoid.
|
// other device is exactly the hard-coded vendor quirk this file exists to avoid.
|
||||||
|
|||||||
Reference in New Issue
Block a user