mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Feat] (MG_State, MG_Impl): transform feedback state, validation and reflection
First stage of GL 3.0 transform feedback: glTransformFeedbackVaryings / glGetTransformFeedbackVarying / glBeginTransformFeedback / glEndTransformFeedback were unimplemented stubs. This adds - per-program capture state: requested varyings apply on the next link and resolve against the last vertex-processing stage's linker objects (with gl_Position/gl_PointSize handled as builtins), failing the link on unknown or duplicate names or exceeded interleaved/separate limits, with offsets and strides computed per GL rules; - context Begin/End state with the GL 3.3 error semantics: invalid primitive modes, redundant Begin/End, missing program or capture-buffer bindings, primitive-mode compatibility at draw time, and the while-active prohibitions on rebinding capture buffers, switching programs, and relinking the captured program; - GetProgramiv TRANSFORM_FEEDBACK_* queries and a 4-slot bound on indexed GL_TRANSFORM_FEEDBACK_BUFFER binding points. KHR-GL33.transform_feedback api_errors/linking_errors/get_xfb_varying now pass; GPU-side capture is the remaining stage.
This commit is contained in:
@@ -211,6 +211,22 @@ namespace MobileGL {
|
||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||
|
||||
// Transform feedback (GL 3.0 core Begin/End; no feedback objects yet)
|
||||
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
|
||||
m_transformFeedbackActive = true;
|
||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||
m_transformFeedbackProgram = program;
|
||||
}
|
||||
void EndTransformFeedback() {
|
||||
m_transformFeedbackActive = false;
|
||||
m_transformFeedbackProgram.reset();
|
||||
}
|
||||
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
|
||||
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
|
||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
||||
return m_transformFeedbackProgram;
|
||||
}
|
||||
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||
@@ -243,6 +259,9 @@ namespace MobileGL {
|
||||
BufferState m_bufferState;
|
||||
VertexArrayState m_vertexArrayState;
|
||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||
Bool m_transformFeedbackActive = false;
|
||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
@@ -170,9 +170,163 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_uniformNameMaxLength = 0;
|
||||
m_attribInNameMaxLength = 0;
|
||||
m_uniformBlockNameMaxLength = 0;
|
||||
m_xfbVaryings.clear();
|
||||
m_xfbStrides.clear();
|
||||
m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
m_xfbVaryingNameMaxLength = 0;
|
||||
m_linkStatus = false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// GL type enum for a vertex-stage output symbol captured by transform
|
||||
// feedback. Covers the scalar/vector/matrix float+integer types transform
|
||||
// feedback may legally capture in GL 3.3.
|
||||
Bool ResolveXfbSymbolType(const glslang::TType& type, GLenum& outType, GLint& outArraySize,
|
||||
Uint32& outBytesPerElement) {
|
||||
outArraySize = type.isArray() ? type.getOuterArraySize() : 1;
|
||||
const Int columns = type.isMatrix() ? type.getMatrixCols() : 1;
|
||||
const Int components = type.isMatrix() ? type.getMatrixRows()
|
||||
: (type.isVector() ? type.getVectorSize() : 1);
|
||||
const glslang::TBasicType basic = type.getBasicType();
|
||||
static constexpr GLenum kFloatTypes[5] = {0, GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4};
|
||||
static constexpr GLenum kIntTypes[5] = {0, GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4};
|
||||
static constexpr GLenum kUintTypes[5] = {0, GL_UNSIGNED_INT, GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3,
|
||||
GL_UNSIGNED_INT_VEC4};
|
||||
if (type.isMatrix()) {
|
||||
if (basic != glslang::EbtFloat) return false;
|
||||
static constexpr GLenum kMatTypes[5][5] = {
|
||||
{}, {},
|
||||
{0, 0, GL_FLOAT_MAT2, GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4},
|
||||
{0, 0, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3, GL_FLOAT_MAT3x4},
|
||||
{0, 0, GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3, GL_FLOAT_MAT4},
|
||||
};
|
||||
if (columns < 2 || columns > 4 || components < 2 || components > 4) return false;
|
||||
outType = kMatTypes[columns][components];
|
||||
} else if (components >= 1 && components <= 4) {
|
||||
switch (basic) {
|
||||
case glslang::EbtFloat: outType = kFloatTypes[components]; break;
|
||||
case glslang::EbtInt: outType = kIntTypes[components]; break;
|
||||
case glslang::EbtUint: outType = kUintTypes[components]; break;
|
||||
default: return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
outBytesPerElement = static_cast<Uint32>(columns * components) * 4u;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool ProgramObject::ResolveTransformFeedbackVaryings() {
|
||||
m_xfbVaryings.clear();
|
||||
m_xfbStrides.clear();
|
||||
m_xfbBufferMode = m_requestedXfbBufferMode;
|
||||
m_xfbVaryingNameMaxLength = 0;
|
||||
if (m_requestedXfbVaryings.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Capture happens at the last vertex-processing stage (geometry, then
|
||||
// tessellation evaluation, then vertex).
|
||||
const glslang::TIntermediate* captureIntermediate = nullptr;
|
||||
for (EShLanguage stage : {EShLangGeometry, EShLangTessEvaluation, EShLangVertex}) {
|
||||
captureIntermediate = m_program->getIntermediate(stage);
|
||||
if (captureIntermediate != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (captureIntermediate == nullptr) {
|
||||
m_infoLog = "Transform feedback varyings requested but the program has no vertex-processing stage.";
|
||||
return false;
|
||||
}
|
||||
const glslang::TIntermAggregate* linkerObjects = captureIntermediate->findLinkerObjects();
|
||||
|
||||
const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS;
|
||||
Uint32 interleavedOffset = 0;
|
||||
for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) {
|
||||
const String& name = m_requestedXfbVaryings[i];
|
||||
for (SizeT j = 0; j < i; ++j) {
|
||||
if (m_requestedXfbVaryings[j] == name) {
|
||||
m_infoLog = "Transform feedback varying '" + name + "' is specified more than once.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
XfbVarying varying;
|
||||
varying.name = name;
|
||||
Uint32 bytesPerElement = 0;
|
||||
Bool resolved = false;
|
||||
if (name == "gl_Position") {
|
||||
varying.type = GL_FLOAT_VEC4;
|
||||
varying.size = 1;
|
||||
bytesPerElement = 16;
|
||||
resolved = true;
|
||||
} else if (name == "gl_PointSize") {
|
||||
varying.type = GL_FLOAT;
|
||||
varying.size = 1;
|
||||
bytesPerElement = 4;
|
||||
resolved = true;
|
||||
} else if (linkerObjects != nullptr) {
|
||||
for (const auto* node : linkerObjects->getSequence()) {
|
||||
const glslang::TIntermSymbol* symbol = node->getAsSymbolNode();
|
||||
if (symbol == nullptr || symbol->getType().getQualifier().storage != glslang::EvqVaryingOut) {
|
||||
continue;
|
||||
}
|
||||
if (symbol->getName() != name.c_str()) {
|
||||
continue;
|
||||
}
|
||||
resolved = ResolveXfbSymbolType(symbol->getType(), varying.type, varying.size, bytesPerElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolved) {
|
||||
m_infoLog = "Transform feedback varying '" + name + "' is not an output of the vertex stage.";
|
||||
return false;
|
||||
}
|
||||
|
||||
varying.byteSize = bytesPerElement * static_cast<Uint32>(varying.size);
|
||||
if (interleaved) {
|
||||
varying.bufferIndex = 0;
|
||||
varying.offsetBytes = interleavedOffset;
|
||||
interleavedOffset += varying.byteSize;
|
||||
} else {
|
||||
varying.bufferIndex = static_cast<Uint32>(i);
|
||||
varying.offsetBytes = 0;
|
||||
}
|
||||
m_xfbVaryingNameMaxLength =
|
||||
std::max(m_xfbVaryingNameMaxLength, static_cast<Int>(name.size()) + 1);
|
||||
m_xfbVaryings.push_back(Move(varying));
|
||||
}
|
||||
|
||||
constexpr Uint32 kMaxSeparateAttribs = 4;
|
||||
constexpr Uint32 kMaxSeparateComponents = 4;
|
||||
constexpr Uint32 kMaxInterleavedComponents = 64;
|
||||
if (interleaved) {
|
||||
if (interleavedOffset > kMaxInterleavedComponents * 4) {
|
||||
m_infoLog = "Transform feedback interleaved capture exceeds "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.";
|
||||
return false;
|
||||
}
|
||||
m_xfbStrides.assign(1, interleavedOffset);
|
||||
} else {
|
||||
if (m_xfbVaryings.size() > kMaxSeparateAttribs) {
|
||||
m_infoLog = "Transform feedback separate capture exceeds "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.";
|
||||
return false;
|
||||
}
|
||||
m_xfbStrides.resize(m_xfbVaryings.size());
|
||||
for (SizeT i = 0; i < m_xfbVaryings.size(); ++i) {
|
||||
if (m_xfbVaryings[i].byteSize > kMaxSeparateComponents * 4) {
|
||||
m_infoLog = "Transform feedback varying '" + m_xfbVaryings[i].name +
|
||||
"' exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS.";
|
||||
return false;
|
||||
}
|
||||
m_xfbStrides[i] = m_xfbVaryings[i].byteSize;
|
||||
}
|
||||
}
|
||||
return 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(),
|
||||
@@ -327,6 +481,12 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (!ValidateFragmentOutputLocations()) {
|
||||
return;
|
||||
}
|
||||
if (!ResolveTransformFeedbackVaryings()) {
|
||||
m_linkStatus = false;
|
||||
MGLOG_E("ProgramObject %u: transform feedback varying resolution failed: %s", m_externalIndex,
|
||||
m_infoLog.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex);
|
||||
GenerateBinary();
|
||||
|
||||
@@ -465,6 +465,33 @@ namespace MobileGL::MG_State::GLState {
|
||||
return it == m_shaders.end() ? -1 : (Int)std::distance(m_shaders.begin(), it);
|
||||
}
|
||||
|
||||
// Transform feedback (GL 3.0 core: glTransformFeedbackVaryings applies on
|
||||
// the NEXT link; the linked snapshot below is what draws and queries see).
|
||||
struct XfbVarying {
|
||||
String name;
|
||||
GLenum type = GL_FLOAT;
|
||||
GLint size = 1; // array element count
|
||||
Uint32 bufferIndex = 0; // capture buffer slot
|
||||
Uint32 offsetBytes = 0; // offset within the capture buffer
|
||||
Uint32 byteSize = 0; // bytes captured per vertex for this varying
|
||||
};
|
||||
void SetTransformFeedbackVaryings(Vector<String>&& names, GLenum bufferMode) {
|
||||
m_requestedXfbVaryings = Move(names);
|
||||
m_requestedXfbBufferMode = bufferMode;
|
||||
}
|
||||
GLenum GetTransformFeedbackBufferMode() const { return m_xfbBufferMode; }
|
||||
SizeT GetTransformFeedbackVaryingCount() const { return m_xfbVaryings.size(); }
|
||||
const XfbVarying* GetTransformFeedbackVarying(SizeT index) const {
|
||||
return index < m_xfbVaryings.size() ? &m_xfbVaryings[index] : nullptr;
|
||||
}
|
||||
const Vector<XfbVarying>& GetTransformFeedbackVaryings() const { return m_xfbVaryings; }
|
||||
// Stride of one captured vertex in the given capture buffer slot.
|
||||
Uint32 GetTransformFeedbackStride(Uint32 bufferIndex) const {
|
||||
return bufferIndex < m_xfbStrides.size() ? m_xfbStrides[bufferIndex] : 0;
|
||||
}
|
||||
SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); }
|
||||
Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||
// name (external index), which is freed to a LIFO list and immediately handed back by
|
||||
@@ -475,6 +502,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
private:
|
||||
void ResetLinkArtifacts();
|
||||
void DoReflection();
|
||||
// Resolves the requested transform feedback varyings against the linked
|
||||
// vertex stage; fails the link (GL semantics) on unknown or duplicate
|
||||
// names or exceeded capture limits.
|
||||
Bool ResolveTransformFeedbackVaryings();
|
||||
void GenerateBinary();
|
||||
void WaitUntilGenerationCompleted() const;
|
||||
void AddDefaultFragmentShaderIfMissing();
|
||||
@@ -558,5 +589,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||
Uint32 m_uboContentVersion = 0;
|
||||
Uint32 m_linkVersion = 0;
|
||||
|
||||
// Transform feedback: request (applies at next link) and linked snapshot.
|
||||
Vector<String> m_requestedXfbVaryings;
|
||||
GLenum m_requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Vector<XfbVarying> m_xfbVaryings;
|
||||
Vector<Uint32> m_xfbStrides;
|
||||
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Int m_xfbVaryingNameMaxLength = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
Reference in New Issue
Block a user