Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support querying and selecting EGL device. #60

Merged
merged 1 commit into from
Sep 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions pyrender/offscreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import os

from .renderer import Renderer
from .platforms import EGLPlatform, OSMesaPlatform, PygletPlatform
from .constants import RenderFlags


Expand Down Expand Up @@ -114,12 +113,18 @@ def delete(self):

def _create(self):
if 'PYOPENGL_PLATFORM' not in os.environ:
from pyrender.platforms.pyglet import PygletPlatform
self._platform = PygletPlatform(self.viewport_width,
self.viewport_height)
elif os.environ['PYOPENGL_PLATFORM'] == 'egl':
self._platform = EGLPlatform(self.viewport_width,
self.viewport_height)
from pyrender.platforms import egl
device_id = int(os.environ.get('EGL_DEVICE_ID', '0'))
egl_device = egl.get_device_by_index(device_id)
self._platform = egl.EGLPlatform(self.viewport_width,
self.viewport_height,
device=egl_device)
elif os.environ['PYOPENGL_PLATFORM'] == 'osmesa':
from pyrender.platforms.osmesa import OSMesaPlatform
self._platform = OSMesaPlatform(self.viewport_width,
self.viewport_height)
else:
Expand Down
6 changes: 6 additions & 0 deletions pyrender/platforms/__init__.py
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
70 changes: 70 additions & 0 deletions pyrender/platforms/base.py
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
214 changes: 214 additions & 0 deletions pyrender/platforms/egl.py
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']
54 changes: 54 additions & 0 deletions pyrender/platforms/osmesa.py
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
Loading