mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Fix, Test] (MG_Backend/DirectGLES, MG_Util): baseInstance reached the shader but never the vertex fetch
This commit is contained in:
@@ -3591,13 +3591,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glDrawRangeElements(mode, start, end, count, type, indices);
|
||||
}
|
||||
|
||||
// True when the driver will apply baseInstance to the vertex fetch itself, in which case the
|
||||
// attribute-offset emulation must stay out of the way. SetCurrentBaseInstance is orthogonal
|
||||
// and runs either way - it feeds the shader's gl_BaseInstance, not the fetch.
|
||||
inline Bool UseNativeBaseInstance() {
|
||||
return g_GLESCapabilities.SupportsBaseInstance;
|
||||
}
|
||||
|
||||
// The emulated shift has to be in place before PrepareForDraw, because that is what syncs the
|
||||
// VAO; a zero here is what un-shifts the arrays for the next ordinary draw.
|
||||
inline Uint32 EmulatedFetchBaseInstance(GLuint baseinstance) {
|
||||
return UseNativeBaseInstance() ? 0u : static_cast<Uint32>(baseinstance);
|
||||
}
|
||||
|
||||
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseInstance(baseinstance);
|
||||
SetCurrentBaseVertex(basevertex);
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex);
|
||||
if (UseNativeBaseInstance()) {
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertexBaseInstanceEXT(mode, count, type, indices, instancecount,
|
||||
basevertex, baseinstance);
|
||||
} else {
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(mode, count, type, indices, instancecount, basevertex);
|
||||
}
|
||||
SetCurrentBaseVertex(0);
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
@@ -3614,9 +3633,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
||||
GLsizei instancecount, GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseInstance(baseinstance);
|
||||
g_GLESFuncs.glDrawElementsInstanced(mode, count, type, indices, instancecount);
|
||||
if (UseNativeBaseInstance()) {
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseInstanceEXT(mode, count, type, indices, instancecount,
|
||||
baseinstance);
|
||||
} else {
|
||||
g_GLESFuncs.glDrawElementsInstanced(mode, count, type, indices, instancecount);
|
||||
}
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
|
||||
@@ -3652,9 +3677,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
GLuint baseinstance) {
|
||||
DrawSyncFlags syncBit = DrawSyncBit::Instancing;
|
||||
const VertexArrayImpl::ScopedFetchBaseInstance fetchScope(EmulatedFetchBaseInstance(baseinstance));
|
||||
PrepareForDraw(syncBit);
|
||||
SetCurrentBaseInstance(baseinstance);
|
||||
g_GLESFuncs.glDrawArraysInstanced(mode, first, count, instancecount);
|
||||
if (UseNativeBaseInstance()) {
|
||||
g_GLESFuncs.glDrawArraysInstancedBaseInstanceEXT(mode, first, count, instancecount, baseinstance);
|
||||
} else {
|
||||
g_GLESFuncs.glDrawArraysInstanced(mode, first, count, instancecount);
|
||||
}
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1490,6 +1490,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glVertexBindingDivisor != nullptr;
|
||||
}
|
||||
|
||||
// Draw state, not VAO state: set by the baseInstance draw entry points around
|
||||
// PrepareForDraw and back to zero as soon as the draw is issued.
|
||||
Uint32 g_pendingFetchBaseInstance = 0;
|
||||
|
||||
void SetPendingFetchBaseInstance(Uint32 baseInstance) {
|
||||
g_pendingFetchBaseInstance = baseInstance;
|
||||
}
|
||||
|
||||
Uint32 GetPendingFetchBaseInstance() {
|
||||
return g_pendingFetchBaseInstance;
|
||||
}
|
||||
|
||||
// The "+ baseInstance" of GL's instanced-array element index, expressed as a byte shift
|
||||
// of the array's own offset. Only divisor'd arrays step per instance, so only they move.
|
||||
//
|
||||
// baseInstance is added to the ELEMENT index, not to instance/divisor - the divisor
|
||||
// therefore does not appear here, and the shift is a whole number of strides.
|
||||
//
|
||||
// A resolved stride of zero is the binding model's "never advance" (see
|
||||
// VertexAttribute::Stride), so such an array reads the same element for every instance
|
||||
// and a baseInstance cannot move it. The arithmetic already yields zero for that case.
|
||||
inline SizeT BaseInstanceByteShift(const MG_State::GLState::VertexAttribute& attrib, Uint32 baseInstance) {
|
||||
if (baseInstance == 0 || attrib.Divisor == 0) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<SizeT>(baseInstance) * static_cast<SizeT>(attrib.Stride);
|
||||
}
|
||||
|
||||
// Declares one attribute through the ES binding-point API, the only spelling that can
|
||||
// carry a stride of zero. Returns false when the attribute has no usable buffer, in
|
||||
// which case nothing was emitted.
|
||||
@@ -1547,7 +1575,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
||||
const Bool attributesDirty = !m_hasSyncedConfigVersion || m_syncedConfigVersion != currentConfigVersion;
|
||||
const Bool indexBufferDirty = currentIndexBufferVersion != m_syncedIndexBufferVersion;
|
||||
if (!attributesDirty && !indexBufferDirty) {
|
||||
|
||||
// The baseInstance shift lives in the attribute offsets the driver already holds, so
|
||||
// a change of baseInstance has to re-emit the divisor'd arrays even when the frontend
|
||||
// config version says nothing moved - and equally has to un-shift them for the next
|
||||
// draw that carries no baseInstance. Resting state is 0 on both sides, so a program
|
||||
// that never calls a *BaseInstance entry point never pays for this compare.
|
||||
const Uint32 fetchBaseInstance = g_pendingFetchBaseInstance;
|
||||
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
|
||||
const Bool emitAttributes = attributesDirty || baseInstanceDirty;
|
||||
if (!emitAttributes && !indexBufferDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1555,8 +1592,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
const auto& allAttributeVersions = stateVAOObject->GetAllAttributeVersions();
|
||||
const auto& allAttributes = stateVAOObject->GetAllAttributes();
|
||||
for (Uint attribIndex = 0; attribIndex < allAttributes.size() && attributesDirty; ++attribIndex) {
|
||||
for (Uint attribIndex = 0; attribIndex < allAttributes.size() && emitAttributes; ++attribIndex) {
|
||||
const auto& attrib = allAttributes[attribIndex];
|
||||
// Only the divisor'd arrays carry the shift, and only an enabled one is worth
|
||||
// re-emitting - a disabled array has no pointer the draw could fetch through,
|
||||
// and may well have no buffer to bind either.
|
||||
const Bool needsSyncBaseInstance = baseInstanceDirty && attrib.Enabled && attrib.Divisor != 0;
|
||||
Bool needsSyncSwitch = allAttributeVersions[attribIndex].SwitchVersion !=
|
||||
m_syncedAttributeVersions[attribIndex].SwitchVersion;
|
||||
if (needsSyncSwitch) {
|
||||
@@ -1571,7 +1612,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_syncedAttributeVersions[attribIndex].FormatVersion;
|
||||
Bool needsSyncBuffer = allAttributeVersions[attribIndex].BufferVersion !=
|
||||
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
||||
if (!needsSyncFormat && !needsSyncBuffer) continue;
|
||||
if (!needsSyncFormat && !needsSyncBuffer && !needsSyncBaseInstance) continue;
|
||||
|
||||
// Defence in depth. The frontend already declines glVertexAttribLFormat on this
|
||||
// backend (SupportsFloat64VertexAttributes is false - ES has no GL_DOUBLE vertex
|
||||
@@ -1611,6 +1652,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!SyncZeroStrideAttribute(attribIndex, attrib)) {
|
||||
continue;
|
||||
}
|
||||
// No BaseInstanceByteShift here on purpose: a zero stride never advances, so
|
||||
// the shift is zero by construction and adding it would only obscure that.
|
||||
if (needsSyncFormat) {
|
||||
g_GLESFuncs.glVertexBindingDivisor(attribIndex, attrib.Divisor);
|
||||
}
|
||||
@@ -1641,16 +1684,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
} // start from a clean slate so the check below is about THIS call
|
||||
}
|
||||
|
||||
const SizeT fetchOffset = attrib.Offset + BaseInstanceByteShift(attrib, fetchBaseInstance);
|
||||
|
||||
if (!attrib.IsInteger) {
|
||||
// GL_BGRA is passed to the driver as the size argument (the driver reorders BGRA).
|
||||
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
|
||||
g_GLESFuncs.glVertexAttribPointer(
|
||||
attribIndex, glSize, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride, (const void*)attrib.Offset);
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride, (const void*)fetchOffset);
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
|
||||
MG_Util::ConvertDataTypeToGLEnum(attrib.Type), attrib.Stride,
|
||||
(const void*)attrib.Offset);
|
||||
(const void*)fetchOffset);
|
||||
}
|
||||
|
||||
if (formatMayBeRefused && g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
@@ -1694,6 +1739,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_syncedConfigVersion = currentConfigVersion;
|
||||
m_hasSyncedConfigVersion = true;
|
||||
}
|
||||
if (emitAttributes) {
|
||||
m_syncedFetchBaseInstance = fetchBaseInstance;
|
||||
}
|
||||
}
|
||||
|
||||
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
|
||||
|
||||
@@ -466,6 +466,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint32 m_syncedConfigVersion = 0;
|
||||
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
|
||||
m_syncedAttributeVersions;
|
||||
// Byte shift currently baked into the instanced arrays' offsets by the baseInstance
|
||||
// emulation (see SetPendingFetchBaseInstance). It is draw state, not VAO state, so it
|
||||
// is deliberately NOT covered by the config version: the frontend never bumps for it.
|
||||
// Kept here because it describes what was last EMITTED, which is what the next sync
|
||||
// has to correct.
|
||||
Uint32 m_syncedFetchBaseInstance = 0;
|
||||
};
|
||||
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
||||
@@ -479,6 +485,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void InvalidateVAOBindingCache();
|
||||
// ES resets the binding to 0 when the currently bound VAO is deleted.
|
||||
void NoteVAOIdDeleted(Uint id);
|
||||
|
||||
// baseInstance emulation for drivers without GL_EXT_base_instance. GL fetches an
|
||||
// instanced array at element "floor(instance / divisor) + baseInstance", and ES has no
|
||||
// way to say the "+ baseInstance" part - so it is folded into the attribute's own byte
|
||||
// offset (baseInstance * stride) for every divisor'd array, which is exactly equivalent.
|
||||
// Must be set BEFORE PrepareForDraw so the VAO sync sees it, and cleared after the draw
|
||||
// so the next one refetches from element 0; ScopedFetchBaseInstance does both.
|
||||
void SetPendingFetchBaseInstance(Uint32 baseInstance);
|
||||
Uint32 GetPendingFetchBaseInstance();
|
||||
|
||||
class ScopedFetchBaseInstance {
|
||||
public:
|
||||
explicit ScopedFetchBaseInstance(Uint32 baseInstance) { SetPendingFetchBaseInstance(baseInstance); }
|
||||
~ScopedFetchBaseInstance() { SetPendingFetchBaseInstance(0); }
|
||||
ScopedFetchBaseInstance(const ScopedFetchBaseInstance&) = delete;
|
||||
ScopedFetchBaseInstance& operator=(const ScopedFetchBaseInstance&) = delete;
|
||||
};
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
|
||||
@@ -136,6 +136,48 @@ void main() {
|
||||
return data;
|
||||
}
|
||||
|
||||
// As CapturePoints, but through the baseInstance entry point, and on a capture buffer
|
||||
// of its own.
|
||||
//
|
||||
// Kept separate from CapturePoints rather than defaulting a parameter, for two
|
||||
// reasons. Every existing caller stays on the draw command that carries no
|
||||
// baseInstance at all, so the negative control is a DIFFERENT command rather than
|
||||
// the same one passed a zero. And baseInstance is the first thing here that needs
|
||||
// several captures in ONE test, which the shared helper cannot currently do: a
|
||||
// second capture into the same buffer object comes back empty on DirectVulkan
|
||||
// (respecifying a buffer that is bound to a transform-feedback binding point does
|
||||
// not reach that binding - reproduced with two plain CapturePoints calls, so it is
|
||||
// neither about baseInstance nor about this helper). A fresh buffer per capture
|
||||
// sidesteps it; without that, this scenario would be pinning that bug instead.
|
||||
std::vector<float> CaptureOwnBufferBaseInstance(GLuint program, int vertexCount, int instanceCount,
|
||||
GLuint baseInstance, bool useBaseInstanceCommand) {
|
||||
const std::size_t floats = static_cast<std::size_t>(vertexCount) * instanceCount * 16;
|
||||
std::vector<float> poison(floats, kPoison);
|
||||
GLuint xfbBuffer = 0;
|
||||
glGenBuffers(1, &xfbBuffer);
|
||||
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
|
||||
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLsizeiptr>(floats * sizeof(float)), poison.data(),
|
||||
GL_DYNAMIC_DRAW);
|
||||
|
||||
glEnable(GL_RASTERIZER_DISCARD);
|
||||
glUseProgram(program);
|
||||
glBeginTransformFeedback(GL_POINTS);
|
||||
if (useBaseInstanceCommand) {
|
||||
glDrawArraysInstancedBaseInstance(GL_POINTS, 0, vertexCount, instanceCount, baseInstance);
|
||||
} else {
|
||||
glDrawArraysInstanced(GL_POINTS, 0, vertexCount, instanceCount);
|
||||
}
|
||||
glEndTransformFeedback();
|
||||
glDisable(GL_RASTERIZER_DISCARD);
|
||||
|
||||
std::vector<float> data(floats, kPoison);
|
||||
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast<GLsizeiptr>(floats * sizeof(float)),
|
||||
data.data());
|
||||
glUseProgram(0);
|
||||
glDeleteBuffers(1, &xfbBuffer);
|
||||
return data;
|
||||
}
|
||||
|
||||
// point p, attribute a, component c
|
||||
float At(const std::vector<float>& data, int point, int attrib, int component) {
|
||||
const std::size_t index = static_cast<std::size_t>(point) * 16 + attrib * 4 + component;
|
||||
@@ -347,6 +389,105 @@ void main() {
|
||||
glDeleteBuffers(1, &vbo);
|
||||
}
|
||||
|
||||
// baseInstance moves the ELEMENT the instanced arrays start at. DirectGLES has no
|
||||
// ES entry point that says so on the drivers we ship against (GL_EXT_base_instance
|
||||
// is absent on Adreno), so it folds the shift into the attribute's own offset - and
|
||||
// the thing that made this worth pinning is that the value used to reach the shader
|
||||
// uniform for gl_BaseInstance and NEVER the fetch, so a draw could report a base
|
||||
// instance it had not actually read from.
|
||||
//
|
||||
// The three draws are the point. Zero first as a negative control, so a backend that
|
||||
// simply ignored baseInstance could not pass on the middle draw alone; and zero AGAIN
|
||||
// last, because the shift is emitted into per-attribute state the VAO twin memoises -
|
||||
// leaving it applied would make every subsequent ordinary draw fetch from the wrong
|
||||
// element, which is a far worse bug than the one being fixed.
|
||||
TEST_F(VertexAttribBindingScenario, BaseInstanceMovesTheInstancedArraysStartElement) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
ResetCurrentAttribs();
|
||||
|
||||
const float instanceData[] = {10.0f, 20.0f, 30.0f, 40.0f};
|
||||
GLuint vbo = 0;
|
||||
glGenBuffers(1, &vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(instanceData), instanceData, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
glVertexAttribFormat(0, 1, GL_FLOAT, GL_FALSE, 0);
|
||||
glVertexAttribBinding(0, 0);
|
||||
glBindVertexBuffer(0, vbo, 0, 4);
|
||||
glVertexBindingDivisor(0, 1);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
const std::vector<float> atZero = CaptureOwnBufferBaseInstance(m_program, 1, 2, 0, true);
|
||||
EXPECT_TRUE(Vec4Is(atZero, 0, 0, 10.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(atZero, 1, 0, 20.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
const std::vector<float> atTwo = CaptureOwnBufferBaseInstance(m_program, 1, 2, 2, true);
|
||||
EXPECT_TRUE(Vec4Is(atTwo, 0, 0, 30.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(atTwo, 1, 0, 40.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
// Nothing about the vertex array changed between these two draws, so only a
|
||||
// backend that actively un-shifts on a baseInstance change gets back to 10/20.
|
||||
const std::vector<float> backToZero = CaptureOwnBufferBaseInstance(m_program, 1, 2, 0, true);
|
||||
EXPECT_TRUE(Vec4Is(backToZero, 0, 0, 10.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(backToZero, 1, 0, 20.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
// And a draw command with no baseInstance parameter at all must be unaffected by
|
||||
// the one that came before it.
|
||||
const std::vector<float> plain = CaptureOwnBufferBaseInstance(m_program, 1, 2, 0, false);
|
||||
EXPECT_TRUE(Vec4Is(plain, 0, 0, 10.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(plain, 1, 0, 20.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDeleteBuffers(1, &vbo);
|
||||
}
|
||||
|
||||
// baseInstance is defined against the instanced arrays only: an array with divisor 0
|
||||
// advances per VERTEX and its start element is "first", which baseInstance does not
|
||||
// touch. An emulation that shifted by offset without checking the divisor would move
|
||||
// this one too, and nothing in the case above would notice.
|
||||
TEST_F(VertexAttribBindingScenario, BaseInstanceLeavesPerVertexArraysWhereTheyWere) {
|
||||
if (!Ready()) GTEST_SKIP();
|
||||
ResetCurrentAttribs();
|
||||
|
||||
const float perVertex[] = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
const float perInstance[] = {10.0f, 20.0f, 30.0f, 40.0f};
|
||||
GLuint buffers[2] = {0, 0};
|
||||
glGenBuffers(2, buffers);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(perVertex), perVertex, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(perInstance), perInstance, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
glVertexAttribFormat(0, 1, GL_FLOAT, GL_FALSE, 0);
|
||||
glVertexAttribBinding(0, 0);
|
||||
glBindVertexBuffer(0, buffers[0], 0, 4);
|
||||
glVertexBindingDivisor(0, 0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glVertexAttribFormat(1, 1, GL_FLOAT, GL_FALSE, 0);
|
||||
glVertexAttribBinding(1, 1);
|
||||
glBindVertexBuffer(1, buffers[1], 0, 4);
|
||||
glVertexBindingDivisor(1, 1);
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// 2 vertices x 2 instances, baseInstance 2. Points come out instance-major.
|
||||
const std::vector<float> data = CaptureOwnBufferBaseInstance(m_program, 2, 2, 2, true);
|
||||
EXPECT_TRUE(Vec4Is(data, 0, 0, 1.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 1, 0, 2.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 2, 0, 1.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 3, 0, 2.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 0, 1, 30.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 1, 1, 30.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 2, 1, 40.0f, 0.0f, 0.0f, 1.0f));
|
||||
EXPECT_TRUE(Vec4Is(data, 3, 1, 40.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDeleteBuffers(2, buffers);
|
||||
}
|
||||
|
||||
// Two attributes on one binding point at different relative offsets, plus a
|
||||
// binding offset: the fetch address is binding offset + relative offset, and the
|
||||
// relative offset must not leak into the binding's own offset.
|
||||
|
||||
@@ -540,6 +540,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawArraysIndirectEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsIndirectEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsBaseVertexEXT)
|
||||
|
||||
INIT_GLES_FUNC_OPTIONAL(glDrawArraysInstancedBaseInstanceEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glDrawElementsInstancedBaseInstanceEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glDrawElementsInstancedBaseVertexBaseInstanceEXT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,6 +855,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// Resolved into caps.TextureBufferSupport below, once the ES version is also known.
|
||||
Bool hasExtTextureBuffer = false;
|
||||
Bool hasOesTextureBuffer = false;
|
||||
// Combined with the three entry points below; DirectGLES emulates baseInstance when this
|
||||
// comes out false, so a stub pointer counting as support would silently break the draws.
|
||||
Bool hasBaseInstanceExtension = false;
|
||||
for (GLint i = 0; i < extCount; ++i) {
|
||||
const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i);
|
||||
if (extension) {
|
||||
@@ -891,7 +898,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
hasOesTextureBuffer = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
|
||||
caps.SupportsBaseInstance = true;
|
||||
hasBaseInstanceExtension = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) {
|
||||
caps.SupportsDisjointTimerQuery = true;
|
||||
@@ -929,6 +936,12 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.SupportsMultiDrawElementsBaseVertex = hasDrawElementsBaseVertexExtension &&
|
||||
hasMultiDrawArraysExtension &&
|
||||
glesFuncs.glMultiDrawElementsBaseVertexEXT != nullptr;
|
||||
// All three, not any: DirectGLES picks native-vs-emulated once per draw entry point off
|
||||
// this single flag, so a driver that resolved only some of them must count as absent.
|
||||
caps.SupportsBaseInstance = hasBaseInstanceExtension &&
|
||||
glesFuncs.glDrawArraysInstancedBaseInstanceEXT != nullptr &&
|
||||
glesFuncs.glDrawElementsInstancedBaseInstanceEXT != nullptr &&
|
||||
glesFuncs.glDrawElementsInstancedBaseVertexBaseInstanceEXT != nullptr;
|
||||
// Core from ES 3.2 on, so an extension string is not required there; below 3.2 the
|
||||
// extension is, and the pointer still has to have resolved either way.
|
||||
const Bool esAtLeast32 = caps.GLESVersion.Major > 3 ||
|
||||
@@ -963,6 +976,8 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
MGLOG_I(" draw elements base vertex (ES 3.2 core or EXT/OES_draw_elements_base_vertex): %s",
|
||||
caps.SupportsDrawElementsBaseVertex ? "yes" : "no");
|
||||
MGLOG_I(" compute shaders (ES 3.1 core): %s", caps.SupportsComputeShader ? "yes" : "no");
|
||||
MGLOG_I(" base instance (EXT_base_instance; emulated by attribute offsets when absent): %s",
|
||||
caps.SupportsBaseInstance ? "yes" : "no");
|
||||
|
||||
MGLOG_I("OpenGL ES capabilities:");
|
||||
glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment);
|
||||
|
||||
@@ -638,6 +638,17 @@ namespace MobileGL {
|
||||
GL_FUNC_TYPEDEF(void, glBruh)
|
||||
GL_FUNC_TYPEDEF(void, glMultiDrawElementsBaseVertexEXT, GLenum mode, const GLsizei* count, GLenum type,
|
||||
const void* const* indices, GLsizei drawcount, const GLint* basevertex)
|
||||
|
||||
// GL_EXT_base_instance. Where a driver has these, the "+ baseInstance" of the
|
||||
// instanced-array element index is the driver's job; where it does not, DirectGLES
|
||||
// folds it into the attribute offsets instead (VertexArrayImpl::BaseInstanceByteShift).
|
||||
GL_FUNC_TYPEDEF(void, glDrawArraysInstancedBaseInstanceEXT, GLenum mode, GLint first, GLsizei count,
|
||||
GLsizei instancecount, GLuint baseinstance)
|
||||
GL_FUNC_TYPEDEF(void, glDrawElementsInstancedBaseInstanceEXT, GLenum mode, GLsizei count, GLenum type,
|
||||
const void* indices, GLsizei instancecount, GLuint baseinstance)
|
||||
GL_FUNC_TYPEDEF(void, glDrawElementsInstancedBaseVertexBaseInstanceEXT, GLenum mode, GLsizei count,
|
||||
GLenum type, const void* indices, GLsizei instancecount, GLint basevertex,
|
||||
GLuint baseinstance)
|
||||
/*
|
||||
namespace Caps {
|
||||
struct GLESCaps {
|
||||
@@ -1034,6 +1045,10 @@ namespace MobileGL {
|
||||
GL_FUNC_DECL(glMultiDrawElementsIndirectEXT)
|
||||
GL_FUNC_DECL(glMultiDrawElementsBaseVertexEXT)
|
||||
|
||||
GL_FUNC_DECL(glDrawArraysInstancedBaseInstanceEXT)
|
||||
GL_FUNC_DECL(glDrawElementsInstancedBaseInstanceEXT)
|
||||
GL_FUNC_DECL(glDrawElementsInstancedBaseVertexBaseInstanceEXT)
|
||||
|
||||
GL_FUNC_DECL(glBruh)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user