"cannot detect OpenGL context" when using Pygame and moderngl?

786 Views Asked by At

I wrote the simple program below:

import pygame as pg
import moderngl as mgl
import sys

class GraphicsEngine:
    def __init__(self, win_size=(1200,700)):
        pg.init()

        self.WIN_SIZE = win_size
        pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
        pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
        pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)

        pg.display.set_mode(self.WIN_SIZE, flags=pg.OPENGL | pg.DOUBLEBUF)

        self.ctx = mgl.create_context()

        self.clock = pg.time.Clock()

    def check_events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
                pg.quit()
                sys.exit()
    
    def render(self):
        self.ctx.clear(color=(0.08, 0.16, 0.18))

        pg.display.flip()

    def run(self):
        while True:
            self.check_events()
            self.render()
            self.clock.tick(60)
            pg.display.set_caption(pg.time.get_fps())



if __name__ == '__main__':
    app=GraphicsEngine()
    app.run()

and I get this error

File "path\main.py", line 41, in <module>
    app=GraphicsEngine()
        ^^^^^^^^^^^^^^^^
  File "path\main.py", line 16, in __init__
    self.ctx = mgl.create_context()
               ^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\\AppData\Roaming\Python\Python311\site->packages\moderngl\context.py", line 1968, in create_context
    ctx.mglo, ctx.version_code = >mgl.create_context(glversion=require, mode=mode, **settings)
                                 >^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\\AppData\Roaming\Python\Python311\site->packages\glcontext\__init__.py", line 69, in create
    return wgl.create_context(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Exception: cannot detect OpenGL context

I tried to reinstall moderngl but nothing changed

2

There are 2 best solutions below

4
wizzwizz4 On

Let's start at the end of your traceback:

  File "C:\Python311\site-packages\glcontext\__init__.py", line 69, in create
    return wgl.create_context(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Exception: cannot detect OpenGL context

Looking in __init__.py, we don't find the string "cannot detect OpenGL context". But, on line 56, we do find:

from glcontext import wgl

In moderngl's source code, we find a file wgl.cpp, with the lines (119–123):

res->hrc = res->m_wglGetCurrentContext();
if (!res->hrc) {
    PyErr_Format(PyExc_Exception, "cannot detect OpenGL context");
    return NULL;
}

I don't know what res->hrc is, but nor do I need to, because it's the return value of res->m_wglGetCurrentContext(), and that function comes from line 80:

res->m_wglGetCurrentContext = (m_wglGetCurrentContextProc)GetProcAddress(res->libgl, "wglGetCurrentContext");

A little earlier (line 73), we see that res->libgl is:

res->libgl = LoadLibraryEx(libgl, NULL, (DWORD)load_mode);

where libgl is (line 50):

const char * libgl = "opengl32.dll";

So there's a problem with the wglGetCurrentContext function from opengl32.dll. Let's check Microsoft's Win32API documentation:

If the calling thread has a current OpenGL rendering context, wglGetCurrentContext returns a handle to that rendering context. Otherwise, the return value is NULL.

The !res->hrc on line 120 is checking for NULL, so this looks like our culprit!

Now, why is there no OpenGL rendering context...?

Standalone hypothesis

The Windows API documentation tells us how to create an OpenGL context – and, indeed, there's a function res->m_wglCreateContext in wgl.cpp. It's called on line 214, inside an if block starting at line 180:

if (!strcmp(mode, "standalone")) {

Where does mode come from? Lines 49–55:

const char * mode = "detect";
const char * libgl = "opengl32.dll";
int glversion = 330;

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ssi", keywords, &mode, &libgl, &glversion)) {
    return NULL;
}

It looks hardcoded… but it doesn't look like it's supposed to be. Let's check the Python documentation for PyArg_ParseTupleAndKeywords:

Parse the parameters of a function that takes both positional and keyword parameters into local variables. The keywords argument is a NULL-terminated array of keyword parameter names. Empty names denote positional-only parameters. Returns true on success; on failure, it returns false and raises the appropriate exception.

Not terribly useful, but Stack Overflow yields some code examples and, put together, we can see that mode is a bog-standard Python kwarg.

So, following the keyword arguments up the traceback, consulting the source code of each function, we find that changing your line 16:

self.ctx = mgl.create_context()

to:

self.ctx = mgl.create_context(standalone=True)

fixes the immediate problem: now an OpenGL context is created. However, it doesn't seem to do anything.

Pygame hypothesis

The ModernGL documentation says:

ModernGL can only create headless contexts (no window), but it can also detect and use contexts from a large range of window libraries.

As Rabbid76 identified, ModernGL is supposed to pick up the context from pygame. The fact it doesn't is curious. They should be created by pygame.init(), so let's track that down.

>>> import pygame
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
>>> pygame.init
<built-in function init>
>>> pygame.init.__doc__
'init() -> (numpass, numfail)\ninitialize all imported pygame modules

So pygame.init is defined in C – as one might expect. base.c's pg_init has an identical function and an identical return type, so I'll assume that's it. Lines 341–343:

/* Put all the module names we want to init in this array */
const char *modnames[] = {
    IMPPREFIX "display", /* Display first, this also inits event,time */
    IMPPREFIX "joystick", IMPPREFIX "font", IMPPREFIX "freetype",
    IMPPREFIX "mixer",
    /* IMPPREFIX "_sdl2.controller", Is this required? Comment for now*/
    NULL};

Lines 361–368:

if (pg_mod_autoinit(modnames[i]))
    success++;
else {
    /* ImportError is neither counted as success nor failure */
    if (!PyErr_ExceptionMatches(PyExc_ImportError))
        fail++;
    PyErr_Clear();
}

pgmod_autoinit looks for a function named _internal_mod_init and, failing that, a function called init. The pygame.display module handles SDL2, which handles OpenGL contexts; display._internal_mod_init doesn't exist, but pygame.display.init does. The relevant section:

const char *drivername;
/* Compatibility:
 * windib video driver was renamed in SDL2, and we don't want it to fail.
 */
drivername = SDL_getenv("SDL_VIDEODRIVER");
if (drivername &&
    !SDL_strncasecmp("windib", drivername, SDL_strlen(drivername))) {
    SDL_setenv("SDL_VIDEODRIVER", "windows", 1);
}
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
    if (!_pg_mac_display_init())
        return NULL;

    if (SDL_InitSubSystem(SDL_INIT_VIDEO))
        return RAISE(pgExc_SDLError, SDL_GetError());
}

(return NULL; means an exception occurred, and it should be re-raised.)

SDL2's code is pretty solid, so there's probably no bug there. However, recall the code from base.c: if this code fails, pygame.init() will swallow the exception, and the only way you'd know anything went wrong is if you checked the return value.

It's probably possible to debug this further by looking into SDL2's source code, to find the conditions under which wglCreateContext doesn't get called, but the easiest way would be to check pygame.init()'s return value.

4
Error On

I found a solution to my problem. it was some problem with environment variables, it was enough to remove one variable that shouldn't be there and reinstall moderngl. thanks for trying to help.