mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[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:
@@ -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;
|
||||
|
||||
@@ -348,6 +348,58 @@ namespace MobileGL {
|
||||
|
||||
// TODO: add other texture types as needed
|
||||
|
||||
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) {
|
||||
if (texture == nullptr) return true;
|
||||
if (!texture->IsComplete()) return false;
|
||||
if (!mipmapped) return true;
|
||||
|
||||
const auto* mipmapTexture = AsMipmapTexture(texture);
|
||||
if (mipmapTexture == nullptr) return true; // no mip chain to be incomplete about
|
||||
|
||||
const UintVec2& levelRange = texture->GetLevelRange();
|
||||
const Uint baseLevel = levelRange.x();
|
||||
const Uint storedLevels = mipmapTexture->GetMipmapLevelCount();
|
||||
if (baseLevel >= storedLevels) return false;
|
||||
|
||||
// An array texture's layer count is not a dimension of the image: it stays put all
|
||||
// the way down the chain (GL 4.6 core 8.14.3). GetMipmapTexelSize reports it in the
|
||||
// slot after the image's own dimensions.
|
||||
const TextureTarget target = texture->GetTarget();
|
||||
Int shrinkingComponents = 3;
|
||||
if (target == TextureTarget::Texture1DArray) {
|
||||
shrinkingComponents = 1;
|
||||
} else if (target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray) {
|
||||
shrinkingComponents = 2;
|
||||
}
|
||||
|
||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
||||
const IntVec3 baseSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, baseLevel);
|
||||
Int largest = 0;
|
||||
for (Int component = 0; component < shrinkingComponents; ++component) {
|
||||
largest = std::max(largest, baseSize[component]);
|
||||
}
|
||||
if (largest <= 0) return false;
|
||||
|
||||
// p = log2 of the largest base dimension: the last level the chain needs
|
||||
// before every dimension has reached 1. TEXTURE_MAX_LEVEL can cut it short.
|
||||
Uint p = 0;
|
||||
for (Int extent = largest; extent > 1; extent >>= 1) ++p;
|
||||
const Uint lastLevel = std::min(baseLevel + p, levelRange.y());
|
||||
|
||||
for (Uint level = baseLevel; level <= lastLevel; ++level) {
|
||||
if (level >= storedLevels) return false;
|
||||
const IntVec3 actual = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
||||
for (Int component = 0; component < 3; ++component) {
|
||||
const Int expected = component < shrinkingComponents
|
||||
? std::max(1, baseSize[component] >> (level - baseLevel))
|
||||
: baseSize[component];
|
||||
if (actual[component] != expected) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -156,6 +156,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
? static_cast<TextureObjectMipmap*>(texture)
|
||||
: nullptr;
|
||||
}
|
||||
// Whether the texture satisfies the mipmap-completeness rules a minification filter
|
||||
// that samples the mip chain imposes (GL 4.6 core 8.17): every level from the base to
|
||||
// the effective max must exist at exactly half the previous one's size. `mipmapped` is
|
||||
// the effective sampler's answer to "does this filter read more than the base level" -
|
||||
// when it is false only base-level completeness matters, which the ordinary
|
||||
// IsComplete() already covers. Sampling an incomplete texture returns (0, 0, 0, 1).
|
||||
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped);
|
||||
|
||||
inline const TextureObjectMipmap* AsMipmapTexture(const ITextureObject* texture) {
|
||||
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
|
||||
? static_cast<const TextureObjectMipmap*>(texture)
|
||||
|
||||
Reference in New Issue
Block a user