[Fix] (Espryt): swizzle GL_BGRA vertex arrays into RGBA streams instead of disabling them

This commit is contained in:
2026-10-12 05:59:29 -04:00
parent 363fce247f
commit 9b5e15dfc8
5 changed files with 228 additions and 83 deletions
@@ -1786,9 +1786,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
memo.pendingMask = pending;
memo.valid = true;
}
if (memo.pendingMask == 0) return;
// Plus the enabled arrays the walk had to disable (Managers.h, GetDisabledEnabledAttribMask).
const Uint32 pendingMask = memo.pendingMask | (vaoTwin->GetDisabledEnabledAttribMask() & memo.activeMask);
if (pendingMask == 0) return;
for (Uint32 remaining = memo.pendingMask; remaining != 0; remaining &= remaining - 1) {
for (Uint32 remaining = pendingMask; remaining != 0; remaining &= remaining - 1) {
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
const auto& currentValue = MG_Pipe::gPipeInputs->GetCurrentVertexAttribute(location);
@@ -1922,9 +1924,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
memo.pendingMask = pending;
memo.valid = true;
}
if (memo.pendingMask == 0) return;
// Plus the enabled arrays the walk had to disable (Managers.h, GetDisabledEnabledAttribMask).
const Uint32 pendingMask = memo.pendingMask | (vaoTwin->GetDisabledEnabledAttribMask() & memo.activeMask);
if (pendingMask == 0) return;
for (Uint32 remaining = memo.pendingMask; remaining != 0; remaining &= remaining - 1) {
for (Uint32 remaining = pendingMask; remaining != 0; remaining &= remaining - 1) {
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
// BOUNDS-CHECKED, exactly as ProgramObject::GetAttribType is not: the frontend's
+133 -67
View File
@@ -3528,7 +3528,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// AND PUBLISH IT, because the readers that have NO frontend object read
// resource->hostBytes and nothing else: the readback flush (Ops_H_Readback), the
// kill-switch map arm of Ops_H_FlushRange, and the fp64 narrowing
// (SyncFloat64AttributeAsFloat32ByHandle, whose legacy counterpart reads
// (SyncConvertedAttributeByHandle, whose legacy counterpart reads
// bufferObject->MappedData()). A twin is created LAZILY - D-A2's row makes
// Ops_H_Create a no-op - so the very first resource_respecify of a buffer finds
// FindBufferResourceForHandle == nullptr and its base is dropped on the floor; with
@@ -4727,6 +4727,38 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
}
// GL_BGRA as a vertex SIZE is desktop-only - ES rejects it outright - and it means
// nothing more than "the element's first and third components are stored swapped".
// So the element is put back in RGBA order and fetched as an ordinary size-4 array of
// the same type: for GL_UNSIGNED_BYTE the first and third BYTES swap, for the two
// 2_10_10_10_REV types the 10-bit fields in bits 0-9 and 20-29 (the core spec's packed
// component layout: with BGRA, x comes from bits 20-29 and z from bits 0-9). The 2-bit
// w field and the middle field stay where they are. Elements are copied word-wise, so
// no source alignment is assumed; the output is tightly packed, one word per element.
void SwizzleBgraElementToRgba(Uint8* element, DataType type) {
if (type == DataType::Uint8) {
std::swap(element[0], element[2]);
return;
}
Uint32 word = 0;
Memcpy(&word, element, sizeof(word));
constexpr Uint32 kField = 0x3FFu;
const Uint32 low = word & kField;
const Uint32 high = (word >> 20) & kField;
word = (word & ~(kField | (kField << 20))) | high | (low << 20);
Memcpy(element, &word, sizeof(word));
}
void SwizzleBgraStreamToRgba(const Uint8* sourceBase, SizeT sourceStride, SizeT elementCount,
DataType type, Vector<Uint8>& outData) {
outData.resize(elementCount * 4);
for (SizeT element = 0; element < elementCount; ++element) {
Uint8* destination = outData.data() + element * 4;
Memcpy(destination, sourceBase + element * sourceStride, 4);
SwizzleBgraElementToRgba(destination, type);
}
}
} // namespace
BackendVertexArrayObject::BackendVertexArrayObject() {
@@ -4924,7 +4956,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
if (!attrib.IsInteger) {
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : static_cast<GLint>(attrib.Size);
const GLint glSize = static_cast<GLint>(attrib.Size);
g_GLESFuncs.glVertexAttribFormat(attribIndex, glSize,
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
attrib.Normalized ? GL_TRUE : GL_FALSE, 0);
@@ -4976,7 +5008,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The same function, driven by the applier's records (D-G4). Every branch of the walk
// survives - the enable/disable block, the fp64 narrowing with its Adreno disable, the
// zero-stride binding-API path, the attribute-buffer bind, the BGRA refusal probe, the
// zero-stride binding-API path, the attribute-buffer bind, the BGRA swizzle, the
// pointer/IPointer at the shifted fetch offset and the divisor - and the gate is
// re-keyed onto three server-owned MGGen counters plus the CSO's {slot, gen}:
//
@@ -4988,7 +5020,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// still the only thing that
// catches a driver-id re-mint no
// counter on either side moves
// m_hasConvertedFloat64Attribute <- UNCHANGED: a narrowed stream is
// m_hasConvertedAttribute <- UNCHANGED: a narrowed stream is
// derived from buffer CONTENT,
// which no serial covers
// m_syncedFetchBaseInstance <- no longer in the gate as a
@@ -5039,7 +5071,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint32 fetchBaseInstance =
BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedFloat64Attribute;
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedAttribute;
if (!emitAttributes && !indexBufferDirty) {
return;
}
@@ -5052,9 +5084,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// through other state, and the client-memory upload rewrites the VAO behind this
// gate, so any of them sends the next sync through the whole walk again.
const Bool buffersOnly = emitAttributes && !elementsDirty && m_lastWalkWasPlain &&
!m_hasConvertedFloat64Attribute;
!m_hasConvertedAttribute;
Bool walkWasPlain = true;
m_hasConvertedFloat64Attribute = false;
m_hasConvertedAttribute = false;
// Rebuilt by every walk; a buffers-only one follows a plain walk, which set none.
if (emitAttributes && !buffersOnly) m_disabledEnabledAttribMask = 0;
Bind();
if (m_hostPointerBufferGeneration != currentBufferIdGeneration) {
@@ -5094,13 +5128,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case4). IsLong is
// carried separately from Type == Float64 on the wire precisely so this test
// can still tell the two apart.
if (attrib.IsLong || attrib.Type == static_cast<Uint32>(DataType::Float64)) {
//
// A GL_BGRA-sized array takes the same stream, swizzled instead of narrowed, and
// the same disable when it cannot be built: GL_BGRA as a vertex SIZE is
// desktop-only, ES rejects it, and the rejected call leaves the array ENABLED
// with no pointer - the shape the Adreno driver dereferences null on inside the
// next draw (KHR-GL43.vertex_attrib_binding.basic-input-case5).
const Bool bgraArray = attrib.IsBgra != 0 && !attrib.IsInteger;
if (attrib.IsLong || attrib.Type == static_cast<Uint32>(DataType::Float64) || bgraArray) {
walkWasPlain = false;
if (attrib.Enabled && attrib.Type == static_cast<Uint32>(DataType::Float64) &&
binding != nullptr &&
SyncFloat64AttributeAsFloat32ByHandle(attribIndex, attrib, *binding, fetchBaseInstance)) {
m_hasConvertedFloat64Attribute = true;
// The narrowing pointed the attribute at its own stream (and may have
const Bool convertible = bgraArray || attrib.Type == static_cast<Uint32>(DataType::Float64);
if (attrib.Enabled && convertible && binding != nullptr &&
SyncConvertedAttributeByHandle(attribIndex, attrib, *binding, fetchBaseInstance)) {
m_hasConvertedAttribute = true;
// The conversion pointed the attribute at its own stream (and may have
// used the binding API), so the pointer and divisor shadow are stale.
// An earlier walk that could not build the stream disabled this array.
m_hostPointer[attribIndex].valid = false;
@@ -5110,9 +5151,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
if (attrib.Enabled) {
MGLOG_W_ONCE("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array whose source "
"stream could not be narrowed to float32 - disabling the array",
attribIndex);
MGLOG_W_ONCE("DirectGLES: vertex attribute %u is a %s array whose source stream could not "
"be converted - disabling the array",
attribIndex, bgraArray ? "GL_BGRA" : "64-bit (GL_DOUBLE)");
// The draw now reads the attribute's current generic value, which the
// application had no reason to set for an enabled array.
m_disabledEnabledAttribMask |= 1u << attribIndex;
}
HostSetEnabled(attribIndex, false);
continue;
@@ -5155,30 +5199,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
// GL_BGRA as a vertex SIZE is desktop-only and ES rejects it, which leaves the
// array ENABLED with no pointer - and the Adreno driver then dereferences null
// inside the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case5). So
// the refusal is observed and the array disabled. Deliberately ONLY this
// format: the per-draw sync must not grow a glGetError round trip for the
// formats real applications use.
const Bool formatMayBeRefused = attrib.IsBgra != 0;
if (formatMayBeRefused) walkWasPlain = false;
if (formatMayBeRefused) {
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
} // start from a clean slate so the check below is about THIS call
}
const SizeT fetchOffset = static_cast<SizeT>(attrib.Offset) +
BaseInstanceByteShiftWire(attrib.Stride, divisor, fetchBaseInstance);
const GLenum glType = MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type));
const GLint glSize = (!attrib.IsInteger && attrib.IsBgra) ? static_cast<GLint>(GL_BGRA)
: static_cast<GLint>(attrib.Size);
const GLint glSize = static_cast<GLint>(attrib.Size);
// The pointer the driver VAO already holds for this attribute - the buffer it
// captured included - needs no glBindBuffer + pointer call. A format that may be
// refused always goes to the driver, since its glGetError is the point.
// captured included - needs no glBindBuffer + pointer call.
auto& shadowPointer = m_hostPointer[attribIndex];
const Bool pointerHeld = VaoShadowOn() && !formatMayBeRefused && shadowPointer.valid &&
const Bool pointerHeld = VaoShadowOn() && shadowPointer.valid &&
shadowPointer.bufferId == attribBuffer->id && shadowPointer.size == glSize &&
shadowPointer.type == static_cast<Uint32>(glType) &&
shadowPointer.stride == static_cast<Int32>(attrib.Stride) &&
@@ -5200,17 +5229,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
attrib.IsInteger != 0, true};
}
if (formatMayBeRefused && g_GLESFuncs.glGetError() != GL_NO_ERROR) {
MGLOG_W_ONCE("DirectGLES: the driver refused the vertex format of attribute %u "
"(size=%d bgra=%d type=%s) - disabling the array so the draw cannot "
"fetch through a pointer the driver never accepted",
attribIndex, static_cast<int>(attrib.Size), attrib.IsBgra ? 1 : 0,
MG_Util::ConvertGLEnumToString(glType).c_str());
shadowPointer.valid = false;
HostSetEnabled(attribIndex, false);
continue;
}
HostSetDivisor(attribIndex, divisor);
}
@@ -5395,8 +5413,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
// A GL_BGRA array is swizzled into RGBA order on the way, as on the buffer path
// (SyncConvertedAttributeByHandle): ES has no BGRA vertex size to declare.
const Bool bgra = attrib.IsBgra && !attrib.IsInteger;
Vector<Uint8> swizzled;
if (bgra) {
swizzled.assign(clientData, clientData + uploadSize);
for (SizeT element = 0; element < static_cast<SizeT>(first + count); ++element) {
SwizzleBgraElementToRgba(swizzled.data() + element * stride, attrib.Type);
}
}
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, bufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize), clientData,
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize),
bgra ? static_cast<const void*>(swizzled.data()) : clientData,
GL_STREAM_DRAW);
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
@@ -5404,7 +5434,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (!attrib.IsInteger) {
const GLint glSize = attrib.IsBgra ? static_cast<GLint>(GL_BGRA) : attrib.Size;
const GLint glSize = bgra ? 4 : attrib.Size;
g_GLESFuncs.glVertexAttribPointer(
attribIndex, glSize, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride),
@@ -5426,7 +5456,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool BackendVertexArrayObject::SyncFloat64AttributeAsFloat32ByHandle(
Bool BackendVertexArrayObject::SyncConvertedAttributeByHandle(
Uint attribIndex, const MGPVertexAttribWire& attrib, const MG_Pipe::MGPVertexBuffer& binding,
Uint32 fetchBaseInstance) {
#ifdef TRACY_ENABLE
@@ -5435,6 +5465,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (attribIndex >= m_convertedAttributeBufferIds.size() || attrib.Size < 1 || attrib.Size > 4) {
return false;
}
// Which of the two conversions: a BGRA array is a 4-byte element whatever its type,
// and only the three types GL allows with it reach here (the frontend refuses the
// rest at the format call).
const DataType sourceType = static_cast<DataType>(attrib.Type);
const Bool bgra = attrib.IsBgra != 0;
if (bgra && sourceType != DataType::Uint8 && sourceType != DataType::Int2101010Rev &&
sourceType != DataType::Uint2101010Rev) {
return false;
}
if (!bgra && sourceType != DataType::Float64) return false;
if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) {
// A client-memory 64-bit array is narrowed on the draw path instead, which is
// the only place its fetch range is known.
@@ -5481,7 +5521,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool sourceGpuWritten =
resource->serverGpuWritten;
if (sourceGpuWritten) {
BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res, "fp64_vertex_narrowing");
BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res,
bgra ? "bgra_vertex_swizzle" : "fp64_vertex_narrowing");
}
const Uint8* const sourceBase =
(resource->persistentMapped && resource->persistentPtr != nullptr)
@@ -5492,8 +5533,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
// A BGRA element is always one 32-bit word, and so is its RGBA replacement; a 64-bit
// element narrows to the float32 vector of the same component count.
const SizeT componentCount = static_cast<SizeT>(attrib.Size);
const SizeT sourceElementSize = componentCount * sizeof(Double);
const SizeT sourceElementSize = bgra ? 4 : componentCount * sizeof(Double);
const SizeT convertedElementSize = bgra ? 4 : componentCount * sizeof(Float);
const SizeT available = sourceSize - static_cast<SizeT>(attrib.Offset);
if (available < sourceElementSize) {
return false;
@@ -5517,60 +5561,82 @@ namespace MobileGL::MG_Backend::DirectGLES {
// frontend lifetime id, and the freshness input is the applier's server-owned
// Serial instead of the frontend change serial. A PERSISTENTLY MAPPED source is
// still never trusted - it is written through the pointer with no call at all, so
// no serial on either side can prove the converted copy is current.
// no serial on either side can prove the converted copy is current. The conversion
// itself is part of the key: one attribute index can carry both shapes over its life,
// over the same bytes.
const Bool memoHit = stream.valid && convertedBufferId != 0 && !resource->persistentMapped &&
!sourceGpuWritten && stream.sourceHandle == binding.Res &&
stream.sourceChangeSerial == sourceSerial &&
stream.sourceOffset == static_cast<SizeT>(attrib.Offset) &&
stream.sourceStride == sourceStride &&
stream.componentCount == componentCount && stream.elementCount == elementCount;
stream.sourceStride == sourceStride && stream.bgra == bgra &&
stream.sourceType == attrib.Type && stream.componentCount == componentCount &&
stream.elementCount == elementCount;
if (!memoHit) {
if (convertedBufferId == 0) {
g_GLESFuncs.glGenBuffers(1, &convertedBufferId);
if (convertedBufferId == 0) {
MGLOG_E_ONCE("Failed to create the float32 scratch buffer for the 64-bit vertex array at "
MGLOG_E_ONCE("Failed to create the scratch buffer for the converted vertex array at "
"attribute %u.",
attribIndex);
return false;
}
}
Vector<Float> converted;
NarrowDoubleStreamToFloat32(sourceBase + attrib.Offset, sourceStride, componentCount, elementCount,
converted);
Vector<Float> narrowed;
Vector<Uint8> swizzled;
const void* convertedData = nullptr;
SizeT convertedBytes = 0;
if (bgra) {
SwizzleBgraStreamToRgba(sourceBase + attrib.Offset, sourceStride, elementCount, sourceType,
swizzled);
convertedData = swizzled.data();
convertedBytes = swizzled.size();
} else {
NarrowDoubleStreamToFloat32(sourceBase + attrib.Offset, sourceStride, componentCount,
elementCount, narrowed);
convertedData = narrowed.data();
convertedBytes = narrowed.size() * sizeof(Float);
}
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, convertedBufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(converted.size() * sizeof(Float)),
converted.data(), GL_STREAM_DRAW);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(convertedBytes), convertedData,
GL_STREAM_DRAW);
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
static_cast<Uint64>(converted.size() * sizeof(Float)));
static_cast<Uint64>(convertedBytes));
}
stream.valid = true;
stream.sourceHandle = binding.Res;
stream.sourceChangeSerial = sourceSerial;
stream.sourceOffset = static_cast<SizeT>(attrib.Offset);
stream.sourceStride = sourceStride;
stream.bgra = bgra;
stream.sourceType = attrib.Type;
stream.componentCount = componentCount;
stream.elementCount = elementCount;
MGLOG_D("DirectGLES: narrowed the 64-bit vertex array at attribute %u to %zu float32 element(s).",
attribIndex, elementCount);
MGLOG_D("DirectGLES: converted the %s vertex array at attribute %u, %zu element(s).",
bgra ? "BGRA" : "64-bit", attribIndex, elementCount);
}
const SizeT convertedElementSize = componentCount * sizeof(Float);
// What the converted stream is to the driver. The swizzled BGRA stream keeps its
// source type and `normalized` (GL accepts BGRA only normalized, and a packed type's
// signedness is the type's own). The narrowed stream is float32, and `normalized` is
// deliberately GL_FALSE there rather than attrib.Normalized: GL ignores it for
// floating-point array types, and honouring it would scale the fetched values.
const GLint convertedSize = bgra ? 4 : static_cast<GLint>(attrib.Size);
const GLenum convertedType = bgra ? MG_Util::ConvertDataTypeToGLEnum(sourceType) : GL_FLOAT;
const GLboolean convertedNormalized = (bgra && attrib.Normalized) ? GL_TRUE : GL_FALSE;
if (neverAdvances) {
// Only the binding-point API can say "stride 0".
if (!HasVertexBindingApi()) {
return false;
}
g_GLESFuncs.glVertexAttribFormat(attribIndex, static_cast<GLint>(attrib.Size), GL_FLOAT, GL_FALSE, 0);
g_GLESFuncs.glVertexAttribFormat(attribIndex, convertedSize, convertedType, convertedNormalized, 0);
g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex);
g_GLESFuncs.glBindVertexBuffer(attribIndex, convertedBufferId, 0, 0);
return true;
}
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, convertedBufferId);
// `normalized` is deliberately GL_FALSE rather than attrib.Normalized: GL ignores it
// for floating-point array types, and honouring it would scale the fetched values.
g_GLESFuncs.glVertexAttribPointer(attribIndex, static_cast<GLint>(attrib.Size), GL_FLOAT, GL_FALSE,
g_GLESFuncs.glVertexAttribPointer(attribIndex, convertedSize, convertedType, convertedNormalized,
static_cast<GLsizei>(convertedElementSize),
(const void*)(firstElement * convertedElementSize));
return true;
+26 -11
View File
@@ -1359,6 +1359,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 elementsSerial = 0;
};
PendingAttribValueMask& GetPendingAttribValueMaskMemo() { return m_pendingAttribValueMask; }
// Attributes the configuration ENABLES but the last walk had to disable in the driver
// VAO (a GL_DOUBLE or GL_BGRA array whose converted stream could not be built). Those
// read the current generic value too, so the value sync pushes it for them as well -
// without that they read the driver's untouched (0,0,0,1) instead of what the
// application last set.
Uint32 GetDisabledEnabledAttribMask() const { return m_disabledEnabledAttribMask; }
private:
// Narrows one enabled GL_DOUBLE array into a tightly packed float32 stream held in
@@ -1369,6 +1375,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// dropping it. Returns false when the stream cannot be built, in which case the
// caller must DISABLE the array - leaving a 64-bit array enabled with no pointer is
// what the Adreno driver turns into a SIGSEGV at the next draw.
//
// The same stream carries the second format ES has no spelling for: a GL_BGRA-sized
// array (GL_UNSIGNED_BYTE or either 2_10_10_10_REV type) is swizzled into RGBA order
// element by element and declared as an ordinary size-4 array of its own type
// (KHR-GL43.vertex_attrib_binding.basic-input-case5). Passing GL_BGRA through is
// GL_INVALID_VALUE on ES, which left the array enabled with no pointer.
// The handle arm of the whole vertex-elements half. Everything it needs arrives in
// the applier's records - the bound CSO's two views, the vertex-buffer set, the
@@ -1378,14 +1390,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// as of P5e (vi) - see the note there.
// Same narrowing, same memo, same Adreno disable; the source bytes are the shadow
// base the resource call carried and the memo key is the buffer's {slot, gen}.
Bool SyncFloat64AttributeAsFloat32ByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
const MG_Pipe::MGPVertexBuffer& binding,
Uint32 fetchBaseInstance);
Bool SyncConvertedAttributeByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
const MG_Pipe::MGPVertexBuffer& binding, Uint32 fetchBaseInstance);
// What the converted float32 stream in m_convertedAttributeBufferIds[i] was built
// from. A hit skips the CPU conversion and the re-upload; the buffer's change serial
// is part of the key, so a glBufferSubData into the source invalidates it.
struct ConvertedFloat64Stream {
// What the converted stream in m_convertedAttributeBufferIds[i] was built from. A
// hit skips the CPU conversion and the re-upload; the buffer's change serial is part
// of the key, so a glBufferSubData into the source invalidates it.
struct ConvertedAttributeStream {
Bool valid = false;
// What replaces it: the source buffer's {slot, gen}. It is the SAME identity
// the rest of the backend now keys on, it cannot be reproduced by a recycled
@@ -1399,6 +1410,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 sourceChangeSerial = 0;
SizeT sourceOffset = 0;
SizeT sourceStride = 0;
// The conversion: a BGRA swizzle of `sourceType` elements, or the fp64 narrowing.
Bool bgra = false;
Uint32 sourceType = 0;
SizeT componentCount = 0;
SizeT elementCount = 0;
};
@@ -1407,16 +1421,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
PendingAttribValueMask m_pendingAttribValueMask;
Uint m_backendVAOId = 0;
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
// Scratch stores for the buffer-backed GL_DOUBLE narrowing. Deliberately separate
// Scratch stores for the buffer-backed GL_DOUBLE narrowing and BGRA swizzle. Deliberately separate
// from m_clientAttributeBufferIds: that one holds the per-draw upload of a
// CLIENT-MEMORY array, and an attribute index can carry both shapes over its life.
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_convertedAttributeBufferIds;
Array<ConvertedFloat64Stream, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
Array<ConvertedAttributeStream, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
m_convertedAttributeStreams;
// True while at least one attribute of this VAO is fed by a converted stream. Such a
// stream is derived from buffer CONTENT, which no VAO version covers, so the config
// version early-out in SyncToBackend must not be trusted while it is set.
Bool m_hasConvertedFloat64Attribute = false;
Bool m_hasConvertedAttribute = false;
Uint32 m_disabledEnabledAttribMask = 0;
Bool m_isInitialized = false;
// ---- what replaces them on the handle arm (D-G4) ---------------------------
// The bound vertex-elements CSO this twin last emitted, and the applier's
@@ -1463,7 +1478,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// enable/disable/divisor re-sets. The shadow lets the walk emit only differences. It
// is valid from creation (a new VAO is all-disabled with divisor 0). Anything that
// writes the VAO behind the walk (client-memory uploads, the fp64 narrowing, the
// binding-API zero stride, a refused BGRA format) forgets the affected part. Pointer
// binding-API zero stride, the BGRA swizzle) forgets the affected part. Pointer
// entries name driver buffer ids, so they also go when g_bufferBackendIdGeneration
// moves: an id can be re-minted.
struct HostAttribPointer {
@@ -750,7 +750,7 @@ void main() {
EXPECT_TRUE(IsGreen(Half(image, 1))) << "command 1 did not draw (" << Half(image, 1) << ")";
}
// ---- fp64 vertex narrowing (Managers.cpp SyncFloat64AttributeAsFloat32ByHandle) ----
// ---- fp64 vertex narrowing (Managers.cpp SyncConvertedAttributeByHandle) ----
//
// A GL_DOUBLE vertex array is narrowed to float on the CPU from the source buffer's bytes,
// and the narrowed copy is memoised on the source's serial - which a GPU write never moves.
@@ -755,4 +755,64 @@ void main() {
glDeleteBuffers(1, &vbo);
}
// GL_BGRA as the SIZE of an array: the first and third components come from swapped places,
// for GL_UNSIGNED_BYTE and for both packed 2_10_10_10_REV types (KHR-GL43.vertex_attrib_binding.
// basic-input-case5). ES has no BGRA vertex size, so a backend that forwards it gets the array
// refused and reads the current value (0,0,0,0 here) instead. Attribute 1 is the plain-RGBA
// control over the same packed word attribute 2 reads as BGRA.
TEST_F(VertexAttribBindingScenario, BgraArraysAreFetchedWithRedAndBlueSwapped) {
if (!Ready()) GTEST_SKIP();
ResetCurrentAttribs();
constexpr int kStride = 12;
unsigned char vertices[2 * kStride] = {};
const auto put = [&](int vertex, int offset, const void* data, std::size_t size) {
std::memcpy(vertices + vertex * kStride + offset, data, size);
};
{
const unsigned char bytes0[4] = {0, 0xff / 4, 0xff / 2, 0xff};
const unsigned int packedUint0 = 0u | (1023u << 10) | (511u << 20) | (1u << 30);
const int packedInt0 = 0 | (511 << 10) | (255 << 20) | (0 << 30);
put(0, 0, bytes0, 4);
put(0, 4, &packedUint0, 4);
put(0, 8, &packedInt0, 4);
const unsigned char bytes1[4] = {0xff, 0xff / 2, 0xff / 4, 0};
const unsigned int packedUint1 = 0u | (1023u << 10) | (511u << 20) | (2u << 30);
const unsigned int packedInt1 = (static_cast<unsigned int>(-511) & 0x3ffu) | (511u << 10) |
(255u << 20) | (3u << 30);
put(1, 0, bytes1, 4);
put(1, 4, &packedUint1, 4);
put(1, 8, &packedInt1, 4);
}
GLuint vbo = 0;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexBuffer(0, vbo, 0, kStride);
glVertexAttribFormat(0, GL_BGRA, GL_UNSIGNED_BYTE, GL_TRUE, 0);
glVertexAttribFormat(1, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 4);
glVertexAttribFormat(2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 4);
glVertexAttribFormat(3, GL_BGRA, GL_INT_2_10_10_10_REV, GL_TRUE, 8);
for (GLuint i = 0; i < 4; ++i) {
glVertexAttribBinding(i, 0);
glEnableVertexAttribArray(i);
}
ASSERT_EQ(glGetError(), static_cast<GLenum>(GL_NO_ERROR));
const std::vector<float> data = CapturePoints(m_program, m_xfbo, 2, 1);
EXPECT_TRUE(Vec4Is(data, 0, 0, 0.5f, 0.25f, 0.0f, 1.0f));
EXPECT_TRUE(Vec4Is(data, 0, 1, 0.0f, 1.0f, 0.5f, 1.0f / 3.0f));
EXPECT_TRUE(Vec4Is(data, 0, 2, 0.5f, 1.0f, 0.0f, 1.0f / 3.0f));
EXPECT_TRUE(Vec4Is(data, 0, 3, 0.5f, 1.0f, 0.0f, 0.0f));
EXPECT_TRUE(Vec4Is(data, 1, 0, 0.25f, 0.5f, 1.0f, 0.0f));
EXPECT_TRUE(Vec4Is(data, 1, 1, 0.0f, 1.0f, 0.5f, 2.0f / 3.0f));
EXPECT_TRUE(Vec4Is(data, 1, 2, 0.5f, 1.0f, 0.0f, 2.0f / 3.0f));
EXPECT_TRUE(Vec4Is(data, 1, 3, 0.5f, 1.0f, -1.0f, -1.0f));
for (GLuint i = 0; i < 4; ++i) glDisableVertexAttribArray(i);
glDeleteBuffers(1, &vbo);
}
} // namespace MGITest