mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
[Fix, Feat, Test] (MG_Backend/DirectGLES, MG_Test): the draw-parameter builtins never reached the draws that carry them, and glMultiDrawArraysIndirectCount had no backend at all
This commit is contained in:
@@ -1002,6 +1002,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
funcsTable.GL.MultiDrawElementsIndirect = MultiDrawElementsIndirect;
|
||||
funcsTable.GL.MultiDrawElementsIndirectCount = MultiDrawElementsIndirectCount;
|
||||
funcsTable.GL.MultiDrawArraysIndirect = MultiDrawArraysIndirect;
|
||||
funcsTable.GL.MultiDrawArraysIndirectCount = MultiDrawArraysIndirectCount;
|
||||
funcsTable.GL.DrawRangeElementsBaseVertex = DrawRangeElementsBaseVertex;
|
||||
funcsTable.GL.DrawRangeElements = DrawRangeElements;
|
||||
funcsTable.GL.DrawElementsInstancedBaseVertexBaseInstance = DrawElementsInstancedBaseVertexBaseInstance;
|
||||
|
||||
@@ -2905,11 +2905,40 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
void SetCurrentBaseVertex(Int32 baseVertex) {
|
||||
if (const auto program = GetCurrentBackendProgram()) {
|
||||
program->SetBaseVertex(baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
Bool CurrentProgramReadsDrawID() {
|
||||
const auto program = GetCurrentBackendProgram();
|
||||
return program != nullptr && program->ReadsDrawID();
|
||||
}
|
||||
|
||||
Bool CurrentProgramReadsBaseVertex() {
|
||||
const auto program = GetCurrentBackendProgram();
|
||||
return program != nullptr && program->ReadsBaseVertex();
|
||||
}
|
||||
|
||||
// The two questions above, asked from BEFORE PrepareForDraw - where neither can be
|
||||
// answered honestly. GetCurrentBackendProgram only sees a twin that a previous draw
|
||||
// already synced, and a twin from before a relink still carries the previous link's
|
||||
// uniform locations, so "no" there means "not known yet" at least as often as it
|
||||
// means no. The multi-draw compute tier has to decide whether to flatten a batch
|
||||
// before PrepareForDraw runs (its dispatch cannot come after the draw state), and
|
||||
// flattening a batch that turns out to need per-sub-draw values is unrecoverable -
|
||||
// so an unanswerable program counts as needing them.
|
||||
Bool CurrentProgramMayNeedPerSubDrawBuiltins(Bool batchCarriesBaseVertices) {
|
||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||
const auto program = GetCurrentBackendProgram();
|
||||
if (!currentProgram || program == nullptr ||
|
||||
program->GetSyncedLinkVersion() != currentProgram->GetLinkVersion()) {
|
||||
return true;
|
||||
}
|
||||
return program->ReadsDrawID() || (batchCarriesBaseVertices && program->ReadsBaseVertex());
|
||||
}
|
||||
|
||||
static Bool SupportsNativeIndirectDraws() {
|
||||
const auto& version = g_GLESCapabilities.GLESVersion;
|
||||
const Bool esVersionOk = version.Major > 3 || (version.Major == 3 && version.Minor >= 1);
|
||||
@@ -2947,16 +2976,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->id);
|
||||
}
|
||||
}
|
||||
// gl_BaseVertex has no SSBO view of its own: the command's baseVertex word is read
|
||||
// from the CPU shadow, so a command whose baseVertex a compute shader wrote this
|
||||
// frame is not observable here (baseInstance is, through the view above). Feeding
|
||||
// the stale-but-usually-correct shadow beats leaving the uniform at the previous
|
||||
// draw's value, which is what a program reading gl_BaseVertex saw before.
|
||||
const Bool feedBaseVertex = CurrentProgramReadsBaseVertex();
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
const SizeT cmdByteOffset = commandOffset + static_cast<SizeT>(i) * stride;
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
if (paramsBinding >= 0 && backendProgram) {
|
||||
// baseInstance is the 5th word of DrawElementsIndirectCommand.
|
||||
backendProgram->SetBaseInstanceWordIndex(static_cast<Int32>((cmdByteOffset + 16) / 4));
|
||||
if (feedBaseVertex) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
SetCurrentBaseVertex(cmd.baseVertex);
|
||||
}
|
||||
} else {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
SetCurrentBaseVertex(cmd.baseVertex);
|
||||
}
|
||||
g_GLESFuncs.glDrawElementsIndirect(mode, type, reinterpret_cast<const void*>(cmdByteOffset));
|
||||
}
|
||||
@@ -2969,6 +3010,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
SetCurrentBaseVertex(cmd.baseVertex);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
@@ -2977,12 +3019,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
SetCurrentDrawID(0);
|
||||
SetCurrentBaseInstance(0);
|
||||
SetCurrentBaseVertex(0);
|
||||
}
|
||||
|
||||
static void ExecuteArraysIndirectCommands(GLenum mode, const Uint8* commandBytes, SizeT commandOffset,
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& drawIndirectBuffer,
|
||||
GLsizei drawcount, GLsizei stride, const char* label) {
|
||||
(void)label;
|
||||
// DrawArraysIndirectCommand has no baseVertex word, so gl_BaseVertex is zero for every
|
||||
// command here. Written BEFORE the draws, not merely restored after them: the previous
|
||||
// draw is what leaves a stale value, and restoring afterwards would only protect the
|
||||
// NEXT draw while these commands ran with the stale one.
|
||||
SetCurrentBaseVertex(0);
|
||||
const Bool useNative = drawIndirectBuffer != nullptr && SupportsNativeIndirectDraws();
|
||||
if (useNative) {
|
||||
const auto backendProgram = GetCurrentBackendProgram();
|
||||
@@ -3269,7 +3317,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
CheckPrimitiveRestartSupported(type);
|
||||
SetCurrentBaseVertex(basevertex);
|
||||
g_GLESFuncs.glDrawElementsBaseVertex(mode, count, type, indices, basevertex);
|
||||
SetCurrentBaseVertex(0);
|
||||
}
|
||||
|
||||
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
|
||||
@@ -3279,6 +3329,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::None;
|
||||
PrepareForDraw(syncBit);
|
||||
|
||||
// This loop IS the emulation - there is no batched tier for the non-indexed form -
|
||||
// so each sub-draw has to be given its own gl_DrawID here, exactly as the indexed
|
||||
// ladder and the indirect executors do. Without it every sub-draw of a
|
||||
// glMultiDrawArrays read draw index 0.
|
||||
const Bool feedDrawID = CurrentProgramReadsDrawID();
|
||||
const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray();
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
// Client-side arrays are uploaded per sub-draw range, like the single DrawArrays path.
|
||||
@@ -3288,8 +3343,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
(*backendVAOSlot)->SyncClientSideAttributesForDrawArrays(currentVAO, first[i], count[i]);
|
||||
}
|
||||
}
|
||||
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
g_GLESFuncs.glDrawArrays(mode, first[i], count[i]);
|
||||
}
|
||||
if (feedDrawID) SetCurrentDrawID(0);
|
||||
}
|
||||
|
||||
// Both glMultiDrawElements entry points are emulated - ES has neither in core - by the
|
||||
@@ -3403,6 +3460,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
// Both counts are read from the CPU shadow, which a buffer with no shadow does not
|
||||
// have - MappedData() is null there and the reads below would be a null dereference,
|
||||
// not a wrong picture. The DirectVulkan twin declines the same way.
|
||||
if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) {
|
||||
MGLOG_E("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or "
|
||||
"draw-indirect buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, parameterBuffer->MappedData() + drawcount, sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
@@ -3444,11 +3510,79 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
drawcount, stride, "MultiDrawArraysIndirect");
|
||||
}
|
||||
|
||||
// The non-indexed twin of MultiDrawElementsIndirectCount, and structurally identical to it:
|
||||
// ES has no GL_PARAMETER_BUFFER at all, so the draw count is read from the CPU shadow of the
|
||||
// bound one and the batch degenerates into an ordinary indirect multi-draw of that many
|
||||
// commands. Missing from the backend table until now, which made every
|
||||
// glMultiDrawArraysIndirectCount an INVALID_OPERATION ("backend does not support
|
||||
// indirect-parameter array draws") on DirectGLES while the extension was advertised.
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount,
|
||||
GLsizei stride) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
#endif
|
||||
if (maxdrawcount <= 0) {
|
||||
return;
|
||||
}
|
||||
if (stride == 0) {
|
||||
stride = sizeof(DrawArraysIndirectCommand);
|
||||
}
|
||||
if (stride < static_cast<GLsizei>(sizeof(DrawArraysIndirectCommand))) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu",
|
||||
stride, sizeof(DrawArraysIndirectCommand));
|
||||
return;
|
||||
}
|
||||
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndirectBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
|
||||
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
auto parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject();
|
||||
if (!drawBuffer) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: no GL_DRAW_INDIRECT_BUFFER is bound");
|
||||
return;
|
||||
}
|
||||
if (!parameterBuffer) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: no GL_PARAMETER_BUFFER is bound");
|
||||
return;
|
||||
}
|
||||
|
||||
drawBuffer->SyncPersistentMappedRange();
|
||||
parameterBuffer->SyncPersistentMappedRange();
|
||||
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
|
||||
sizeof(DrawArraysIndirectCommand);
|
||||
if (commandBytes > drawBuffer->GetSize()) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
if (drawcount < 0 || static_cast<SizeT>(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: invalid GL_PARAMETER_BUFFER binding or range");
|
||||
return;
|
||||
}
|
||||
|
||||
// See the indexed twin: no CPU shadow means no count to read, not a wrong one.
|
||||
if (parameterBuffer->MappedData() == nullptr || drawBuffer->MappedData() == nullptr) {
|
||||
MGLOG_E("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or "
|
||||
"draw-indirect buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, parameterBuffer->MappedData() + drawcount, sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
ExecuteArraysIndirectCommands(mode, drawBuffer->MappedData() + commandOffset, commandOffset, drawBuffer,
|
||||
static_cast<GLsizei>(actualDrawCount), stride, "MultiDrawArraysIndirectCount");
|
||||
}
|
||||
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer;
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseVertex(basevertex);
|
||||
g_GLESFuncs.glDrawRangeElementsBaseVertex(mode, start, end, count, type, indices, basevertex);
|
||||
SetCurrentBaseVertex(0);
|
||||
}
|
||||
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) {
|
||||
@@ -3462,7 +3596,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseInstance(baseinstance);
|
||||
SetCurrentBaseVertex(basevertex);
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex);
|
||||
SetCurrentBaseVertex(0);
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
|
||||
@@ -3470,7 +3606,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
GLsizei instancecount, GLint basevertex) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseVertex(basevertex);
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex);
|
||||
SetCurrentBaseVertex(0);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
GLsizei maxdrawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount,
|
||||
GLsizei stride);
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
const void* indices, GLint basevertex);
|
||||
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
|
||||
|
||||
@@ -155,6 +155,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// (possibly GPU-written) indirect command buffer, so its declaration expands into a
|
||||
// std430 SSBO view of that buffer indexed by a CPU-computed word index, with the plain
|
||||
// mg_BaseInstance uniform as the fallback for non-indirect draws.
|
||||
//
|
||||
// The word index is stored ONE-BASED, so that zero - the value every GLSL uniform starts
|
||||
// at - is the "not an indirect draw" sentinel. Nothing seeds this uniform before a
|
||||
// program's first draw, and the non-indirect draw entry points never write it at all, so a
|
||||
// zero-based index with a negative sentinel would leave every such draw reading
|
||||
// mg_indirectWords[0] out of a storage buffer no one bound. That is not a silent zero on a
|
||||
// real driver: it returned garbage on Adreno, and a garbage gl_BaseInstance pushed the CTS
|
||||
// shader_draw_parameters geometry clean off screen.
|
||||
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType) {
|
||||
if (shaderType != GL_VERTEX_SHADER) {
|
||||
return source;
|
||||
@@ -208,12 +216,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
" { highp uint mg_indirectWords[]; };\n";
|
||||
if (rebaseInstanceId) {
|
||||
machinery += String("#define ") + ZERO_BASED_INSTANCE_ID_NAME + " (gl_InstanceID - ((" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " >= 0) ? int(mg_indirectWords[uint(" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + ")]) : 0))\n";
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " > 0) ? int(mg_indirectWords[uint(" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " - 1)]) : 0))\n";
|
||||
}
|
||||
machinery += String("#define ") + BASE_INSTANCE_LOWERED_NAME + " ((" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " >= 0) ? int(mg_indirectWords[uint(" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + ")]) : " + BASE_INSTANCE_UNIFORM_NAME + ")";
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " > 0) ? int(mg_indirectWords[uint(" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " - 1)]) : " + BASE_INSTANCE_UNIFORM_NAME + ")";
|
||||
source.replace(pos, declaration.size(), machinery);
|
||||
break;
|
||||
}
|
||||
@@ -4594,6 +4602,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_baseInstanceUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId,
|
||||
BASE_INSTANCE_UNIFORM_NAME);
|
||||
m_drawIdUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId, DRAW_ID_UNIFORM_NAME);
|
||||
m_baseVertexUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId,
|
||||
BASE_VERTEX_UNIFORM_NAME);
|
||||
m_baseInstanceWordIndexUniformLocation =
|
||||
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME);
|
||||
// The mg_IndirectParams block binding is baked into the ESSL (ES cannot rebind
|
||||
@@ -4772,14 +4782,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceUniformLocation, static_cast<GLint>(baseInstance));
|
||||
}
|
||||
// A direct value disables the indirect-command-buffer read.
|
||||
SetBaseInstanceWordIndex(-1);
|
||||
}
|
||||
|
||||
// The uniform is written one-based so that its GLSL initial value, zero, already reads
|
||||
// as "no indirect command" - see PromoteDrawParameterGlobalsToUniforms.
|
||||
void BackendProgramObjectImpl::SetBaseInstanceWordIndex(Int32 wordIndex) const {
|
||||
if (m_baseInstanceWordIndexUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation, -1);
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation,
|
||||
wordIndex < 0 ? 0 : wordIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::SetBaseInstanceWordIndex(Int32 wordIndex) const {
|
||||
if (m_baseInstanceWordIndexUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation, wordIndex);
|
||||
void BackendProgramObjectImpl::SetBaseVertex(Int32 baseVertex) const {
|
||||
if (m_baseVertexUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseVertexUniformLocation, baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,14 +82,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// GLES core supports only GL_PRIMITIVE_RESTART_FIXED_INDEX. Throws when the app enabled
|
||||
// the arbitrary GL_PRIMITIVE_RESTART with a non-fixed index for this index type.
|
||||
void CheckPrimitiveRestartSupported(GLenum indexType);
|
||||
// Feed the current program's gl_BaseInstance / gl_DrawID emulation uniforms. Both are
|
||||
// no-ops when the program does not read the corresponding builtin.
|
||||
// Feed the current program's gl_BaseInstance / gl_DrawID / gl_BaseVertex emulation
|
||||
// uniforms. All are no-ops when the program does not read the corresponding builtin.
|
||||
void SetCurrentBaseInstance(Uint32 baseInstance);
|
||||
void SetCurrentDrawID(Uint32 drawId);
|
||||
// GL's gl_BaseVertex is the base-vertex parameter of an indexed draw and zero for every
|
||||
// command that has none - including all the DrawArrays forms - so every draw path that
|
||||
// does not carry one must leave this at zero rather than inherit the last draw's value.
|
||||
void SetCurrentBaseVertex(Int32 baseVertex);
|
||||
// True when the current program actually reads gl_DrawID, i.e. when a batched
|
||||
// (single driver call) multi-draw tier would have to feed it one value for the whole
|
||||
// batch and would therefore be wrong.
|
||||
Bool CurrentProgramReadsDrawID();
|
||||
// Same question for gl_BaseVertex: a batched multi-draw tier cannot give each sub-draw
|
||||
// its own base vertex through a uniform either.
|
||||
Bool CurrentProgramReadsBaseVertex();
|
||||
// Both of the above, conservatively, for a caller that must decide BEFORE PrepareForDraw
|
||||
// has synced the program - where "does not read it" is indistinguishable from "cannot be
|
||||
// asked yet". Answers true whenever the backend twin is missing or predates the current
|
||||
// link.
|
||||
Bool CurrentProgramMayNeedPerSubDrawBuiltins(Bool batchCarriesBaseVertices);
|
||||
|
||||
template <typename StateObject, typename BackendObject>
|
||||
class StateBackendObjectRegistry {
|
||||
@@ -1025,9 +1037,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void SetBaseInstance(Uint32 baseInstance) const;
|
||||
void SetBaseInstanceWordIndex(Int32 wordIndex) const;
|
||||
void SetDrawID(Uint32 drawId) const;
|
||||
void SetBaseVertex(Int32 baseVertex) const;
|
||||
// True when the transpiled program kept a gl_DrawID uniform, i.e. SetDrawID
|
||||
// actually reaches a shader read rather than being discarded.
|
||||
Bool ReadsDrawID() const { return m_drawIdUniformLocation >= 0; }
|
||||
// Same for gl_BaseVertex: only a program that reads it pays for the per-draw
|
||||
// uniform write, and only such a program needs the reset after one.
|
||||
Bool ReadsBaseVertex() const { return m_baseVertexUniformLocation >= 0; }
|
||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||
// False when the last SyncToBackend could not produce a usable program (a
|
||||
@@ -1077,6 +1093,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint m_backendGlobalUBOId = 0;
|
||||
Int m_baseInstanceUniformLocation = -1;
|
||||
Int m_drawIdUniformLocation = -1;
|
||||
Int m_baseVertexUniformLocation = -1;
|
||||
Int m_baseInstanceWordIndexUniformLocation = -1;
|
||||
Int m_indirectParamsBinding = -1;
|
||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||
|
||||
@@ -274,17 +274,22 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
// the batch's own shape - not the driver - rules it out; the compute tier keeps
|
||||
// its remaining feasibility checks inside its implementation, where the data it
|
||||
// has to walk is already in hand.
|
||||
GLESMultiDrawMode ResolveTierForBatch(Bool programReadsDrawID, Bool hasIndexBuffer) {
|
||||
GLESMultiDrawMode ResolveTierForBatch(Bool programReadsDrawID, Bool perSubDrawBaseVertex,
|
||||
Bool hasIndexBuffer) {
|
||||
ResolveTierOnce();
|
||||
GLESMultiDrawMode tier = g_resolvedTier;
|
||||
|
||||
// Batched tiers issue one driver entry for the whole batch, so the emulated
|
||||
// gl_DrawID uniform can only hold one value across every sub-draw. A program
|
||||
// that reads gl_DrawID gets an unrolled tier, which feeds each sub-draw its
|
||||
// own index (the spec's value); nothing else observes the difference.
|
||||
// own index (the spec's value); nothing else observes the difference. The
|
||||
// emulated gl_BaseVertex is one uniform for the same reason, so a batch whose
|
||||
// sub-draws carry their own base vertices unrolls too - even the Ext tier,
|
||||
// which hands the driver the whole basevertex array, can only leave ONE value
|
||||
// in the uniform the shader reads.
|
||||
const Bool batched = tier == GLESMultiDrawMode::Ext || tier == GLESMultiDrawMode::MultiIndirect ||
|
||||
tier == GLESMultiDrawMode::Compute;
|
||||
if (batched && programReadsDrawID) {
|
||||
if (batched && (programReadsDrawID || perSubDrawBaseVertex)) {
|
||||
tier = SupportsTier(GLESMultiDrawMode::BaseVertex) ? GLESMultiDrawMode::BaseVertex
|
||||
: GLESMultiDrawMode::DrawElements;
|
||||
}
|
||||
@@ -371,7 +376,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Bool RunIndirect(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex, Bool batched, Bool feedDrawID) {
|
||||
GLsizei drawcount, const GLint* basevertex, Bool batched, Bool feedDrawID,
|
||||
Bool feedBaseVertex) {
|
||||
if (!SupportsTier(batched ? GLESMultiDrawMode::MultiIndirect : GLESMultiDrawMode::Indirect)) return false;
|
||||
const SizeT indexSize = IndexTypeSize(type);
|
||||
if (indexSize == 0) return false;
|
||||
@@ -413,10 +419,12 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
} else {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(basevertex ? basevertex[i] : 0);
|
||||
const SizeT commandOffset = commandBase + static_cast<SizeT>(i) * sizeof(DrawElementsIndirectCommand);
|
||||
g_GLESFuncs.glDrawElementsIndirect(mode, type, reinterpret_cast<const void*>(commandOffset));
|
||||
}
|
||||
if (feedDrawID) SetCurrentDrawID(0);
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(0);
|
||||
}
|
||||
BufferImpl::BindBufferId(GL_DRAW_INDIRECT_BUFFER, previousIndirectBinding);
|
||||
NoteTierExecuted(batched ? GLESMultiDrawMode::MultiIndirect : GLESMultiDrawMode::Indirect);
|
||||
@@ -428,15 +436,17 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Bool RunBaseVertexLoop(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID) {
|
||||
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID, Bool feedBaseVertex) {
|
||||
if (!SupportsTier(GLESMultiDrawMode::BaseVertex)) return false;
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] <= 0) continue;
|
||||
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(basevertex ? basevertex[i] : 0);
|
||||
g_GLESFuncs.glDrawElementsBaseVertex(mode, count[i], type, indices[i],
|
||||
basevertex ? basevertex[i] : 0);
|
||||
}
|
||||
if (feedDrawID) SetCurrentDrawID(0);
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(0);
|
||||
NoteTierExecuted(GLESMultiDrawMode::BaseVertex);
|
||||
return true;
|
||||
}
|
||||
@@ -446,7 +456,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Bool RunRebasedDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
||||
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID) {
|
||||
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID,
|
||||
Bool feedBaseVertex) {
|
||||
const SizeT indexSize = IndexTypeSize(type);
|
||||
if (indexSize == 0) return false;
|
||||
|
||||
@@ -500,11 +511,16 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
if (count[i] <= 0) continue;
|
||||
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
// The base vertex is folded into the rewritten index stream here, so the
|
||||
// driver sees none - but gl_BaseVertex still has to report the value the
|
||||
// application passed for this sub-draw.
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(basevertex ? basevertex[i] : 0);
|
||||
g_GLESFuncs.glDrawElements(mode, count[i], GL_UNSIGNED_INT,
|
||||
reinterpret_cast<const void*>(indexBase + cursor * sizeof(Uint32)));
|
||||
cursor += static_cast<SizeT>(count[i]);
|
||||
}
|
||||
if (feedDrawID) SetCurrentDrawID(0);
|
||||
if (feedBaseVertex) SetCurrentBaseVertex(0);
|
||||
BufferImpl::BindBufferId(GL_ELEMENT_ARRAY_BUFFER, previousIndexBinding);
|
||||
NoteTierExecuted(GLESMultiDrawMode::DrawElements);
|
||||
return true;
|
||||
@@ -837,8 +853,15 @@ void main() {
|
||||
// afterwards would mean unpicking the program, SSBO and index bindings
|
||||
// PrepareForDraw just made, and a dispatch inside an open transform feedback
|
||||
// span is not legal at all. On success it hands back a flattened index stream.
|
||||
// A batch whose sub-draws carry their own base vertices cannot be flattened either
|
||||
// when the program reads gl_BaseVertex: one draw call leaves one uniform value.
|
||||
// Asked conservatively because this decision precedes PrepareForDraw - see
|
||||
// CurrentProgramMayNeedPerSubDrawBuiltins. Flattening is the irreversible half:
|
||||
// once the batch is one draw the values are gone, whereas declining to flatten only
|
||||
// costs the unrolled tier.
|
||||
FlattenedStream flattened;
|
||||
if (ResolvedTier() == GLESMultiDrawMode::Compute && !CurrentProgramReadsDrawID()) {
|
||||
if (ResolvedTier() == GLESMultiDrawMode::Compute &&
|
||||
!CurrentProgramMayNeedPerSubDrawBuiltins(basevertex != nullptr)) {
|
||||
FlattenWithCompute(mode, count, type, indices, drawcount, basevertex, flattened);
|
||||
}
|
||||
|
||||
@@ -852,8 +875,11 @@ void main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that PrepareForDraw has synced the program, both questions have real answers;
|
||||
// the tier choice and the per-sub-draw feeds use those, not the guess above.
|
||||
const Bool feedDrawID = CurrentProgramReadsDrawID();
|
||||
const GLESMultiDrawMode tier = ResolveTierForBatch(feedDrawID, hasIndexBuffer);
|
||||
const Bool feedBaseVertex = basevertex != nullptr && CurrentProgramReadsBaseVertex();
|
||||
const GLESMultiDrawMode tier = ResolveTierForBatch(feedDrawID, feedBaseVertex, hasIndexBuffer);
|
||||
|
||||
Bool drawn = false;
|
||||
switch (tier) {
|
||||
@@ -861,16 +887,19 @@ void main() {
|
||||
drawn = RunExt(mode, count, type, indices, drawcount, basevertex);
|
||||
break;
|
||||
case GLESMultiDrawMode::MultiIndirect:
|
||||
drawn = RunIndirect(mode, count, type, indices, drawcount, basevertex, /*batched=*/true, feedDrawID);
|
||||
drawn = RunIndirect(mode, count, type, indices, drawcount, basevertex, /*batched=*/true, feedDrawID,
|
||||
feedBaseVertex);
|
||||
break;
|
||||
case GLESMultiDrawMode::Indirect:
|
||||
drawn = RunIndirect(mode, count, type, indices, drawcount, basevertex, /*batched=*/false, feedDrawID);
|
||||
drawn = RunIndirect(mode, count, type, indices, drawcount, basevertex, /*batched=*/false, feedDrawID,
|
||||
feedBaseVertex);
|
||||
break;
|
||||
case GLESMultiDrawMode::BaseVertex:
|
||||
drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID);
|
||||
drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
|
||||
break;
|
||||
case GLESMultiDrawMode::DrawElements:
|
||||
drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID);
|
||||
drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID,
|
||||
feedBaseVertex);
|
||||
break;
|
||||
case GLESMultiDrawMode::Compute:
|
||||
// Its pre-pass ran above; reaching here means it declined this batch's shape.
|
||||
@@ -883,8 +912,13 @@ void main() {
|
||||
// below are the floor: a base-vertex replay where the driver has one, and the
|
||||
// rewritten index stream where it does not. Both are safe for any batch these
|
||||
// entry points can receive.
|
||||
if (!drawn) drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID);
|
||||
if (!drawn) drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID);
|
||||
if (!drawn) {
|
||||
drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
|
||||
}
|
||||
if (!drawn) {
|
||||
drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID,
|
||||
feedBaseVertex);
|
||||
}
|
||||
if (!drawn) {
|
||||
MGLOG_E("DirectGLES multi-draw: no usable tier for a %d sub-draw batch (mode 0x%x, type 0x%x); "
|
||||
"the batch was dropped",
|
||||
|
||||
@@ -346,8 +346,10 @@ void main() {
|
||||
|
||||
EXPECT_NE(rewritten.find("int instance = mg_ZeroBasedInstanceID + mg_BaseInstanceLowered;"),
|
||||
MobileGL::String::npos);
|
||||
EXPECT_NE(rewritten.find("#define mg_ZeroBasedInstanceID (gl_InstanceID - ((mg_BaseInstanceWordIndex >= 0) ? "
|
||||
"int(mg_indirectWords[uint(mg_BaseInstanceWordIndex)]) : 0))"),
|
||||
// One-based word index: zero is the "not an indirect draw" sentinel because that is
|
||||
// the value a GLSL uniform starts at and no draw path writes it before the first draw.
|
||||
EXPECT_NE(rewritten.find("#define mg_ZeroBasedInstanceID (gl_InstanceID - ((mg_BaseInstanceWordIndex > 0) ? "
|
||||
"int(mg_indirectWords[uint(mg_BaseInstanceWordIndex - 1)]) : 0))"),
|
||||
MobileGL::String::npos);
|
||||
EXPECT_NE(rewritten.find(
|
||||
"layout(std430, binding = 12) readonly buffer mg_IndirectParams { highp uint mg_indirectWords[]; };"),
|
||||
@@ -356,6 +358,38 @@ void main() {
|
||||
EXPECT_EQ(CountOccurrences(rewritten, "gl_InstanceID"), 1u);
|
||||
}
|
||||
|
||||
// The sentinel itself, on the builtin it exists for. A zero-based index with a
|
||||
// negative "off" value made every NON-indirect draw of such a program read
|
||||
// mg_indirectWords[0] out of a storage buffer nothing had bound - the uniform starts
|
||||
// at zero and no non-indirect draw path writes it - which is where the CTS
|
||||
// shader_draw_parameters cases lost their geometry on Adreno. Pinned as text because
|
||||
// this contract lives in two places at once: the generated ESSL below and the +1 that
|
||||
// BackendProgramObjectImpl::SetBaseInstanceWordIndex applies.
|
||||
TEST(DirectGLESSanity, TheIndirectWordIndexIsOneBasedSoItsUnwrittenValueMeansNotIndirect) {
|
||||
const ScopedGLESCapabilitiesOverride capsGuard;
|
||||
auto& caps = MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities;
|
||||
caps.IndirectDrawInstanceIdIncludesBaseInstance = false;
|
||||
caps.MaxShaderStorageBufferBindings = 13;
|
||||
|
||||
const MobileGL::String source = R"(#version 310 es
|
||||
highp int mg_BaseInstanceLowered;
|
||||
void main() {
|
||||
gl_Position = vec4(float(mg_BaseInstanceLowered));
|
||||
}
|
||||
)";
|
||||
|
||||
const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(
|
||||
source, GL_VERTEX_SHADER);
|
||||
|
||||
EXPECT_NE(rewritten.find("#define mg_BaseInstanceLowered ((mg_BaseInstanceWordIndex > 0) ? "
|
||||
"int(mg_indirectWords[uint(mg_BaseInstanceWordIndex - 1)]) : mg_BaseInstance)"),
|
||||
MobileGL::String::npos)
|
||||
<< rewritten;
|
||||
// A zero-based form would spell either of these; neither may survive.
|
||||
EXPECT_EQ(rewritten.find("mg_BaseInstanceWordIndex >= 0"), MobileGL::String::npos);
|
||||
EXPECT_EQ(rewritten.find("uint(mg_BaseInstanceWordIndex)"), MobileGL::String::npos);
|
||||
}
|
||||
|
||||
TEST(DirectGLESSanity, KeepsInstanceIdWhenIndirectDrawsAreConforming) {
|
||||
const ScopedGLESCapabilitiesOverride capsGuard;
|
||||
auto& caps = MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities;
|
||||
|
||||
Reference in New Issue
Block a user