mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix] (MG_State, MG_Impl): GL-order capture for geometry triangle strips
Vulkan transform feedback captures odd strip triangles as (i, i+2, i+1) while GL table 10.1 decomposes them as (i+1, i, i+2). When the capture stage is a triangle-strip geometry shader whose EmitVertex/EndPrimitive sequence is statically knowable (no emission under control flow), link time extracts the per-invocation strip lengths from the glslang AST, and EndTransformFeedback rotates each odd triangle's captured vertex records into GL order in place (bounded by the binding ranges' whole-triangle capacity; raw input primitives tracked per Begin/End). KHR-GL33.transform_feedback.geometry passes - the family is 21/21.
This commit is contained in:
@@ -74,6 +74,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackActive()) return;
|
||||
Uint64 primitives = CountPrimitivesForDraw(mode, count);
|
||||
if (primitives == 0) return;
|
||||
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives);
|
||||
|
||||
Uint64 verticesPerPrimitive = 1;
|
||||
switch (mode) {
|
||||
@@ -579,6 +580,66 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program);
|
||||
}
|
||||
|
||||
// Vulkan transform feedback captures triangle strips in plain (i, i+1, i+2)
|
||||
// vertex order, but GL decomposes odd strip triangles as (i+1, i, i+2)
|
||||
// (GL 4.6 table 10.1). With the geometry stage's statically-known strip
|
||||
// lengths the captured records are reordered in place: swap the first two
|
||||
// vertex records of every odd triangle within each emitted strip.
|
||||
static void FixupGsStripCaptureOrder(const SharedPtr<MG_State::GLState::ProgramObject>& program,
|
||||
Uint64 inputPrimitives) {
|
||||
if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) {
|
||||
return;
|
||||
}
|
||||
const auto& stripTriangles = program->GetGsStripTriangles();
|
||||
|
||||
// Global triangle indices whose leading vertex pair must swap.
|
||||
Vector<Uint64> swapTriangles;
|
||||
Uint64 triangleBase = 0;
|
||||
for (Uint64 input = 0; input < inputPrimitives; ++input) {
|
||||
for (const Uint32 stripLength : stripTriangles) {
|
||||
for (Uint32 t = 1; t < stripLength; t += 2) {
|
||||
swapTriangles.push_back(triangleBase + t);
|
||||
}
|
||||
triangleBase += stripLength;
|
||||
}
|
||||
}
|
||||
if (swapTriangles.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) {
|
||||
const Uint32 stride = program->GetTransformFeedbackStride(static_cast<Uint32>(bufferIndex));
|
||||
if (stride == 0) continue;
|
||||
const auto& bindingPoint =
|
||||
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||
static_cast<Uint>(bufferIndex));
|
||||
const auto& buffer = bindingPoint.GetBoundObject();
|
||||
if (buffer == nullptr) continue;
|
||||
const Range1D range = bindingPoint.GetRange();
|
||||
const Uint8* mapped = buffer->MappedData();
|
||||
if (mapped == nullptr) continue;
|
||||
// The geometry stage amplifies, so the CPU vertex counter does not bound
|
||||
// the capture; the binding range's whole-triangle capacity does.
|
||||
const Uint64 rangeBytes = range.end > range.start ? static_cast<Uint64>(range.end - range.start) : 0;
|
||||
const Uint64 capturedTriangles = std::min<Uint64>(triangleBase, (rangeBytes / stride) / 3);
|
||||
|
||||
// Observed Vulkan capture order for odd strip triangles is (i, i+2, i+1)
|
||||
// (winding preserved by swapping the trailing pair); GL wants
|
||||
// (i+1, i, i+2), which is one rotation away: (a,b,c) -> (c,a,b).
|
||||
Vector<Uint8> scratch(stride);
|
||||
for (const Uint64 triangle : swapTriangles) {
|
||||
if (triangle >= capturedTriangles) break;
|
||||
const SizeT v0Offset = static_cast<SizeT>(range.start) + static_cast<SizeT>(triangle * 3) * stride;
|
||||
const SizeT v1Offset = v0Offset + stride;
|
||||
const SizeT v2Offset = v1Offset + stride;
|
||||
Memcpy(scratch.data(), mapped + v2Offset, stride);
|
||||
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v1Offset, stride}, v2Offset);
|
||||
buffer->WritebackFromBackend({const_cast<Uint8*>(mapped) + v0Offset, stride}, v1Offset);
|
||||
buffer->WritebackFromBackend({scratch.data(), stride}, v0Offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EndTransformFeedback(void) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
@@ -586,6 +647,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not active."));
|
||||
return;
|
||||
}
|
||||
const auto capturedProgram = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||
const Uint64 inputPrimitives = MG_State::pGLContext->GetTransformFeedbackInputPrimitives();
|
||||
MG_State::pGLContext->EndTransformFeedback();
|
||||
// Captured results must be visible to MapBuffer/GetBufferSubData after
|
||||
// End; the capture targets are host-coherent GPU memory, so completing
|
||||
@@ -599,6 +662,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
}
|
||||
FixupGsStripCaptureOrder(capturedProgram, inputPrimitives);
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -218,6 +218,7 @@ namespace MobileGL {
|
||||
m_transformFeedbackProgram = program;
|
||||
++m_transformFeedbackGeneration;
|
||||
m_transformFeedbackCapturedVertices = 0;
|
||||
m_transformFeedbackInputPrimitives = 0;
|
||||
}
|
||||
void EndTransformFeedback() {
|
||||
m_transformFeedbackActive = false;
|
||||
@@ -244,6 +245,12 @@ namespace MobileGL {
|
||||
m_transformFeedbackCapturedVertices += vertices;
|
||||
}
|
||||
Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; }
|
||||
// Raw assembled input primitives fed to the capture stage since Begin
|
||||
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
|
||||
void AddTransformFeedbackInputPrimitives(Uint64 primitives) {
|
||||
m_transformFeedbackInputPrimitives += primitives;
|
||||
}
|
||||
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; }
|
||||
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
@@ -283,6 +290,7 @@ namespace MobileGL {
|
||||
Uint64 m_transformFeedbackGeneration = 0;
|
||||
Uint64 m_transformFeedbackPrimitiveCounter = 0;
|
||||
Uint64 m_transformFeedbackCapturedVertices = 0;
|
||||
Uint64 m_transformFeedbackInputPrimitives = 0;
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
@@ -324,9 +324,76 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_xfbStrides[i] = m_xfbVaryings[i].byteSize;
|
||||
}
|
||||
}
|
||||
|
||||
ResolveGsTriangleStripCapture(captureIntermediate);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Extracts a geometry shader's per-invocation EmitVertex/EndPrimitive sequence
|
||||
// when it is statically knowable (no emit inside selection/loop/switch). Vulkan
|
||||
// transform feedback captures triangle strips in plain (i, i+1, i+2) order while
|
||||
// GL decomposes odd strip triangles as (i+1, i, i+2) (GL 4.6 table 10.1); with
|
||||
// the static strip lengths the capture buffer can be reordered after EndTF.
|
||||
class GsEmitSequenceTraverser final : public glslang::TIntermTraverser {
|
||||
public:
|
||||
bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override {
|
||||
if (node->getOp() == glslang::EOpEmitVertex) {
|
||||
++emitCount;
|
||||
hasEmit = true;
|
||||
} else if (node->getOp() == glslang::EOpEndPrimitive) {
|
||||
FlushStrip();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool visitSelection(glslang::TVisit, glslang::TIntermSelection*) override {
|
||||
inControlFlow = true;
|
||||
return true;
|
||||
}
|
||||
bool visitLoop(glslang::TVisit, glslang::TIntermLoop*) override {
|
||||
inControlFlow = true;
|
||||
return true;
|
||||
}
|
||||
bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch*) override {
|
||||
inControlFlow = true;
|
||||
return true;
|
||||
}
|
||||
void FlushStrip() {
|
||||
if (emitCount >= 3) {
|
||||
stripTriangles.push_back(static_cast<Uint32>(emitCount - 2));
|
||||
}
|
||||
emitCount = 0;
|
||||
}
|
||||
|
||||
Vector<Uint32> stripTriangles;
|
||||
Uint32 emitCount = 0;
|
||||
Bool hasEmit = false;
|
||||
Bool inControlFlow = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void ProgramObject::ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate) {
|
||||
m_gsStripTriangles.clear();
|
||||
m_gsStripCaptureFixup = false;
|
||||
if (captureIntermediate == nullptr || m_program == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (m_program->getIntermediate(EShLangGeometry) != captureIntermediate) {
|
||||
return;
|
||||
}
|
||||
if (captureIntermediate->getOutputPrimitive() != glslang::ElgTriangleStrip) {
|
||||
return;
|
||||
}
|
||||
GsEmitSequenceTraverser traverser;
|
||||
const_cast<glslang::TIntermediate*>(captureIntermediate)->getTreeRoot()->traverse(&traverser);
|
||||
traverser.FlushStrip(); // the invocation end acts as an implicit EndPrimitive
|
||||
if (!traverser.hasEmit || traverser.inControlFlow || traverser.stripTriangles.empty()) {
|
||||
return;
|
||||
}
|
||||
m_gsStripTriangles = Move(traverser.stripTriangles);
|
||||
m_gsStripCaptureFixup = true;
|
||||
}
|
||||
|
||||
bool ProgramObject::ShaderIsAttached(const SharedPtr<ShaderObject>& shader) {
|
||||
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),
|
||||
|
||||
@@ -491,6 +491,12 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); }
|
||||
Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; }
|
||||
// True when the capture stage is a triangle-strip geometry shader with a
|
||||
// statically-known emit sequence: the Vulkan capture order then needs the GL
|
||||
// odd-triangle vertex swap after EndTransformFeedback.
|
||||
Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; }
|
||||
// Triangles per strip, in emission order, for ONE geometry invocation.
|
||||
const Vector<Uint32>& GetGsStripTriangles() const { return m_gsStripTriangles; }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||
@@ -506,6 +512,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// vertex stage; fails the link (GL semantics) on unknown or duplicate
|
||||
// names or exceeded capture limits.
|
||||
Bool ResolveTransformFeedbackVaryings();
|
||||
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
|
||||
void GenerateBinary();
|
||||
void WaitUntilGenerationCompleted() const;
|
||||
void AddDefaultFragmentShaderIfMissing();
|
||||
@@ -595,6 +602,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
GLenum m_requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Vector<XfbVarying> m_xfbVaryings;
|
||||
Vector<Uint32> m_xfbStrides;
|
||||
Vector<Uint32> m_gsStripTriangles;
|
||||
Bool m_gsStripCaptureFixup = false;
|
||||
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Int m_xfbVaryingNameMaxLength = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user