mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Fix] (DirectGLES): generate a three-channel float mip chain on the CPU
glGenerateMipmap requires the level-0 format to be colour-renderable, and ES has no colour-renderable three-channel float format at all - so an ES driver rejects GL_RGB16F and GL_RGB32F where every desktop driver accepts them, and the error was forwarded to the application. KHR-GL40.texture_gather.plain-gather-float-2d-rgb and its offset- sibling build their texture that way and fail on the leftover error alone. The blit-based emulation already used for GL_R11F_G11F_B10F is no help: it renders level n from level n-1, so it needs exactly the renderability that is missing. But a format the driver cannot render into is a format nothing can have rendered into either, which makes the frontend's own copy of the texels authoritative for precisely these formats. So the chain is box-filtered there and the levels are marked dirty; the backend sync that follows uploads them like any other texture data. Deliberately narrow: only the two formats whose texels are a plain float array, and only when they are what the texture actually holds. Every other format keeps the driver's behaviour, error included.
This commit is contained in:
@@ -3953,6 +3953,78 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// ES has no colour-renderable three-channel float format, and its glGenerateMipmap
|
||||
// requires one, so it rejects GL_RGB16F and GL_RGB32F outright where every desktop
|
||||
// driver accepts them. Both store a plain array of floats, and a format the driver
|
||||
// cannot render into is a format nothing can have rendered into - so the frontend's own
|
||||
// copy of the texels is the authority, and the chain can be filtered there and carried
|
||||
// down by the ordinary upload path. Returns false for anything else, leaving the
|
||||
// driver's answer (including its error) in place.
|
||||
static Bool GenerateThreeChannelFloatMipmapOnCpu(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (!texture) return false;
|
||||
const TextureInternalFormat format = texture->GetFormat();
|
||||
const Bool isHalf = format == TextureInternalFormat::RGB16F;
|
||||
if (!isHalf && format != TextureInternalFormat::RGB32F) return false;
|
||||
|
||||
auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(texture.get());
|
||||
if (mipmapTexture == nullptr) return false;
|
||||
const Uint levelCount = mipmapTexture->GetMipmapLevelCount();
|
||||
constexpr Int kChannels = 3;
|
||||
|
||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
||||
for (Uint level = 1; level < levelCount; ++level) {
|
||||
const IntVec3 srcSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, level - 1);
|
||||
const IntVec3 dstSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
||||
if (srcSize.x() <= 0 || srcSize.y() <= 0 || dstSize.x() <= 0 || dstSize.y() <= 0) return false;
|
||||
auto* src = static_cast<Uint8*>(mipmapTexture->MapMipmapData(uploadTarget, level - 1));
|
||||
auto* dst = static_cast<Uint8*>(mipmapTexture->MapMipmapData(uploadTarget, level));
|
||||
if (src == nullptr || dst == nullptr) return false;
|
||||
|
||||
const SizeT componentBytes = isHalf ? sizeof(Uint16) : sizeof(Float);
|
||||
const SizeT texelBytes = componentBytes * kChannels;
|
||||
const auto load = [&](const Uint8* base, Int x, Int y, Int channel) {
|
||||
const Uint8* texel = base + (static_cast<SizeT>(y) * srcSize.x() + x) * texelBytes +
|
||||
channel * componentBytes;
|
||||
if (isHalf) {
|
||||
Uint16 bits = 0;
|
||||
Memcpy(&bits, texel, sizeof(bits));
|
||||
return MG_Util::DecodeHalfBitsToFloat(bits);
|
||||
}
|
||||
Float value = 0.0f;
|
||||
Memcpy(&value, texel, sizeof(value));
|
||||
return value;
|
||||
};
|
||||
|
||||
// Box filter over the 2x2 source footprint, clamped where a dimension is
|
||||
// already 1 (GL 4.6 core 8.14.4 leaves the exact filter to the implementation
|
||||
// and this is the one it describes for power-of-two levels).
|
||||
for (Int y = 0; y < dstSize.y(); ++y) {
|
||||
for (Int x = 0; x < dstSize.x(); ++x) {
|
||||
const Int x0 = std::min(x * 2, srcSize.x() - 1);
|
||||
const Int x1 = std::min(x * 2 + 1, srcSize.x() - 1);
|
||||
const Int y0 = std::min(y * 2, srcSize.y() - 1);
|
||||
const Int y1 = std::min(y * 2 + 1, srcSize.y() - 1);
|
||||
for (Int channel = 0; channel < kChannels; ++channel) {
|
||||
const Float average = 0.25f * (load(src, x0, y0, channel) + load(src, x1, y0, channel) +
|
||||
load(src, x0, y1, channel) + load(src, x1, y1, channel));
|
||||
Uint8* texel = dst + (static_cast<SizeT>(y) * dstSize.x() + x) * texelBytes +
|
||||
channel * componentBytes;
|
||||
if (isHalf) {
|
||||
const Uint16 bits = MG_Util::EncodeFloatToHalfBits(average);
|
||||
Memcpy(texel, &bits, sizeof(bits));
|
||||
} else {
|
||||
Memcpy(texel, &average, sizeof(average));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mipmapTexture->MarkStorageDirty(uploadTarget, level, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenerateMipmap(GLenum target) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
@@ -3962,9 +4034,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target));
|
||||
auto& texture = slot.GetBoundObject();
|
||||
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
||||
if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat())) {
|
||||
if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat()) ||
|
||||
texture->GetFormat() == TextureInternalFormat::RGB16F ||
|
||||
texture->GetFormat() == TextureInternalFormat::RGB32F) {
|
||||
EnsureGenerateMipmapStorageAllocated(texture);
|
||||
}
|
||||
// Filtered on the CPU before the backend sync, so the dirty levels ride down with it.
|
||||
if (GenerateThreeChannelFloatMipmapOnCpu(texture)) {
|
||||
TextureImpl::SyncTextureObjectToBackend(texture);
|
||||
return;
|
||||
}
|
||||
auto& backendTexture = TextureImpl::SyncTextureObjectToBackend(texture);
|
||||
|
||||
if (IsDepthOnlyFormat(texture->GetFormat())) {
|
||||
|
||||
Reference in New Issue
Block a user