Ask whether a colour format is renderable per target, not in general

The framebuffer-completeness check scanned every row of the backend's
format-capability cache and called the format renderable if any target
said so. That was already loose, and it broke outright once DirectGLES
started widening three-channel formats so they stay renderable as
multisample storage: the caveat capability recorded for the multisample
target made GL_RGB8_SNORM look renderable everywhere, so an ordinary 2D
GL_RGB8_SNORM texture attachment reported GL_FRAMEBUFFER_COMPLETE while
the driver's own framebuffer was INCOMPLETE_ATTACHMENT.

KHR-GL3x.packed_pixels stopped skipping those formats and read a
framebuffer that could not be read, so all 18 of its rgb8_snorm cases got
back an untouched buffer.

Pass the row the attachment actually lives in - the texture's target, or
the renderbuffer row - and consult only that one; a format is still asked
about in general when the caller has no target.
This commit is contained in:
BZLZHH
2026-08-02 04:54:48 -04:00
parent 440e569c98
commit ad03e059e0
@@ -101,7 +101,14 @@ namespace MobileGL::MG_Impl::GLImpl {
// shared-exponent, SNORM, three-channel norm16/float32/sRGB and three-channel integer formats. // shared-exponent, SNORM, three-channel norm16/float32/sRGB and three-channel integer formats.
// Desktop GL treats those as texture-only too (not in the GL 3.3 required-renderable list), so // Desktop GL treats those as texture-only too (not in the GL 3.3 required-renderable list), so
// reporting GL_FRAMEBUFFER_UNSUPPORTED for them is legal. // reporting GL_FRAMEBUFFER_UNSUPPORTED for them is legal.
Bool IsColorInternalFormatRenderable(TextureInternalFormat format) { //
// `capabilityTargetIndex` is the row of the cache the attachment actually lives in;
// kFormatCapabilityTargetCount asks about the format in general. Asking per target matters
// because a capability recorded for one of them says nothing about the others: DirectGLES
// widens three-channel formats to four channels to keep them renderable as *multisample*
// storage, and a format that survives only through that substitution is still texture-only
// on every ordinary target.
Bool IsColorInternalFormatRenderable(TextureInternalFormat format, SizeT capabilityTargetIndex) {
const SizeT formatIndex = static_cast<SizeT>(format); const SizeT formatIndex = static_cast<SizeT>(format);
if (MG_Backend::pActiveBackendObject && formatIndex < MG_Backend::kFormatCapabilityFormatCount) { if (MG_Backend::pActiveBackendObject && formatIndex < MG_Backend::kFormatCapabilityFormatCount) {
const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities(); const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities();
@@ -113,8 +120,11 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_Backend::FormatCapability::Creatable); MG_Backend::FormatCapability::Creatable);
} }
if (cachePopulated) { if (cachePopulated) {
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount; const Bool singleTarget = capabilityTargetIndex < MG_Backend::kFormatCapabilityTargetCount;
++targetIndex) { const SizeT firstTarget = singleTarget ? capabilityTargetIndex : 0;
const SizeT lastTarget =
singleTarget ? capabilityTargetIndex + 1 : MG_Backend::kFormatCapabilityTargetCount;
for (SizeT targetIndex = firstTarget; targetIndex < lastTarget; ++targetIndex) {
if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex],
MG_Backend::FormatCapability::FramebufferRenderable) || MG_Backend::FormatCapability::FramebufferRenderable) ||
MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex],
@@ -163,12 +173,17 @@ namespace MobileGL::MG_Impl::GLImpl {
const auto& attachment = attachments[i]; const auto& attachment = attachments[i];
if (!attachment.IsValid()) continue; if (!attachment.IsValid()) continue;
TextureInternalFormat format = TextureInternalFormat::Unknown; TextureInternalFormat format = TextureInternalFormat::Unknown;
SizeT capabilityTargetIndex = MG_Backend::kFormatCapabilityTargetCount;
if (attachment.IsTexture() && attachment.GetTexture()) { if (attachment.IsTexture() && attachment.GetTexture()) {
format = attachment.GetTexture()->GetFormat(); format = attachment.GetTexture()->GetFormat();
capabilityTargetIndex =
MG_Backend::GetFormatCapabilityTargetIndex(attachment.GetTexture()->GetTarget());
} else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) { } else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
format = attachment.GetRenderbuffer()->GetInternalFormat(); format = attachment.GetRenderbuffer()->GetInternalFormat();
capabilityTargetIndex = MG_Backend::GetRenderbufferFormatCapabilityTargetIndex();
} }
if (format != TextureInternalFormat::Unknown && !IsColorInternalFormatRenderable(format)) { if (format != TextureInternalFormat::Unknown &&
!IsColorInternalFormatRenderable(format, capabilityTargetIndex)) {
return true; return true;
} }
} }