mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 22: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_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_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,
|
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};
|
E_GL_ARB_shader_image_size};
|
||||||
// Only advertised when the device driver actually has usable timer queries
|
// Only advertised when the device driver actually has usable timer queries
|
||||||
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
||||||
|
|||||||
@@ -1120,6 +1120,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
g_lastUsedBackendProgramId = 0;
|
g_lastUsedBackendProgramId = 0;
|
||||||
return;
|
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());
|
const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get());
|
||||||
Bool exist = (backendProgramIt != g_backendProgramObjects.end());
|
Bool exist = (backendProgramIt != g_backendProgramObjects.end());
|
||||||
auto& backendObj = exist ? backendProgramIt->second : g_backendProgramObjects.GetOrCreate(currentProgram);
|
auto& backendObj = exist ? backendProgramIt->second : g_backendProgramObjects.GetOrCreate(currentProgram);
|
||||||
@@ -1133,7 +1150,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (!backendObj->GetBackendProgramId() ||
|
if (!backendObj->GetBackendProgramId() ||
|
||||||
backendObj->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
backendObj->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
||||||
backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||||
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask) {
|
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask ||
|
||||||
|
backendObj->GetFragColorBroadcastCount() != g_fragColorBroadcastCount) {
|
||||||
backendObj->SyncToBackend(currentProgram);
|
backendObj->SyncToBackend(currentProgram);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3181,6 +3181,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
Uint32 g_snormFallbackClampOutputMask = 0;
|
Uint32 g_snormFallbackClampOutputMask = 0;
|
||||||
|
Uint g_fragColorBroadcastCount = 1;
|
||||||
Uint32 g_unormFallbackClampOutputMask = 0;
|
Uint32 g_unormFallbackClampOutputMask = 0;
|
||||||
Uint g_lastUsedBackendProgramId = 0;
|
Uint g_lastUsedBackendProgramId = 0;
|
||||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
||||||
@@ -3234,6 +3235,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
||||||
m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask;
|
m_snormFallbackClampOutputMask = g_snormFallbackClampOutputMask;
|
||||||
m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask;
|
m_unormFallbackClampOutputMask = g_unormFallbackClampOutputMask;
|
||||||
|
m_fragColorBroadcastCount = g_fragColorBroadcastCount;
|
||||||
|
|
||||||
// Detach all existing shaders
|
// Detach all existing shaders
|
||||||
GLint attachedCount = 0;
|
GLint attachedCount = 0;
|
||||||
@@ -3347,6 +3349,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
source = RemoveLayoutBinding(source);
|
source = RemoveLayoutBinding(source);
|
||||||
source = ProcessOutColorLocations(source);
|
source = ProcessOutColorLocations(source);
|
||||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||||
|
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
||||||
source = EmulateTextureLodBias(source);
|
source = EmulateTextureLodBias(source);
|
||||||
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
||||||
source = PromoteDrawParameterGlobalsToUniforms(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; }
|
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||||
|
Uint GetFragColorBroadcastCount() const { return m_fragColorBroadcastCount; }
|
||||||
|
|
||||||
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||||
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||||
@@ -622,6 +623,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Int m_indirectParamsBinding = -1;
|
Int m_indirectParamsBinding = -1;
|
||||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||||
Uint32 m_unormFallbackClampOutputMask = 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;
|
Bool m_isInitialized = false;
|
||||||
|
|
||||||
Int m_globalUboBackendBlockIndex = -1;
|
Int m_globalUboBackendBlockIndex = -1;
|
||||||
@@ -635,6 +639,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
extern Uint32 g_snormFallbackClampOutputMask;
|
extern Uint32 g_snormFallbackClampOutputMask;
|
||||||
extern Uint32 g_unormFallbackClampOutputMask;
|
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()
|
// 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
|
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||||
// ES context is recreated.
|
// ES context is recreated.
|
||||||
|
|||||||
@@ -279,6 +279,55 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return glslCode;
|
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) {
|
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
|
|||||||
@@ -104,6 +104,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
String ClampNormFallbackOutputs(String glslCode, GLenum shaderType, Uint32 snormOutputMask,
|
||||||
Uint32 unormOutputMask);
|
Uint32 unormOutputMask);
|
||||||
String ForceFlatIntegerVaryings(const String& glslCode, GLenum shaderType);
|
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);
|
String RemoveLayoutBinding(const String& glslCode);
|
||||||
// Prefix of the per-sampler float uniform that carries GL_TEXTURE_LOD_BIAS into
|
// 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.
|
// the shader (see EmulateTextureLodBias); the suffix is the sampler's own name.
|
||||||
|
|||||||
Reference in New Issue
Block a user