sync pyglet and lib-not-dr

This commit is contained in:
shenjack 2023-11-03 16:24:59 +08:00
parent 8857166e67
commit b28ef5c580
Signed by: shenjack
GPG Key ID: 7B1134A979775551
12 changed files with 338 additions and 151 deletions

View File

@ -9,26 +9,11 @@ import inspect
from types import FrameType
from typing import List, Optional
from .structers import LogMessage
from lib_not_dr.logger.structers import LogMessage
from lib_not_dr.logger.outstream import BaseOutputStream
from lib_not_dr.types.options import Options
class BaseOutputStream(Options):
name = 'BaseOutputStream'
level: int = 20
enable: bool = True
def write_stdout(self, message: LogMessage) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.write_stdout is not implemented')
def write_stderr(self, message: LogMessage) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.write_stderr is not implemented')
def flush(self) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.flush is not implemented')
class Logger(Options):
name = 'Logger-v2'

View File

@ -0,0 +1,62 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
import sys
from lib_not_dr.types.options import Options
from lib_not_dr.logger.structers import LogMessage
from lib_not_dr.logger.formatter import BaseFormatter, StdFormatter
__all__ = [
'BaseOutputStream'
]
class BaseOutputStream(Options):
name = 'BaseOutputStream'
level: int = 20
enable: bool = True
def write_stdout(self, message: LogMessage) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.write_stdout is not implemented')
def write_stderr(self, message: LogMessage) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.write_stderr is not implemented')
def flush(self) -> None:
raise NotImplementedError(f'{self.__class__.__name__}.flush is not implemented')
class StdioOutputStream(BaseOutputStream):
name = 'StdioOutputStream'
formatter: BaseFormatter = StdFormatter()
def write_stdout(self, message: LogMessage) -> None:
if not self.enable:
return None
if message.level < self.level:
return None
print(self.formatter.format_message(message), end='', flush=message.flush)
return None
def write_stderr(self, message: LogMessage) -> None:
if not self.enable:
return None
if message.level < self.level:
return None
print(self.formatter.format_message(message), end='', flush=message.flush, file=sys.stderr)
return None
def flush(self) -> None:
"""
flush stdout and stderr
:return: None
"""
print('', end='', flush=True)
print('', end='', flush=True, file=sys.stderr)
return None

View File

@ -1,6 +1,7 @@
import weakref
from enum import Enum
import threading
from typing import Tuple
import pyglet
@ -209,11 +210,12 @@ class CanvasConfig(Config):
class ObjectSpace:
def __init__(self):
# Textures and buffers scheduled for deletion
# the next time this object space is active.
# Objects scheduled for deletion the next time this object space is active.
self.doomed_textures = []
self.doomed_buffers = []
self.doomed_shader_programs = []
self.doomed_shaders = []
self.doomed_renderbuffers = []
class Context:
@ -236,6 +238,7 @@ class Context:
self.canvas = None
self.doomed_vaos = []
self.doomed_framebuffers = []
if context_share:
self.object_space = context_share.object_space
@ -277,28 +280,44 @@ class Context:
self._info = gl_info.GLInfo()
self._info.set_active_context()
# Release Textures, Buffers, and VAOs on this context scheduled for
# deletion. Note that the garbage collector may introduce a race
# condition, so operate on a copy, and clear the list afterward.
if self.object_space.doomed_textures:
textures = self.object_space.doomed_textures[:]
textures = (gl.GLuint * len(textures))(*textures)
gl.glDeleteTextures(len(textures), textures)
self.object_space.doomed_textures.clear()
self._delete_objects(self.object_space.doomed_textures, gl.glDeleteTextures)
if self.object_space.doomed_buffers:
buffers = self.object_space.doomed_buffers[:]
buffers = (gl.GLuint * len(buffers))(*buffers)
gl.glDeleteBuffers(len(buffers), buffers)
self.object_space.doomed_buffers.clear()
self._delete_objects(self.object_space.doomed_buffers, gl.glDeleteBuffers)
if self.object_space.doomed_shader_programs:
for program_id in self.object_space.doomed_shader_programs:
gl.glDeleteProgram(program_id)
self.object_space.doomed_shader_programs.clear()
self._delete_objects_one_by_one(self.object_space.doomed_shader_programs,
gl.glDeleteProgram)
if self.object_space.doomed_shaders:
self._delete_objects_one_by_one(self.object_space.doomed_shaders, gl.glDeleteShader)
if self.object_space.doomed_renderbuffers:
self._delete_objects(self.object_space.doomed_renderbuffers, gl.glDeleteRenderbuffers)
if self.doomed_vaos:
vaos = self.doomed_vaos[:]
vaos = (gl.GLuint * len(vaos))(*vaos)
gl.glDeleteVertexArrays(len(vaos), vaos)
self.doomed_vaos.clear()
self._delete_objects(self.doomed_vaos, gl.glDeleteVertexArrays)
if self.doomed_framebuffers:
self._delete_objects(self.doomed_framebuffers, gl.glDeleteFramebuffers)
# For the functions below:
# The garbage collector introduces a race condition.
# The provided list might be appended to (and only appended to) while this
# method runs, as it's a `doomed_*` list either on the context or
# its object space. This is why we drain it relying on `pop`s atomicity.
def _delete_objects(self, list_, deletion_func):
"""Release all OpenGL objects in the given list using the supplied
deletion function with the signature ``(GLuint count, GLuint *names)``.
"""
to_delete = []
while list_:
to_delete.append(list_.pop())
deletion_func(len(to_delete), (gl.GLuint * len(to_delete))(*to_delete))
def _delete_objects_one_by_one(self, list_, deletion_func):
"""Similar to ``_delete_objects``, but assumes the deletion functions's
signature to be ``(GLuint name)``, calling it once for each object.
"""
while list_:
deletion_func(gl.GLuint(list_.pop()))
def destroy(self):
"""Release the context.
@ -318,6 +337,27 @@ class Context:
if gl._shadow_window is not None:
gl._shadow_window.switch_to()
def _safe_to_operate_on_object_space(self):
"""Return whether it is safe to interact with this context's object
space.
This is considered to be the case if the currently active context's
object space is the same as this context's object space and this
method is called from the main thread.
"""
return (
self.object_space is gl.current_context.object_space and
threading.current_thread() is threading.main_thread()
)
def _safe_to_operate_on(self):
"""Return whether it is safe to interact with this context.
This is considered to be the case if it's the current context and this
method is called from the main thread.
"""
return gl.current_context is self and threading.current_thread() is threading.main_thread()
def create_program(self, *sources: Tuple[str, str], program_class=None):
"""Create a ShaderProgram from OpenGL GLSL source.
@ -347,25 +387,30 @@ class Context:
return program
def delete_texture(self, texture_id):
"""Safely delete a Texture belonging to this context.
"""Safely delete a Texture belonging to this context's object space.
Usually, the Texture is released immediately using
``glDeleteTextures``, however if another context that does not share
this context's object space is currently active, the deletion will
be deferred until an appropriate context is activated.
This method will delete the texture immediately via
``glDeleteTextures`` if the current context's object space is the same
as this context's object space and it is called from the main thread.
Otherwise, the texture will only be marked for deletion, postponing
it until any context with the same object space becomes active again.
This makes it safe to call from anywhere, including other threads.
:Parameters:
`texture_id` : int
The OpenGL name of the Texture to delete.
"""
if self.object_space is gl.current_context.object_space:
if self._safe_to_operate_on_object_space():
gl.glDeleteTextures(1, gl.GLuint(texture_id))
else:
self.object_space.doomed_textures.append(texture_id)
def delete_buffer(self, buffer_id):
"""Safely delete a Buffer object belonging to this context.
"""Safely delete a Buffer object belonging to this context's object
space.
This method behaves similarly to `delete_texture`, though for
``glDeleteBuffers`` instead of ``glDeleteTextures``.
@ -376,30 +421,14 @@ class Context:
.. versionadded:: 1.1
"""
if self.object_space is gl.current_context.object_space and False:
if self._safe_to_operate_on_object_space():
gl.glDeleteBuffers(1, gl.GLuint(buffer_id))
else:
self.object_space.doomed_buffers.append(buffer_id)
def delete_vao(self, vao_id):
"""Safely delete a Vertex Array Object belonging to this context.
This method behaves similarly to `delete_texture`, though for
``glDeleteVertexArrays`` instead of ``glDeleteTextures``.
:Parameters:
`vao_id` : int
The OpenGL name of the Vertex Array to delete.
.. versionadded:: 2.0
"""
if gl.current_context is self:
gl.glDeleteVertexArrays(1, gl.GLuint(vao_id))
else:
self.doomed_vaos.append(vao_id)
def delete_shader_program(self, program_id):
"""Safely delete a Shader Program belonging to this context.
"""Safely delete a Shader Program belonging to this context's
object space.
This method behaves similarly to `delete_texture`, though for
``glDeleteProgram`` instead of ``glDeleteTextures``.
@ -410,11 +439,84 @@ class Context:
.. versionadded:: 2.0
"""
if gl.current_context is self:
gl.glDeleteProgram(program_id)
if self._safe_to_operate_on_object_space():
gl.glDeleteProgram(gl.GLuint(program_id))
else:
self.object_space.doomed_shader_programs.append(program_id)
def delete_shader(self, shader_id):
"""Safely delete a Shader belonging to this context's object space.
This method behaves similarly to `delete_texture`, though for
``glDeleteShader`` instead of ``glDeleteTextures``.
:Parameters:
`shader_id` : int
The OpenGL name of the Shader to delete.
.. versionadded:: 2.0.10
"""
if self._safe_to_operate_on_object_space():
gl.glDeleteShader(gl.GLuint(shader_id))
else:
self.object_space.doomed_shaders.append(shader_id)
def delete_renderbuffer(self, rbo_id):
"""Safely delete a Renderbuffer Object belonging to this context's
object space.
This method behaves similarly to `delete_texture`, though for
``glDeleteRenderbuffers`` instead of ``glDeleteTextures``.
:Parameters:
`rbo_id` : int
The OpenGL name of the Shader Program to delete.
.. versionadded:: 2.0.10
"""
if self._safe_to_operate_on_object_space():
gl.glDeleteRenderbuffers(1, gl.GLuint(rbo_id))
else:
self.object_space.doomed_renderbuffers.append(rbo_id)
def delete_vao(self, vao_id):
"""Safely delete a Vertex Array Object belonging to this context.
If this context is not the current context or this method is not
called from the main thread, its deletion will be postponed until
this context is next made active again.
Otherwise, this method will immediately delete the VAO via
``glDeleteVertexArrays``.
:Parameters:
`vao_id` : int
The OpenGL name of the Vertex Array to delete.
.. versionadded:: 2.0
"""
if self._safe_to_operate_on():
gl.glDeleteVertexArrays(1, gl.GLuint(vao_id))
else:
self.doomed_vaos.append(vao_id)
def delete_framebuffer(self, fbo_id):
"""Safely delete a Framebuffer Object belonging to this context.
This method behaves similarly to `delete_vao`, though for
``glDeleteFramebuffers`` instead of ``glDeleteVertexArrays``.
:Parameters:
`fbo_id` : int
The OpenGL name of the Framebuffer Object to delete.
.. versionadded:: 2.0.10
"""
if self._safe_to_operate_on():
gl.glDeleteFramebuffers(1, gl.GLuint(fbo_id))
else:
self.doomed_framebuffers.append(fbo_id)
def get_info(self):
"""Get the OpenGL information for this context.

View File

@ -62,8 +62,7 @@ def errcheck(result, func, arguments):
print(name)
from pyglet import gl
context = gl.current_context
if not context:
if not gl.current_context:
raise GLException('No GL context; create a Window first')
error = gl.glGetError()
if error:

View File

@ -674,6 +674,7 @@ class Shader:
"""
def __init__(self, source_string: str, shader_type: str):
self._context = pyglet.gl.current_context
self._id = None
self.type = shader_type
@ -689,6 +690,7 @@ class Shader:
source_length = c_int(len(shader_source_utf8))
shader_id = glCreateShader(shader_type)
self._id = shader_id
glShaderSource(shader_id, 1, byref(source_buffer_pointer), source_length)
glCompileShader(shader_id)
@ -709,8 +711,6 @@ class Shader:
elif _debug_gl_shaders:
print(self._get_shader_log(shader_id))
self._id = shader_id
@property
def id(self):
return self._id
@ -735,16 +735,19 @@ class Shader:
glGetShaderSource(shader_id, source_length, None, source_str)
return source_str.value.decode('utf8')
def __del__(self):
try:
glDeleteShader(self._id)
if _debug_gl_shaders:
print(f"Destroyed {self.type} Shader '{self._id}'")
def delete(self):
glDeleteShader(self._id)
self._id = None
except Exception:
# Interpreter is shutting down,
# or Shader failed to compile.
pass
def __del__(self):
if self._id is not None:
try:
self._context.delete_shader(self._id)
if _debug_gl_shaders:
print(f"Destroyed {self.type} Shader '{self._id}'")
self._id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def __repr__(self):
return "{0}(id={1}, type={2})".format(self.__class__.__name__, self.id, self.type)
@ -756,6 +759,8 @@ class ShaderProgram:
__slots__ = '_id', '_context', '_attributes', '_uniforms', '_uniform_blocks', '__weakref__'
def __init__(self, *shaders: Shader):
self._id = None
assert shaders, "At least one Shader object is required."
self._id = _link_program(*shaders)
self._context = pyglet.gl.current_context
@ -799,13 +804,17 @@ class ShaderProgram:
def __exit__(self, *_):
glUseProgram(0)
def delete(self):
glDeleteProgram(self._id)
self._id = None
def __del__(self):
try:
self._context.delete_shader_program(self.id)
except Exception:
# Interpreter is shutting down,
# or ShaderProgram failed to link.
pass
if self._id is not None:
try:
self._context.delete_shader_program(self._id)
self._id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def __setitem__(self, key, value):
try:
@ -930,6 +939,8 @@ class ComputeShaderProgram:
def __init__(self, source: str):
"""Create an OpenGL ComputeShaderProgram from source."""
self._id = None
if not (gl_info.have_version(4, 3) or gl_info.have_extension("GL_ARB_compute_shader")):
raise ShaderException("Compute Shader not supported. OpenGL Context version must be at least "
"4.3 or higher, or 4.2 with the 'GL_ARB_compute_shader' extension.")
@ -1002,13 +1013,17 @@ class ComputeShaderProgram:
def __exit__(self, *_):
glUseProgram(0)
def delete(self):
glDeleteProgram(self._id)
self._id = None
def __del__(self):
try:
self._context.delete_shader_program(self.id)
except Exception:
# Interpreter is shutting down,
# or ShaderProgram failed to link.
pass
if self._id is not None:
try:
self._context.delete_shader_program(self._id)
self._id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def __setitem__(self, key, value):
try:

View File

@ -27,10 +27,8 @@ class VertexArray:
glBindVertexArray(0)
def delete(self):
try:
glDeleteVertexArrays(1, self._id)
except Exception:
pass
glDeleteVertexArrays(1, self._id)
self._id = None
__enter__ = bind
@ -38,11 +36,12 @@ class VertexArray:
glBindVertexArray(0)
def __del__(self):
try:
self._context.delete_vao(self.id)
# Python interpreter is shutting down:
except ImportError:
pass
if self._id is not None:
try:
self._context.delete_vao(self.id)
self._id = None
except (ImportError, AttributeError):
pass # Interpreter is shutting down
def __repr__(self):
return "{}(id={})".format(self.__class__.__name__, self._id.value)

View File

@ -164,21 +164,18 @@ class BufferObject(AbstractBuffer):
def unmap(self):
glUnmapBuffer(GL_ARRAY_BUFFER)
def __del__(self):
try:
if self.id is not None:
self._context.delete_buffer(self.id)
except:
pass
def delete(self):
buffer_id = GLuint(self.id)
try:
glDeleteBuffers(1, buffer_id)
except Exception:
pass
glDeleteBuffers(1, self.id)
self.id = None
def __del__(self):
if self.id is not None:
try:
self._context.delete_buffer(self.id)
self.id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def resize(self, size):
# Map, create a copy, then reinitialize.
temp = (ctypes.c_byte * size)()

View File

@ -452,7 +452,7 @@ class TextEntry(WidgetBase):
self._height = value
self._layout.height = value
self._outline.height = value
@property
def focus(self) -> bool:
return self._focus

View File

@ -1220,11 +1220,20 @@ class Texture(AbstractImage):
self.id = tex_id
self._context = pyglet.gl.current_context
def delete(self):
"""Delete this texture and the memory it occupies.
After this, it may not be used anymore.
"""
glDeleteTextures(1, self.id)
self.id = None
def __del__(self):
try:
self._context.delete_texture(self.id)
except Exception:
pass
if self.id is not None:
try:
self._context.delete_texture(self.id)
self.id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def bind(self, texture_unit: int = 0):
"""Bind to a specific Texture Unit by number."""
@ -1479,8 +1488,13 @@ class TextureRegion(Texture):
return "{}(id={}, size={}x{}, owner={}x{})".format(self.__class__.__name__, self.id, self.width, self.height,
self.owner.width, self.owner.height)
def delete(self):
"""Deleting a TextureRegion has no effect. Operate on the owning
texture instead.
"""
pass
def __del__(self):
# only the owner Texture should handle deletion
pass

View File

@ -13,6 +13,7 @@ class Renderbuffer:
def __init__(self, width, height, internal_format, samples=1):
"""Create an instance of a Renderbuffer object."""
self._context = pyglet.gl.current_context
self._id = GLuint()
self._width = width
self._height = height
@ -49,13 +50,15 @@ class Renderbuffer:
def delete(self):
glDeleteRenderbuffers(1, self._id)
self._id = None
def __del__(self):
try:
glDeleteRenderbuffers(1, self._id)
# Python interpreter is shutting down:
except Exception:
pass
if self._id is not None:
try:
self._context.delete_renderbuffer(self._id.value)
self._id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
def __repr__(self):
return "{}(id={})".format(self.__class__.__name__, self._id.value)
@ -71,6 +74,7 @@ class Framebuffer:
.. versionadded:: 2.0
"""
self._context = pyglet.gl.current_context
self._id = GLuint()
glGenFramebuffers(1, self._id)
self._attachment_types = 0
@ -105,10 +109,16 @@ class Framebuffer:
self.unbind()
def delete(self):
try:
glDeleteFramebuffers(1, self._id)
except Exception:
pass
glDeleteFramebuffers(1, self._id)
self._id = None
def __del__(self):
if self._id is not None:
try:
self._context.delete_framebuffer(self._id.value)
self._id = None
except (AttributeError, ImportError):
pass # Interpreter is shutting down
@property
def is_complete(self):
@ -203,12 +213,5 @@ class Framebuffer:
self._height = max(renderbuffer.height, self._height)
self.unbind()
def __del__(self):
try:
glDeleteFramebuffers(1, self._id)
# Python interpreter is shutting down:
except Exception:
pass
def __repr__(self):
return "{}(id={})".format(self.__class__.__name__, self._id.value)

View File

@ -4,7 +4,6 @@ import fcntl
import ctypes
import warnings
from os import readv
from ctypes import c_uint16 as _u16
from ctypes import c_int16 as _s16
from ctypes import c_uint32 as _u32
@ -22,6 +21,8 @@ from pyglet.input.base import Device, RelativeAxis, AbsoluteAxis, Button, Joysti
from pyglet.input.base import DeviceOpenException, ControllerManager
from pyglet.input.controller import get_mapping, Relation, create_guid
c = pyglet.lib.load_library('c')
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
@ -408,7 +409,7 @@ class EvdevDevice(XlibSelectDevice, Device):
return
try:
bytes_read = readv(self._fileno, self._event_buffer)
bytes_read = c.read(self._fileno, self._event_buffer, self._event_size)
except OSError:
self.close()
return

View File

@ -273,9 +273,10 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
conventions. This will ensure it is not obscured by other windows,
and appears on an appropriate screen for the user.
To render into a window, you must first call `switch_to`, to make
it the current OpenGL context. If you use only one window in the
application, there is no need to do this.
To render into a window, you must first call its :py:meth:`.switch_to`
method to make it the active OpenGL context. If you use only one
window in your application, you can skip this step as it will always
be the active context.
"""
# Filled in by metaclass with the names of all methods on this (sub)class
@ -638,7 +639,8 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
"""Clear the window.
This is a convenience method for clearing the color and depth
buffer. The window must be the active context (see `switch_to`).
buffer. The window must be the active context (see
:py:meth:`.switch_to`).
"""
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
@ -646,10 +648,12 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
"""Close the window.
After closing the window, the GL context will be invalid. The
window instance cannot be reused once closed (see also `set_visible`).
window instance cannot be reused once closed. To re-use windows,
see :py:meth:`.set_visible` instead.
The `pyglet.app.EventLoop.on_window_close` event is dispatched on
`pyglet.app.event_loop` when this method is called.
The :py:meth:`pyglet.app.EventLoop.on_window_close` event is
dispatched by the :py:attr:`pyglet.app.event_loop` when this method
is called.
"""
from pyglet import app
if not self._context:
@ -676,7 +680,7 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
and advanced applications that must integrate their event loop
into another framework.
Typical applications should use `pyglet.app.run`.
Typical applications should use :py:func:`pyglet.app.run`.
"""
raise NotImplementedError('abstract')
@ -715,11 +719,14 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
"""Swap the OpenGL front and back buffers.
Call this method on a double-buffered window to update the
visible display with the back buffer. The contents of the back buffer
is undefined after this operation.
visible display with the back buffer. Windows are
double-buffered by default unless you turn this feature off.
Windows are double-buffered by default. This method is called
automatically by `EventLoop` after the :py:meth:`~pyglet.window.Window.on_draw` event.
The contents of the back buffer are undefined after this operation.
The default :py:attr:`~pyglet.app.event_loop` automatically
calls this method after the window's
:py:meth:`~pyglet.window.Window.on_draw` event.
"""
raise NotImplementedError('abstract')
@ -1158,10 +1165,13 @@ class BaseWindow(EventDispatcher, metaclass=_WindowMetaclass):
def switch_to(self):
"""Make this window the current OpenGL rendering context.
Only one OpenGL context can be active at a time. This method sets
the current window's context to be current. You should use this
method in preference to `pyglet.gl.Context.set_current`, as it may
perform additional initialisation functions.
Only one OpenGL context can be active at a time. This method
sets the current window context as the active one.
In most cases, you should use this method instead of directly
calling :py:meth:`pyglet.gl.Context.set_current`. The latter
will not perform platform-specific state management tasks for
you.
"""
raise NotImplementedError('abstract')