mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (ShaderTranspiler): lower gl_NumSamples onto a reserved global-UBO uniform, restore ES preamble extension macros, tolerate a repeated #version
This commit is contained in:
@@ -34,8 +34,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
// Takes the ALREADY-RESOLVED draw program rather than looking it up: GLContext::GetProgramForDraw
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
// is not a plain getter (it settles the program's link and SPIR-V jobs so every version a
|
||||||
|
// backend samples during this draw describes the program it is drawing), so the draw funnel
|
||||||
|
// below resolves it exactly once and hands it to both users.
|
||||||
|
static Bool ValidateResolvedProgramForDraw(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram,
|
||||||
|
const char* functionName) {
|
||||||
if (!ValidateProgramForExecution(currentProgram, functionName)) return false;
|
if (!ValidateProgramForExecution(currentProgram, functionName)) return false;
|
||||||
|
|
||||||
// GL 4.6 core 7.4.1, the pipeline validation rule every vertex-transferring command
|
// GL 4.6 core 7.4.1, the pipeline validation rule every vertex-transferring command
|
||||||
@@ -52,8 +56,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// deliberately NOT rejected: the rule above names the three pre-rasterization stages, and
|
// deliberately NOT rejected: the rule above names the three pre-rasterization stages, and
|
||||||
// nothing else here should start refusing draws GL accepts.
|
// nothing else here should start refusing draws GL accepts.
|
||||||
//
|
//
|
||||||
// Only ValidateCurrentProgramForExecution, never ValidateProgramForExecution itself, so a
|
// On the DRAW path only, never in ValidateProgramForExecution itself, so a dispatch -
|
||||||
// dispatch - which shares that helper and legitimately has no vertex stage - is untouched.
|
// which shares that helper and legitimately has no vertex stage - is untouched.
|
||||||
const Bool hasPreRasterizationStage = currentProgram->HasLinkedShaderStage(ShaderStage::Geometry) ||
|
const Bool hasPreRasterizationStage = currentProgram->HasLinkedShaderStage(ShaderStage::Geometry) ||
|
||||||
currentProgram->HasLinkedShaderStage(ShaderStage::TessControl) ||
|
currentProgram->HasLinkedShaderStage(ShaderStage::TessControl) ||
|
||||||
currentProgram->HasLinkedShaderStage(ShaderStage::TessEval);
|
currentProgram->HasLinkedShaderStage(ShaderStage::TessEval);
|
||||||
@@ -69,6 +73,35 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gl_NumSamples has no SPIR-V built-in, so the source pipeline lowers it onto a reserved
|
||||||
|
// default-block uniform (see InjectNumSamplesBuiltinShim). This is where that uniform is paid
|
||||||
|
// for: the value is a property of the DRAW FRAMEBUFFER, not of the program, so one program
|
||||||
|
// drawn into a 4x target and then into the default framebuffer must see 4 and then 1 - which
|
||||||
|
// rules out baking it at link time.
|
||||||
|
//
|
||||||
|
// Per draw rather than on framebuffer changes because the pair (program, framebuffer) is what
|
||||||
|
// decides the value and either half can move between draws. It costs a phase-A flag read for
|
||||||
|
// every program that has no shim, and a 4-byte compare for the ones that do: the write only
|
||||||
|
// bumps the UBO content version when the number actually changes, so a run of draws into one
|
||||||
|
// framebuffer re-uploads nothing.
|
||||||
|
static void PublishDrawFramebufferSampleCount(const SharedPtr<MG_State::GLState::ProgramObject>& program) {
|
||||||
|
if (!program || !program->UsesReservedNumSamples()) return;
|
||||||
|
// GL 4.6 core 15.2.2: gl_NumSamples is the number of samples in the framebuffer, or ONE
|
||||||
|
// when the target is not multisampled - where glGetIntegerv(GL_SAMPLES) answers zero.
|
||||||
|
program->WriteReservedNumSamples(static_cast<Int>(std::max<GLint>(ResolveDrawFramebufferSampleCount(), 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The one funnel every drawing command passes through. Order is load-bearing: validate first
|
||||||
|
// (a rejected draw must leave state alone), then publish the sample count - which reads the
|
||||||
|
// DRAW FRAMEBUFFER binding, so it has to run after the caller's framebuffer state is settled
|
||||||
|
// and before the backend consumes the program's UBO content version.
|
||||||
|
static Bool PrepareCurrentProgramForDraw(const char* functionName) {
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
if (!ValidateResolvedProgramForDraw(currentProgram, functionName)) return false;
|
||||||
|
PublishDrawFramebufferSampleCount(currentProgram);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// A dispatch resolves its program through the DISPATCH accessor: with a pipeline bound
|
// A dispatch resolves its program through the DISPATCH accessor: with a pipeline bound
|
||||||
// that is the pipeline's compute stage program, not the graphics composite a draw would
|
// that is the pipeline's compute stage program, not the graphics composite a draw would
|
||||||
// build - which no longer contains a compute stage to find at all.
|
// build - which no longer contains a compute stage to find at all.
|
||||||
@@ -828,14 +861,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride);
|
MultiDrawArraysIndirect_Backend(mode, indirect, drawcount, stride);
|
||||||
}
|
}
|
||||||
@@ -913,7 +946,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// NegativeApiErrorsTest.IndirectParameterDrawsCheckBothBuffers pins the INVALID_VALUE
|
// NegativeApiErrorsTest.IndirectParameterDrawsCheckBothBuffers pins the INVALID_VALUE
|
||||||
// they produce for a call made with no program bound. Same precedence decision, and
|
// they produce for a call made with no program bound. Same precedence decision, and
|
||||||
// the same reason, as DispatchComputeIndirect above.
|
// the same reason, as DispatchComputeIndirect above.
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount;
|
auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount;
|
||||||
if (!multiDrawElementsIndirectCount) {
|
if (!multiDrawElementsIndirectCount) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -934,7 +967,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// See MultiDrawElementsIndirectCount, including why this one goes last.
|
// See MultiDrawElementsIndirectCount, including why this one goes last.
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount;
|
auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount;
|
||||||
if (!multiDrawArraysIndirectCount) {
|
if (!multiDrawArraysIndirectCount) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -949,7 +982,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||||
const void* indices, GLint basevertex) {
|
const void* indices, GLint basevertex) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
||||||
@@ -959,7 +992,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
DrawRangeElements_Backend(mode, start, end, count, type, indices);
|
||||||
}
|
}
|
||||||
@@ -967,7 +1000,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
DrawElementsInstancedBaseVertexBaseInstance_Backend(mode, count, type, indices, instancecount, basevertex,
|
||||||
baseinstance);
|
baseinstance);
|
||||||
@@ -976,7 +1009,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLint basevertex) {
|
GLsizei instancecount, GLint basevertex) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
||||||
@@ -987,21 +1020,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||||
GLsizei instancecount, GLuint baseinstance) {
|
GLsizei instancecount, GLuint baseinstance) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
DrawElementsInstancedBaseInstance_Backend(mode, count, type, indices, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
DrawElementsInstanced_Backend(mode, count, type, indices, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return;
|
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return;
|
||||||
@@ -1011,21 +1044,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||||
GLuint baseinstance) {
|
GLuint baseinstance) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
DrawArraysInstancedBaseInstance_Backend(mode, first, count, instancecount, baseinstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
DrawArraysInstanced_Backend(mode, first, count, instancecount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return;
|
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return;
|
||||||
DrawArraysIndirect_Backend(mode, indirect);
|
DrawArraysIndirect_Backend(mode, indirect);
|
||||||
@@ -1033,7 +1066,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
if (!ValidateNonNegativeDrawArgument(__func__, "count", count)) return;
|
||||||
@@ -1043,7 +1076,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawArrays_Backend(mode, first, count);
|
DrawArrays_Backend(mode, first, count);
|
||||||
@@ -1051,7 +1084,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (drawcount < 0) {
|
if (drawcount < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -1065,7 +1098,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount) {
|
GLsizei drawcount) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
MultiDrawElements_Backend(mode, count, type, indices, drawcount);
|
||||||
}
|
}
|
||||||
@@ -1073,7 +1106,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
|
||||||
GLsizei drawcount, const GLint* basevertex) {
|
GLsizei drawcount, const GLint* basevertex) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||||
if (!ValidateNonNegativeDrawArgument(__func__, "drawcount", drawcount)) return;
|
if (!ValidateNonNegativeDrawArgument(__func__, "drawcount", drawcount)) return;
|
||||||
@@ -1097,7 +1130,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
|
||||||
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
if (!ValidatePrimitiveModeEnum(__func__, mode)) return;
|
||||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
if (!PrepareCurrentProgramForDraw(__func__)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
AccountTransformFeedbackPrimitives(mode, count);
|
||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
@@ -1521,7 +1554,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// (GL 4.6 core 10.3.7).
|
// (GL 4.6 core 10.3.7).
|
||||||
static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream,
|
static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream,
|
||||||
GLsizei instancecount) {
|
GLsizei instancecount) {
|
||||||
if (!ValidateCurrentProgramForExecution(functionName)) return;
|
if (!PrepareCurrentProgramForDraw(functionName)) return;
|
||||||
if (!ValidatePrimitiveModeForBackend(functionName, mode)) return;
|
if (!ValidatePrimitiveModeForBackend(functionName, mode)) return;
|
||||||
if (instancecount < 0) {
|
if (instancecount < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
|
|||||||
@@ -304,24 +304,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLint ResolveDrawFramebufferSampleCount() {
|
|
||||||
const auto& drawFbo =
|
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
|
||||||
if (!drawFbo) return 0;
|
|
||||||
|
|
||||||
GLint maxSamples = 0;
|
|
||||||
for (const auto& attachment : drawFbo->GetAllAttachmentObjects()) {
|
|
||||||
if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
|
|
||||||
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetRenderbuffer()->GetSamples()));
|
|
||||||
} else if (attachment.IsTexture() && attachment.GetTexture()) {
|
|
||||||
// Multisample texture attachments count too (GL_SAMPLE_BUFFERS must
|
|
||||||
// report 1 for any multisampled draw framebuffer).
|
|
||||||
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetTexture()->GetSamples()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxSamples;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordIndexedOnlyGetterError(const char* functionName, GLenum pname) {
|
void RecordIndexedOnlyGetterError(const char* functionName, GLenum pname) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
@@ -484,6 +466,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, kFrontendMaxSamples);
|
return std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, kFrontendMaxSamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Declared in GL_Getter.h, so that the draw path can feed the same number to the reserved
|
||||||
|
// gl_NumSamples stand-in that glGetIntegerv(GL_SAMPLES) reports.
|
||||||
|
GLint ResolveDrawFramebufferSampleCount() {
|
||||||
|
const auto& drawFbo =
|
||||||
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||||
|
if (!drawFbo) return 0;
|
||||||
|
|
||||||
|
GLint maxSamples = 0;
|
||||||
|
for (const auto& attachment : drawFbo->GetAllAttachmentObjects()) {
|
||||||
|
if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
|
||||||
|
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetRenderbuffer()->GetSamples()));
|
||||||
|
} else if (attachment.IsTexture() && attachment.GetTexture()) {
|
||||||
|
// Multisample texture attachments count too (GL_SAMPLE_BUFFERS must
|
||||||
|
// report 1 for any multisampled draw framebuffer).
|
||||||
|
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetTexture()->GetSamples()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxSamples;
|
||||||
|
}
|
||||||
|
|
||||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||||
const GLubyte* GetString(GLenum name) {
|
const GLubyte* GetString(GLenum name) {
|
||||||
static String vendorString;
|
static String vendorString;
|
||||||
|
|||||||
@@ -28,4 +28,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// core minimum. Frontend multisample validators have to honour this ceiling for every
|
// core minimum. Frontend multisample validators have to honour this ceiling for every
|
||||||
// format, otherwise MobileGL rejects a sample count it advertised itself.
|
// format, otherwise MobileGL rejects a sample count it advertised itself.
|
||||||
GLint GetAdvertisedMaxSamples();
|
GLint GetAdvertisedMaxSamples();
|
||||||
|
// What glGetIntegerv(GL_SAMPLES) answers for the CURRENT draw framebuffer: the largest sample
|
||||||
|
// count over its attachments, and 0 for a single-sample or default framebuffer (GL 4.6 core
|
||||||
|
// 9.2.3 / 22.2 - GL_SAMPLE_BUFFERS is 1 exactly when this is non-zero).
|
||||||
|
//
|
||||||
|
// Shared rather than duplicated because two callers need the identical number and disagreeing
|
||||||
|
// would be a silent bug: the query itself, and the draw path's write of the reserved
|
||||||
|
// gl_NumSamples stand-in - a shader comparing gl_NumSamples against glGetIntegerv(GL_SAMPLES)
|
||||||
|
// is exactly what the sample_variables CTS does.
|
||||||
|
GLint ResolveDrawFramebufferSampleCount();
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -1136,6 +1136,20 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
in.externalIndex, uniform.name.c_str());
|
in.externalIndex, uniform.name.c_str());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// The gl_NumSamples stand-in InjectNumSamplesBuiltinShim declared. It is a driver
|
||||||
|
// uniform, not the application's: gl_NumSamples is a BUILT-IN, so a conformant
|
||||||
|
// implementation reports nothing for it in GL_ACTIVE_UNIFORMS, glGetActiveUniform or
|
||||||
|
// glGetUniformLocation, and nothing may write it through glUniform* either. Filtering
|
||||||
|
// it here does both, and costs it no storage: BuildGlobalUboRouting takes its offset
|
||||||
|
// from the SPIR-V metadata by name, not from the GL location space.
|
||||||
|
if (isGlobalUboMember(uniform) &&
|
||||||
|
uniform.name == MG_Util::ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME) {
|
||||||
|
artifacts.usesReservedNumSamples = true;
|
||||||
|
MGLOG_D("ProgramObject %u: Reflection - reserved gl_NumSamples stand-in '%s' hidden from the GL "
|
||||||
|
"uniform surface",
|
||||||
|
in.externalIndex, uniform.name.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (isBufferVariable(uniform)) {
|
if (isBufferVariable(uniform)) {
|
||||||
MGLOG_D("ProgramObject %u: Reflection - buffer variable '%s' filtered from the GL uniform "
|
MGLOG_D("ProgramObject %u: Reflection - buffer variable '%s' filtered from the GL uniform "
|
||||||
"surface",
|
"surface",
|
||||||
|
|||||||
@@ -787,6 +787,33 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
void MarkUBOContentDirty() const {
|
void MarkUBOContentDirty() const {
|
||||||
if (++m_uboContentVersion == ~0u) m_uboContentVersion = 0;
|
if (++m_uboContentVersion == ~0u) m_uboContentVersion = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- the reserved gl_NumSamples stand-in (ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME) ----
|
||||||
|
//
|
||||||
|
// PHASE A: answerable without joining the SPIR-V job, which is what lets the draw path ask
|
||||||
|
// every program this question and pay nothing for the overwhelming majority that say no.
|
||||||
|
Bool UsesReservedNumSamples() const { return Artifacts().usesReservedNumSamples; }
|
||||||
|
|
||||||
|
// Publishes `samples` into the global-UBO shadow. Returns false when there is nowhere to
|
||||||
|
// put it - no shim in this program, no SPIR-V (a cancelled phase B), or the optimizer
|
||||||
|
// dropped the member because nothing read it after all - all of which are ordinary states,
|
||||||
|
// not errors. A value-identical write is dropped without bumping the content version, so a
|
||||||
|
// steady stream of draws into one framebuffer does not force a re-upload per draw.
|
||||||
|
Bool WriteReservedNumSamples(Int samples) {
|
||||||
|
if (!UsesReservedNumSamples()) return false;
|
||||||
|
SpirvArtifacts& spirv = Spirv();
|
||||||
|
const Uint offset = spirv.reservedNumSamplesOffset;
|
||||||
|
if (offset == kInvalidUniformOffset) return false;
|
||||||
|
if (static_cast<SizeT>(offset) + sizeof(Int) > spirv.globalUboScratch.size()) return false;
|
||||||
|
|
||||||
|
Uint8* const slot = spirv.globalUboScratch.data() + offset;
|
||||||
|
Int current = 0;
|
||||||
|
Memcpy(¤t, slot, sizeof(Int));
|
||||||
|
if (current == samples) return true;
|
||||||
|
Memcpy(slot, &samples, sizeof(Int));
|
||||||
|
MarkUBOContentDirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// ---- glUniform* inside the phase-A -> phase-B window ----
|
// ---- glUniform* inside the phase-A -> phase-B window ----
|
||||||
//
|
//
|
||||||
// True while the program is fully linked and fully queryable but its uniform shadow's
|
// True while the program is fully linked and fully queryable but its uniform shadow's
|
||||||
@@ -1296,6 +1323,14 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
std::set<String> uniformBlocksWithoutBinding;
|
std::set<String> uniformBlocksWithoutBinding;
|
||||||
|
|
||||||
Uint activeUniformCount = 0;
|
Uint activeUniformCount = 0;
|
||||||
|
// This program's fragment stage read gl_NumSamples, so the source pipeline lowered it
|
||||||
|
// onto the reserved default-block uniform (ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME)
|
||||||
|
// and the draw path owes it the draw framebuffer's sample count before every draw.
|
||||||
|
//
|
||||||
|
// PHASE A on purpose, even though the byte offset it needs is phase-B output: the
|
||||||
|
// gate has to be answerable without joining the SPIR-V job, or every draw of every
|
||||||
|
// program would pay a join to discover it has nothing to write.
|
||||||
|
Bool usesReservedNumSamples = false;
|
||||||
Uint maxUniformLocation = 0;
|
Uint maxUniformLocation = 0;
|
||||||
Int uniformNameMaxLength = 0;
|
Int uniformNameMaxLength = 0;
|
||||||
Int attribInNameMaxLength = 0;
|
Int attribInNameMaxLength = 0;
|
||||||
@@ -1348,6 +1383,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// kInvalidUniformOffset. Sized maxUniformLocation + 1 by the routing pass.
|
// kInvalidUniformOffset. Sized maxUniformLocation + 1 by the routing pass.
|
||||||
Vector<Uint> uniformOffsets;
|
Vector<Uint> uniformOffsets;
|
||||||
Vector<Uint8> globalUboScratch;
|
Vector<Uint8> globalUboScratch;
|
||||||
|
// Byte offset of the reserved gl_NumSamples stand-in inside globalUboScratch, or
|
||||||
|
// kInvalidUniformOffset. Taken by NAME from the SPIR-V metadata rather than through
|
||||||
|
// uniformOffsets, because the member has no GL location at all: the link task keeps
|
||||||
|
// it out of the GL-visible uniform index space so no application can see or write it.
|
||||||
|
Uint reservedNumSamplesOffset = kInvalidUniformOffset;
|
||||||
// False for a program whose SPIR-V was never produced (phase B cancelled at
|
// False for a program whose SPIR-V was never produced (phase B cancelled at
|
||||||
// teardown or by a relink) or whose optimizer run failed. GL has no way to
|
// teardown or by a relink) or whose optimizer run failed. GL has no way to
|
||||||
// retract a LINK_STATUS it already reported true, so such a program stays
|
// retract a LINK_STATUS it already reported true, so such a program stays
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
artifacts.uniformOffsets.clear();
|
artifacts.uniformOffsets.clear();
|
||||||
artifacts.globalUboScratch.clear();
|
artifacts.globalUboScratch.clear();
|
||||||
|
artifacts.reservedNumSamplesOffset = ProgramObject::kInvalidUniformOffset;
|
||||||
// kInvalidUniformOffset marks locations that end up without global-UBO backing
|
// kInvalidUniformOffset marks locations that end up without global-UBO backing
|
||||||
// (e.g. the optimizer eliminated every use of the uniform); the fallback pass
|
// (e.g. the optimizer eliminated every use of the uniform); the fallback pass
|
||||||
// below gives those locations tail storage so glUniform* always has a target.
|
// below gives those locations tail storage so glUniform* always has a target.
|
||||||
@@ -311,6 +312,18 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
artifacts.globalUboScratch.resize(size);
|
artifacts.globalUboScratch.resize(size);
|
||||||
}
|
}
|
||||||
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
|
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
|
||||||
|
// The gl_NumSamples stand-in is routed by NAME and nothing else. It has no GL
|
||||||
|
// location to look up - DoReflection hides it from the GL uniform index space
|
||||||
|
// precisely so no application can address it - so the lookup below would find
|
||||||
|
// nothing and log it as unbacked. Only the fragment stage declares it, and
|
||||||
|
// every stage's copy sits at the same offset in the one shared global UBO.
|
||||||
|
if (name == NUM_SAMPLES_UNIFORM_NAME) {
|
||||||
|
artifacts.reservedNumSamplesOffset = offset;
|
||||||
|
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - reserved gl_NumSamples stand-in '%s' "
|
||||||
|
"backed at UBO offset %u",
|
||||||
|
externalIndex, name.c_str(), offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// SPIRV-Reflect leaf names never carry a "[0]" suffix; frontend
|
// SPIRV-Reflect leaf names never carry a "[0]" suffix; frontend
|
||||||
// reflection keys arrays as "arr[0]" (GL naming), so retry with the
|
// reflection keys arrays as "arr[0]" (GL naming), so retry with the
|
||||||
// suffix before declaring the uniform unbacked.
|
// suffix before declaring the uniform unbacked.
|
||||||
|
|||||||
@@ -234,6 +234,17 @@ namespace {
|
|||||||
// Whether the parsed #version directive is a well-formed one MobileGL should rewrite. A
|
// Whether the parsed #version directive is a well-formed one MobileGL should rewrite. A
|
||||||
// malformed directive (see IsRecognizedGlslVersion) is left alone for glslang to reject.
|
// malformed directive (see IsRecognizedGlslVersion) is left alone for glslang to reject.
|
||||||
bool hasValidVersionDirective = false;
|
bool hasValidVersionDirective = false;
|
||||||
|
// Every extension the source NAMES in an "#extension <name> : <behavior>" directive, and
|
||||||
|
// the subset whose behavior switches it on. Both are needed and they are not the same
|
||||||
|
// question: glslang's ES preamble defines an extension's macro whatever behavior the
|
||||||
|
// shader later asks for (it is a preamble, it runs first), while whether gl_NumSamples is
|
||||||
|
// a legal identifier depends on the extension actually being ENABLED.
|
||||||
|
std::set<MobileGL::String> namedExtensions;
|
||||||
|
std::set<MobileGL::String> enabledExtensions;
|
||||||
|
// Byte ranges [begin, end) of every #version directive AFTER the first that repeats it
|
||||||
|
// exactly - same version number, same profile, both well-formed. See
|
||||||
|
// BlankRedundantVersionDirectives for why these are tolerated and nothing else is.
|
||||||
|
Vector<std::pair<SizeT, SizeT>> redundantVersionDirectives;
|
||||||
|
|
||||||
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
|
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
|
||||||
};
|
};
|
||||||
@@ -262,7 +273,7 @@ namespace {
|
|||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
const MobileGL::String directive = ReadDirectiveIdentifier(code, probe, lineEnd);
|
const MobileGL::String directive = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
|
||||||
if (directive == "version" && !info.HasVersionDirective()) {
|
if (directive == "version") {
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
unsigned version = 0;
|
unsigned version = 0;
|
||||||
bool hasVersionDigits = false;
|
bool hasVersionDigits = false;
|
||||||
@@ -272,30 +283,39 @@ namespace {
|
|||||||
probe++;
|
probe++;
|
||||||
}
|
}
|
||||||
if (hasVersionDigits) {
|
if (hasVersionDigits) {
|
||||||
info.version = version;
|
|
||||||
info.versionDirectiveStart = directiveStart;
|
|
||||||
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
|
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
const MobileGL::String profile = ReadDirectiveIdentifier(code, probe, lineEnd);
|
const MobileGL::String profileToken = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
MobileGL::ShaderProfile profile = MobileGL::ShaderProfile::Core;
|
||||||
bool profileTokenValid = true;
|
bool profileTokenValid = true;
|
||||||
if (profile.empty() || profile == "core") {
|
if (profileToken.empty() || profileToken == "core") {
|
||||||
info.profile = MobileGL::ShaderProfile::Core;
|
profile = MobileGL::ShaderProfile::Core;
|
||||||
} else if (profile == "es" || profile == "ES") {
|
} else if (profileToken == "es" || profileToken == "ES") {
|
||||||
info.profile = MobileGL::ShaderProfile::ES;
|
profile = MobileGL::ShaderProfile::ES;
|
||||||
} else if (profile == "compatibility") {
|
} else if (profileToken == "compatibility") {
|
||||||
info.profile = MobileGL::ShaderProfile::Compatibility;
|
profile = MobileGL::ShaderProfile::Compatibility;
|
||||||
} else {
|
} else {
|
||||||
// "#version 330 foo": an unrecognized profile keyword. Keep Core for any
|
// "#version 330 foo": an unrecognized profile keyword. Keep Core for any
|
||||||
// downstream routing, but mark the directive malformed.
|
// downstream routing, but mark the directive malformed.
|
||||||
info.profile = MobileGL::ShaderProfile::Core;
|
profile = MobileGL::ShaderProfile::Core;
|
||||||
profileTokenValid = false;
|
profileTokenValid = false;
|
||||||
}
|
}
|
||||||
// Comments are already masked to spaces, so anything non-blank left on the
|
// Comments are already masked to spaces, so anything non-blank left on the
|
||||||
// line is real trailing garbage: "#version 330 foobar" / "#version 330.0".
|
// line is real trailing garbage: "#version 330 foobar" / "#version 330.0".
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
const bool hasTrailingTokens = probe < lineEnd;
|
const bool hasTrailingTokens = probe < lineEnd;
|
||||||
info.hasValidVersionDirective =
|
const bool directiveIsValid =
|
||||||
IsRecognizedGlslVersion(info.version) && profileTokenValid && !hasTrailingTokens;
|
IsRecognizedGlslVersion(version) && profileTokenValid && !hasTrailingTokens;
|
||||||
|
|
||||||
|
if (!info.HasVersionDirective()) {
|
||||||
|
info.version = version;
|
||||||
|
info.profile = profile;
|
||||||
|
info.versionDirectiveStart = directiveStart;
|
||||||
|
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
|
||||||
|
info.hasValidVersionDirective = directiveIsValid;
|
||||||
|
} else if (directiveIsValid && info.hasValidVersionDirective && version == info.version &&
|
||||||
|
profile == info.profile) {
|
||||||
|
info.redundantVersionDirectives.push_back({directiveStart, lineEnd});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (directive == "extension") {
|
} else if (directive == "extension") {
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
@@ -309,6 +329,10 @@ namespace {
|
|||||||
extension == "GL_NV_gpu_shader5";
|
extension == "GL_NV_gpu_shader5";
|
||||||
const bool enablesExtension = behavior == "enable" || behavior == "require" ||
|
const bool enablesExtension = behavior == "enable" || behavior == "require" ||
|
||||||
behavior == "warn";
|
behavior == "warn";
|
||||||
|
if (!extension.empty()) {
|
||||||
|
info.namedExtensions.insert(extension);
|
||||||
|
if (enablesExtension) info.enabledExtensions.insert(extension);
|
||||||
|
}
|
||||||
// Gate the whole source if it ever opts into either extension. This is deliberately
|
// Gate the whole source if it ever opts into either extension. This is deliberately
|
||||||
// conservative around conditional directives and keeps legal sample qualifiers intact.
|
// conservative around conditional directives and keeps legal sample qualifiers intact.
|
||||||
info.enablesGpuShader5 = info.enablesGpuShader5 || (isGpuShader5 && enablesExtension);
|
info.enablesGpuShader5 = info.enablesGpuShader5 || (isGpuShader5 && enablesExtension);
|
||||||
@@ -367,9 +391,40 @@ namespace {
|
|||||||
// (FindAfterVersionDirective -> InspectShaderLanguage). Each branch below leaves the bytes
|
// (FindAfterVersionDirective -> InspectShaderLanguage). Each branch below leaves the bytes
|
||||||
// ahead of the directive untouched apart from the BOM erase, and each replacement text is
|
// ahead of the directive untouched apart from the BOM erase, and each replacement text is
|
||||||
// exactly one newline-terminated line, so the arithmetic is exact in all three cases.
|
// exactly one newline-terminated line, so the arithmetic is exact in all three cases.
|
||||||
|
// An exact repeat of the #version directive the shader already declared, blanked out.
|
||||||
|
//
|
||||||
|
// Strictly a repeat: InspectShaderLanguage only records a range here when the FIRST directive
|
||||||
|
// was well-formed and the later one is well-formed, names the same version number and the same
|
||||||
|
// profile, and is therefore semantically a no-op. Everything else - a differing version, a
|
||||||
|
// malformed one, or a lone #version that is simply not first - is left exactly where the
|
||||||
|
// application put it, so KHR-GL33.shaders.preprocessor.directive.version_not_first_statement_*
|
||||||
|
// and the version_invalid_token_* family keep failing to compile the way they must.
|
||||||
|
//
|
||||||
|
// Why tolerate even the repeat: glShaderSource concatenates its strings with nothing added
|
||||||
|
// between them (GL 4.6 core 7.1), and a caller that puts a #version at the head of BOTH strings
|
||||||
|
// gets the second one spliced into the tail of the first - which is exactly what VK-GL-CTS's
|
||||||
|
// ShaderImageLoadStoreBase::BuildProgram does (kGLSLPrec ends without a newline, and
|
||||||
|
// NegativeUniform's own sources begin with "#version 310 es"). Desktop drivers accept it; the
|
||||||
|
// duplicate says nothing new, so honouring it costs no semantics.
|
||||||
|
//
|
||||||
|
// Blanked rather than erased so that every offset in `info` - which was measured against this
|
||||||
|
// same source - stays valid, and so the line count, and with it __LINE__ and every glslang
|
||||||
|
// diagnostic, is untouched.
|
||||||
|
void BlankRedundantVersionDirectives(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
||||||
|
for (const auto& [begin, end] : info.redundantVersionDirectives) {
|
||||||
|
if (begin >= source.size() || end > source.size() || begin >= end) continue;
|
||||||
|
std::fill(source.begin() + static_cast<std::ptrdiff_t>(begin),
|
||||||
|
source.begin() + static_cast<std::ptrdiff_t>(end), ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SizeT NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
SizeT NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
||||||
const SizeT bomBytes = info.hasUtf8Bom ? 3 : 0;
|
const SizeT bomBytes = info.hasUtf8Bom ? 3 : 0;
|
||||||
|
|
||||||
|
// First, while every offset in `info` still refers to the untouched source. Each range
|
||||||
|
// lies strictly after the first directive, so nothing below has to account for it.
|
||||||
|
BlankRedundantVersionDirectives(source, info);
|
||||||
|
|
||||||
// A malformed #version (329, 331, bad profile, float/trailing tokens) is left exactly as the
|
// A malformed #version (329, 331, bad profile, float/trailing tokens) is left exactly as the
|
||||||
// application wrote it so glslang rejects it - rewriting it to "#version 330 core" would
|
// application wrote it so glslang rejects it - rewriting it to "#version 330 core" would
|
||||||
// silently legalize the CTS directive.version_* rejection cases. Still drop a leading BOM so
|
// silently legalize the CTS directive.version_* rejection cases. Still drop a leading BOM so
|
||||||
@@ -1435,6 +1490,158 @@ namespace {
|
|||||||
"#define gl_DepthRange mg_DepthRange\n";
|
"#define gl_DepthRange mg_DepthRange\n";
|
||||||
source.insert(afterVersion.Get(source), shim);
|
source.insert(afterVersion.Get(source), shim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whole-identifier search over an already-masked source. A bare find() would fire on
|
||||||
|
// "mg_NumSamplesFoo" and on the word inside a comment; this fires only on the token.
|
||||||
|
bool MaskedSourceHasIdentifier(const MobileGL::String& masked, MobileGL::StringView identifier) {
|
||||||
|
SizeT pos = 0;
|
||||||
|
while ((pos = masked.find(identifier.data(), pos, identifier.size())) != MobileGL::String::npos) {
|
||||||
|
const SizeT end = pos + identifier.size();
|
||||||
|
const bool hasLeftBoundary = pos == 0 || !IsIdentifierChar(masked[pos - 1]);
|
||||||
|
const bool hasRightBoundary = end >= masked.size() || !IsIdentifierChar(masked[end]);
|
||||||
|
if (hasLeftBoundary && hasRightBoundary) return true;
|
||||||
|
pos = end;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The extension macros glslang's ES preamble defines and its DESKTOP preamble does not
|
||||||
|
// (TParseVersions::getPreamble, Versions.cpp). Transcribed rather than derived because the
|
||||||
|
// preamble is a string literal inside glslang with no programmatic accessor; the SET is what
|
||||||
|
// matters, and it is stable - these are the AEP/OES/EXT names ESSL has carried since 3.10.
|
||||||
|
//
|
||||||
|
// GL_ES and GL_FRAGMENT_PRECISION_HIGH are DELIBERATELY absent. The shader really is being
|
||||||
|
// compiled as desktop by the time this runs, so flipping an `#ifdef GL_ES` branch would hand
|
||||||
|
// glslang the ESSL half of a shader written to be portable - which is the branch that does not
|
||||||
|
// parse under core 4.60. (GL_FRAGMENT_PRECISION_HIGH is in glslang's desktop preamble anyway.)
|
||||||
|
bool IsEsOnlyPreambleExtensionMacro(const MobileGL::String& name, unsigned version) {
|
||||||
|
// Guarded by an ES version in glslang's preamble; the rest are unconditional.
|
||||||
|
if (name == "GL_NV_shader_noperspective_interpolation") return version >= 300;
|
||||||
|
|
||||||
|
static const std::set<MobileGL::String> kEsOnlyPreambleMacros = {
|
||||||
|
"GL_ANDROID_extension_pack_es31a",
|
||||||
|
"GL_EXT_YUV_target",
|
||||||
|
"GL_EXT_blend_func_extended",
|
||||||
|
"GL_EXT_frag_depth",
|
||||||
|
"GL_EXT_geometry_point_size",
|
||||||
|
"GL_EXT_geometry_shader",
|
||||||
|
"GL_EXT_gpu_shader5",
|
||||||
|
"GL_EXT_primitive_bounding_box",
|
||||||
|
"GL_EXT_shader_implicit_conversions",
|
||||||
|
"GL_EXT_shader_io_blocks",
|
||||||
|
"GL_EXT_shader_texture_lod",
|
||||||
|
"GL_EXT_shadow_samplers",
|
||||||
|
"GL_EXT_tessellation_point_size",
|
||||||
|
"GL_EXT_tessellation_shader",
|
||||||
|
"GL_EXT_texture_buffer",
|
||||||
|
"GL_EXT_texture_cube_map_array",
|
||||||
|
"GL_OES_EGL_image_external",
|
||||||
|
"GL_OES_EGL_image_external_essl3",
|
||||||
|
"GL_OES_geometry_point_size",
|
||||||
|
"GL_OES_geometry_shader",
|
||||||
|
"GL_OES_gpu_shader5",
|
||||||
|
"GL_OES_primitive_bounding_box",
|
||||||
|
"GL_OES_sample_variables",
|
||||||
|
"GL_OES_shader_image_atomic",
|
||||||
|
"GL_OES_shader_io_blocks",
|
||||||
|
"GL_OES_shader_multisample_interpolation",
|
||||||
|
"GL_OES_standard_derivatives",
|
||||||
|
"GL_OES_tessellation_point_size",
|
||||||
|
"GL_OES_tessellation_shader",
|
||||||
|
"GL_OES_texture_3D",
|
||||||
|
"GL_OES_texture_buffer",
|
||||||
|
"GL_OES_texture_cube_map_array",
|
||||||
|
"GL_OES_texture_storage_multisample_2d_array",
|
||||||
|
};
|
||||||
|
return kEsOnlyPreambleMacros.count(name) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNormalizedVersionDirective rewrites every ES-profile shader to "#version 460 core", so
|
||||||
|
// glslang deduces a desktop profile and emits its DESKTOP preamble - and every ES-only
|
||||||
|
// extension macro the shader is entitled to disappears with it. A CTS shader guarded by
|
||||||
|
// `#if !GL_OES_sample_variables / this is broken / #endif` then takes the broken branch.
|
||||||
|
//
|
||||||
|
// The extension BEHAVIOUR survives the rewrite (glslang honours "#extension X : require" under
|
||||||
|
// either profile), so this is a preamble-fidelity gap and nothing more; restoring the macro is
|
||||||
|
// the whole fix.
|
||||||
|
//
|
||||||
|
// Strictly limited to extensions the source itself NAMES in an #extension directive. Any macro
|
||||||
|
// injected into a desktop parse can flip a preprocessor branch, and the ES preamble carries
|
||||||
|
// three dozen of them - defining the lot would rewrite shaders that never asked.
|
||||||
|
void InjectEsPreambleExtensionMacros(const ShaderLanguageInfo& info, MobileGL::String& source,
|
||||||
|
AfterVersionAnchor& afterVersion) {
|
||||||
|
// Only where the rewrite actually happened: a malformed directive is left for glslang to
|
||||||
|
// reject, and a desktop source already gets the preamble it is entitled to.
|
||||||
|
if (info.profile != MobileGL::ShaderProfile::ES) return;
|
||||||
|
if (!info.hasValidVersionDirective) return;
|
||||||
|
if (info.namedExtensions.empty()) return;
|
||||||
|
|
||||||
|
MobileGL::String shim;
|
||||||
|
// std::set iteration order, so the injected block is deterministic for the translation
|
||||||
|
// cache and for the byte-exact preprocessor tests.
|
||||||
|
for (const MobileGL::String& extension : info.namedExtensions) {
|
||||||
|
if (!IsEsOnlyPreambleExtensionMacro(extension, info.version)) continue;
|
||||||
|
shim += "#define " + extension + " 1\n";
|
||||||
|
}
|
||||||
|
if (shim.empty()) return;
|
||||||
|
source.insert(afterVersion.Get(source), shim);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gl_NumSamples is legal in this source only where glslang would have declared it with a
|
||||||
|
// non-SPIR-V target (Initialize.cpp): desktop from 4.00 core, or from 1.30 with
|
||||||
|
// ARB_sample_shading; ESSL from 3.20, or from 3.10 with OES_sample_variables.
|
||||||
|
//
|
||||||
|
// The gate matters because the shim ends in "#define gl_NumSamples mg_NumSamples", and a
|
||||||
|
// #define is not scoped by anything: defining it for a source where the built-in does not
|
||||||
|
// exist would silently legalize a shader a conformant implementation rejects.
|
||||||
|
bool SourceMayUseSampleVariables(const ShaderLanguageInfo& info) {
|
||||||
|
if (!info.HasVersionDirective() || !info.hasValidVersionDirective) return false;
|
||||||
|
if (info.profile == MobileGL::ShaderProfile::ES) {
|
||||||
|
if (info.version >= 320) return true;
|
||||||
|
return info.version >= 310 && info.enabledExtensions.count("GL_OES_sample_variables") != 0;
|
||||||
|
}
|
||||||
|
if (info.version >= 400) return true;
|
||||||
|
return info.version >= 130 && info.enabledExtensions.count("GL_ARB_sample_shading") != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gl_NumSamples has no SPIR-V built-in to lower to, so glslang declares it only when it is NOT
|
||||||
|
// targeting SPIR-V - both the desktop branch and the ES branch of Initialize.cpp wrap the
|
||||||
|
// `uniform int gl_NumSamples;` line in `if (spvVersion.spv == 0)`. MobileGL always targets
|
||||||
|
// SPIR-V (ShaderCompiler sets EShTargetSpv on the OpenGL path as well as the Vulkan one), so
|
||||||
|
// the symbol is never in the table and every shader that reads it dies at compile time with
|
||||||
|
// "'gl_NumSamples' : undeclared identifier".
|
||||||
|
//
|
||||||
|
// Lower it to a real uniform instead. `uniform int mg_NumSamples;` is a default-block uniform,
|
||||||
|
// which the relaxed parse folds into MGL_GLOBAL_UBO - the one buffer BOTH backends already
|
||||||
|
// upload per draw - and the draw path writes the current draw framebuffer's sample count into
|
||||||
|
// it. Deliberately not a link-time constant: one program may be drawn into framebuffers of
|
||||||
|
// different sample counts, and baking the count at link would quietly hand it the wrong one.
|
||||||
|
//
|
||||||
|
// The alternative - deleting the `spvVersion.spv == 0` guard in the glslang fork - is worse,
|
||||||
|
// and not only because it is a fork change: glslang would then place a `gl_`-prefixed member
|
||||||
|
// inside MGL_GLOBAL_UBO, and ESSL reserves `gl_`, so the ES driver would reject SPIRV-Cross's
|
||||||
|
// output on the DirectGLES path.
|
||||||
|
void InjectNumSamplesBuiltinShim(MobileGL::ShaderStage stage, const ShaderLanguageInfo& info,
|
||||||
|
MobileGL::String& source, AfterVersionAnchor& afterVersion) {
|
||||||
|
// gl_NumSamples exists in the fragment stage only, in every profile.
|
||||||
|
if (stage != MobileGL::ShaderStage::Fragment) return;
|
||||||
|
if (!SourceMayUseSampleVariables(info)) return;
|
||||||
|
// Cheap reject before paying for the mask; the token cannot be there if the bytes are not.
|
||||||
|
if (source.find("gl_NumSamples") == MobileGL::String::npos) return;
|
||||||
|
|
||||||
|
const MobileGL::String masked = MaskCommentsAndQuotedText(source);
|
||||||
|
if (!MaskedSourceHasIdentifier(masked, "gl_NumSamples")) return;
|
||||||
|
// Someone already occupies the name - a re-preprocess of an already-shimmed source, or an
|
||||||
|
// application that happens to use it. Either way a second declaration would not compile.
|
||||||
|
if (MaskedSourceHasIdentifier(masked, MobileGL::MG_Util::ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const char* shim =
|
||||||
|
"uniform int mg_NumSamples;\n"
|
||||||
|
"#define gl_NumSamples mg_NumSamples\n";
|
||||||
|
source.insert(afterVersion.Get(source), shim);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace MobileGL {
|
namespace MobileGL {
|
||||||
@@ -1463,6 +1670,12 @@ namespace MobileGL {
|
|||||||
// via MaskCommentsAndQuotedText/TokenizeCode, so the source we hand glslang keeps them.
|
// via MaskCommentsAndQuotedText/TokenizeCode, so the source we hand glslang keeps them.
|
||||||
NormalizeLineDirectives(source, afterVersion.Get(source));
|
NormalizeLineDirectives(source, afterVersion.Get(source));
|
||||||
|
|
||||||
|
// Before anything that could branch on one: an ES source rewritten to desktop has
|
||||||
|
// lost glslang's ES preamble, and the macros it carried are what the shader's own
|
||||||
|
// #if guards read. Keyed off originalLanguage because the directive has already
|
||||||
|
// been rewritten by now and no longer says "es".
|
||||||
|
InjectEsPreambleExtensionMacros(originalLanguage, source, afterVersion);
|
||||||
|
|
||||||
// noperspective is intentionally NOT touched here. It is core in desktop GLSL (1.30+)
|
// noperspective is intentionally NOT touched here. It is core in desktop GLSL (1.30+)
|
||||||
// and maps to the core SPIR-V NoPerspective decoration, which DirectVulkan renders
|
// and maps to the core SPIR-V NoPerspective decoration, which DirectVulkan renders
|
||||||
// natively and SPIRV-Cross turns into ESSL `noperspective` + the
|
// natively and SPIRV-Cross turns into ESSL `noperspective` + the
|
||||||
@@ -1487,6 +1700,7 @@ namespace MobileGL {
|
|||||||
|
|
||||||
ModernizeLegacyGLSL(stage, source, afterVersion);
|
ModernizeLegacyGLSL(stage, source, afterVersion);
|
||||||
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
||||||
|
InjectNumSamplesBuiltinShim(stage, originalLanguage, source, afterVersion);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,20 @@ namespace MobileGL {
|
|||||||
namespace MG_Util {
|
namespace MG_Util {
|
||||||
namespace ShaderTranspiler {
|
namespace ShaderTranspiler {
|
||||||
inline const char* GLOBAL_UBO_NAME = "MGL_GLOBAL_UBO";
|
inline const char* GLOBAL_UBO_NAME = "MGL_GLOBAL_UBO";
|
||||||
|
// The default-block uniform InjectNumSamplesBuiltinShim declares to stand in for the
|
||||||
|
// gl_NumSamples built-in, which glslang does not put in the symbol table under a
|
||||||
|
// SPIR-V target (Initialize.cpp guards both the desktop and the ES declaration on
|
||||||
|
// `spvVersion.spv == 0`, and MobileGL always targets SPIR-V). The relaxed parse folds
|
||||||
|
// it into GLOBAL_UBO_NAME like any other default-block uniform, which is what lets
|
||||||
|
// BOTH backends pick the value up from the one buffer they already upload; the link
|
||||||
|
// task keeps it out of the GL-visible uniform surface, and the draw path writes the
|
||||||
|
// current draw framebuffer's sample count into it.
|
||||||
|
//
|
||||||
|
// RESERVED, not merely conventional: a shader that declares this name itself keeps
|
||||||
|
// the shim from firing (the injector bails on it), but if it declares the name AND
|
||||||
|
// uses gl_NumSamples the link task will still hide its uniform. That is the same
|
||||||
|
// bargain every mg_-prefixed rewrite in this pipeline strikes.
|
||||||
|
inline const char* NUM_SAMPLES_UNIFORM_NAME = "mg_NumSamples";
|
||||||
// glslang's Vulkan-relaxed parse rewrites every atomic_uint into a member of a
|
// glslang's Vulkan-relaxed parse rewrites every atomic_uint into a member of a
|
||||||
// synthesized storage block named "<this>_<GL atomic-counter binding>"
|
// synthesized storage block named "<this>_<GL atomic-counter binding>"
|
||||||
// (ParseContextBase::growAtomicCounterBlock). That block IS the GL atomic counter
|
// (ParseContextBase::growAtomicCounterBlock). That block IS the GL atomic counter
|
||||||
|
|||||||
Reference in New Issue
Block a user