From 5c5b0149757243eea35d907599743a5201b08ca1 Mon Sep 17 00:00:00 2001 From: Shunsuke Date: Mon, 4 May 2020 11:29:20 +0900 Subject: [PATCH] added egl option --- apps/render_data.py | 18 +++-- lib/renderer/gl/__init__.py | 3 - lib/renderer/gl/cam_render.py | 17 +++-- lib/renderer/gl/framework.py | 4 +- lib/renderer/gl/glcontext.py | 120 ++++++++++++++++++++++++++++++++++ lib/renderer/gl/init_gl.py | 24 +++++++ lib/renderer/gl/prt_render.py | 5 +- lib/renderer/gl/render.py | 97 ++++++++++++++------------- 8 files changed, 217 insertions(+), 71 deletions(-) create mode 100755 lib/renderer/gl/glcontext.py create mode 100644 lib/renderer/gl/init_gl.py diff --git a/apps/render_data.py b/apps/render_data.py index 0bae9b992..b9bbb7630 100755 --- a/apps/render_data.py +++ b/apps/render_data.py @@ -2,7 +2,6 @@ from lib.renderer.camera import Camera import numpy as np -from lib.renderer.gl.prt_render import PRTRender from lib.renderer.mesh import load_obj_mesh, compute_tangent, compute_normal, load_obj_mesh_mtl from lib.renderer.camera import Camera import os @@ -12,7 +11,7 @@ import random import pyexr import argparse - +from tqdm import tqdm def make_rotate(rx, ry, rz): @@ -135,7 +134,7 @@ def rotateBand2(x, R): d2 += sh4 * (r4z * r4z + s_c4_div_c3_x2) d3 += sh4_x * r4z d4 += sh4_x * r4x - sh4_y * r4y - + dst = x dst[0] = d0 dst[1] = -d1 @@ -213,7 +212,7 @@ def render_prt_ortho(out_path, folder_name, subject_name, shs, rndr, rndr_uv, an os.system(cmd) for p in pitch: - for y in range(0, 360, angl_step): + for y in tqdm(range(0, 360, angl_step)): R = np.matmul(make_rotate(math.radians(p), 0, 0), make_rotate(0, math.radians(y), 0)) if up_axis == 2: R = np.matmul(R, make_rotate(math.radians(90),0,0)) @@ -273,10 +272,17 @@ def render_prt_ortho(out_path, folder_name, subject_name, shs, rndr, rndr_uv, an parser.add_argument('-i', '--input', type=str, default='/home/shunsuke/Downloads/rp_dennis_posed_004_OBJ') parser.add_argument('-o', '--out_dir', type=str, default='/home/shunsuke/Documents/hf_human') parser.add_argument('-m', '--ms_rate', type=int, default=1, help='higher ms rate results in less aliased output. MESA renderer only supports ms_rate=1.') + parser.add_argument('-e', '--egl', action='store_true', help='egl rendering option. use this when rendering with headless server with NVIDIA GPU') + parser.add_argument('-s', '--size', type=int, default=512, help='rendering image size') args = parser.parse_args() - rndr = PRTRender(width=512, height=512, ms_rate=args.ms_rate) - rndr_uv = PRTRender(width=512, height=512, uv_mode=True) + # NOTE: GL context has to be created before any other OpenGL function loads. + from lib.renderer.gl.init_gl import initialize_GL_context + initialize_GL_context(width=args.size, height=args.size, egl=args.egl) + + from lib.renderer.gl.prt_render import PRTRender + rndr = PRTRender(width=args.size, height=args.size, ms_rate=args.ms_rate, egl=args.egl) + rndr_uv = PRTRender(width=args.size, height=args.size, uv_mode=True, egl=args.egl) if args.input[-1] == '/': args.input = args.input[:-1] diff --git a/lib/renderer/gl/__init__.py b/lib/renderer/gl/__init__.py index 1d55be6d4..e69de29bb 100755 --- a/lib/renderer/gl/__init__.py +++ b/lib/renderer/gl/__init__.py @@ -1,3 +0,0 @@ -from .framework import * -from .render import * -from .cam_render import * diff --git a/lib/renderer/gl/cam_render.py b/lib/renderer/gl/cam_render.py index 9aef63f43..7b766af05 100755 --- a/lib/renderer/gl/cam_render.py +++ b/lib/renderer/gl/cam_render.py @@ -1,16 +1,18 @@ -from OpenGL.GLUT import * - from .render import Render +GLUT = None class CamRender(Render): def __init__(self, width=1600, height=1200, name='Cam Renderer', - program_files=['simple.fs', 'simple.vs'], color_size=1, ms_rate=1): - Render.__init__(self, width, height, name, program_files, color_size, ms_rate=ms_rate) + program_files=['simple.fs', 'simple.vs'], color_size=1, ms_rate=1, egl=False): + Render.__init__(self, width, height, name, program_files, color_size, ms_rate=ms_rate, egl=egl) self.camera = None - glutDisplayFunc(self.display) - glutKeyboardFunc(self.keyboard) + if not egl: + global GLUT + import OpenGL.GLUT as GLUT + GLUT.glutDisplayFunc(self.display) + GLUT.glutKeyboardFunc(self.keyboard) def set_camera(self, camera): self.camera = camera @@ -42,4 +44,5 @@ def keyboard(self, key, x, y): self.projection_matrix, self.model_view_matrix = self.camera.get_gl_matrix() def show(self): - glutMainLoop() + if GLUT is not None: + GLUT.glutMainLoop() diff --git a/lib/renderer/gl/framework.py b/lib/renderer/gl/framework.py index b7133ebf2..a4375b659 100755 --- a/lib/renderer/gl/framework.py +++ b/lib/renderer/gl/framework.py @@ -8,10 +8,8 @@ # to be imported properly. import os - from OpenGL.GL import * - # Function that creates and compiles shaders according to the given type (a GL enum value) and # shader program (a file containing a GLSL program). def loadShader(shaderType, shaderFile): @@ -89,4 +87,4 @@ def findFileOrThrow(strBasename): if os.path.isfile(strFilename): return strFilename - raise IOError('Could not find target file ' + strBasename) + raise IOError('Could not find target file ' + strBasename) \ No newline at end of file diff --git a/lib/renderer/gl/glcontext.py b/lib/renderer/gl/glcontext.py new file mode 100755 index 000000000..0d4cb7d72 --- /dev/null +++ b/lib/renderer/gl/glcontext.py @@ -0,0 +1,120 @@ +"""Headless GPU-accelerated OpenGL context creation on Google Colaboratory. + +Typical usage: + + # Optional PyOpenGL configuratiopn can be done here. + # import OpenGL + # OpenGL.ERROR_CHECKING = True + + # 'glcontext' must be imported before any OpenGL.* API. + from lucid.misc.gl.glcontext import create_opengl_context + + # Now it's safe to import OpenGL and EGL functions + import OpenGL.GL as gl + + # create_opengl_context() creates a GL context that is attached to an + # offscreen surface of the specified size. Note that rendering to buffers + # of other sizes and formats is still possible with OpenGL Framebuffers. + # + # Users are expected to directly use the EGL API in case more advanced + # context management is required. + width, height = 640, 480 + create_opengl_context((width, height)) + + # OpenGL context is available here. + +""" + +from __future__ import print_function + +# pylint: disable=unused-import,g-import-not-at-top,g-statement-before-imports + +try: + import OpenGL +except: + print('This module depends on PyOpenGL.') + print('Please run "\033[1m!pip install -q pyopengl\033[0m" ' + 'prior importing this module.') + raise + +import ctypes +from ctypes import pointer, util +import os + +os.environ['PYOPENGL_PLATFORM'] = 'egl' + +# OpenGL loading workaround. +# +# * PyOpenGL tries to load libGL, but we need libOpenGL, see [1,2]. +# This could have been solved by a symlink libGL->libOpenGL, but: +# +# * Python 2.7 can't find libGL and linEGL due to a bug (see [3]) +# in ctypes.util, that was only wixed in Python 3.6. +# +# So, the only solution I've found is to monkeypatch ctypes.util +# [1] https://devblogs.nvidia.com/egl-eye-opengl-visualization-without-x-server/ +# [2] https://devblogs.nvidia.com/linking-opengl-server-side-rendering/ +# [3] https://bugs.python.org/issue9998 +_find_library_old = ctypes.util.find_library +try: + + def _find_library_new(name): + return { + 'GL': 'libOpenGL.so', + 'EGL': 'libEGL.so', + }.get(name, _find_library_old(name)) + util.find_library = _find_library_new + import OpenGL.GL as gl + import OpenGL.EGL as egl +except: + print('Unable to load OpenGL libraries. ' + 'Make sure you use GPU-enabled backend.') + print('Press "Runtime->Change runtime type" and set ' + '"Hardware accelerator" to GPU.') + raise +finally: + util.find_library = _find_library_old + + +def create_opengl_context(surface_size=(640, 480)): + """Create offscreen OpenGL context and make it current. + + Users are expected to directly use EGL API in case more advanced + context management is required. + + Args: + surface_size: (width, height), size of the offscreen rendering surface. + """ + egl_display = egl.eglGetDisplay(egl.EGL_DEFAULT_DISPLAY) + + major, minor = egl.EGLint(), egl.EGLint() + egl.eglInitialize(egl_display, pointer(major), pointer(minor)) + + config_attribs = [ + egl.EGL_SURFACE_TYPE, egl.EGL_PBUFFER_BIT, egl.EGL_BLUE_SIZE, 8, + egl.EGL_GREEN_SIZE, 8, egl.EGL_RED_SIZE, 8, egl.EGL_DEPTH_SIZE, 24, + egl.EGL_RENDERABLE_TYPE, egl.EGL_OPENGL_BIT, egl.EGL_NONE + ] + config_attribs = (egl.EGLint * len(config_attribs))(*config_attribs) + + num_configs = egl.EGLint() + egl_cfg = egl.EGLConfig() + egl.eglChooseConfig(egl_display, config_attribs, pointer(egl_cfg), 1, + pointer(num_configs)) + + width, height = surface_size + pbuffer_attribs = [ + egl.EGL_WIDTH, + width, + egl.EGL_HEIGHT, + height, + egl.EGL_NONE, + ] + pbuffer_attribs = (egl.EGLint * len(pbuffer_attribs))(*pbuffer_attribs) + egl_surf = egl.eglCreatePbufferSurface(egl_display, egl_cfg, pbuffer_attribs) + + egl.eglBindAPI(egl.EGL_OPENGL_API) + + egl_context = egl.eglCreateContext(egl_display, egl_cfg, egl.EGL_NO_CONTEXT, + None) + egl.eglMakeCurrent(egl_display, egl_surf, egl_surf, egl_context) \ No newline at end of file diff --git a/lib/renderer/gl/init_gl.py b/lib/renderer/gl/init_gl.py new file mode 100644 index 000000000..1d2c7e6ba --- /dev/null +++ b/lib/renderer/gl/init_gl.py @@ -0,0 +1,24 @@ +_glut_window = None +_context_inited = None + +def initialize_GL_context(width=512, height=512, egl=False): + ''' + default context uses GLUT + ''' + if not egl: + import OpenGL.GLUT as GLUT + display_mode = GLUT.GLUT_DOUBLE | GLUT.GLUT_RGB | GLUT.GLUT_DEPTH + global _glut_window + if _glut_window is None: + GLUT.glutInit() + GLUT.glutInitDisplayMode(display_mode) + GLUT.glutInitWindowSize(width, height) + GLUT.glutInitWindowPosition(0, 0) + _glut_window = GLUT.glutCreateWindow("My Render.") + else: + from .glcontext import create_opengl_context + global _context_inited + if _context_inited is None: + create_opengl_context((width, height)) + _context_inited = True + diff --git a/lib/renderer/gl/prt_render.py b/lib/renderer/gl/prt_render.py index 891ea9915..92c8a6257 100755 --- a/lib/renderer/gl/prt_render.py +++ b/lib/renderer/gl/prt_render.py @@ -4,11 +4,10 @@ from .framework import * from .cam_render import CamRender - class PRTRender(CamRender): - def __init__(self, width=1600, height=1200, name='PRT Renderer', uv_mode=False, ms_rate=1): + def __init__(self, width=1600, height=1200, name='PRT Renderer', uv_mode=False, ms_rate=1, egl=False): program_files = ['prt.vs', 'prt.fs'] if not uv_mode else ['prt_uv.vs', 'prt_uv.fs'] - CamRender.__init__(self, width, height, name, program_files=program_files, color_size=8, ms_rate=ms_rate) + CamRender.__init__(self, width, height, name, program_files=program_files, color_size=8, ms_rate=ms_rate, egl=egl) # WARNING: this differs from vertex_buffer and vertex_data in Render self.vert_buffer = {} diff --git a/lib/renderer/gl/render.py b/lib/renderer/gl/render.py index 627108237..57c219386 100755 --- a/lib/renderer/gl/render.py +++ b/lib/renderer/gl/render.py @@ -1,32 +1,25 @@ +from ctypes import * + import numpy as np -from OpenGL.GLUT import * from .framework import * -_glut_window = None +GLUT = None +# NOTE: Render class assumes GL context is created already. class Render: def __init__(self, width=1600, height=1200, name='GL Renderer', - program_files=['simple.fs', 'simple.vs'], color_size=1, ms_rate=1): + program_files=['simple.fs', 'simple.vs'], color_size=1, ms_rate=1, egl=False): self.width = width self.height = height self.name = name - self.display_mode = GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH self.use_inverse_depth = False + self.egl = egl + + glEnable(GL_DEPTH_TEST) - global _glut_window - if _glut_window is None: - glutInit() - glutInitDisplayMode(self.display_mode) - glutInitWindowSize(self.width, self.height) - glutInitWindowPosition(0, 0) - _glut_window = glutCreateWindow("My Render.") - - # glEnable(GL_DEPTH_CLAMP) - glEnable(GL_DEPTH_TEST) - - glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE) - glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE) - glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE) + glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE) + glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE) + glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE) # init program shader_list = [] @@ -152,7 +145,10 @@ def __init__(self, width=1600, height=1200, name='GL Renderer', self.model_view_matrix = None self.projection_matrix = None - glutDisplayFunc(self.display) + if not egl: + global GLUT + import OpenGL.GLUT as GLUT + GLUT.glutDisplayFunc(self.display) def init_quad_program(self): @@ -266,46 +262,49 @@ def get_z_value(self): return z def display(self): - # First we draw a scene. - # Notice the result is stored in the texture buffer. self.draw() - # Then we return to the default frame buffer since we will display on the screen. - glBindFramebuffer(GL_FRAMEBUFFER, 0) + if not self.egl: + # First we draw a scene. + # Notice the result is stored in the texture buffer. - # Do the clean-up. - glClearColor(0.0, 0.0, 0.0, 0.0) - glClear(GL_COLOR_BUFFER_BIT) + # Then we return to the default frame buffer since we will display on the screen. + glBindFramebuffer(GL_FRAMEBUFFER, 0) - # We draw a rectangle which covers the whole screen. - glUseProgram(self.quad_program) - glBindBuffer(GL_ARRAY_BUFFER, self.quad_buffer) + # Do the clean-up. + glClearColor(0.0, 0.0, 0.0, 0.0) + glClear(GL_COLOR_BUFFER_BIT) - size_of_double = 8 - glEnableVertexAttribArray(0) - glVertexAttribPointer(0, 2, GL_DOUBLE, GL_FALSE, 4 * size_of_double, None) - glEnableVertexAttribArray(1) - glVertexAttribPointer(1, 2, GL_DOUBLE, GL_FALSE, 4 * size_of_double, c_void_p(2 * size_of_double)) + # We draw a rectangle which covers the whole screen. + glUseProgram(self.quad_program) + glBindBuffer(GL_ARRAY_BUFFER, self.quad_buffer) - glDisable(GL_DEPTH_TEST) + size_of_double = 8 + glEnableVertexAttribArray(0) + glVertexAttribPointer(0, 2, GL_DOUBLE, GL_FALSE, 4 * size_of_double, None) + glEnableVertexAttribArray(1) + glVertexAttribPointer(1, 2, GL_DOUBLE, GL_FALSE, 4 * size_of_double, c_void_p(2 * size_of_double)) - # The stored texture is then mapped to this rectangle. - # properly assing color buffer texture - glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_2D, self.screen_texture[0]) - glUniform1i(glGetUniformLocation(self.quad_program, 'screenTexture'), 0) + glDisable(GL_DEPTH_TEST) - glDrawArrays(GL_TRIANGLES, 0, 6) + # The stored texture is then mapped to this rectangle. + # properly assing color buffer texture + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, self.screen_texture[0]) + glUniform1i(glGetUniformLocation(self.quad_program, 'screenTexture'), 0) - glDisableVertexAttribArray(1) - glDisableVertexAttribArray(0) + glDrawArrays(GL_TRIANGLES, 0, 6) - glEnable(GL_DEPTH_TEST) - glBindBuffer(GL_ARRAY_BUFFER, 0) - glUseProgram(0) + glDisableVertexAttribArray(1) + glDisableVertexAttribArray(0) + + glEnable(GL_DEPTH_TEST) + glBindBuffer(GL_ARRAY_BUFFER, 0) + glUseProgram(0) - glutSwapBuffers() - glutPostRedisplay() + GLUT.glutSwapBuffers() + GLUT.glutPostRedisplay() def show(self): - glutMainLoop() + if not self.egl: + GLUT.glutMainLoop()