mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (GL): report FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS and treat an attached layer past the last one as an incomplete attachment
This commit is contained in:
@@ -106,6 +106,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Which status an incomplete framebuffer reports. GL 4.6 core 9.4.2 leaves the choice open
|
||||
// when several rules fail; the layer rule is checked first because it is a whole-framebuffer
|
||||
// property no single attachment's state explains.
|
||||
GLenum IncompleteFramebufferStatus(const MG_State::GLState::FramebufferObject& framebufferObject) {
|
||||
if (framebufferObject.HasLayerTargetMismatch()) return GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS;
|
||||
return HasDefinedAttachment(framebufferObject) ? GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
|
||||
: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||
}
|
||||
|
||||
// Whether the backend can actually attach this color format to a framebuffer. Preferred
|
||||
// source of truth is the backend's probed format-capability cache (real glCheckFramebufferStatus
|
||||
// probes, so extensions like EXT_render_snorm are respected). Formats a probe-less backend
|
||||
@@ -2329,15 +2338,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return GL_FRAMEBUFFER_UNDEFINED;
|
||||
}
|
||||
|
||||
// TODO: distinguish GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT and GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
|
||||
// TODO: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER,
|
||||
// additional GL_FRAMEBUFFER_UNSUPPORTED cases, GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
|
||||
// GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS
|
||||
if (!framebufferObject->CheckCompleteness()) {
|
||||
return HasDefinedAttachment(*framebufferObject) ?
|
||||
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
||||
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||
}
|
||||
// additional GL_FRAMEBUFFER_UNSUPPORTED cases, GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
|
||||
if (!framebufferObject->CheckCompleteness()) return IncompleteFramebufferStatus(*framebufferObject);
|
||||
if (HasNonRenderableColorAttachment(*framebufferObject)) {
|
||||
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||
}
|
||||
@@ -2361,11 +2364,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto framebufferObject = GetNamedFramebufferObject_State(framebuffer, "CheckNamedFramebufferStatus_State");
|
||||
if (!framebufferObject) return GL_FRAMEBUFFER_UNDEFINED;
|
||||
|
||||
if (!framebufferObject->CheckCompleteness()) {
|
||||
return HasDefinedAttachment(*framebufferObject) ?
|
||||
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT :
|
||||
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
|
||||
}
|
||||
if (!framebufferObject->CheckCompleteness()) return IncompleteFramebufferStatus(*framebufferObject);
|
||||
if (HasNonRenderableColorAttachment(*framebufferObject)) {
|
||||
return GL_FRAMEBUFFER_UNSUPPORTED;
|
||||
}
|
||||
|
||||
@@ -93,8 +93,31 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
Bool FramebufferAttachmentObject::IsComplete() const {
|
||||
if (IsTexture()) {
|
||||
Bool complete = m_texture->IsComplete();
|
||||
return complete;
|
||||
if (!m_texture->IsComplete()) return false;
|
||||
// GL 4.6 core 9.4.1: a single layer of a three-dimensional, array or cube map array
|
||||
// texture must exist, i.e. FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER has to be smaller than
|
||||
// the level's depth / layer count. Attaching it is legal up to the implementation's
|
||||
// layer limit (9.2.8), so this is where an out-of-range layer is caught. A cube map
|
||||
// array counts layer-faces, which is the depth it was specified with.
|
||||
if (!m_layered) {
|
||||
const IntVec3 size = GetSize();
|
||||
Int layerCount = -1;
|
||||
switch (m_texture->GetTarget()) {
|
||||
case TextureTarget::Texture1DArray:
|
||||
layerCount = size.y();
|
||||
break;
|
||||
case TextureTarget::Texture3D:
|
||||
case TextureTarget::Texture2DArray:
|
||||
case TextureTarget::Texture2DMultisampleArray:
|
||||
case TextureTarget::TextureCubeMapArray:
|
||||
layerCount = size.z();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (layerCount >= 0 && m_textureLayer >= layerCount) return false;
|
||||
}
|
||||
return true;
|
||||
} else if (IsRenderbuffer()) {
|
||||
Bool complete = m_renderbuffer->IsAllocated();
|
||||
return complete;
|
||||
@@ -194,7 +217,33 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
if (validAttachmentCount == 0) return false;
|
||||
return true;
|
||||
return !HasLayerTargetMismatch();
|
||||
}
|
||||
|
||||
Bool FramebufferObject::HasLayerTargetMismatch() const {
|
||||
Bool anyLayered = false;
|
||||
Bool anyNonLayered = false;
|
||||
TextureTarget layeredColorTarget = TextureTarget::Unknown;
|
||||
Bool colorTargetsDiffer = false;
|
||||
for (SizeT index = 0; index < m_attachmentObjects.size(); ++index) {
|
||||
const auto& attachment = m_attachmentObjects[index];
|
||||
if (!attachment.IsValid() || attachment.IsEmpty()) continue;
|
||||
// A renderbuffer is never layered.
|
||||
const Bool layered = attachment.IsTexture() && attachment.IsLayered();
|
||||
(layered ? anyLayered : anyNonLayered) = true;
|
||||
|
||||
const auto type = static_cast<FramebufferAttachmentType>(index);
|
||||
if (!layered || type < FramebufferAttachmentType::Color0 || type > FramebufferAttachmentType::ColorMax) {
|
||||
continue;
|
||||
}
|
||||
const TextureTarget target = attachment.GetTexture()->GetTarget();
|
||||
if (layeredColorTarget == TextureTarget::Unknown) {
|
||||
layeredColorTarget = target;
|
||||
} else if (layeredColorTarget != target) {
|
||||
colorTargetsDiffer = true;
|
||||
}
|
||||
}
|
||||
return anyLayered && (anyNonLayered || colorTargetsDiffer);
|
||||
}
|
||||
|
||||
void FramebufferObject::SetDrawBuffer(Uint index, FramebufferAttachmentType buffer) {
|
||||
|
||||
@@ -128,6 +128,10 @@ namespace MobileGL {
|
||||
const FramebufferAttachmentObject& GetAttachment(FramebufferAttachmentType type) const;
|
||||
const FramebufferAttachmentObjectArray& GetAllAttachmentObjects() const;
|
||||
Bool CheckCompleteness() const;
|
||||
// GL 4.6 core 9.4.2 FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: some populated attachment is
|
||||
// layered while another is not, or the layered color attachments come from textures of
|
||||
// different targets. CheckCompleteness() includes it; this tells the status apart.
|
||||
Bool HasLayerTargetMismatch() const;
|
||||
// aka. `buffer` as in glDrawBuffers/glReadBuffers
|
||||
void SetDrawBuffer(Uint index, FramebufferAttachmentType buffer);
|
||||
const FramebufferAttachmentArray& GetDrawBuffers() const;
|
||||
|
||||
@@ -1634,3 +1634,69 @@ TEST_F(FramebufferTest, NamedFramebufferTextureRejectsALevelTheTextureDoesNotHav
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
// GL 4.6 core 9.4.2: if any populated attachment is layered, all of them must be, and the layered
|
||||
// color attachments must come from textures of one target - otherwise the framebuffer is
|
||||
// FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS (KHR-GL4x geometry_shader.layered_fbo,
|
||||
// texture_cube_map_array.fbo_incompleteness).
|
||||
TEST_F(FramebufferTest, MixedLayeredAttachmentsAreIncompleteLayerTargets) {
|
||||
GLuint textures[3] = {};
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &textures[0]);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D_ARRAY, 1, &textures[1]);
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &textures[2]);
|
||||
MG_Impl::GLImpl::TextureStorage2D(textures[0], 1, GL_RGBA8, 8, 8);
|
||||
MG_Impl::GLImpl::TextureStorage3D(textures[1], 1, GL_RGBA8, 8, 8, 4);
|
||||
MG_Impl::GLImpl::TextureStorage3D(textures[2], 1, GL_RGBA8, 8, 8, 4);
|
||||
GLuint framebuffer = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
// One layered (the whole 2D array) beside one that is not.
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, textures[1], 0);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT1, textures[0], 0);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::CheckNamedFramebufferStatus(framebuffer, GL_DRAW_FRAMEBUFFER),
|
||||
static_cast<GLenum>(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS));
|
||||
|
||||
// Both layered, but from a 2D array and a 3D texture.
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT1, textures[2], 0);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::CheckNamedFramebufferStatus(framebuffer, GL_DRAW_FRAMEBUFFER),
|
||||
static_cast<GLenum>(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS));
|
||||
|
||||
// The positive control: one layered target throughout.
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT1, 0, 0);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::CheckNamedFramebufferStatus(framebuffer, GL_DRAW_FRAMEBUFFER),
|
||||
static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::DeleteTextures(3, textures);
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
// GL 4.6 core 9.4.1: a single layer of a 3D / array texture has to exist. Attaching layer == depth
|
||||
// is legal (only the implementation's layer limit is checked at attach time); the attachment is
|
||||
// what is incomplete.
|
||||
TEST_F(FramebufferTest, AttachedLayerPastTheLastLayerIsIncompleteAttachment) {
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture);
|
||||
MG_Impl::GLImpl::TextureStorage3D(texture, 1, GL_RGBA8, 8, 8, 2);
|
||||
GLuint framebuffer = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
|
||||
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
MG_Impl::GLImpl::FramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture, 0, 2);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::CheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),
|
||||
static_cast<GLenum>(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT));
|
||||
|
||||
MG_Impl::GLImpl::FramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture, 0, 1);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::CheckFramebufferStatus(GL_DRAW_FRAMEBUFFER),
|
||||
static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user