[Fix] (MG_State, DirectGLES): sample a mipmap-incomplete texture as black

A minification filter that reads the mip chain requires every level from the base down
to hold exactly half the previous one's size; a texture that does not is incomplete and
every lookup on it returns (0, 0, 0, 1) (GL 4.6 core 8.17). Nothing checked it.

The ES driver cannot catch this on MobileGL's behalf, which is why it has to be a
frontend rule here: the backend texture is immutable storage allocated from the level
set as it stood, so a level the application later redefined at a different size never
reaches the driver at all, and the ES texture stays complete. That is exactly what
KHR-GL40.texture_gather.incomplete-texture does - it redefines level 1 of a complete
chain as 1x1 - and it read the original contents back.

The check runs where the sampling bindings are established, and an incomplete texture
simply leaves its native target unbound: an unbound ES target samples as (0, 0, 0, 1),
which is the answer GL asks for, with no scratch texture to keep around.

An array texture's layer count is not one of the dimensions that halves, so the
comparison only shrinks the components that belong to the image itself - getting that
wrong turned eight *-2darray cases black.
This commit is contained in:
BZLZHH
2026-08-04 10:49:51 -04:00
parent 5437947240
commit ae6949e459
3 changed files with 74 additions and 0 deletions
@@ -1613,6 +1613,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
// A texture whose mip chain does not satisfy the filter's completeness
// rules samples as (0, 0, 0, 1). The ES driver cannot work that out for
// itself here: the backend texture is immutable storage, so a level the
// application redefined at the wrong size never reached it. Leaving the
// native target unbound produces exactly the incomplete-texture result.
const auto& effectiveSampler = textureUnit.GetSamplerObject()
? textureUnit.GetSamplerObject()
: textureObject->GetSamplerObject();
const Bool mipmappedFilter =
effectiveSampler && effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None;
if (!MG_State::GLState::IsMipmapCompleteForFilter(textureObject.get(), mipmappedFilter)) {
continue;
}
// Bind texture object
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue;