mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Feat] (Program): report the geometry and tessellation link properties glGetProgramiv had no source for
This commit is contained in:
@@ -747,9 +747,71 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = programObject->GetSeparable() ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
|
||||
// The geometry and tessellation link properties (GL 4.6 core table 23.35). Same shape as
|
||||
// GL_COMPUTE_WORK_GROUP_SIZE above, and for the same reason: "a linked program object
|
||||
// with a geometry shader" is one whose EXECUTABLE has the stage, so an
|
||||
// attached-but-not-yet-linked shader must give INVALID_OPERATION rather than the previous
|
||||
// link's value. The geometry three used to be listed here only to fall through into the
|
||||
// INVALID_ENUM default, and the tessellation five were not listed at all.
|
||||
case GL_GEOMETRY_VERTICES_OUT:
|
||||
case GL_GEOMETRY_INPUT_TYPE:
|
||||
case GL_GEOMETRY_OUTPUT_TYPE:
|
||||
case GL_GEOMETRY_SHADER_INVOCATIONS: {
|
||||
if (!programObject->GetLinkStatus() || !programObject->HasLinkedShaderStage(ShaderStage::Geometry)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(program) +
|
||||
" is not a linked program object with a geometry shader."));
|
||||
return;
|
||||
}
|
||||
switch (pname) {
|
||||
case GL_GEOMETRY_VERTICES_OUT: *params = programObject->GetGeometryVerticesOut(); break;
|
||||
case GL_GEOMETRY_INPUT_TYPE: *params = static_cast<GLint>(programObject->GetGeometryInputType()); break;
|
||||
case GL_GEOMETRY_OUTPUT_TYPE: *params = static_cast<GLint>(programObject->GetGeometryOutputType()); break;
|
||||
default: *params = programObject->GetGeometryShaderInvocations(); break;
|
||||
}
|
||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||
break;
|
||||
}
|
||||
case GL_TESS_CONTROL_OUTPUT_VERTICES: {
|
||||
if (!programObject->GetLinkStatus() || !programObject->HasLinkedShaderStage(ShaderStage::TessControl)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", __func__,
|
||||
std::to_string(program) +
|
||||
" is not a linked program object with a tessellation control shader."));
|
||||
return;
|
||||
}
|
||||
*params = programObject->GetTessControlOutputVertices();
|
||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||
break;
|
||||
}
|
||||
case GL_TESS_GEN_MODE:
|
||||
case GL_TESS_GEN_SPACING:
|
||||
case GL_TESS_GEN_VERTEX_ORDER:
|
||||
case GL_TESS_GEN_POINT_MODE: {
|
||||
if (!programObject->GetLinkStatus() || !programObject->HasLinkedShaderStage(ShaderStage::TessEval)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", __func__,
|
||||
std::to_string(program) +
|
||||
" is not a linked program object with a tessellation evaluation shader."));
|
||||
return;
|
||||
}
|
||||
switch (pname) {
|
||||
case GL_TESS_GEN_MODE: *params = static_cast<GLint>(programObject->GetTessGenMode()); break;
|
||||
case GL_TESS_GEN_SPACING: *params = static_cast<GLint>(programObject->GetTessGenSpacing()); break;
|
||||
case GL_TESS_GEN_VERTEX_ORDER:
|
||||
*params = static_cast<GLint>(programObject->GetTessGenVertexOrder());
|
||||
break;
|
||||
default: *params = programObject->GetTessGenPointMode() ? GL_TRUE : GL_FALSE; break;
|
||||
}
|
||||
MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MGLOG_D("%s: %s", __func__, MG_Util::ConvertGLEnumToString(pname).c_str());
|
||||
MG_State::pGLContext->RecordError(
|
||||
|
||||
@@ -670,9 +670,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
return;
|
||||
}
|
||||
|
||||
// GL_GEOMETRY_INPUT_TYPE. A draw's primitive type has to be compatible with it
|
||||
// (GL 4.6 core 11.3.1), so it is resolved for every link, not only a capturing one.
|
||||
// The geometry stage's link properties. GL_GEOMETRY_INPUT_TYPE is load-bearing beyond the
|
||||
// query surface - a draw's primitive type has to be compatible with it (GL 4.6 core
|
||||
// 11.3.1) - so this block runs for every link, not only a capturing one. The other three
|
||||
// are pure glGetProgramiv answers that previously had no source at all.
|
||||
artifacts.gsInputPrimitive = GL_NONE;
|
||||
artifacts.gsOutputPrimitive = GL_NONE;
|
||||
artifacts.gsMaxVertices = 0;
|
||||
artifacts.gsInvocations = 0;
|
||||
if (const glslang::TIntermediate* gs = artifacts.program->getIntermediate(EShLangGeometry)) {
|
||||
switch (gs->getInputPrimitive()) {
|
||||
case glslang::ElgPoints: artifacts.gsInputPrimitive = GL_POINTS; break;
|
||||
@@ -682,6 +687,45 @@ namespace MobileGL::MG_State::GLState {
|
||||
case glslang::ElgTrianglesAdjacency: artifacts.gsInputPrimitive = GL_TRIANGLES_ADJACENCY; break;
|
||||
default: break;
|
||||
}
|
||||
switch (gs->getOutputPrimitive()) {
|
||||
case glslang::ElgPoints: artifacts.gsOutputPrimitive = GL_POINTS; break;
|
||||
case glslang::ElgLineStrip: artifacts.gsOutputPrimitive = GL_LINE_STRIP; break;
|
||||
case glslang::ElgTriangleStrip: artifacts.gsOutputPrimitive = GL_TRIANGLE_STRIP; break;
|
||||
default: break;
|
||||
}
|
||||
// glslang leaves both at TQualifier::layoutNotSet (-1) when the shader declared no
|
||||
// such layout, and `invocations` defaults to one per GLSL 4.60 4.4.2.2 - so clamp
|
||||
// rather than forward, or GL_GEOMETRY_SHADER_INVOCATIONS reports the sentinel.
|
||||
artifacts.gsMaxVertices = std::max(gs->getVertices(), 0);
|
||||
artifacts.gsInvocations = std::max(gs->getInvocations(), 1);
|
||||
}
|
||||
|
||||
// The tessellation evaluation stage's link properties, GL 4.6 core table 23.35: the
|
||||
// primitive generator's mode, spacing, winding and point mode. (The control stage's
|
||||
// output patch size is captured below, together with the limit check that goes with it.)
|
||||
artifacts.tessGenMode = GL_NONE;
|
||||
artifacts.tessGenSpacing = GL_NONE;
|
||||
artifacts.tessGenVertexOrder = GL_NONE;
|
||||
artifacts.tessGenPointMode = false;
|
||||
if (const glslang::TIntermediate* tes = artifacts.program->getIntermediate(EShLangTessEvaluation)) {
|
||||
switch (tes->getInputPrimitive()) {
|
||||
case glslang::ElgTriangles: artifacts.tessGenMode = GL_TRIANGLES; break;
|
||||
case glslang::ElgQuads: artifacts.tessGenMode = GL_QUADS; break;
|
||||
case glslang::ElgIsolines: artifacts.tessGenMode = GL_ISOLINES; break;
|
||||
default: break;
|
||||
}
|
||||
// GLSL 4.60 4.4.2.3: equal_spacing and ccw are the defaults, which is what an unset
|
||||
// qualifier means here.
|
||||
switch (tes->getVertexSpacing()) {
|
||||
case glslang::EvsFractionalEven: artifacts.tessGenSpacing = GL_FRACTIONAL_EVEN; break;
|
||||
case glslang::EvsFractionalOdd: artifacts.tessGenSpacing = GL_FRACTIONAL_ODD; break;
|
||||
default: artifacts.tessGenSpacing = GL_EQUAL; break;
|
||||
}
|
||||
switch (tes->getVertexOrder()) {
|
||||
case glslang::EvoCw: artifacts.tessGenVertexOrder = GL_CW; break;
|
||||
default: artifacts.tessGenVertexOrder = GL_CCW; break;
|
||||
}
|
||||
artifacts.tessGenPointMode = tes->getPointMode();
|
||||
}
|
||||
|
||||
// GL_TESS_CONTROL_OUTPUT_VERTICES, i.e. the `layout(vertices = N) out` the control stage
|
||||
|
||||
@@ -1356,6 +1356,23 @@ namespace MobileGL::MG_State::GLState {
|
||||
// tessellation control stage, or 0 when the program has none. Checked against
|
||||
// GL_MAX_PATCH_VERTICES at link (GL 4.6 core 11.2.1.1).
|
||||
Int tcsOutputVertices = 0;
|
||||
// The rest of the geometry stage's link properties, and the tessellation evaluation
|
||||
// stage's. Every one of these is a glGetProgramiv answer that had no source at all:
|
||||
// the query surface listed the geometry pnames only to fall through to
|
||||
// GL_INVALID_ENUM, and the GL_TESS_GEN_* pnames were not mentioned anywhere. They
|
||||
// come from the linked intermediates for the same reason gsInputPrimitive and
|
||||
// tcsOutputVertices do - glslang has already merged the compilation units' layout
|
||||
// qualifiers and diagnosed contradictions, so the linked program is the thing that
|
||||
// knows.
|
||||
GLenum gsOutputPrimitive = GL_NONE;
|
||||
Int gsMaxVertices = 0;
|
||||
Int gsInvocations = 0;
|
||||
// The tessellation evaluation stage's layout: GL_QUADS / GL_TRIANGLES / GL_ISOLINES,
|
||||
// GL_EQUAL / GL_FRACTIONAL_EVEN / GL_FRACTIONAL_ODD, GL_CW / GL_CCW, and point mode.
|
||||
GLenum tessGenMode = GL_NONE;
|
||||
GLenum tessGenSpacing = GL_NONE;
|
||||
GLenum tessGenVertexOrder = GL_NONE;
|
||||
Bool tessGenPointMode = false;
|
||||
GLenum xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Int xfbVaryingNameMaxLength = 0;
|
||||
Bool xfbNeedsScatteredCapture = false;
|
||||
@@ -1545,10 +1562,22 @@ namespace MobileGL::MG_State::GLState {
|
||||
// GL_LINES_ADJACENCY, GL_TRIANGLES or GL_TRIANGLES_ADJACENCY), or GL_NONE when the
|
||||
// program has no geometry stage. Draws must present a compatible primitive type.
|
||||
GLenum GetGeometryInputType() const { return Artifacts().gsInputPrimitive; }
|
||||
// GL_GEOMETRY_OUTPUT_TYPE (GL_POINTS, GL_LINE_STRIP or GL_TRIANGLE_STRIP),
|
||||
// GL_GEOMETRY_VERTICES_OUT and GL_GEOMETRY_SHADER_INVOCATIONS of the linked geometry
|
||||
// stage. Meaningless without one - glGetProgramiv raises INVALID_OPERATION there.
|
||||
GLenum GetGeometryOutputType() const { return Artifacts().gsOutputPrimitive; }
|
||||
Int GetGeometryVerticesOut() const { return Artifacts().gsMaxVertices; }
|
||||
Int GetGeometryShaderInvocations() const { return Artifacts().gsInvocations; }
|
||||
// GL_TESS_CONTROL_OUTPUT_VERTICES of the linked tessellation control stage, or 0 when
|
||||
// the program has no such stage. Never greater than GL_MAX_PATCH_VERTICES: a program
|
||||
// that declared more does not link at all (GL 4.6 core 11.2.1.1).
|
||||
Int GetTessControlOutputVertices() const { return Artifacts().tcsOutputVertices; }
|
||||
// GL_TESS_GEN_MODE / _SPACING / _VERTEX_ORDER / _POINT_MODE of the linked tessellation
|
||||
// evaluation stage.
|
||||
GLenum GetTessGenMode() const { return Artifacts().tessGenMode; }
|
||||
GLenum GetTessGenSpacing() const { return Artifacts().tessGenSpacing; }
|
||||
GLenum GetTessGenVertexOrder() const { return Artifacts().tessGenVertexOrder; }
|
||||
Bool GetTessGenPointMode() const { return Artifacts().tessGenPointMode; }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||
|
||||
@@ -3973,3 +3973,166 @@ void main() { o_color = vec4(1.0); }
|
||||
EXPECT_FALSE(programObject->WriteReservedNumSamples(4));
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// glGetProgramiv's geometry and tessellation link properties (GL 4.6 core table 23.35). None of
|
||||
// these had a source: GL_GEOMETRY_VERTICES_OUT / _INPUT_TYPE / _OUTPUT_TYPE were listed in the
|
||||
// switch only to fall through into the GL_INVALID_ENUM default, GL_GEOMETRY_SHADER_INVOCATIONS
|
||||
// and the five GL_TESS_* pnames were not listed at all, and the link recorded nothing but the
|
||||
// geometry INPUT primitive. 72 of the tessellation family's 116 failing conformance bodies died
|
||||
// on the first of these queries, before touching a single tessellation feature.
|
||||
namespace {
|
||||
GLuint CompileStage(GLenum type, const char* source) {
|
||||
const GLuint shader = CreateShader(type);
|
||||
ShaderSource(shader, 1, &source, nullptr);
|
||||
CompileShader(shader);
|
||||
GLint status = GL_FALSE;
|
||||
GetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
char infoLog[2048] = "";
|
||||
GetShaderInfoLog(shader, sizeof(infoLog), nullptr, infoLog);
|
||||
ADD_FAILURE() << "stage " << type << " failed to compile: " << infoLog;
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint LinkStages(const std::vector<std::pair<GLenum, const char*>>& stages) {
|
||||
const GLuint program = CreateProgram();
|
||||
for (const auto& [type, source] : stages) {
|
||||
const GLuint shader = CompileStage(type, source);
|
||||
AttachShader(program, shader);
|
||||
DeleteShader(shader);
|
||||
}
|
||||
LinkProgram(program);
|
||||
GLint linkStatus = GL_FALSE;
|
||||
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
|
||||
if (linkStatus != GL_TRUE) {
|
||||
char infoLog[2048] = "";
|
||||
GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog);
|
||||
ADD_FAILURE() << "link failed: " << infoLog;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
constexpr const char* kPassthroughVs = R"(#version 460 core
|
||||
void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }
|
||||
)";
|
||||
constexpr const char* kPassthroughFs = R"(#version 460 core
|
||||
out vec4 mgColor;
|
||||
void main() { mgColor = vec4(1.0); }
|
||||
)";
|
||||
} // namespace
|
||||
|
||||
TEST_F(ProgramTest, GetProgramivReportsTheGeometryStageLinkProperties) {
|
||||
constexpr const char* gs = R"(#version 460 core
|
||||
layout(triangles, invocations = 3) in;
|
||||
layout(line_strip, max_vertices = 7) out;
|
||||
void main() {
|
||||
for (int i = 0; i < 3; ++i) { gl_Position = gl_in[i].gl_Position; EmitVertex(); }
|
||||
EndPrimitive();
|
||||
}
|
||||
)";
|
||||
const GLuint program =
|
||||
LinkStages({{GL_VERTEX_SHADER, kPassthroughVs}, {GL_GEOMETRY_SHADER, gs}, {GL_FRAGMENT_SHADER, kPassthroughFs}});
|
||||
|
||||
GLint value = -1;
|
||||
GetProgramiv(program, GL_GEOMETRY_INPUT_TYPE, &value);
|
||||
EXPECT_EQ(value, GL_TRIANGLES);
|
||||
GetProgramiv(program, GL_GEOMETRY_OUTPUT_TYPE, &value);
|
||||
EXPECT_EQ(value, GL_LINE_STRIP);
|
||||
GetProgramiv(program, GL_GEOMETRY_VERTICES_OUT, &value);
|
||||
EXPECT_EQ(value, 7);
|
||||
GetProgramiv(program, GL_GEOMETRY_SHADER_INVOCATIONS, &value);
|
||||
EXPECT_EQ(value, 3);
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
// ...and INVALID_OPERATION, not INVALID_ENUM, on a program that has no geometry stage: GL
|
||||
// says "a linked program object with a geometry shader", which the conformance suite checks
|
||||
// from both sides.
|
||||
const GLuint noGeometry = LinkStages({{GL_VERTEX_SHADER, kPassthroughVs}, {GL_FRAGMENT_SHADER, kPassthroughFs}});
|
||||
for (const GLenum pname : {GL_GEOMETRY_INPUT_TYPE, GL_GEOMETRY_OUTPUT_TYPE, GL_GEOMETRY_VERTICES_OUT,
|
||||
GL_GEOMETRY_SHADER_INVOCATIONS}) {
|
||||
GetProgramiv(noGeometry, pname, &value);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_INVALID_OPERATION)) << "pname " << pname;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ProgramTest, GetProgramivReportsTheTessellationStageLinkProperties) {
|
||||
constexpr const char* tcs = R"(#version 460 core
|
||||
layout(vertices = 3) out;
|
||||
void main() {
|
||||
gl_TessLevelOuter[0] = 1.0; gl_TessLevelOuter[1] = 1.0; gl_TessLevelOuter[2] = 1.0;
|
||||
gl_TessLevelInner[0] = 1.0;
|
||||
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
|
||||
}
|
||||
)";
|
||||
constexpr const char* tes = R"(#version 460 core
|
||||
layout(quads, fractional_odd_spacing, cw, point_mode) in;
|
||||
void main() { gl_Position = gl_in[0].gl_Position; }
|
||||
)";
|
||||
const GLuint program = LinkStages({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||
{GL_TESS_CONTROL_SHADER, tcs},
|
||||
{GL_TESS_EVALUATION_SHADER, tes},
|
||||
{GL_FRAGMENT_SHADER, kPassthroughFs}});
|
||||
|
||||
GLint value = -1;
|
||||
GetProgramiv(program, GL_TESS_CONTROL_OUTPUT_VERTICES, &value);
|
||||
EXPECT_EQ(value, 3);
|
||||
GetProgramiv(program, GL_TESS_GEN_MODE, &value);
|
||||
EXPECT_EQ(value, GL_QUADS);
|
||||
GetProgramiv(program, GL_TESS_GEN_SPACING, &value);
|
||||
EXPECT_EQ(value, GL_FRACTIONAL_ODD);
|
||||
GetProgramiv(program, GL_TESS_GEN_VERTEX_ORDER, &value);
|
||||
EXPECT_EQ(value, GL_CW);
|
||||
GetProgramiv(program, GL_TESS_GEN_POINT_MODE, &value);
|
||||
EXPECT_EQ(value, GL_TRUE);
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
// GLSL 4.60 4.4.2.3 defaults: equal_spacing, ccw, no point mode.
|
||||
constexpr const char* defaultTes = R"(#version 460 core
|
||||
layout(triangles) in;
|
||||
void main() { gl_Position = gl_in[0].gl_Position; }
|
||||
)";
|
||||
const GLuint defaults = LinkStages({{GL_VERTEX_SHADER, kPassthroughVs},
|
||||
{GL_TESS_CONTROL_SHADER, tcs},
|
||||
{GL_TESS_EVALUATION_SHADER, defaultTes},
|
||||
{GL_FRAGMENT_SHADER, kPassthroughFs}});
|
||||
GetProgramiv(defaults, GL_TESS_GEN_MODE, &value);
|
||||
EXPECT_EQ(value, GL_TRIANGLES);
|
||||
GetProgramiv(defaults, GL_TESS_GEN_SPACING, &value);
|
||||
EXPECT_EQ(value, GL_EQUAL);
|
||||
GetProgramiv(defaults, GL_TESS_GEN_VERTEX_ORDER, &value);
|
||||
EXPECT_EQ(value, GL_CCW);
|
||||
GetProgramiv(defaults, GL_TESS_GEN_POINT_MODE, &value);
|
||||
EXPECT_EQ(value, GL_FALSE);
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
const GLuint noTess = LinkStages({{GL_VERTEX_SHADER, kPassthroughVs}, {GL_FRAGMENT_SHADER, kPassthroughFs}});
|
||||
for (const GLenum pname : {GL_TESS_CONTROL_OUTPUT_VERTICES, GL_TESS_GEN_MODE, GL_TESS_GEN_SPACING,
|
||||
GL_TESS_GEN_VERTEX_ORDER, GL_TESS_GEN_POINT_MODE}) {
|
||||
GetProgramiv(noTess, pname, &value);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_INVALID_OPERATION)) << "pname " << pname;
|
||||
}
|
||||
}
|
||||
|
||||
// The context-wide tessellation state the same conformance group reads before it links anything.
|
||||
// glGetBooleanv and glGetFloatv both have to answer GL_PATCH_DEFAULT_OUTER_LEVEL, which is
|
||||
// FLOAT state - a delegation that writes element 0 only would leave the other three components
|
||||
// as whatever was in the caller's stack.
|
||||
TEST_F(ProgramTest, ContextWideTessellationPropertiesAnswerEveryWidth) {
|
||||
GLfloat outer[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
|
||||
GetFloatv(GL_PATCH_DEFAULT_OUTER_LEVEL, outer);
|
||||
for (const GLfloat level : outer) EXPECT_FLOAT_EQ(level, 1.0f);
|
||||
|
||||
GLfloat inner[2] = {-1.0f, -1.0f};
|
||||
GetFloatv(GL_PATCH_DEFAULT_INNER_LEVEL, inner);
|
||||
for (const GLfloat level : inner) EXPECT_FLOAT_EQ(level, 1.0f);
|
||||
|
||||
GLboolean outerBools[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE};
|
||||
GetBooleanv(GL_PATCH_DEFAULT_OUTER_LEVEL, outerBools);
|
||||
for (const GLboolean level : outerBools) EXPECT_EQ(level, GL_TRUE);
|
||||
|
||||
GLint restart = -1;
|
||||
GetIntegerv(GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, &restart);
|
||||
EXPECT_EQ(restart, GL_FALSE);
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user