Files
MobileGL/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h
T

94 lines
5.5 KiB
C++

// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.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 "source/opt/pass.h"
#include "spirv-tools/optimizer.hpp"
#include <Includes.h>
namespace MobileGL {
namespace MG_Util {
namespace ShaderTranspiler {
// GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so MobileGL floors every
// multisample ceiling it advertises to 4 (GL_Getter's kFrontendMaxSamples) no matter
// what the ES driver reports. The realised allocation cannot be floored the same way -
// the driver would simply reject it - so DirectGLES clamps the count it passes to
// glTexStorage*Multisample down to what the format really supports
// (ClampSamplesToBackendSupport). On Adreno and on Mali's Immortalis-G925 that is ONE
// sample for every integer format, while the frontend keeps telling the application
// GL_MAX_INTEGER_SAMPLES is 4.
//
// A shader written against the advertised ceiling therefore fetches a sample the
// backing storage does not have. KHR-GL33/40/41.texture_swizzle.functional_* and
// KHR-GLxx.texture_size_promotion.functional bake `texelFetch(usampler2DMS, coord, 3)`
// in as a literal, and the fetch comes back as 0/garbage ("Found pixel with wrong
// value", "read value = 0") on a texture the backend quietly allocated with one
// sample.
//
// This pass closes that gap from the shader side: for every fetch of a multisampled
// image it clamps the Sample image-operand to the backend's REAL maximum for that
// image's category, so the lookup lands inside the allocation the backend made.
// - K >= advertisedMaxSamples: the category is not squeezed, nothing is rewritten.
// - K <= 1: the Sample operand becomes a constant 0 of its own type - the only
// sample that exists.
// - 1 < K < advertisedMaxSamples: the operand is wrapped in min(operand, K-1),
// which leaves an in-range index exactly as it was.
// Only the UPPER bound is clamped. A negative index is out of range in GL before this
// pass and after it alike, and MobileGL is not the component that should be inventing
// a value for it.
//
// Category comes from the OpTypeImage: an OpTypeInt sampled type is the integer
// class (GL_MAX_INTEGER_SAMPLES), a float one is depth when the image's Depth operand
// is exactly 1 and colour otherwise. That last clause is deliberate: glslang writes
// Depth 0 for a plain sampler and 2 ("unknown") wherever it cannot tell, and GLSL has
// no multisampled shadow sampler at all, so only an explicit 1 is treated as a depth
// image and everything else falls to the colour limit - which is the one a
// mis-classified image would want anyway.
//
// DirectGLES transpile path only. DirectVulkan allocates the sample count it was
// asked for and must see the module unchanged.
class ClampMultisampleFetchPass : public spvtools::opt::Pass {
public:
// The three backend-REAL per-category ceilings, plus the count the GL frontend
// advertises (GL_Getter's GetAdvertisedMaxSamples). A category whose real ceiling
// already reaches the advertised one is left completely alone.
ClampMultisampleFetchPass(Int32 maxColorSamples, Int32 maxIntegerSamples,
Int32 maxDepthSamples, Int32 advertisedMaxSamples)
: m_maxColorSamples(maxColorSamples),
m_maxIntegerSamples(maxIntegerSamples),
m_maxDepthSamples(maxDepthSamples),
m_advertisedMaxSamples(advertisedMaxSamples) {}
const char* name() const override { return "clamp-multisample-fetch"; }
Status Process() override;
// Whether the module declares any multisampled image type, i.e. whether running
// this pass could change anything. Answered from a single parse so the caller can
// skip the optimizer round trip entirely - which is every shader but the handful
// that read a multisample texture directly.
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
// Same question answered from an already-built module, so one parse can feed
// several gates (ShaderCompiler::ProbeSpirvGateFeatures).
static bool DeclaresMultisampledImage(spvtools::opt::IRContext* context);
static spvtools::Optimizer::PassToken CreateClampMultisampleFetchPass(
Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples,
Int32 advertisedMaxSamples);
private:
Int32 m_maxColorSamples;
Int32 m_maxIntegerSamples;
Int32 m_maxDepthSamples;
Int32 m_advertisedMaxSamples;
};
} // namespace ShaderTranspiler
} // namespace MG_Util
} // namespace MobileGL