pyglet / pyglet

pyglet is a cross-platform windowing and multimedia library for Python, for developing games and other visually rich applications.
http://pyglet.org
BSD 3-Clause "New" or "Revised" License
1.77k stars 294 forks source link

pyglet.gl.lib.GLException when trying to draw text while multiple windows are open #1102

Open ItsVeeBot opened 1 week ago

ItsVeeBot commented 1 week ago

Describe the bug When opening multiple windows and attempting to draw text to one of them, Pyglet throws a pyglet.gl.lib.GLException:

Traceback (most recent call last):
  File "D:\VideoKaraokePrototype\Proto2\pyglet_multi_windows.py", line 16, in <module>
    pyglet.app.run()
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\app\__init__.py", line 76, in run
    event_loop.run(interval)
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\app\base.py", line 163, in run
    timeout = self.idle()
              ^^^^^^^^^^^
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\app\base.py", line 224, in idle
    self.clock.call_scheduled_functions(dt)
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\clock.py", line 217, in call_scheduled_functions
    item.func(now - item.last_ts, *item.args, **item.kwargs)
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\app\base.py", line 118, in _redraw_windows
    window.dispatch_event('on_draw')
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\window\__init__.py", line 672, in dispatch_event
    super().dispatch_event(*args)
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\event.py", line 380, in dispatch_event
    if handler(*args):
       ^^^^^^^^^^^^^^
  File "D:\VideoKaraokePrototype\Proto2\pyglet_multi_windows.py", line 13, in on_draw
    instructions_label.draw()
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\text\layout\base.py", line 1422, in draw
    self._batch.draw()
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\graphics\__init__.py", line 409, in draw
    func()
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\graphics\__init__.py", line 338, in <lambda>
    draw_list.append((lambda d, m: lambda: d.draw(m))(domain, mode))
                                           ^^^^^^^^^
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\graphics\vertexdomain.py", line 395, in draw
    self.vao.bind()
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\graphics\vertexarray.py", line 23, in bind
    glBindVertexArray(self._id)
  File "C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\gl\lib.py", line 78, in errcheck
    raise GLException(f'(0x{error}): {msg}')
pyglet.gl.lib.GLException: (0x1282): Invalid operation. The specified operation is not allowed in the current state.

System Information:

Platform
------------------------------------------------------------------------------
platform:  Windows-11-10.0.22631-SP0
release:   11
machine:   AMD64

Python
------------------------------------------------------------------------------
implementation: CPython
sys.version: 3.12.3 (tags/v3.12.3:f6650f9, Apr  9 2024, 14:05:25) [MSC v.1938 64 bit (AMD64)]
sys.maxint: 9223372036854775807
os.getcwd(): D:\VideoKaraokePrototype\Proto2

pyglet
------------------------------------------------------------------------------
pyglet.version: 2.0.15
pyglet.compat_platform: win32
pyglet.__file__: C:\Users\Bryan Hauser\.virtualenvs\Proto2-UJiBmJLC\Lib\site-packages\pyglet\__init__.py
pyglet.options['audio'] = ('xaudio2', 'directsound', 'openal', 'pulse', 'silent')
pyglet.options['debug_font'] = False
pyglet.options['debug_gl'] = True
pyglet.options['debug_gl_trace'] = False
pyglet.options['debug_gl_trace_args'] = False
pyglet.options['debug_gl_shaders'] = False
pyglet.options['debug_graphics_batch'] = False
pyglet.options['debug_lib'] = False
pyglet.options['debug_media'] = False
pyglet.options['debug_texture'] = False
pyglet.options['debug_trace'] = False
pyglet.options['debug_trace_args'] = False
pyglet.options['debug_trace_depth'] = 1
pyglet.options['debug_trace_flush'] = True
pyglet.options['debug_win32'] = False
pyglet.options['debug_input'] = False
pyglet.options['debug_x11'] = False
pyglet.options['shadow_window'] = True
pyglet.options['vsync'] = None
pyglet.options['xsync'] = True
pyglet.options['xlib_fullscreen_override_redirect'] = False
pyglet.options['search_local_libs'] = True
pyglet.options['win32_gdi_font'] = False
pyglet.options['headless'] = False
pyglet.options['headless_device'] = 0
pyglet.options['win32_disable_shaping'] = False
pyglet.options['dw_legacy_naming'] = False
pyglet.options['win32_disable_xinput'] = False
pyglet.options['com_mta'] = False
pyglet.options['osx_alt_loop'] = False

pyglet.window
------------------------------------------------------------------------------
display: <pyglet.canvas.win32.Win32Display object at 0x00000227928F9820>
screens[0]: Win32Screen(x=0, y=0, width=5120, height=1440)
config['double_buffer'] = 1
config['stereo'] = 0
config['buffer_size'] = 32
config['aux_buffers'] = 4
config['sample_buffers'] = 0
config['samples'] = 0
config['red_size'] = 8
config['green_size'] = 8
config['blue_size'] = 8
config['alpha_size'] = 0
config['depth_size'] = 24
config['stencil_size'] = 0
config['accum_red_size'] = 16
config['accum_green_size'] = 16
config['accum_blue_size'] = 16
config['accum_alpha_size'] = 16
config['major_version'] = 3
config['minor_version'] = 3
config['forward_compatible'] = None
config['opengl_api'] = 'gl'
config['debug'] = None
context: Win32ARBContext(id=2368984375296, share=Win32Context(id=2369023726832, share=None))

window.context._info
------------------------------------------------------------------------------
gl_info.get_version(): (3, 3)
gl_info.get_vendor(): NVIDIA Corporation
gl_info.get_renderer(): NVIDIA GeForce RTX 3060/PCIe/SSE2

pyglet.gl.glx_info
------------------------------------------------------------------------------
GLX not available.

pyglet.media
------------------------------------------------------------------------------
audio driver: <pyglet.media.drivers.xaudio2.adaptation.XAudio2Driver object at 0x00000227947E9B50>

pyglet.media.ffmpeg
------------------------------------------------------------------------------
FFmpeg version: 6.1.1-full_build-www.gyan.dev

pyglet.media.drivers.openal
------------------------------------------------------------------------------
Library: <CDLL 'openal32', handle 10000000 at 0x2279c805af0>
Version: 1.1
Extensions:
   ALC_ENUMERATE_ALL_EXT
   ALC_ENUMERATION_EXT
   ALC_EXT_CAPTURE
   ALC_EXT_EFX

pyglet.input.wintab
------------------------------------------------------------------------------
WinTab: Wintab Digitizer Services 2.0 (Spec 2.0)

How To Reproduce

import pyglet

first_window = pyglet.window.Window(width = 640, height = 480)
second_window = pyglet.window.Window(width = 640, height = 480)

instructions_label = pyglet.text.Label('Hi I am a label', font_name='Times New Roman', font_size=36, x=first_window.width//2, y=first_window.height//4, anchor_x='center', anchor_y='center')
instructions_label.color = (255,255,255,255)

@first_window.event
def on_draw():
    first_window.clear()
    instructions_label.draw()

pyglet.app.run()
benmoran56 commented 1 week ago

You can only draw or interact with pyglet objects from within the same context they were created in. See this example: https://github.com/pyglet/pyglet/blob/master/examples/window/multiple_windows.py

This also applies to events that get dispatched when another context is current.

matanox commented 14 hours ago

@ItsVeeBot what this means is that your code should manage the active OpenGL context that your process holds to when it is drawing or preparing elements for drawing through pyglet API. This abstract imperative translates to employing calls to Window.switch_to() such that your active OpenGL context is that of the window that you are drawing or preparing elements for drawing for.

Because there is only one OpenGL context active at any time ― you can wake up in a callback from one window, with your active OpenGL context being that of another window of your application.

Window.switch_to() switches the OpenGL context to that of the Window object that it is called on, which is how you switch to an OpenGL context to match the window that you want to draw for.

There have been some suggestions to alleviate this onus away from user code, but currently this is what you have to accomplish to avoid this exact type of crash ― analyze your flows to figure where you need to tell pyglet that the OpenGL context might need to be switched to the window that you mean to draw to ― there's always one active OpenGL context and pyglet does not guess which window's OpenGL context it should be unless you tell it.

I hope this is somewhat of a fair summary, as I'd like to cement my understanding of it before cleaning up my own multi-window application to be as minimal as possible yet sufficiently safe in setting the OpenGL context.

matanox commented 14 hours ago

Maybe some API for identifying the OpenGL context (?!) could be one way for developers to scaffold this safety around their code flows rather than relying purely on theoretical analyses of their application code.