mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
EnsureGeneratedMipmapStorageAllocated branched on the transport. The split arm
planned the generation window from BASE_LEVEL/MAX_LEVEL and refused to touch
immutable storage (70c8230b); the monolith arm rebuilt the chain from the bound
object's level-0 extent and then truncated it. Through a view that is
destructive in a way the split arm had already been fixed for: a view's level-0
extent is one slice of the owner's layers, so a two-layer view of a four-layer
array resized every owner level the generation touched to two layers - the
storage behind the owner's other layers was gone - and redefined the owner's
levels out of the view's window as well.
Both arms now take one path. An immutable texture or a view returns before any
allocation: it already owns every level generation can write. A mutable texture
gets levels from the plan's Base..End window and nothing else moves - the chain
is not truncated, because neither MAX_LEVEL nor a generate is a truncation (a
level-0 respecification drops the stale tail itself, in
DiscardMipmapChainOnBaseRespecification). MipmapGenerationPlan.h is no longer
behind MOBILEGL_BUILD_DISAGGREGATED for the same reason: the window is a
property of the GL call, not of how the backend is reached, and the plan the
wire carrier publishes (MGPMipPlan::LevelCount, end-exclusive) is this one.
The window rule was asserted only by the split-only F1WireScenario, whose
fixture skips under every other transport - so the monolith arm, which is what
a non-disaggregated library runs for every application, had no coverage at all.
TextureViewScenario.GenerateMipmapThroughAViewLeavesTheOwnerShapeAlone runs in
an integration-gpu lane under both transports and pins the owner's layer counts,
its own level extents, and the pixels of the levels and layers outside the
view's window. It is DirectGLES-only, and the skip names why: Magma reallocates
the chain through the bound object in its own generate path
(DirectVulkan/Renderer/VulkanRenderer.cpp's EnsureGenerateMipmapStorageAllocated),
and a CPU sub-image into the layers of mip 2 or 3 does not read back through
Magma at all - a run of the same scenario with the generate removed fails
identically - so the per-level colours this case is built on are unobservable on
that backend rather than merely wrong.
47 lines
2.2 KiB
C++
47 lines
2.2 KiB
C++
// MobileGL - MobileGL/MG_Impl/GLImpl/Texture/MipmapGenerationPlan.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
#pragma once
|
|
#include <Includes.h>
|
|
|
|
// The plan is the GL call's own window, not a transport's, so both arms of
|
|
// EnsureGeneratedMipmapStorageAllocated compute it here and the wire carrier
|
|
// (MGPMipPlan::LevelCount) publishes the same end-exclusive number.
|
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
|
#include <algorithm>
|
|
|
|
namespace MobileGL::MG_Impl::GLImpl {
|
|
struct MipmapGenerationRange {
|
|
Uint Base = 0;
|
|
Uint End = 0; // logical end-exclusive, including the source base level
|
|
};
|
|
|
|
inline MipmapGenerationRange ComputeMipmapGenerationRange(
|
|
const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget uploadTarget) {
|
|
const auto range = texture.GetLevelRange();
|
|
MipmapGenerationRange result{range.x(), range.x()};
|
|
const Uint available = texture.GetMipmapLevelCount();
|
|
if (result.Base >= available || range.y() < result.Base) return result;
|
|
const auto extent = texture.GetMipmapTexelSize(uploadTarget, result.Base);
|
|
if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return result;
|
|
Int largest = extent.x();
|
|
if (texture.GetTarget() != TextureTarget::Texture1D &&
|
|
texture.GetTarget() != TextureTarget::Texture1DArray)
|
|
largest = std::max(largest, extent.y());
|
|
if (texture.GetTarget() == TextureTarget::Texture3D) largest = std::max(largest, extent.z());
|
|
Uint levels = 1;
|
|
while (largest > 1) { largest /= 2; ++levels; }
|
|
Uint64 end = std::min(static_cast<Uint64>(result.Base) + levels,
|
|
static_cast<Uint64>(range.y()) + 1);
|
|
// A view's level count is already relative to its own window. Neither
|
|
// immutable storage nor a view may acquire levels beyond that allocation.
|
|
if (texture.IsImmutable()) end = std::min(end, static_cast<Uint64>(available));
|
|
result.End = static_cast<Uint>(end);
|
|
return result;
|
|
}
|
|
}
|