mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (DirectGLES, MG_Test): request GL_OES_viewport_array in the emitted ESSL, or lower the builtin away
This commit is contained in:
@@ -4899,6 +4899,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
effectiveSpirv = &loweredSpirv;
|
effectiveSpirv = &loweredSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ESSL cannot express gl_ViewportIndex either, but unlike the draw parameters
|
||||||
|
// there IS an extension that provides it - so this runs only when the driver does
|
||||||
|
// NOT advertise GL_OES_viewport_array. A driver that does keeps the builtin and
|
||||||
|
// gets the `#extension` request added to the decompiled source below instead.
|
||||||
|
// Demoting the builtin costs the multi-viewport routing (every invocation lands in
|
||||||
|
// viewport 0), which is the degradation ViewportArrayScenario already documents
|
||||||
|
// for this backend; NOT demoting it costs the whole program, because the stage
|
||||||
|
// fails to compile and every draw made with it silently renders nothing.
|
||||||
|
// Gated on the module actually declaring the output, so no other stage pays an
|
||||||
|
// optimizer round trip for it.
|
||||||
|
Vector<unsigned int> loweredViewportSpirv;
|
||||||
|
if (!g_GLESCapabilities.SupportsViewportArray &&
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresViewportIndexBuiltin(*effectiveSpirv) &&
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::LowerViewportIndexForEssl(
|
||||||
|
*effectiveSpirv, loweredViewportSpirv, enableSpirvValidation) &&
|
||||||
|
!loweredViewportSpirv.empty()) {
|
||||||
|
effectiveSpirv = &loweredViewportSpirv;
|
||||||
|
MGLOG_D("Program %u stage %s writes gl_ViewportIndex, which this ES driver has "
|
||||||
|
"no GL_OES_viewport_array for. The builtin was demoted to a plain "
|
||||||
|
"global; every invocation renders into viewport 0.",
|
||||||
|
m_backendProgramId,
|
||||||
|
MG_Util::ConvertGLEnumToString(glShaderType).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// GLSL ES has no ARRAY vertex inputs, and SPIRV-Cross refuses the whole module
|
// GLSL ES has no ARRAY vertex inputs, and SPIRV-Cross refuses the whole module
|
||||||
// rather than emulating them, so this has to happen before it sees the binary.
|
// rather than emulating them, so this has to happen before it sees the binary.
|
||||||
Vector<unsigned int> splitArrayInputSpirv;
|
Vector<unsigned int> splitArrayInputSpirv;
|
||||||
@@ -5080,6 +5104,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
source = RequestExtendedImageFormats(std::move(source),
|
source = RequestExtendedImageFormats(std::move(source),
|
||||||
imageFormatBake.needsExtendedImageFormats &&
|
imageFormatBake.needsExtendedImageFormats &&
|
||||||
g_GLESCapabilities.SupportsExtendedImageFormats);
|
g_GLESCapabilities.SupportsExtendedImageFormats);
|
||||||
|
// The third header-level rewrite, for the builtin SPIRV-Cross prints bare:
|
||||||
|
// gl_ViewportIndex is in no version of ESSL core, so without this directive the
|
||||||
|
// stage does not compile and the whole program - not just its viewport routing -
|
||||||
|
// is lost. The token probe keeps the line off every other program and the
|
||||||
|
// capability gate keeps it off drivers that would hard-error on an unadvertised
|
||||||
|
// name; a driver without the extension took the LowerViewportIndexPass fallback
|
||||||
|
// above and its source no longer names the builtin at all, so the two are mutually
|
||||||
|
// exclusive by construction. Read `source` BEFORE it is moved from.
|
||||||
|
const Bool needsViewportArrayExtension = g_GLESCapabilities.SupportsViewportArray &&
|
||||||
|
source.find("gl_ViewportIndex") != String::npos;
|
||||||
|
source = RequestViewportArrayExtension(std::move(source), needsViewportArrayExtension);
|
||||||
|
|
||||||
source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject);
|
source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject);
|
||||||
// The completion half of the format bake, for the formats SPIRV-Cross throws on
|
// The completion half of the format bake, for the formats SPIRV-Cross throws on
|
||||||
|
|||||||
@@ -569,6 +569,43 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return glslCode;
|
return glslCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String RequestViewportArrayExtension(String glslCode, Bool needed) {
|
||||||
|
#ifdef TRACY_ENABLE
|
||||||
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
#endif
|
||||||
|
// gl_ViewportIndex is desktop GL 4.1 core and is in ESSL only under
|
||||||
|
// GL_OES_viewport_array. SPIRV-Cross prints the identifier as-is and requests no
|
||||||
|
// extension for it - three lines away from the BuiltInLayer case, which DOES ask for
|
||||||
|
// one on ES - so an untouched decompile reaches the driver naming a builtin its core
|
||||||
|
// language has never heard of. The stage then fails to compile, the program is marked
|
||||||
|
// unusable and every draw made with it renders nothing while raising no GL error.
|
||||||
|
//
|
||||||
|
// Same `needed` contract as RequestExtendedImageFormats, and the same hard rule:
|
||||||
|
// `#extension` on a name the driver does not advertise is itself a compile error
|
||||||
|
// (ARM's compiler is strict about it), so this must never be emitted speculatively.
|
||||||
|
// A driver without the extension does not come through here at all - its module took
|
||||||
|
// the LowerViewportIndexPass fallback and the emitted source no longer names the
|
||||||
|
// builtin.
|
||||||
|
static constexpr const char* kDirective = "#extension GL_OES_viewport_array : require\n";
|
||||||
|
static constexpr const char* kExtName = "GL_OES_viewport_array";
|
||||||
|
if (!needed || glslCode.find(kExtName) != String::npos) {
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
// Right after the #version line, for the reason spelled out above: it is the only
|
||||||
|
// position that must stay first, and ForceSupporterOutput's scan for the LAST
|
||||||
|
// #extension directive still finds whichever one that ends up being.
|
||||||
|
const SizeT versionPos = glslCode.find("#version");
|
||||||
|
if (versionPos == String::npos) {
|
||||||
|
return kDirective + glslCode;
|
||||||
|
}
|
||||||
|
const SizeT lineEnd = glslCode.find('\n', versionPos);
|
||||||
|
if (lineEnd == String::npos) {
|
||||||
|
return glslCode + "\n" + kDirective;
|
||||||
|
}
|
||||||
|
glslCode.insert(lineEnd + 1, kDirective);
|
||||||
|
return glslCode;
|
||||||
|
}
|
||||||
|
|
||||||
String BakeImageFormatQualifiers(String glslCode,
|
String BakeImageFormatQualifiers(String glslCode,
|
||||||
const UnorderedMap<String, String>& esslFormatByUniformName) {
|
const UnorderedMap<String, String>& esslFormatByUniformName) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
|
|||||||
@@ -154,6 +154,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// extension - requesting an unadvertised extension is itself a compile error, so this is
|
// extension - requesting an unadvertised extension is itself a compile error, so this is
|
||||||
// never emitted speculatively. A no-op when not needed or already present.
|
// never emitted speculatively. A no-op when not needed or already present.
|
||||||
String RequestExtendedImageFormats(String glslCode, Bool needed);
|
String RequestExtendedImageFormats(String glslCode, Bool needed);
|
||||||
|
// Adds `#extension GL_OES_viewport_array : require` when the emitted ESSL names
|
||||||
|
// gl_ViewportIndex. SPIRV-Cross prints that identifier and asks for nothing (unlike
|
||||||
|
// gl_Layer, which it backs with GL_NV_viewport_array2 on ES) and ESSL has no core
|
||||||
|
// spelling for it at any version, so the request has to be made here or the stage does
|
||||||
|
// not compile - which loses the whole program, not just the multi-viewport routing.
|
||||||
|
// `needed` is the caller's answer for the same reason as above: only it knows whether the
|
||||||
|
// driver advertises the extension, and requesting an unadvertised one is itself a compile
|
||||||
|
// error, so this is never emitted speculatively. A no-op when not needed or already
|
||||||
|
// present.
|
||||||
|
String RequestViewportArrayExtension(String glslCode, Bool needed);
|
||||||
// Writes a format layout qualifier into the image declarations named in
|
// Writes a format layout qualifier into the image declarations named in
|
||||||
// `esslFormatByUniformName` that still have none. The completion half of the image-format
|
// `esslFormatByUniformName` that still have none. The completion half of the image-format
|
||||||
// bake, and ONLY that: the SPIR-V pass (BakeImageFormatsPass) is what normally puts the
|
// bake, and ONLY that: the SPIR-V pass (BakeImageFormatsPass) is what normally puts the
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ForceFlatIntegerVaryings;
|
|||||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITE_ALIAS_PREFIX;
|
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITE_ALIAS_PREFIX;
|
||||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemoveLayoutBinding;
|
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemoveLayoutBinding;
|
||||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestExtendedImageFormats;
|
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestExtendedImageFormats;
|
||||||
|
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestViewportArrayExtension;
|
||||||
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::SplitReadWriteImageUniforms;
|
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::SplitReadWriteImageUniforms;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -550,3 +551,55 @@ void main() { imageStore(uni_image, ivec2(0), uvec4(1u)); }
|
|||||||
EXPECT_EQ(out, source);
|
EXPECT_EQ(out, source);
|
||||||
EXPECT_EQ(CountOf(out, "GL_NV_image_formats"), 1u) << out;
|
EXPECT_EQ(CountOf(out, "GL_NV_image_formats"), 1u) << out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GL_OES_viewport_array directive -------------------------------------------------------------
|
||||||
|
|
||||||
|
// SPIRV-Cross prints gl_ViewportIndex bare and requests nothing for it, and ESSL has no core
|
||||||
|
// spelling at any version - so without this directive the stage fails to compile, the program is
|
||||||
|
// marked unusable and every draw made with it silently renders nothing.
|
||||||
|
TEST(RequestViewportArrayExtensionTest, TheDirectiveGoesRightAfterTheVersionLine) {
|
||||||
|
const String source = R"(#version 320 es
|
||||||
|
layout(points) in;
|
||||||
|
layout(points, max_vertices = 1) out;
|
||||||
|
void main() { gl_ViewportIndex = gl_InvocationID; EmitVertex(); }
|
||||||
|
)";
|
||||||
|
const String out = RequestViewportArrayExtension(source, true);
|
||||||
|
EXPECT_TRUE(Contains(out, "#version 320 es\n#extension GL_OES_viewport_array : require\n")) << out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never speculatively: ARM's compiler hard-errors on an `#extension` naming a string the driver
|
||||||
|
// does not advertise, so the caller's "not needed" answer has to be honoured exactly. A driver
|
||||||
|
// without the extension gets the LowerViewportIndexPass fallback instead.
|
||||||
|
TEST(RequestViewportArrayExtensionTest, NotNeededMeansNotEmitted) {
|
||||||
|
const String source = R"(#version 320 es
|
||||||
|
layout(points) in;
|
||||||
|
layout(points, max_vertices = 1) out;
|
||||||
|
void main() { gl_ViewportIndex = gl_InvocationID; EmitVertex(); }
|
||||||
|
)";
|
||||||
|
EXPECT_EQ(RequestViewportArrayExtension(source, false), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RequestViewportArrayExtensionTest, AnAlreadyPresentDirectiveIsNotDuplicated) {
|
||||||
|
const String source = R"(#version 320 es
|
||||||
|
#extension GL_OES_viewport_array : require
|
||||||
|
layout(points) in;
|
||||||
|
layout(points, max_vertices = 1) out;
|
||||||
|
void main() { gl_ViewportIndex = gl_InvocationID; EmitVertex(); }
|
||||||
|
)";
|
||||||
|
const String out = RequestViewportArrayExtension(source, true);
|
||||||
|
EXPECT_EQ(out, source);
|
||||||
|
EXPECT_EQ(CountOf(out, "GL_OES_viewport_array"), 1u) << out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The two image directives and this one share the insertion point, so a shader that needs both
|
||||||
|
// must end up with both - and with #version still first.
|
||||||
|
TEST(RequestViewportArrayExtensionTest, CoexistsWithTheImageFormatDirective) {
|
||||||
|
const String source = R"(#version 320 es
|
||||||
|
layout(r8ui, binding = 1) uniform writeonly highp uimage2D uni_image;
|
||||||
|
void main() { gl_ViewportIndex = 1; imageStore(uni_image, ivec2(0), uvec4(1u)); }
|
||||||
|
)";
|
||||||
|
const String out = RequestViewportArrayExtension(RequestExtendedImageFormats(source, true), true);
|
||||||
|
EXPECT_EQ(out.find("#version 320 es"), 0u) << out;
|
||||||
|
EXPECT_TRUE(Contains(out, "#extension GL_NV_image_formats : require\n")) << out;
|
||||||
|
EXPECT_TRUE(Contains(out, "#extension GL_OES_viewport_array : require\n")) << out;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user