mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
131 lines
9.0 KiB
C++
131 lines
9.0 KiB
C++
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/WidenImageFormatsPass.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 "spirv-tools/optimizer.hpp"
|
|
#include "source/opt/pass.h"
|
|
|
|
#include <Includes.h>
|
|
|
|
namespace MobileGL {
|
|
namespace MG_Util {
|
|
namespace ShaderTranspiler {
|
|
// Emulates the desktop-GL image formats GLSL ES cannot spell by CHANNEL WIDENING: a
|
|
// storage image DECLARED `layout(rg32f)` is re-declared `layout(rgba32f)` and every
|
|
// access through it is masked back to the two channels GL says it has.
|
|
//
|
|
// WHY IT IS NEEDED AT ALL. GL 4.2 has forty image formats; GLSL ES 3.1 has thirteen,
|
|
// and GL_NV_image_formats - the only extension that adds the rest - is advertised by
|
|
// none of Adreno 830, Mali-G1-Ultra MC12 or Mali-G925-Immortalis MC12 (probed on all
|
|
// three, with `#extension ... : enable` also rejected, so "the driver implements it
|
|
// unadvertised" is refuted rather than assumed). A shader that declares one of the
|
|
// other twenty-six therefore has NO legal ESSL spelling, and it fails in one of two
|
|
// ways: SPIRV-Cross throws for its is_desktop_only_format set and no text is produced
|
|
// at all, or the token reaches the driver and is rejected ("'rg32f' : not a legal
|
|
// layout qualifier id"). Either way the stage is lost, the backend program is
|
|
// unusable, and every draw with it silently renders nothing while the frontend keeps
|
|
// reporting GL_LINK_STATUS = TRUE. Dropping the qualifier instead is not an escape:
|
|
// all three drivers reject a format-LESS image declaration outright ("all images have
|
|
// to define layout format" / "S0001: Image must specify a format layout qualifier"),
|
|
// readonly and writeonly alike, at both #version 310 es and 320 es. And unlike a
|
|
// numeric limit there is nothing honest to report either - GL has no "this image
|
|
// format is unsupported" query - so the format has to be emulated.
|
|
//
|
|
// WHAT WIDENING MEANS. Seventeen of the twenty-six have a core ESSL format of the
|
|
// SAME PER-CHANNEL WIDTH AND COMPONENT TYPE, differing only in channel count
|
|
// (rg32f -> rgba32f, r8ui -> rgba8ui, rg8_snorm -> rgba8_snorm, ...). Carried in one
|
|
// of those the emulation is EXACT, not approximate: every value is representable bit
|
|
// for bit, and GL's own image semantics do the rest -
|
|
//
|
|
// * imageLoad on a format with fewer than four channels returns (r, 0, 0, 1);
|
|
// * imageStore drops the components the format does not have.
|
|
//
|
|
// so the two surplus channels of the carrier are not free storage, they are values GL
|
|
// already defines. This pass pins them: every OpImageWrite through a widened image has
|
|
// its texel replaced by (r[, g[, b]], 0.., 1) and every OpImageRead has its result
|
|
// masked the same way. Masking BOTH is deliberate belt and braces - the write mask
|
|
// alone keeps the storage canonical for a sampler and for glGetTexImage, the read mask
|
|
// alone survives storage this shader never wrote (glTexStorage with no upload, whose
|
|
// surplus channels are undefined).
|
|
//
|
|
// The other NINE (r11f_g11f_b10f, rgb10_a2, rgb10_a2ui, rgba16, rg16, r16,
|
|
// rgba16_snorm, rg16_snorm, r16_snorm) have NO same-width core carrier and are
|
|
// deliberately NOT widened here: every carrier for them is either lossy or changes the
|
|
// numeric domain of the texture a `sampler2D` would read from it. They keep the honest
|
|
// "no GLSL ES spelling" diagnostic instead of silently changing an application's
|
|
// quantisation behaviour.
|
|
//
|
|
// MUST MOVE WITH THE OTHER TWO LAYERS. The widening is not a shader-local rewrite: the
|
|
// ES texture behind the image has to be allocated in the carrier format too, and
|
|
// glBindImageTexture has to be handed the carrier (on Adreno the bind of the narrow
|
|
// format is GL_INVALID_VALUE for nineteen of the twenty-six, and on both Malis for
|
|
// twenty-five). Both are done in DirectGLES against the same table below, so the two
|
|
// sides agree by construction rather than by convention. Binding a narrow texture
|
|
// through a wide image is NOT an option: every tested driver accepts it silently, so
|
|
// it reads and writes out of bounds undetected.
|
|
//
|
|
// ESSL ONLY. DirectVulkan takes the declared format natively and resolves the view
|
|
// format from the same bind state, so the module must reach it unchanged.
|
|
class WidenImageFormatsPass final : public spvtools::opt::Pass {
|
|
public:
|
|
// `onlyFormatsSpirvCrossRefusesToPrint` narrows the pass to the formats that have
|
|
// no ESSL route even on a driver that DOES advertise GL_NV_image_formats.
|
|
// SPIRV-Cross's is_desktop_only_format set - r8ui, rg16f, r16i and fifteen others -
|
|
// makes it THROW for an ESSL target rather than print a token, and the throw takes
|
|
// the stage with it whatever the driver could have accepted. Mesa is exactly that
|
|
// case: it advertises the extension, so nothing else needs widening there, and
|
|
// `layout(r8ui) uimage2D` still lost its whole program until this ran for it.
|
|
//
|
|
// Off, the pass widens every format in the table, which is what a driver without
|
|
// the extension needs. The caller sets it from
|
|
// g_GLESCapabilities.SupportsExtendedImageFormats, and the SAME rule decides
|
|
// whether the ES texture storage and the glBindImageTexture argument widen
|
|
// (TextureImpl::GetImageBindableStorageWidening) - all three have to agree or the
|
|
// shader addresses a texel size the storage does not have.
|
|
explicit WidenImageFormatsPass(bool onlyFormatsSpirvCrossRefusesToPrint = false)
|
|
: m_onlyFormatsSpirvCrossRefusesToPrint(onlyFormatsSpirvCrossRefusesToPrint) {}
|
|
|
|
const char* name() const override { return "mobilegl-widen-image-formats"; }
|
|
Status Process() override;
|
|
|
|
// Whether the module declares a storage image whose format this pass would widen,
|
|
// i.e. whether running it could change anything. Answered from a single parse so
|
|
// the caller can skip the optimizer run entirely - which is every shader but a
|
|
// handful. `onlyFormatsSpirvCrossRefusesToPrint` must match what the run will use,
|
|
// or the gate answers a question the pass is not being asked.
|
|
static bool DeclaresWidenableImageFormat(const Vector<Uint32>& binary,
|
|
bool onlyFormatsSpirvCrossRefusesToPrint = false);
|
|
// The same question asked of a module the caller has ALREADY parsed, so a stage
|
|
// that has to answer several gate questions pays one BuildModule rather than one
|
|
// per gate - see ShaderCompiler::ProbeSpirvGateFeatures, and the ~10% it cost
|
|
// compile-heavy CTS cases when two gates each parsed for themselves.
|
|
static bool DeclaresWidenableImageFormat(spvtools::opt::IRContext* context,
|
|
bool onlyFormatsSpirvCrossRefusesToPrint = false);
|
|
|
|
// The core-ESSL GL internal format that carries `glInternalFormat` exactly, or 0
|
|
// when the format needs no widening (it is core already) or cannot be widened
|
|
// exactly (the nine above, and anything that is not an image format at all).
|
|
// Used by DirectGLES for the texture storage and the glBindImageTexture argument,
|
|
// so that all three layers pick the same carrier.
|
|
static Uint WidenedCoreEsslImageFormat(Uint glInternalFormat);
|
|
|
|
// Channels the GL internal format really has (1-4), or 0 when it is not one of the
|
|
// forty image formats. The count the widened accesses are masked back to.
|
|
static Uint ImageFormatChannelCount(Uint glInternalFormat);
|
|
|
|
static spvtools::Optimizer::PassToken CreateWidenImageFormatsPass(
|
|
bool onlyFormatsSpirvCrossRefusesToPrint = false);
|
|
|
|
private:
|
|
bool m_onlyFormatsSpirvCrossRefusesToPrint = false;
|
|
};
|
|
} // namespace ShaderTranspiler
|
|
} // namespace MG_Util
|
|
} // namespace MobileGL
|