From 925b29f5fc4e2abeddab7292f250f937bb819c67 Mon Sep 17 00:00:00 2001 From: yunline Date: Wed, 8 Jan 2025 21:55:27 +0800 Subject: [PATCH 1/5] Implement `display.gl_get_proc` --- buildconfig/stubs/pygame/display.pyi | 3 +- src_c/display.c | 41 ++++++++++++++++++++++++++++ src_py/_ffi.py | 4 +++ src_py/meson.build | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src_py/_ffi.py diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index 5124e92b47..ae9a6ff8f7 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable -from typing import Optional, Union, overload, Literal +from typing import Optional, Union, overload, Literal, Any from typing_extensions import deprecated # added in 3.13 from pygame.constants import FULLSCREEN @@ -70,6 +70,7 @@ def mode_ok( ) -> int: ... def gl_get_attribute(flag: int, /) -> int: ... def gl_set_attribute(flag: int, value: int, /) -> None: ... +def gl_get_proc(proc_name: str) -> Any: ... def get_active() -> bool: ... def iconify() -> bool: ... def toggle_fullscreen() -> int: ... diff --git a/src_c/display.c b/src_c/display.c index 3ff35d4fa0..741397a2e7 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2938,6 +2938,33 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) return NULL; } +static PyObject *pg_proc_from_address = NULL; + +static PyObject * +pg_gl_get_proc(PyObject *self, PyObject *arg) +{ + if (!PyUnicode_Check(arg)) { + return RAISE(PyExc_TypeError, "'proc_name' should be a string"); + } + const char *proc_name = PyUnicode_AsUTF8(arg); + if (!proc_name) { + return NULL; + } + unsigned long long proc_addr = + (unsigned long long)SDL_GL_GetProcAddress(proc_name); + if (!proc_addr) { + return RAISE(pgExc_SDLError, SDL_GetError()); + } + PyObject *proc_from_address_args = Py_BuildValue("(K)", proc_addr); + if (!proc_from_address_args) { + return NULL; + } + if (!pg_proc_from_address) { + return RAISE(PyExc_TypeError, "'_proc_from_address' object is NULL"); + } + return PyObject_CallObject(pg_proc_from_address, proc_from_address_args); +} + static PyMethodDef _pg_display_methods[] = { {"init", (PyCFunction)pg_display_init, METH_NOARGS, DOC_DISPLAY_INIT}, {"quit", (PyCFunction)pg_display_quit, METH_NOARGS, DOC_DISPLAY_QUIT}, @@ -3015,6 +3042,7 @@ static PyMethodDef _pg_display_methods[] = { METH_VARARGS | METH_KEYWORDS, DOC_DISPLAY_SETALLOWSCREENSAVER}, {"message_box", (PyCFunction)pg_message_box, METH_VARARGS | METH_KEYWORDS, DOC_DISPLAY_MESSAGEBOX}, + {"gl_get_proc", (PyCFunction)pg_gl_get_proc, METH_O, "doc"}, {NULL, NULL, 0, NULL}}; #ifndef PYPY_VERSION @@ -3065,6 +3093,19 @@ MODINIT_DEFINE(display) return NULL; } + /* load _ffi module for display.get_gl_proc function */ + PyObject *pg_ffi_module = PyImport_ImportModule("pygame._ffi"); + if (!pg_ffi_module) { + return NULL; + } + + pg_proc_from_address = + PyObject_GetAttrString(pg_ffi_module, "_proc_from_address"); + if (!pg_proc_from_address) { + return NULL; + } + Py_DECREF(pg_ffi_module); + /* create the module */ module = PyModule_Create(&_module); if (module == NULL) { diff --git a/src_py/_ffi.py b/src_py/_ffi.py new file mode 100644 index 0000000000..6a579f6c6c --- /dev/null +++ b/src_py/_ffi.py @@ -0,0 +1,4 @@ +import ctypes + +def _proc_from_address(addr): + return ctypes.CFUNCTYPE(None)(addr) diff --git a/src_py/meson.build b/src_py/meson.build index 541c54cd69..5eba01ced9 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -4,6 +4,7 @@ python_sources = files( '_camera_opencv.py', '_data_classes.py', '_debug.py', + '_ffi.py', '_sprite.py', 'camera.py', 'colordict.py', From 9cf479763a62c85336dfdaa6651b42954a0354a0 Mon Sep 17 00:00:00 2001 From: yunline Date: Wed, 8 Jan 2025 22:29:28 +0800 Subject: [PATCH 2/5] Fix format and compiler warnings --- src_c/display.c | 13 ++++++++----- src_py/_ffi.py | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src_c/display.c b/src_c/display.c index 741397a2e7..f6110c5e1a 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2950,19 +2950,22 @@ pg_gl_get_proc(PyObject *self, PyObject *arg) if (!proc_name) { return NULL; } - unsigned long long proc_addr = - (unsigned long long)SDL_GL_GetProcAddress(proc_name); + + void *proc_addr = SDL_GL_GetProcAddress(proc_name); if (!proc_addr) { return RAISE(pgExc_SDLError, SDL_GetError()); } - PyObject *proc_from_address_args = Py_BuildValue("(K)", proc_addr); - if (!proc_from_address_args) { + PyObject *proc_addr_obj = PyLong_FromVoidPtr(proc_addr); + if (!proc_addr_obj) { return NULL; } if (!pg_proc_from_address) { return RAISE(PyExc_TypeError, "'_proc_from_address' object is NULL"); } - return PyObject_CallObject(pg_proc_from_address, proc_from_address_args); + PyObject *retv = + PyObject_CallFunction(pg_proc_from_address, "(O)", proc_addr_obj); + Py_DECREF(proc_addr_obj); + return retv; } static PyMethodDef _pg_display_methods[] = { diff --git a/src_py/_ffi.py b/src_py/_ffi.py index 6a579f6c6c..ac0a674254 100644 --- a/src_py/_ffi.py +++ b/src_py/_ffi.py @@ -1,4 +1,5 @@ import ctypes + def _proc_from_address(addr): return ctypes.CFUNCTYPE(None)(addr) From bd98daea79e2a73912b46cd75619ca16ce7adca8 Mon Sep 17 00:00:00 2001 From: yunline Date: Thu, 9 Jan 2025 12:20:12 +0800 Subject: [PATCH 3/5] Update gl_get_proc function - SDL3 compat - Use WINFUNCTYPE (stdcall) on win32 - Use ctypes._FuncPointer in stubs --- buildconfig/stubs/pygame/display.pyi | 5 +++-- src_c/display.c | 19 ++++++++++++------- src_py/_ffi.py | 6 +++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi index ae9a6ff8f7..bb37b97e5b 100644 --- a/buildconfig/stubs/pygame/display.pyi +++ b/buildconfig/stubs/pygame/display.pyi @@ -1,5 +1,6 @@ from collections.abc import Iterable -from typing import Optional, Union, overload, Literal, Any +from ctypes import _FuncPointer +from typing import Optional, Union, overload, Literal from typing_extensions import deprecated # added in 3.13 from pygame.constants import FULLSCREEN @@ -70,7 +71,7 @@ def mode_ok( ) -> int: ... def gl_get_attribute(flag: int, /) -> int: ... def gl_set_attribute(flag: int, value: int, /) -> None: ... -def gl_get_proc(proc_name: str) -> Any: ... +def gl_get_proc(proc_name: str) -> _FuncPointer: ... def get_active() -> bool: ... def iconify() -> bool: ... def toggle_fullscreen() -> int: ... diff --git a/src_c/display.c b/src_c/display.c index f6110c5e1a..f983ab2b2b 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2938,7 +2938,7 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) return NULL; } -static PyObject *pg_proc_from_address = NULL; +static PyObject *pg_gl_proc_from_address = NULL; static PyObject * pg_gl_get_proc(PyObject *self, PyObject *arg) @@ -2951,7 +2951,12 @@ pg_gl_get_proc(PyObject *self, PyObject *arg) return NULL; } - void *proc_addr = SDL_GL_GetProcAddress(proc_name); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_FunctionPointer proc_addr; +#else + void *proc_addr; +#endif + proc_addr = SDL_GL_GetProcAddress(proc_name); if (!proc_addr) { return RAISE(pgExc_SDLError, SDL_GetError()); } @@ -2959,11 +2964,11 @@ pg_gl_get_proc(PyObject *self, PyObject *arg) if (!proc_addr_obj) { return NULL; } - if (!pg_proc_from_address) { + if (!pg_gl_proc_from_address) { return RAISE(PyExc_TypeError, "'_proc_from_address' object is NULL"); } PyObject *retv = - PyObject_CallFunction(pg_proc_from_address, "(O)", proc_addr_obj); + PyObject_CallFunction(pg_gl_proc_from_address, "(O)", proc_addr_obj); Py_DECREF(proc_addr_obj); return retv; } @@ -3102,9 +3107,9 @@ MODINIT_DEFINE(display) return NULL; } - pg_proc_from_address = - PyObject_GetAttrString(pg_ffi_module, "_proc_from_address"); - if (!pg_proc_from_address) { + pg_gl_proc_from_address = + PyObject_GetAttrString(pg_ffi_module, "_gl_proc_from_address"); + if (!pg_gl_proc_from_address) { return NULL; } Py_DECREF(pg_ffi_module); diff --git a/src_py/_ffi.py b/src_py/_ffi.py index ac0a674254..b40cdaea31 100644 --- a/src_py/_ffi.py +++ b/src_py/_ffi.py @@ -1,5 +1,9 @@ import ctypes +import sys -def _proc_from_address(addr): +def _gl_proc_from_address(addr): + if sys.platform == "win32": + # use __stdcall on win32 + return ctypes.WINFUNCTYPE(None)(addr) return ctypes.CFUNCTYPE(None)(addr) From e51dbca6d21044e9ff1e293d3137e3d65f544693 Mon Sep 17 00:00:00 2001 From: yunline Date: Fri, 10 Jan 2025 15:10:56 +0800 Subject: [PATCH 4/5] Update gl_get_proc function - Import ctypes directly in display.c - Don't use SDL_GetError when SDL_GL_GetProcAddress returns NULL --- src_c/display.c | 53 +++++++++++++++++++++++++++++----------------- src_py/_ffi.py | 9 -------- src_py/meson.build | 1 - 3 files changed, 34 insertions(+), 29 deletions(-) delete mode 100644 src_py/_ffi.py diff --git a/src_c/display.c b/src_c/display.c index f983ab2b2b..1d863867c4 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -2938,7 +2938,7 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs) return NULL; } -static PyObject *pg_gl_proc_from_address = NULL; +static PyObject *ctypes_functype = NULL; static PyObject * pg_gl_get_proc(PyObject *self, PyObject *arg) @@ -2958,17 +2958,45 @@ pg_gl_get_proc(PyObject *self, PyObject *arg) #endif proc_addr = SDL_GL_GetProcAddress(proc_name); if (!proc_addr) { - return RAISE(pgExc_SDLError, SDL_GetError()); + PyErr_Format(pgExc_SDLError, "Unable to get OpenGL function '%s'", + proc_name); + return NULL; } PyObject *proc_addr_obj = PyLong_FromVoidPtr(proc_addr); if (!proc_addr_obj) { return NULL; } - if (!pg_gl_proc_from_address) { - return RAISE(PyExc_TypeError, "'_proc_from_address' object is NULL"); + + // load ctypes_functype if it's NULL + if (!ctypes_functype) { + PyObject *ctypes_module = PyImport_ImportModule("ctypes"); + if (!ctypes_module) { + return NULL; + } + + PyObject *ctypes_functype_factory; +#if defined(_WIN32) + // gl proc need to be called with WINFUNCTYPE (stdcall) on win32 + ctypes_functype_factory = + PyObject_GetAttrString(ctypes_module, "WINFUNCTYPE"); +#else + ctypes_functype_factory = + PyObject_GetAttrString(ctypes_module, "CFUNCTYPE"); +#endif + Py_DECREF(ctypes_module); + if (!ctypes_functype_factory) { + return NULL; + } + + ctypes_functype = + PyObject_CallOneArg(ctypes_functype_factory, Py_None); + Py_DECREF(ctypes_functype_factory); + if (!ctypes_functype) { + return NULL; + } } - PyObject *retv = - PyObject_CallFunction(pg_gl_proc_from_address, "(O)", proc_addr_obj); + + PyObject *retv = PyObject_CallOneArg(ctypes_functype, proc_addr_obj); Py_DECREF(proc_addr_obj); return retv; } @@ -3101,19 +3129,6 @@ MODINIT_DEFINE(display) return NULL; } - /* load _ffi module for display.get_gl_proc function */ - PyObject *pg_ffi_module = PyImport_ImportModule("pygame._ffi"); - if (!pg_ffi_module) { - return NULL; - } - - pg_gl_proc_from_address = - PyObject_GetAttrString(pg_ffi_module, "_gl_proc_from_address"); - if (!pg_gl_proc_from_address) { - return NULL; - } - Py_DECREF(pg_ffi_module); - /* create the module */ module = PyModule_Create(&_module); if (module == NULL) { diff --git a/src_py/_ffi.py b/src_py/_ffi.py deleted file mode 100644 index b40cdaea31..0000000000 --- a/src_py/_ffi.py +++ /dev/null @@ -1,9 +0,0 @@ -import ctypes -import sys - - -def _gl_proc_from_address(addr): - if sys.platform == "win32": - # use __stdcall on win32 - return ctypes.WINFUNCTYPE(None)(addr) - return ctypes.CFUNCTYPE(None)(addr) diff --git a/src_py/meson.build b/src_py/meson.build index 5eba01ced9..541c54cd69 100644 --- a/src_py/meson.build +++ b/src_py/meson.build @@ -4,7 +4,6 @@ python_sources = files( '_camera_opencv.py', '_data_classes.py', '_debug.py', - '_ffi.py', '_sprite.py', 'camera.py', 'colordict.py', From 88f372b7d6119eb2db989ee39d09b11d9b208cbd Mon Sep 17 00:00:00 2001 From: yunline Date: Thu, 16 Jan 2025 14:10:18 +0800 Subject: [PATCH 5/5] Add gl_get_proc to docs --- docs/reST/ref/display.rst | 7 +++++++ src_c/display.c | 3 ++- src_c/doc/display_doc.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/reST/ref/display.rst b/docs/reST/ref/display.rst index 310ebb38fa..0e499be690 100644 --- a/docs/reST/ref/display.rst +++ b/docs/reST/ref/display.rst @@ -540,6 +540,13 @@ required). .. ## pygame.display.gl_set_attribute ## +.. function:: gl_get_proc + + | :sl:`Get an OpenGL function by name` + | :sg:`gl_get_proc(proc_name) -> ctypes._FuncPointer` + + .. # pygame.display.gl_get_proc ## + .. function:: get_active | :sl:`Returns True when the display is active on the screen` diff --git a/src_c/display.c b/src_c/display.c index 1d863867c4..eec4754a1f 100644 --- a/src_c/display.c +++ b/src_c/display.c @@ -3078,7 +3078,8 @@ static PyMethodDef _pg_display_methods[] = { METH_VARARGS | METH_KEYWORDS, DOC_DISPLAY_SETALLOWSCREENSAVER}, {"message_box", (PyCFunction)pg_message_box, METH_VARARGS | METH_KEYWORDS, DOC_DISPLAY_MESSAGEBOX}, - {"gl_get_proc", (PyCFunction)pg_gl_get_proc, METH_O, "doc"}, + {"gl_get_proc", (PyCFunction)pg_gl_get_proc, METH_O, + DOC_DISPLAY_GLGETPROC}, {NULL, NULL, 0, NULL}}; #ifndef PYPY_VERSION diff --git a/src_c/doc/display_doc.h b/src_c/doc/display_doc.h index 17b881bd49..ebcb06694c 100644 --- a/src_c/doc/display_doc.h +++ b/src_c/doc/display_doc.h @@ -15,6 +15,7 @@ #define DOC_DISPLAY_MODEOK "mode_ok(size, flags=0, depth=0, display=0) -> depth\nPick the best color depth for a display mode" #define DOC_DISPLAY_GLGETATTRIBUTE "gl_get_attribute(flag, /) -> value\nGet the value for an OpenGL flag for the current display" #define DOC_DISPLAY_GLSETATTRIBUTE "gl_set_attribute(flag, value, /) -> None\nRequest an OpenGL display attribute for the display mode" +#define DOC_DISPLAY_GLGETPROC "gl_get_proc(proc_name) -> ctypes._FuncPointer\nGet an OpenGL function by name" #define DOC_DISPLAY_GETACTIVE "get_active() -> bool\nReturns True when the display is active on the screen" #define DOC_DISPLAY_ICONIFY "iconify() -> bool\nIconify the display surface" #define DOC_DISPLAY_TOGGLEFULLSCREEN "toggle_fullscreen() -> int\nSwitch between fullscreen and windowed displays"