mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Fix] (DirectGLES): broadcast legacy gl_FragColor to every draw buffer
Legacy GLSL's gl_FragColor goes to every enabled draw buffer (GL 4.6 15.2.3),
but ShaderSourceProcessor lowers it to a single mg_FragColor output, which only
ever reaches draw buffer 0. Everything past the first attachment kept its
pre-draw contents.
Replicated across the enabled draw buffers with copies at the end of main.
Gated on the count so the ordinary single-target shader is byte-for-byte what it
was: the pass is a no-op below two draw buffers, and the count comes from the
frontend draw framebuffer at program-sync time (not from the backend framebuffer
sync, which only runs later in PrepareForDraw - a program compiled against a
stale count would not be relinked until the draw after the one that needed it).
It joins the snorm/unorm clamp masks as framebuffer state the shader is compiled
against, with the same relink-on-change check.
Also advertises GL_ARB_explicit_attrib_location and GL_ARB_texture_multisample,
which DirectGLES implements for every version it advertises but only listed for
DirectVulkan. Both are core from GL 3.2/3.3 on, so an app targeting 3.0/3.1
reaches them only through the extension string - without the former the CTS
picks an entirely different draw_buffers shader, and without the latter
KHR-GL31.texture_size_promotion.functional crashed outright.
KHR-GL3{0,1,2,3}.draw_buffers.draw_buffers_1 now passes on all four versions,
and texture_size_promotion.functional on GL31 downgrades from a crash to a
(still open) comparison failure.
This commit is contained in:
@@ -832,6 +832,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
||||
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding,
|
||||
// Both are core from GL 3.2/3.3 on and implemented here for
|
||||
// every advertised version, but an app targeting 3.0/3.1
|
||||
// only reaches them through the extension string - the CTS
|
||||
// picks a whole different shader for draw_buffers without
|
||||
// explicit_attrib_location. DirectVulkan advertises both.
|
||||
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample,
|
||||
E_GL_ARB_shader_image_size};
|
||||
// Only advertised when the device driver actually has usable timer queries
|
||||
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
||||
|
||||
@@ -1120,6 +1120,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_lastUsedBackendProgramId = 0;
|
||||
return;
|
||||
}
|
||||
// Read from the frontend rather than from the backend framebuffer sync, which
|
||||
// only runs later in PrepareForDraw: a program compiled against a stale count
|
||||
// would not be relinked until the draw after the one that needed it.
|
||||
{
|
||||
Uint enabledDrawBuffers = 0;
|
||||
if (const auto& drawFBO =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()) {
|
||||
const auto& drawBuffers = drawFBO->GetDrawBuffers();
|
||||
for (Uint i = 0; i < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; ++i) {
|
||||
if (drawBuffers[i] != FramebufferAttachmentType::None) {
|
||||
enabledDrawBuffers = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_fragColorBroadcastCount = std::max<Uint>(enabledDrawBuffers, 1);
|
||||
}
|
||||
|
||||
const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get());
|
||||
Bool exist = (backendProgramIt != g_backendProgramObjects.end());
|
||||
auto& backendObj = exist ? backendProgramIt->second : g_backendProgramObjects.GetOrCreate(currentProgram);
|
||||
@@ -1133,7 +1150,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!backendObj->GetBackendProgramId() ||
|
||||
backendObj->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
||||
backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask) {
|
||||
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask ||
|
||||
backendObj->GetFragColorBroadcastCount() != g_fragColorBroadcastCount) {
|
||||
backendObj->SyncToBackend(currentProgram);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3181,6 +3181,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
namespace PrgramImpl {
|
||||
Uint32 g_snormFallbackClampOutputMask = 0;
|
||||
Uint g_fragColorBroadcastCount = 1;
|
||||
Uint32 g_unormFallbackClampOutputMask = 0;
|
||||
Uint g_lastUsedBackendProgramId = 0;
|
||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
||||
@@ -3234,6 +3235,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
||||
m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask;
|
||||
m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask;
|
||||
m_fragColorBroadcastCount = g_fragColorBroadcastCount;
|
||||
|
||||
// Detach all existing shaders
|
||||
GLint attachedCount = 0;
|
||||
@@ -3347,6 +3349,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
source = RemoveLayoutBinding(source);
|
||||
source = ProcessOutColorLocations(source);
|
||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
||||
source = EmulateTextureLodBias(source);
|
||||
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
||||
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
|
||||
|
||||
@@ -596,6 +596,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||
Uint GetFragColorBroadcastCount() const { return m_fragColorBroadcastCount; }
|
||||
|
||||
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||
@@ -622,6 +623,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Int m_indirectParamsBinding = -1;
|
||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||
// Draw buffers a legacy gl_FragColor write has to reach (see
|
||||
// PrgramImpl::BroadcastLegacyFragColor); 1 keeps the plain single-output shader.
|
||||
Uint m_fragColorBroadcastCount = 1;
|
||||
Bool m_isInitialized = false;
|
||||
|
||||
Int m_globalUboBackendBlockIndex = -1;
|
||||
@@ -635,6 +639,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
extern Uint32 g_snormFallbackClampOutputMask;
|
||||
extern Uint32 g_unormFallbackClampOutputMask;
|
||||
// Draw buffers the current draw framebuffer enables. Like the clamp masks above it
|
||||
// is framebuffer state that the shader has to be compiled against, so a program
|
||||
// whose snapshot no longer matches is relinked.
|
||||
extern Uint g_fragColorBroadcastCount;
|
||||
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||
// ES context is recreated.
|
||||
|
||||
@@ -279,6 +279,55 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
// The name is the marker: ShaderSourceProcessor only emits it when the source
|
||||
// wrote gl_FragColor, and such a shader can have no other output.
|
||||
static const char* const kLoweredName = "mg_FragColor";
|
||||
if (shaderType != GL_FRAGMENT_SHADER || drawBufferCount <= 1) {
|
||||
return glslCode;
|
||||
}
|
||||
static const std::regex declRegex(
|
||||
R"(layout\s*\(\s*location\s*=\s*0\s*\)\s*out\s+((?:lowp|mediump|highp)\s+)?vec4\s+mg_FragColor\s*;)");
|
||||
std::smatch declMatch;
|
||||
if (!std::regex_search(glslCode, declMatch, declRegex)) {
|
||||
return glslCode;
|
||||
}
|
||||
const String precision = declMatch[1].matched ? declMatch[1].str() : String();
|
||||
|
||||
String replicaDecls;
|
||||
String replicaCopies;
|
||||
for (Uint location = 1; location < drawBufferCount; ++location) {
|
||||
const String name = String(kLoweredName) + "_" + std::to_string(location);
|
||||
replicaDecls += "\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " +
|
||||
name + ";";
|
||||
replicaCopies += "\n " + name + " = " + kLoweredName + ";";
|
||||
}
|
||||
|
||||
static const std::regex mainRegex(R"(void\s+main\s*\([^)]*\)\s*\{)");
|
||||
std::smatch mainMatch;
|
||||
if (!std::regex_search(glslCode, mainMatch, mainRegex)) {
|
||||
return glslCode;
|
||||
}
|
||||
SizeT bracePos = static_cast<SizeT>(mainMatch.position(0) + mainMatch.length(0) - 1);
|
||||
Int depth = 0;
|
||||
for (SizeT pos = bracePos; pos < glslCode.size(); ++pos) {
|
||||
if (glslCode[pos] == '{') {
|
||||
++depth;
|
||||
} else if (glslCode[pos] == '}') {
|
||||
--depth;
|
||||
if (depth == 0) {
|
||||
glslCode.insert(pos, replicaCopies + "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
glslCode.insert(static_cast<SizeT>(declMatch.position(0)) + declMatch[0].str().size(), replicaDecls);
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
|
||||
@@ -104,6 +104,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||
Uint32 unormOutputMask);
|
||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
||||
// Legacy GLSL's gl_FragColor is broadcast to every enabled draw buffer (GL 4.6
|
||||
// 15.2.3), but ShaderSourceProcessor lowers it to the single output mg_FragColor,
|
||||
// which only ever reaches draw buffer 0. Replicates it across `drawBufferCount`
|
||||
// outputs and copies the value into them at the end of main. A no-op for
|
||||
// drawBufferCount <= 1, i.e. for everything but a framebuffer that actually
|
||||
// enables several draw buffers, so the ordinary single-target shader is untouched.
|
||||
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount);
|
||||
String RemoveLayoutBinding(const String& glslCode);
|
||||
// Prefix of the per-sampler float uniform that carries GL_TEXTURE_LOD_BIAS into
|
||||
// the shader (see EmulateTextureLodBias); the suffix is the sampler's own name.
|
||||
|
||||
Reference in New Issue
Block a user