Browse Source

GL: better handle a case of async link failures.

The new async APIs were just checking the link status, and printing the
linker error. Because drivers commonly do all that in a single step,
without really separating compilation from linking (or at least that's
what I thought?), I assumed the linker error would contain *also* the
compilation error, if any.

But on a quick check with Mesa that's not the case, I only get "error:
linking with uncompiled/unspecialized shader", which is very useless.
Which means, to get proper error output, the checkLink() function now
explicitly takes a list of the input shaders. It will unconditionally go
through them at the beginning and call checkCompile() on each.

To further encourage the shaders to be passed, there's no default
argument -- so if the application calls checkCompile() on its own for
some reason, it has to pass an empty list to checkLink().
pull/589/head
Vladimír Vondruš 4 years ago
parent
commit
c9d7365937
  1. 3
      doc/snippets/MagnumGL.cpp
  2. 11
      src/Magnum/GL/AbstractShaderProgram.cpp
  3. 37
      src/Magnum/GL/AbstractShaderProgram.h
  4. 34
      src/Magnum/GL/Shader.h
  5. 69
      src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp
  6. 3
      src/Magnum/Shaders/DistanceFieldVectorGL.cpp
  7. 3
      src/Magnum/Shaders/FlatGL.cpp
  8. 11
      src/Magnum/Shaders/MeshVisualizerGL.cpp
  9. 3
      src/Magnum/Shaders/PhongGL.cpp
  10. 3
      src/Magnum/Shaders/VectorGL.cpp
  11. 3
      src/Magnum/Shaders/VertexColorGL.cpp

3
doc/snippets/MagnumGL.cpp

@ -25,6 +25,7 @@
#include <tuple> /* for std::tie() :( */ #include <tuple> /* for std::tie() :( */
#include <Corrade/Containers/ArrayViewStl.h> #include <Corrade/Containers/ArrayViewStl.h>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
@ -300,7 +301,7 @@ MyShader::MyShader(NoInitT) {}
MyShader::MyShader(CompileState&& state): MyShader::MyShader(CompileState&& state):
MyShader{static_cast<MyShader&&>(std::move(state))} MyShader{static_cast<MyShader&&>(std::move(state))}
{ {
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
DOXYGEN_ELLIPSIS() DOXYGEN_ELLIPSIS()
} }

11
src/Magnum/GL/AbstractShaderProgram.cpp

@ -27,6 +27,7 @@
#include "AbstractShaderProgram.h" #include "AbstractShaderProgram.h"
#include <Corrade/Containers/Array.h> #include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/StridedArrayView.h> #include <Corrade/Containers/StridedArrayView.h>
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
#include <Corrade/Containers/String.h> #include <Corrade/Containers/String.h>
@ -591,7 +592,13 @@ void AbstractShaderProgram::submitLink() {
glLinkProgram(_id); glLinkProgram(_id);
} }
bool AbstractShaderProgram::checkLink() { bool AbstractShaderProgram::checkLink(const Containers::Iterable<Shader> shaders) {
/* If any compilation failed, abort without even checking the link status.
The checkCompile() API is called always, to print also compilation
warnings even in case everything still manages to link well. */
for(Shader& shader: shaders)
if(!shader.checkCompile()) return false;
GLint success, logLength; GLint success, logLength;
glGetProgramiv(_id, GL_LINK_STATUS, &success); glGetProgramiv(_id, GL_LINK_STATUS, &success);
glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength); glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength);
@ -633,7 +640,7 @@ bool AbstractShaderProgram::checkLink() {
bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<AbstractShaderProgram>> shaders) { bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<AbstractShaderProgram>> shaders) {
for(AbstractShaderProgram& shader: shaders) shader.submitLink(); for(AbstractShaderProgram& shader: shaders) shader.submitLink();
bool allSuccess = true; bool allSuccess = true;
for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink(); for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink({});
return allSuccess; return allSuccess;
} }

37
src/Magnum/GL/AbstractShaderProgram.h

@ -446,9 +446,9 @@ the application to schedule other work in the meantime.
Async compilation and linking can be implemented by using Async compilation and linking can be implemented by using
@ref Shader::submitCompile() and @ref submitLink(), followed by @ref Shader::submitCompile() and @ref submitLink(), followed by
@ref checkLink() (and optionally @ref Shader::checkCompile()), instead of @ref checkLink() (which optionally delegates to @ref Shader::checkCompile()),
@ref Shader::compile() and @ref link(). Calling the submit functions will instead of @ref Shader::compile() and @ref link(). Calling the submit functions
trigger a (potentially async) compilation and linking, calling the check will trigger a (potentially async) compilation and linking, calling the check
functions will check the operation result, potentially stalling if the async functions will check the operation result, potentially stalling if the async
operation isn't finished yet. operation isn't finished yet.
@ -476,9 +476,9 @@ creation capability while keeping also the simple constructor is the following:
@cpp CompileState @ce instance. @cpp CompileState @ce instance.
4. A @cpp MyShader(CompileState&&) @ce constructor then takes over the base 4. A @cpp MyShader(CompileState&&) @ce constructor then takes over the base
of @cpp CompileState @ce by delegating it into the move constructor. Then of @cpp CompileState @ce by delegating it into the move constructor. Then
it calls @ref checkLink() (and if that fails also @ref Shader::checkCompile() it calls @ref checkLink(), passing all input shaders to it for a complete
to provide more context) and then performs any remaining post-link steps context in case of an error, and finally performs any remaining post-link
such as uniform setup. steps such as uniform setup.
5. The original @cpp MyShader() @ce constructor now only passes the result of 5. The original @cpp MyShader() @ce constructor now only passes the result of
@cpp compile() @ce to @cpp MyShader(CompileState&&) @ce. @cpp compile() @ce to @cpp MyShader(CompileState&&) @ce.
@ -1551,16 +1551,31 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject {
* @m_since_latest * @m_since_latest
* *
* Has to be called only if @ref submitLink() was called before. * Has to be called only if @ref submitLink() was called before.
*
* If @p shaders are not empty, first calls @ref Shader::checkCompile()
* on each. If a compilation failure is reached, returns @cpp false @ce
* without even checking link status. To have error messages with full
* context in case of a failed shader compilation or linking, an
* application is encouraged to pass all input @ref Shader instances to
* this function or, if not possible, explicitly call
* @ref Shader::checkCompile() on each.
*
* Then, link status is checked and a message (if any) is printed
* Returns @cpp false @ce if linking failed, @cpp true @ce on success. * Returns @cpp false @ce if linking failed, @cpp true @ce on success.
* Linker message (if any) is printed to error output. The function * If linking failed, it first goes through @p shaders and calls
* will stall until a (potentially async) linking operation finishes, * @ref Shader::checkCompile() on each until a failure is reached. If
* you can use @ref isLinkFinished() to check the status instead. See * no compilation failed, a linker message is printed to error output.
* @ref GL-AbstractShaderProgram-async for more information. * The function will stall until a (potentially async) linking
* operation finishes, you can use @ref isLinkFinished() to check the
* status instead. See @ref GL-AbstractShaderProgram-async for more
* information.
* @see @ref Shader::checkCompile(), @fn_gl_keyword{GetProgram} with * @see @ref Shader::checkCompile(), @fn_gl_keyword{GetProgram} with
* @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH},
* @fn_gl_keyword{GetProgramInfoLog} * @fn_gl_keyword{GetProgramInfoLog}
*/ */
bool checkLink(); /* No default argument is provided in order to *really* encourage apps
to pass the shaders here */
bool checkLink(Containers::Iterable<Shader> shaders);
/** /**
* @brief Get uniform location * @brief Get uniform location

34
src/Magnum/GL/Shader.h

@ -645,13 +645,16 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject {
* @m_since_latest * @m_since_latest
* *
* You can call @ref isCompileFinished() or @ref checkCompile() after, * You can call @ref isCompileFinished() or @ref checkCompile() after,
* but in most cases it's enough to defer that to after * but it's recommended to instead immediately call
* @ref AbstractShaderProgram::attachShader() and * @ref AbstractShaderProgram::attachShader() and
* @relativeref{AbstractShaderProgram,submitLink()} were called, and * @relativeref{AbstractShaderProgram,submitLink()}, then optionally
* then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} * continue with @relativeref{AbstractShaderProgram,isLinkFinished()}
* or @relativeref{AbstractShaderProgram,checkLink()} on the final * and pass all input shaders to
* program --- if compilation would fail, subsequent linking will as * @relativeref{AbstractShaderProgram,checkLink()} on the final program
* well. See @ref GL-AbstractShaderProgram-async for more information. * --- if compilation would fail, subsequent linking will as well, and
* @relativeref{AbstractShaderProgram,checkLink()} will print the
* compilation error if linking failed due to that. See
* @ref GL-AbstractShaderProgram-async for more information.
* @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader}
*/ */
void submitCompile(); void submitCompile();
@ -660,14 +663,17 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject {
* @brief Check shader compilation status and await completion * @brief Check shader compilation status and await completion
* @m_since_latest * @m_since_latest
* *
* Has to be called only if @ref submitCompile() was called before. In * Has to be called only if @ref submitCompile() was called before.
* most cases it's enough to defer this check to after * It's however recommended to instead immediately call
* @ref AbstractShaderProgram::attachShader() and * @ref AbstractShaderProgram::attachShader() and
* @relativeref{AbstractShaderProgram,submitLink()} were called, and * @relativeref{AbstractShaderProgram,submitLink()}, then optionally
* then continuing with @relativeref{AbstractShaderProgram,isLinkFinished()} * continue with @relativeref{AbstractShaderProgram,isLinkFinished()}
* or @relativeref{AbstractShaderProgram,checkLink()} on the final * and pass all input shaders to
* program --- if compilation would fail, subsequent linking will as * @relativeref{AbstractShaderProgram,checkLink()} on the final program
* well. See @ref GL-AbstractShaderProgram-async for more information. * --- if compilation would fail, subsequent linking will as well, and
* @relativeref{AbstractShaderProgram,checkLink()} will print the
* compilation error if linking failed due to that. See
* @ref GL-AbstractShaderProgram-async for more information.
* @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and
* @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog}
*/ */
@ -684,7 +690,7 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject {
* available, the function always returns @cpp true @ce --- i.e., as if * available, the function always returns @cpp true @ce --- i.e., as if
* the compilation was done synchronously. * the compilation was done synchronously.
* *
* In most cases it's enough to only wait for the final link to finish, * It's however recommended to wait only for the final link to finish,
* and not for particular compilations --- i.e., right after * and not for particular compilations --- i.e., right after
* @ref submitCompile() continue with * @ref submitCompile() continue with
* @ref AbstractShaderProgram::attachShader() and * @ref AbstractShaderProgram::attachShader() and

69
src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp

@ -24,6 +24,7 @@
*/ */
#include <sstream> #include <sstream>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Containers/StringStl.h> /** @todo remove when Shader is <string>-free */ #include <Corrade/Containers/StringStl.h> /** @todo remove when Shader is <string>-free */
#include <Corrade/TestSuite/Compare/Container.h> #include <Corrade/TestSuite/Compare/Container.h>
@ -74,6 +75,7 @@ struct AbstractShaderProgramGLTest: OpenGLTester {
void linkFailure(); void linkFailure();
void linkFailureAsync(); void linkFailureAsync();
void linkFailureAsyncShaderList();
void uniformNotFound(); void uniformNotFound();
@ -116,6 +118,7 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() {
&AbstractShaderProgramGLTest::linkFailure, &AbstractShaderProgramGLTest::linkFailure,
&AbstractShaderProgramGLTest::linkFailureAsync, &AbstractShaderProgramGLTest::linkFailureAsync,
&AbstractShaderProgramGLTest::linkFailureAsyncShaderList,
&AbstractShaderProgramGLTest::uniformNotFound, &AbstractShaderProgramGLTest::uniformNotFound,
&AbstractShaderProgramGLTest::uniform, &AbstractShaderProgramGLTest::uniform,
@ -335,7 +338,7 @@ void AbstractShaderProgramGLTest::createAsync() {
while(!program.isLinkFinished()) while(!program.isLinkFinished())
Utility::System::sleep(100); Utility::System::sleep(100);
CORRADE_VERIFY(program.checkLink()); CORRADE_VERIFY(program.checkLink({vert, frag}));
CORRADE_VERIFY(program.isLinkFinished()); CORRADE_VERIFY(program.isLinkFinished());
const bool valid = program.validate().first; const bool valid = program.validate().first;
@ -544,16 +547,76 @@ void AbstractShaderProgramGLTest::linkFailureAsync() {
CORRADE_VERIFY(out.str().empty()); CORRADE_VERIFY(out.str().empty());
/* ... only the final check should. In this case it's "error: linking with /* ... only the final check should. In this case it's "error: linking with
uncompiled/unspecialized shader" as well. */ uncompiled/unspecialized shader" as well, but if the shaders would be
supplied like in linkFailureAsyncShaderList() below, it'd print the
shader failure instead. */
{ {
Error redirectError{&out}; Error redirectError{&out};
CORRADE_VERIFY(!program.checkLink()); CORRADE_VERIFY(!program.checkLink({}));
} }
CORRADE_VERIFY(program.isLinkFinished()); CORRADE_VERIFY(program.isLinkFinished());
CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:",
TestSuite::Compare::StringHasPrefix); TestSuite::Compare::StringHasPrefix);
} }
void AbstractShaderProgramGLTest::linkFailureAsyncShaderList() {
Shader vert(
#ifndef MAGNUM_TARGET_GLES
#ifndef CORRADE_TARGET_APPLE
Version::GL210
#else
Version::GL310
#endif
#else
Version::GLES200
#endif
, Shader::Type::Vertex);
vert.addSource("void main() {}\n");
Shader frag(
#ifndef MAGNUM_TARGET_GLES
#ifndef CORRADE_TARGET_APPLE
Version::GL210
#else
Version::GL310
#endif
#else
Version::GLES200
#endif
, Shader::Type::Fragment);
frag.addSource("[fu] bleh error #:! stuff\n");
vert.submitCompile();
frag.submitCompile();
MyPublicShader program;
program.attachShaders({vert, frag});
/* The link submission should not print anything ... */
{
std::ostringstream out;
Error redirectError{&out};
program.submitLink();
CORRADE_VERIFY(out.str().empty());
}
/* ... only the final check should. Vertex shader should be fine, but
fragment should fail. */
std::ostringstream out;
{
Error redirectError{&out};
CORRADE_VERIFY(!program.checkLink({vert, frag}));
}
CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:",
TestSuite::Compare::StringHasPrefix);
/* The linker error (which would most probably say something like "error:
linking with uncompiled/unspecialized shader") should not be even
printed */
CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:",
TestSuite::Compare::StringNotContains);
}
void AbstractShaderProgramGLTest::uniformNotFound() { void AbstractShaderProgramGLTest::uniformNotFound() {
MyPublicShader program; MyPublicShader program;

3
src/Magnum/Shaders/DistanceFieldVectorGL.cpp

@ -27,6 +27,7 @@
#include "DistanceFieldVectorGL.h" #include "DistanceFieldVectorGL.h"
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Resource.h> #include <Corrade/Utility/Resource.h>
@ -170,7 +171,7 @@ template<UnsignedInt dimensions> DistanceFieldVectorGL<dimensions>::DistanceFiel
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

3
src/Magnum/Shaders/FlatGL.cpp

@ -27,6 +27,7 @@
#include "FlatGL.h" #include "FlatGL.h"
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Resource.h> #include <Corrade/Utility/Resource.h>
@ -245,7 +246,7 @@ template<UnsignedInt dimensions> FlatGL<dimensions>::FlatGL(CompileState&& state
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

11
src/Magnum/Shaders/MeshVisualizerGL.cpp

@ -27,6 +27,7 @@
#include "MeshVisualizerGL.h" #include "MeshVisualizerGL.h"
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/FormatStl.h> #include <Corrade/Utility/FormatStl.h>
#include <Corrade/Utility/Resource.h> #include <Corrade/Utility/Resource.h>
@ -536,7 +537,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); if(state._geom)
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom}));
else
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;
@ -912,7 +916,10 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); if(state._geom)
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag, *state._geom}));
else
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

3
src/Magnum/Shaders/PhongGL.cpp

@ -30,6 +30,7 @@
#include <Corrade/Containers/Array.h> #include <Corrade/Containers/Array.h>
#endif #endif
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Containers/StringView.h> #include <Corrade/Containers/StringView.h>
#include <Corrade/Containers/StringStl.h> #include <Corrade/Containers/StringStl.h>
@ -355,7 +356,7 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast<PhongGL&&>(std::move
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

3
src/Magnum/Shaders/VectorGL.cpp

@ -27,6 +27,7 @@
#include "VectorGL.h" #include "VectorGL.h"
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Resource.h> #include <Corrade/Utility/Resource.h>
@ -172,7 +173,7 @@ template<UnsignedInt dimensions> VectorGL<dimensions>::VectorGL(CompileState&& s
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

3
src/Magnum/Shaders/VertexColorGL.cpp

@ -27,6 +27,7 @@
#include "VertexColorGL.h" #include "VertexColorGL.h"
#include <Corrade/Containers/EnumSet.hpp> #include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Reference.h> #include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Resource.h> #include <Corrade/Utility/Resource.h>
@ -150,7 +151,7 @@ template<UnsignedInt dimensions> VertexColorGL<dimensions>::VertexColorGL(Compil
if(!id()) return; if(!id()) return;
#endif #endif
CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink()); CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({state._vert, state._frag}));
const GL::Context& context = GL::Context::current(); const GL::Context& context = GL::Context::current();
const GL::Version version = state._version; const GL::Version version = state._version;

Loading…
Cancel
Save