mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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:
@@ -1351,6 +1351,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return;
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, pointIndex)) return;
|
||||||
|
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer bindings cannot change while transform "
|
||||||
|
"feedback is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex);
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, pointIndex);
|
||||||
@@ -1384,6 +1392,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
|
||||||
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
if (!BufferImpl::ValidateBufferBindingPointTarget(bufferTarget)) return;
|
||||||
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return;
|
if (!BufferImpl::ValidateBufferBindingPointIndex(bufferTarget, index)) return;
|
||||||
|
if (bufferTarget == BufferTarget::TransformFeedback && MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer bindings cannot change while transform "
|
||||||
|
"feedback is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index);
|
MG_State::pGLContext->TouchBufferBindingPoint(bufferTarget, index);
|
||||||
|
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, index);
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl {
|
|||||||
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxShaderStorageBufferBindings;
|
||||||
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
pointCount = std::min(pointCount, static_cast<SizeT>(std::max(backendCount, 0)));
|
||||||
}
|
}
|
||||||
|
if (target == BufferTarget::TransformFeedback) {
|
||||||
|
// GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS bounds the indexed capture
|
||||||
|
// binding points in GL 3.3 (no ARB_transform_feedback3).
|
||||||
|
pointCount = std::min<SizeT>(pointCount, 4);
|
||||||
|
}
|
||||||
|
|
||||||
if (index < pointCount) {
|
if (index < pointCount) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -66,6 +66,34 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// While transform feedback is active the draw's primitive type must match
|
||||||
|
// the feedback primitive mode (GL 3.3 core 13.2.2).
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
||||||
|
Bool compatible = false;
|
||||||
|
switch (feedbackMode) {
|
||||||
|
case GL_POINTS:
|
||||||
|
compatible = mode == GL_POINTS;
|
||||||
|
break;
|
||||||
|
case GL_LINES:
|
||||||
|
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
||||||
|
break;
|
||||||
|
case GL_TRIANGLES:
|
||||||
|
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!compatible) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", functionName,
|
||||||
|
"Primitive mode is incompatible with the active transform feedback primitive mode."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,4 +466,54 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
DrawElements_Backend(mode, count, type, indices);
|
DrawElements_Backend(mode, count, type, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BeginTransformFeedback(GLenum primitiveMode) {
|
||||||
|
if (primitiveMode != GL_POINTS && primitiveMode != GL_LINES && primitiveMode != GL_TRIANGLES) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"primitiveMode must be GL_POINTS, GL_LINES or GL_TRIANGLES."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is already active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& program = MG_State::pGLContext->GetCurrentProgram();
|
||||||
|
if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"No program with transform feedback varyings is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Every capture buffer slot the program's mode uses must have a buffer bound.
|
||||||
|
const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount();
|
||||||
|
for (SizeT i = 0; i < usedBufferCount; ++i) {
|
||||||
|
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(i));
|
||||||
|
if (point.GetBoundObject() == nullptr) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->BeginTransformFeedback(primitiveMode, program);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndTransformFeedback(void) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->EndTransformFeedback();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||||
|
void BeginTransformFeedback(GLenum primitiveMode);
|
||||||
|
void EndTransformFeedback(void);
|
||||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||||
void DispatchComputeIndirect(GLintptr indirect);
|
void DispatchComputeIndirect(GLintptr indirect);
|
||||||
void MemoryBarrier(GLbitfield barriers);
|
void MemoryBarrier(GLbitfield barriers);
|
||||||
|
|||||||
@@ -236,12 +236,12 @@ DECLARE_GL_FUNCTION_HEAD(void, DeleteVertexArrays, GLsizei n, const GLuint* arra
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
DECLARE_GL_FUNCTION_HEAD(void, GenVertexArrays, GLsizei n, GLuint* arrays) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenVertexArrays, n, arrays)
|
||||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsVertexArray, GLuint array) DECLARE_GL_FUNCTION_END(GLboolean, IsVertexArray, array)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
DECLARE_GL_FUNCTION_HEAD(void, GetIntegeri_v, GLenum target, GLuint index, GLint* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetIntegeri_v, target, index, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
DECLARE_GL_FUNCTION_HEAD(void, BeginTransformFeedback, GLenum primitiveMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginTransformFeedback, primitiveMode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndTransformFeedback)
|
DECLARE_GL_FUNCTION_HEAD(void, EndTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferRange, GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferRange, target, index, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, BindBufferBase, GLenum target, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBufferBase, target, index, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackVaryings, GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackVaryings, program, count, varyings, bufferMode)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackVarying, GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, GLenum* type, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackVarying, program, index, bufSize, length, size, type, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribIPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribIPointer, index, size, type, stride, pointer)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIiv, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIiv, index, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribIuiv, GLuint index, GLenum pname, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribIuiv, index, pname, params)
|
||||||
|
|||||||
@@ -613,6 +613,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = programObject->GetActiveUniformBlocksMaxNameLength() + 1;
|
*params = programObject->GetActiveUniformBlocksMaxNameLength() + 1;
|
||||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||||
break;
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_VARYINGS:
|
||||||
|
*params = static_cast<GLint>(programObject->GetTransformFeedbackVaryingCount());
|
||||||
|
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||||
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
|
||||||
|
*params = static_cast<GLint>(programObject->GetTransformFeedbackBufferMode());
|
||||||
|
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||||
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
|
||||||
|
*params = programObject->GetTransformFeedbackVaryingMaxLength();
|
||||||
|
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||||
|
break;
|
||||||
case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3
|
case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3
|
||||||
if (!programObject->GetLinkStatus() || programObject->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
if (!programObject->GetLinkStatus() || programObject->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
@@ -632,9 +644,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
case GL_PROGRAM_BINARY_LENGTH:
|
case GL_PROGRAM_BINARY_LENGTH:
|
||||||
|
|
||||||
case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
|
|
||||||
case GL_TRANSFORM_FEEDBACK_VARYINGS:
|
|
||||||
case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
|
|
||||||
case GL_GEOMETRY_VERTICES_OUT:
|
case GL_GEOMETRY_VERTICES_OUT:
|
||||||
case GL_GEOMETRY_INPUT_TYPE:
|
case GL_GEOMETRY_INPUT_TYPE:
|
||||||
case GL_GEOMETRY_OUTPUT_TYPE:
|
case GL_GEOMETRY_OUTPUT_TYPE:
|
||||||
@@ -857,6 +866,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!programObject) return;
|
if (!programObject) return;
|
||||||
MGLOG_D("%s: linking program %d", __func__, program);
|
MGLOG_D("%s: linking program %d", __func__, program);
|
||||||
|
|
||||||
|
// Relinking the program an active transform feedback captures from would
|
||||||
|
// invalidate its varyings mid-capture (GL 3.3 core 2.11.3).
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
MG_State::pGLContext->GetTransformFeedbackProgram().get() == programObject.get()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"The program used by active transform feedback cannot be relinked."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static Bool allowVSOnlyPrograms;
|
static Bool allowVSOnlyPrograms;
|
||||||
static Bool initialized = false;
|
static Bool initialized = false;
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
@@ -900,6 +921,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void UseProgram_State(GLuint program) {
|
void UseProgram_State(GLuint program) {
|
||||||
MGLOG_D("UseProgram_State: program=%u", program);
|
MGLOG_D("UseProgram_State: program=%u", program);
|
||||||
|
|
||||||
|
// GL 3.3 core 2.11.3: the program in use may not change while transform
|
||||||
|
// feedback is active (there is no pause in 3.3).
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"The current program cannot change while transform feedback is active."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (program == 0) {
|
if (program == 0) {
|
||||||
MG_State::pGLContext->UseProgram(0);
|
MG_State::pGLContext->UseProgram(0);
|
||||||
return;
|
return;
|
||||||
@@ -2225,4 +2256,66 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void ValidateProgram(GLuint program) {
|
void ValidateProgram(GLuint program) {
|
||||||
ValidateProgram_State(program);
|
ValidateProgram_State(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) {
|
||||||
|
auto& programObject = TryToGetProgramObject(program);
|
||||||
|
if (!programObject) return;
|
||||||
|
if (bufferMode != GL_INTERLEAVED_ATTRIBS && bufferMode != GL_SEPARATE_ATTRIBS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufferMode is not a valid capture mode."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (count < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "count must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// GL 3.3 core: SEPARATE_ATTRIBS count may not exceed the separate-attrib limit.
|
||||||
|
if (bufferMode == GL_SEPARATE_ATTRIBS && count > 4) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"count exceeds GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Vector<String> names;
|
||||||
|
names.reserve(static_cast<SizeT>(count));
|
||||||
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
|
names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[i] : "");
|
||||||
|
}
|
||||||
|
programObject->SetTransformFeedbackVaryings(Move(names), bufferMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
||||||
|
GLenum* type, GLchar* name) {
|
||||||
|
auto& programObject = TryToGetProgramObject(program);
|
||||||
|
if (!programObject) return;
|
||||||
|
if (!programObject->GetLinkStatus()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(program) + " has not been successfully linked."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* varying = programObject->GetTransformFeedbackVarying(index);
|
||||||
|
if (varying == nullptr) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", __func__,
|
||||||
|
"index is not an active transform feedback varying of the program."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (size != nullptr) *size = varying->size;
|
||||||
|
if (type != nullptr) *type = varying->type;
|
||||||
|
GLsizei written = 0;
|
||||||
|
if (name != nullptr && bufSize > 0) {
|
||||||
|
written = std::min<GLsizei>(bufSize - 1, static_cast<GLsizei>(varying->name.size()));
|
||||||
|
Memcpy(name, varying->name.data(), static_cast<SizeT>(written));
|
||||||
|
name[written] = '\0';
|
||||||
|
}
|
||||||
|
if (length != nullptr) *length = written;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -138,4 +138,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||||
void ValidateProgram(GLuint program);
|
void ValidateProgram(GLuint program);
|
||||||
|
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
|
||||||
|
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
||||||
|
GLenum* type, GLchar* name);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -211,6 +211,22 @@ namespace MobileGL {
|
|||||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||||
const IntVec4& GetScissorBox() const; // 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
|
// Framebuffer
|
||||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||||
@@ -243,6 +259,9 @@ namespace MobileGL {
|
|||||||
BufferState m_bufferState;
|
BufferState m_bufferState;
|
||||||
VertexArrayState m_vertexArrayState;
|
VertexArrayState m_vertexArrayState;
|
||||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||||
|
Bool m_transformFeedbackActive = false;
|
||||||
|
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||||
|
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||||
TextureState m_textureState;
|
TextureState m_textureState;
|
||||||
ProgramState m_programState;
|
ProgramState m_programState;
|
||||||
RenderState m_renderState;
|
RenderState m_renderState;
|
||||||
|
|||||||
@@ -170,9 +170,163 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
m_uniformNameMaxLength = 0;
|
m_uniformNameMaxLength = 0;
|
||||||
m_attribInNameMaxLength = 0;
|
m_attribInNameMaxLength = 0;
|
||||||
m_uniformBlockNameMaxLength = 0;
|
m_uniformBlockNameMaxLength = 0;
|
||||||
|
m_xfbVaryings.clear();
|
||||||
|
m_xfbStrides.clear();
|
||||||
|
m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||||
|
m_xfbVaryingNameMaxLength = 0;
|
||||||
m_linkStatus = false;
|
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) {
|
bool ProgramObject::ShaderIsAttached(const SharedPtr<ShaderObject>& shader) {
|
||||||
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
||||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),
|
auto it = std::find_if(m_shaders.begin(), m_shaders.end(),
|
||||||
@@ -327,6 +481,12 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
if (!ValidateFragmentOutputLocations()) {
|
if (!ValidateFragmentOutputLocations()) {
|
||||||
return;
|
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);
|
MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex);
|
||||||
GenerateBinary();
|
GenerateBinary();
|
||||||
|
|||||||
@@ -465,6 +465,33 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return it == m_shaders.end() ? -1 : (Int)std::distance(m_shaders.begin(), it);
|
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; }
|
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
// 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
|
// 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:
|
private:
|
||||||
void ResetLinkArtifacts();
|
void ResetLinkArtifacts();
|
||||||
void DoReflection();
|
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 GenerateBinary();
|
||||||
void WaitUntilGenerationCompleted() const;
|
void WaitUntilGenerationCompleted() const;
|
||||||
void AddDefaultFragmentShaderIfMissing();
|
void AddDefaultFragmentShaderIfMissing();
|
||||||
@@ -558,5 +589,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
mutable Uint32 m_backendHashMemoVersion = ~0u;
|
||||||
Uint32 m_uboContentVersion = 0;
|
Uint32 m_uboContentVersion = 0;
|
||||||
Uint32 m_linkVersion = 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
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|||||||
Reference in New Issue
Block a user