-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support querying and selecting EGL device.
This commit uses the EGL query extension to query devices and allows for selecting a specific GPU. This is crucial when using shared GPU resources. The GPU index can be set using the environment variable 'EGL_DEVICE_ID'.
- Loading branch information
Keunhong Park
committed
Aug 21, 2019
1 parent
1cb07d9
commit 5ae1bb1
Showing
7 changed files
with
423 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Platforms for generating offscreen OpenGL contexts for rendering. | ||
Author: Matthew Matl | ||
""" | ||
|
||
from pyrender.platforms.base import Platform |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import abc | ||
|
||
import six | ||
|
||
|
||
@six.add_metaclass(abc.ABCMeta) | ||
class Platform(object): | ||
"""Base class for all OpenGL platforms. | ||
Parameters | ||
---------- | ||
viewport_width : int | ||
The width of the main viewport, in pixels. | ||
viewport_height : int | ||
The height of the main viewport, in pixels | ||
""" | ||
|
||
def __init__(self, viewport_width, viewport_height): | ||
self.viewport_width = viewport_width | ||
self.viewport_height = viewport_height | ||
|
||
@property | ||
def viewport_width(self): | ||
"""int : The width of the main viewport, in pixels. | ||
""" | ||
return self._viewport_width | ||
|
||
@viewport_width.setter | ||
def viewport_width(self, value): | ||
self._viewport_width = value | ||
|
||
@property | ||
def viewport_height(self): | ||
"""int : The height of the main viewport, in pixels. | ||
""" | ||
return self._viewport_height | ||
|
||
@viewport_height.setter | ||
def viewport_height(self, value): | ||
self._viewport_height = value | ||
|
||
@abc.abstractmethod | ||
def init_context(self): | ||
"""Create an OpenGL context. | ||
""" | ||
pass | ||
|
||
@abc.abstractmethod | ||
def make_current(self): | ||
"""Make the OpenGL context current. | ||
""" | ||
pass | ||
|
||
@abc.abstractmethod | ||
def delete_context(self): | ||
"""Delete the OpenGL context. | ||
""" | ||
pass | ||
|
||
@abc.abstractmethod | ||
def supports_framebuffers(self): | ||
"""Returns True if the method supports framebuffer rendering. | ||
""" | ||
pass | ||
|
||
def __del__(self): | ||
try: | ||
self.delete_context() | ||
except Exception: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
import ctypes | ||
import os | ||
|
||
import OpenGL.platform | ||
|
||
from .base import Platform | ||
|
||
EGL_PLATFORM_DEVICE_EXT = 0x313F | ||
EGL_DRM_DEVICE_FILE_EXT = 0x3233 | ||
|
||
|
||
def _ensure_egl_loaded(): | ||
plugin = OpenGL.platform.PlatformPlugin.by_name('egl') | ||
if plugin is None: | ||
raise RuntimeError("EGL platform plugin is not available.") | ||
|
||
plugin_class = plugin.load() | ||
plugin.loaded = True | ||
# create instance of this platform implementation | ||
plugin = plugin_class() | ||
|
||
plugin.install(vars(OpenGL.platform)) | ||
|
||
|
||
_ensure_egl_loaded() | ||
from OpenGL import EGL as egl | ||
|
||
|
||
def _get_egl_func(func_name, res_type, *arg_types): | ||
address = egl.eglGetProcAddress(func_name) | ||
if address is None: | ||
return None | ||
|
||
proto = ctypes.CFUNCTYPE(res_type) | ||
proto.argtypes = arg_types | ||
func = proto(address) | ||
return func | ||
|
||
|
||
def _get_egl_struct(struct_name): | ||
from OpenGL._opaque import opaque_pointer_cls | ||
return opaque_pointer_cls(struct_name) | ||
|
||
|
||
# These are not defined in PyOpenGL by default. | ||
_EGLDeviceEXT = _get_egl_struct('EGLDeviceEXT') | ||
_eglGetPlatformDisplayEXT = _get_egl_func('eglGetPlatformDisplayEXT', egl.EGLDisplay) | ||
_eglQueryDevicesEXT = _get_egl_func('eglQueryDevicesEXT', egl.EGLBoolean) | ||
_eglQueryDeviceStringEXT = _get_egl_func('eglQueryDeviceStringEXT', ctypes.c_char_p) | ||
|
||
|
||
def query_devices(): | ||
if _eglQueryDevicesEXT is None: | ||
raise RuntimeError("EGL query extension is not loaded or is not supported.") | ||
|
||
num_devices = egl.EGLint() | ||
success = _eglQueryDevicesEXT(0, None, ctypes.pointer(num_devices)) | ||
if not success or num_devices.value < 1: | ||
return [] | ||
|
||
devices = (_EGLDeviceEXT * num_devices.value)() # array of size num_devices | ||
success = _eglQueryDevicesEXT(num_devices.value, devices, ctypes.pointer(num_devices)) | ||
if not success or num_devices.value < 1: | ||
return [] | ||
|
||
return [EGLDevice(devices[i]) for i in range(num_devices.value)] | ||
|
||
|
||
def get_default_device(): | ||
# Fall back to not using query extension. | ||
if _eglQueryDevicesEXT is None: | ||
return EGLDevice(None) | ||
|
||
return query_devices()[0] | ||
|
||
|
||
def get_device_by_index(device_id): | ||
if _eglQueryDevicesEXT is None and device_id == 0: | ||
return get_default_device() | ||
|
||
devices = query_devices() | ||
if device_id >= len(devices): | ||
raise ValueError('Invalid device ID ({})'.format(device_id, len(devices))) | ||
return devices[device_id] | ||
|
||
|
||
class EGLDevice: | ||
|
||
def __init__(self, display=None): | ||
self._display = display | ||
|
||
def get_display(self): | ||
if self._display is None: | ||
return egl.eglGetDisplay(egl.EGL_DEFAULT_DISPLAY) | ||
|
||
return _eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, self._display, None) | ||
|
||
@property | ||
def name(self): | ||
if self._display is None: | ||
return 'default' | ||
|
||
name = _eglQueryDeviceStringEXT(self._display, EGL_DRM_DEVICE_FILE_EXT) | ||
if name is None: | ||
return None | ||
|
||
return name.decode('ascii') | ||
|
||
def __repr__(self): | ||
return f"<EGLDevice(name={self.name!r})>" | ||
|
||
|
||
class EGLPlatform(Platform): | ||
"""Renders using EGL (not currently working on Ubuntu). | ||
""" | ||
|
||
def __init__(self, viewport_width, viewport_height, device: EGLDevice = None): | ||
super(EGLPlatform, self).__init__(viewport_width, viewport_height) | ||
if device is None: | ||
device = get_default_device() | ||
|
||
self._egl_device = device | ||
self._egl_display = None | ||
self._egl_context = None | ||
|
||
def init_context(self): | ||
_ensure_egl_loaded() | ||
|
||
from OpenGL.EGL import ( | ||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BLUE_SIZE, | ||
EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_DEPTH_SIZE, | ||
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, | ||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_CONFORMANT, | ||
EGL_NONE, EGL_DEFAULT_DISPLAY, EGL_NO_CONTEXT, | ||
EGL_OPENGL_API, EGL_CONTEXT_MAJOR_VERSION, | ||
EGL_CONTEXT_MINOR_VERSION, | ||
EGL_CONTEXT_OPENGL_PROFILE_MASK, | ||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, | ||
eglGetDisplay, eglInitialize, eglChooseConfig, | ||
eglBindAPI, eglCreateContext, EGLConfig | ||
) | ||
from OpenGL import arrays | ||
|
||
config_attributes = arrays.GLintArray.asArray([ | ||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, | ||
EGL_BLUE_SIZE, 8, | ||
EGL_RED_SIZE, 8, | ||
EGL_GREEN_SIZE, 8, | ||
EGL_DEPTH_SIZE, 24, | ||
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, | ||
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, | ||
EGL_CONFORMANT, EGL_OPENGL_BIT, | ||
EGL_NONE | ||
]) | ||
context_attributes = arrays.GLintArray.asArray([ | ||
EGL_CONTEXT_MAJOR_VERSION, 4, | ||
EGL_CONTEXT_MINOR_VERSION, 1, | ||
EGL_CONTEXT_OPENGL_PROFILE_MASK, | ||
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, | ||
EGL_NONE | ||
]) | ||
major, minor = ctypes.c_long(), ctypes.c_long() | ||
num_configs = ctypes.c_long() | ||
configs = (EGLConfig * 1)() | ||
|
||
# Cache DISPLAY if necessary and get an off-screen EGL display | ||
orig_dpy = None | ||
if 'DISPLAY' in os.environ: | ||
orig_dpy = os.environ['DISPLAY'] | ||
del os.environ['DISPLAY'] | ||
|
||
self._egl_display = self._egl_device.get_display() | ||
if orig_dpy is not None: | ||
os.environ['DISPLAY'] = orig_dpy | ||
|
||
# Initialize EGL | ||
assert eglInitialize(self._egl_display, major, minor) | ||
assert eglChooseConfig( | ||
self._egl_display, config_attributes, configs, 1, num_configs | ||
) | ||
|
||
# Bind EGL to the OpenGL API | ||
assert eglBindAPI(EGL_OPENGL_API) | ||
|
||
# Create an EGL context | ||
self._egl_context = eglCreateContext( | ||
self._egl_display, configs[0], | ||
EGL_NO_CONTEXT, context_attributes | ||
) | ||
|
||
# Make it current | ||
self.make_current() | ||
|
||
def make_current(self): | ||
from OpenGL.EGL import eglMakeCurrent, EGL_NO_SURFACE | ||
assert eglMakeCurrent( | ||
self._egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, | ||
self._egl_context | ||
) | ||
|
||
def delete_context(self): | ||
from OpenGL.EGL import eglDestroyContext, eglTerminate | ||
if self._egl_display is not None: | ||
if self._egl_context is not None: | ||
eglDestroyContext(self._egl_display, self._egl_context) | ||
self._egl_context = None | ||
eglTerminate(self._egl_display) | ||
self._egl_display = None | ||
|
||
def supports_framebuffers(self): | ||
return True | ||
|
||
|
||
__all__ = ['EGLPlatform'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from .base import Platform | ||
|
||
|
||
__all__ = ['OSMesaPlatform'] | ||
|
||
|
||
class OSMesaPlatform(Platform): | ||
"""Renders into a software buffer using OSMesa. Requires special versions | ||
of OSMesa to be installed, plus PyOpenGL upgrade. | ||
""" | ||
|
||
def __init__(self, viewport_width, viewport_height): | ||
super(OSMesaPlatform, self).__init__(viewport_width, viewport_height) | ||
self._context = None | ||
self._buffer = None | ||
|
||
def init_context(self): | ||
from OpenGL import arrays | ||
from OpenGL.osmesa import ( | ||
OSMesaCreateContextAttribs, OSMESA_FORMAT, | ||
OSMESA_RGBA, OSMESA_PROFILE, OSMESA_CORE_PROFILE, | ||
OSMESA_CONTEXT_MAJOR_VERSION, OSMESA_CONTEXT_MINOR_VERSION, | ||
OSMESA_DEPTH_BITS | ||
) | ||
|
||
attrs = arrays.GLintArray.asArray([ | ||
OSMESA_FORMAT, OSMESA_RGBA, | ||
OSMESA_DEPTH_BITS, 24, | ||
OSMESA_PROFILE, OSMESA_CORE_PROFILE, | ||
OSMESA_CONTEXT_MAJOR_VERSION, 3, | ||
OSMESA_CONTEXT_MINOR_VERSION, 3, | ||
0 | ||
]) | ||
self._context = OSMesaCreateContextAttribs(attrs, None) | ||
self._buffer = arrays.GLubyteArray.zeros( | ||
(self.viewport_height, self.viewport_width, 4) | ||
) | ||
|
||
def make_current(self): | ||
from OpenGL import GL as gl | ||
from OpenGL.osmesa import OSMesaMakeCurrent | ||
assert(OSMesaMakeCurrent( | ||
self._context, self._buffer, gl.GL_UNSIGNED_BYTE, | ||
self.viewport_width, self.viewport_height | ||
)) | ||
|
||
def delete_context(self): | ||
from OpenGL.osmesa import OSMesaDestroyContext | ||
OSMesaDestroyContext(self._context) | ||
self._context = None | ||
self._buffer = None | ||
|
||
def supports_framebuffers(self): | ||
return False |
Oops, something went wrong.