| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import math |
| | import os |
| | import functools |
| | import contextlib |
| | import numpy as np |
| | import OpenGL.GL as gl |
| | import OpenGL.GL.ARB.texture_float |
| | import dnnlib |
| |
|
| | |
| |
|
| | def init_egl(): |
| | assert os.environ['PYOPENGL_PLATFORM'] == 'egl' |
| | import OpenGL.EGL as egl |
| | import ctypes |
| |
|
| | |
| | display = egl.eglGetDisplay(egl.EGL_DEFAULT_DISPLAY) |
| | assert display != egl.EGL_NO_DISPLAY |
| | major = ctypes.c_int32() |
| | minor = ctypes.c_int32() |
| | ok = egl.eglInitialize(display, major, minor) |
| | assert ok |
| | assert major.value * 10 + minor.value >= 14 |
| |
|
| | |
| | config_attribs = [ |
| | egl.EGL_RENDERABLE_TYPE, egl.EGL_OPENGL_BIT, |
| | egl.EGL_SURFACE_TYPE, egl.EGL_PBUFFER_BIT, |
| | egl.EGL_NONE |
| | ] |
| | configs = (ctypes.c_int32 * 1)() |
| | num_configs = ctypes.c_int32() |
| | ok = egl.eglChooseConfig(display, config_attribs, configs, 1, num_configs) |
| | assert ok |
| | assert num_configs.value == 1 |
| | config = configs[0] |
| |
|
| | |
| | surface_attribs = [ |
| | egl.EGL_WIDTH, 1, |
| | egl.EGL_HEIGHT, 1, |
| | egl.EGL_NONE |
| | ] |
| | surface = egl.eglCreatePbufferSurface(display, config, surface_attribs) |
| | assert surface != egl.EGL_NO_SURFACE |
| |
|
| | |
| | ok = egl.eglBindAPI(egl.EGL_OPENGL_API) |
| | assert ok |
| | context = egl.eglCreateContext(display, config, egl.EGL_NO_CONTEXT, None) |
| | assert context != egl.EGL_NO_CONTEXT |
| | ok = egl.eglMakeCurrent(display, surface, surface, context) |
| | assert ok |
| |
|
| | |
| |
|
| | _texture_formats = { |
| | ('uint8', 1): dnnlib.EasyDict(type=gl.GL_UNSIGNED_BYTE, format=gl.GL_LUMINANCE, internalformat=gl.GL_LUMINANCE8), |
| | ('uint8', 2): dnnlib.EasyDict(type=gl.GL_UNSIGNED_BYTE, format=gl.GL_LUMINANCE_ALPHA, internalformat=gl.GL_LUMINANCE8_ALPHA8), |
| | ('uint8', 3): dnnlib.EasyDict(type=gl.GL_UNSIGNED_BYTE, format=gl.GL_RGB, internalformat=gl.GL_RGB8), |
| | ('uint8', 4): dnnlib.EasyDict(type=gl.GL_UNSIGNED_BYTE, format=gl.GL_RGBA, internalformat=gl.GL_RGBA8), |
| | ('float32', 1): dnnlib.EasyDict(type=gl.GL_FLOAT, format=gl.GL_LUMINANCE, internalformat=OpenGL.GL.ARB.texture_float.GL_LUMINANCE32F_ARB), |
| | ('float32', 2): dnnlib.EasyDict(type=gl.GL_FLOAT, format=gl.GL_LUMINANCE_ALPHA, internalformat=OpenGL.GL.ARB.texture_float.GL_LUMINANCE_ALPHA32F_ARB), |
| | ('float32', 3): dnnlib.EasyDict(type=gl.GL_FLOAT, format=gl.GL_RGB, internalformat=gl.GL_RGB32F), |
| | ('float32', 4): dnnlib.EasyDict(type=gl.GL_FLOAT, format=gl.GL_RGBA, internalformat=gl.GL_RGBA32F), |
| | } |
| |
|
| | def get_texture_format(dtype, channels): |
| | return _texture_formats[(np.dtype(dtype).name, int(channels))] |
| |
|
| | |
| |
|
| | def prepare_texture_data(image): |
| | image = np.asarray(image) |
| | if image.ndim == 2: |
| | image = image[:, :, np.newaxis] |
| | if image.dtype.name == 'float64': |
| | image = image.astype('float32') |
| | return image |
| |
|
| | |
| |
|
| | def draw_pixels(image, *, pos=0, zoom=1, align=0, rint=True): |
| | pos = np.broadcast_to(np.asarray(pos, dtype='float32'), [2]) |
| | zoom = np.broadcast_to(np.asarray(zoom, dtype='float32'), [2]) |
| | align = np.broadcast_to(np.asarray(align, dtype='float32'), [2]) |
| | image = prepare_texture_data(image) |
| | height, width, channels = image.shape |
| | size = zoom * [width, height] |
| | pos = pos - size * align |
| | if rint: |
| | pos = np.rint(pos) |
| | fmt = get_texture_format(image.dtype, channels) |
| |
|
| | gl.glPushAttrib(gl.GL_CURRENT_BIT | gl.GL_PIXEL_MODE_BIT) |
| | gl.glPushClientAttrib(gl.GL_CLIENT_PIXEL_STORE_BIT) |
| | gl.glRasterPos2f(pos[0], pos[1]) |
| | gl.glPixelZoom(zoom[0], -zoom[1]) |
| | gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) |
| | gl.glDrawPixels(width, height, fmt.format, fmt.type, image) |
| | gl.glPopClientAttrib() |
| | gl.glPopAttrib() |
| |
|
| | |
| |
|
| | def read_pixels(width, height, *, pos=0, dtype='uint8', channels=3): |
| | pos = np.broadcast_to(np.asarray(pos, dtype='float32'), [2]) |
| | dtype = np.dtype(dtype) |
| | fmt = get_texture_format(dtype, channels) |
| | image = np.empty([height, width, channels], dtype=dtype) |
| |
|
| | gl.glPushClientAttrib(gl.GL_CLIENT_PIXEL_STORE_BIT) |
| | gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) |
| | gl.glReadPixels(int(np.round(pos[0])), int(np.round(pos[1])), width, height, fmt.format, fmt.type, image) |
| | gl.glPopClientAttrib() |
| | return np.flipud(image) |
| |
|
| | |
| |
|
| | class Texture: |
| | def __init__(self, *, image=None, width=None, height=None, channels=None, dtype=None, bilinear=True, mipmap=True): |
| | self.gl_id = None |
| | self.bilinear = bilinear |
| | self.mipmap = mipmap |
| |
|
| | |
| | if image is not None: |
| | image = prepare_texture_data(image) |
| | self.height, self.width, self.channels = image.shape |
| | self.dtype = image.dtype |
| | else: |
| | assert width is not None and height is not None |
| | self.width = width |
| | self.height = height |
| | self.channels = channels if channels is not None else 3 |
| | self.dtype = np.dtype(dtype) if dtype is not None else np.uint8 |
| |
|
| | |
| | assert isinstance(self.width, int) and self.width >= 0 |
| | assert isinstance(self.height, int) and self.height >= 0 |
| | assert isinstance(self.channels, int) and self.channels >= 1 |
| | assert self.is_compatible(width=width, height=height, channels=channels, dtype=dtype) |
| |
|
| | |
| | self.gl_id = gl.glGenTextures(1) |
| | with self.bind(): |
| | gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) |
| | gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) |
| | gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR if self.bilinear else gl.GL_NEAREST) |
| | gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR_MIPMAP_LINEAR if self.mipmap else gl.GL_NEAREST) |
| | self.update(image) |
| |
|
| | def delete(self): |
| | if self.gl_id is not None: |
| | gl.glDeleteTextures([self.gl_id]) |
| | self.gl_id = None |
| |
|
| | def __del__(self): |
| | try: |
| | self.delete() |
| | except: |
| | pass |
| |
|
| | @contextlib.contextmanager |
| | def bind(self): |
| | prev_id = gl.glGetInteger(gl.GL_TEXTURE_BINDING_2D) |
| | gl.glBindTexture(gl.GL_TEXTURE_2D, self.gl_id) |
| | yield |
| | gl.glBindTexture(gl.GL_TEXTURE_2D, prev_id) |
| |
|
| | def update(self, image): |
| | if image is not None: |
| | image = prepare_texture_data(image) |
| | assert self.is_compatible(image=image) |
| | with self.bind(): |
| | fmt = get_texture_format(self.dtype, self.channels) |
| | gl.glPushClientAttrib(gl.GL_CLIENT_PIXEL_STORE_BIT) |
| | gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) |
| | gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, fmt.internalformat, self.width, self.height, 0, fmt.format, fmt.type, image) |
| | if self.mipmap: |
| | gl.glGenerateMipmap(gl.GL_TEXTURE_2D) |
| | gl.glPopClientAttrib() |
| |
|
| | def draw(self, *, pos=0, zoom=1, align=0, rint=False, color=1, alpha=1, rounding=0): |
| | zoom = np.broadcast_to(np.asarray(zoom, dtype='float32'), [2]) |
| | size = zoom * [self.width, self.height] |
| | with self.bind(): |
| | gl.glPushAttrib(gl.GL_ENABLE_BIT) |
| | gl.glEnable(gl.GL_TEXTURE_2D) |
| | draw_rect(pos=pos, size=size, align=align, rint=rint, color=color, alpha=alpha, rounding=rounding) |
| | gl.glPopAttrib() |
| |
|
| | def is_compatible(self, *, image=None, width=None, height=None, channels=None, dtype=None): |
| | if image is not None: |
| | if image.ndim != 3: |
| | return False |
| | ih, iw, ic = image.shape |
| | if not self.is_compatible(width=iw, height=ih, channels=ic, dtype=image.dtype): |
| | return False |
| | if width is not None and self.width != width: |
| | return False |
| | if height is not None and self.height != height: |
| | return False |
| | if channels is not None and self.channels != channels: |
| | return False |
| | if dtype is not None and self.dtype != dtype: |
| | return False |
| | return True |
| |
|
| | |
| |
|
| | class Framebuffer: |
| | def __init__(self, *, texture=None, width=None, height=None, channels=None, dtype=None, msaa=0): |
| | self.texture = texture |
| | self.gl_id = None |
| | self.gl_color = None |
| | self.gl_depth_stencil = None |
| | self.msaa = msaa |
| |
|
| | |
| | if texture is not None: |
| | assert isinstance(self.texture, Texture) |
| | self.width = texture.width |
| | self.height = texture.height |
| | self.channels = texture.channels |
| | self.dtype = texture.dtype |
| | else: |
| | assert width is not None and height is not None |
| | self.width = width |
| | self.height = height |
| | self.channels = channels if channels is not None else 4 |
| | self.dtype = np.dtype(dtype) if dtype is not None else np.float32 |
| |
|
| | |
| | assert isinstance(self.width, int) and self.width >= 0 |
| | assert isinstance(self.height, int) and self.height >= 0 |
| | assert isinstance(self.channels, int) and self.channels >= 1 |
| | assert width is None or width == self.width |
| | assert height is None or height == self.height |
| | assert channels is None or channels == self.channels |
| | assert dtype is None or dtype == self.dtype |
| |
|
| | |
| | self.gl_id = gl.glGenFramebuffers(1) |
| | with self.bind(): |
| |
|
| | |
| | if self.texture is not None: |
| | assert self.msaa == 0 |
| | gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.texture.gl_id, 0) |
| | else: |
| | fmt = get_texture_format(self.dtype, self.channels) |
| | self.gl_color = gl.glGenRenderbuffers(1) |
| | gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self.gl_color) |
| | gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, self.msaa, fmt.internalformat, self.width, self.height) |
| | gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_RENDERBUFFER, self.gl_color) |
| |
|
| | |
| | self.gl_depth_stencil = gl.glGenRenderbuffers(1) |
| | gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self.gl_depth_stencil) |
| | gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, self.msaa, gl.GL_DEPTH24_STENCIL8, self.width, self.height) |
| | gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_STENCIL_ATTACHMENT, gl.GL_RENDERBUFFER, self.gl_depth_stencil) |
| |
|
| | def delete(self): |
| | if self.gl_id is not None: |
| | gl.glDeleteFramebuffers([self.gl_id]) |
| | self.gl_id = None |
| | if self.gl_color is not None: |
| | gl.glDeleteRenderbuffers(1, [self.gl_color]) |
| | self.gl_color = None |
| | if self.gl_depth_stencil is not None: |
| | gl.glDeleteRenderbuffers(1, [self.gl_depth_stencil]) |
| | self.gl_depth_stencil = None |
| |
|
| | def __del__(self): |
| | try: |
| | self.delete() |
| | except: |
| | pass |
| |
|
| | @contextlib.contextmanager |
| | def bind(self): |
| | prev_fbo = gl.glGetInteger(gl.GL_FRAMEBUFFER_BINDING) |
| | prev_rbo = gl.glGetInteger(gl.GL_RENDERBUFFER_BINDING) |
| | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.gl_id) |
| | if self.width is not None and self.height is not None: |
| | gl.glViewport(0, 0, self.width, self.height) |
| | yield |
| | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, prev_fbo) |
| | gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, prev_rbo) |
| |
|
| | def blit(self, dst=None): |
| | assert dst is None or isinstance(dst, Framebuffer) |
| | with self.bind(): |
| | gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, 0 if dst is None else dst.fbo) |
| | gl.glBlitFramebuffer(0, 0, self.width, self.height, 0, 0, self.width, self.height, gl.GL_COLOR_BUFFER_BIT, gl.GL_NEAREST) |
| |
|
| | |
| |
|
| | def draw_shape(vertices, *, mode=gl.GL_TRIANGLE_FAN, pos=0, size=1, color=1, alpha=1): |
| | assert vertices.ndim == 2 and vertices.shape[1] == 2 |
| | pos = np.broadcast_to(np.asarray(pos, dtype='float32'), [2]) |
| | size = np.broadcast_to(np.asarray(size, dtype='float32'), [2]) |
| | color = np.broadcast_to(np.asarray(color, dtype='float32'), [3]) |
| | alpha = np.clip(np.broadcast_to(np.asarray(alpha, dtype='float32'), []), 0, 1) |
| |
|
| | gl.glPushClientAttrib(gl.GL_CLIENT_VERTEX_ARRAY_BIT) |
| | gl.glPushAttrib(gl.GL_CURRENT_BIT | gl.GL_TRANSFORM_BIT) |
| | gl.glMatrixMode(gl.GL_MODELVIEW) |
| | gl.glPushMatrix() |
| |
|
| | gl.glEnableClientState(gl.GL_VERTEX_ARRAY) |
| | gl.glEnableClientState(gl.GL_TEXTURE_COORD_ARRAY) |
| | gl.glVertexPointer(2, gl.GL_FLOAT, 0, vertices) |
| | gl.glTexCoordPointer(2, gl.GL_FLOAT, 0, vertices) |
| | gl.glTranslate(pos[0], pos[1], 0) |
| | gl.glScale(size[0], size[1], 1) |
| | gl.glColor4f(color[0] * alpha, color[1] * alpha, color[2] * alpha, alpha) |
| | gl.glDrawArrays(mode, 0, vertices.shape[0]) |
| |
|
| | gl.glPopMatrix() |
| | gl.glPopAttrib() |
| | gl.glPopClientAttrib() |
| |
|
| | |
| |
|
| | def draw_arrow(x1, y1, x2, y2, l=10, width=1.0): |
| | |
| | dx = x2 - x1 |
| | dy = y2 - y1 |
| | length = math.sqrt(dx**2 + dy**2) |
| | if length < l: |
| | return |
| | angle = math.atan2(dy, dx) |
| | |
| | |
| | gl.glPushMatrix() |
| | |
| | |
| | gl.glTranslatef(x1, y1, 0.0) |
| | gl.glRotatef(angle * 180.0 / math.pi, 0.0, 0.0, 1.0) |
| | |
| | |
| | gl.glLineWidth(width) |
| | |
| | |
| | |
| | gl.glBegin(gl.GL_LINES) |
| | |
| | |
| | gl.glVertex2f(0.0, 0.0) |
| | gl.glVertex2f(length, 0.0) |
| | |
| | |
| | gl.glVertex2f(length, 0.0) |
| | gl.glVertex2f(length - 2 * l, l) |
| | gl.glVertex2f(length, 0.0) |
| | gl.glVertex2f(length - 2 * l, -l) |
| | |
| | |
| | gl.glEnd() |
| | |
| | |
| | gl.glPopMatrix() |
| |
|
| | |
| |
|
| | def draw_rect(*, pos=0, pos2=None, size=None, align=0, rint=False, color=1, alpha=1, rounding=0): |
| | assert pos2 is None or size is None |
| | pos = np.broadcast_to(np.asarray(pos, dtype='float32'), [2]) |
| | pos2 = np.broadcast_to(np.asarray(pos2, dtype='float32'), [2]) if pos2 is not None else None |
| | size = np.broadcast_to(np.asarray(size, dtype='float32'), [2]) if size is not None else None |
| | size = size if size is not None else pos2 - pos if pos2 is not None else np.array([1, 1], dtype='float32') |
| | pos = pos - size * align |
| | if rint: |
| | pos = np.rint(pos) |
| | rounding = np.broadcast_to(np.asarray(rounding, dtype='float32'), [2]) |
| | rounding = np.minimum(np.abs(rounding) / np.maximum(np.abs(size), 1e-8), 0.5) |
| | if np.min(rounding) == 0: |
| | rounding *= 0 |
| | vertices = _setup_rect(float(rounding[0]), float(rounding[1])) |
| | draw_shape(vertices, mode=gl.GL_TRIANGLE_FAN, pos=pos, size=size, color=color, alpha=alpha) |
| |
|
| | @functools.lru_cache(maxsize=10000) |
| | def _setup_rect(rx, ry): |
| | t = np.linspace(0, np.pi / 2, 1 if max(rx, ry) == 0 else 64) |
| | s = 1 - np.sin(t); c = 1 - np.cos(t) |
| | x = [c * rx, 1 - s * rx, 1 - c * rx, s * rx] |
| | y = [s * ry, c * ry, 1 - s * ry, 1 - c * ry] |
| | v = np.stack([x, y], axis=-1).reshape(-1, 2) |
| | return v.astype('float32') |
| |
|
| | |
| |
|
| | def draw_circle(*, center=0, radius=100, hole=0, color=1, alpha=1): |
| | hole = np.broadcast_to(np.asarray(hole, dtype='float32'), []) |
| | vertices = _setup_circle(float(hole)) |
| | draw_shape(vertices, mode=gl.GL_TRIANGLE_STRIP, pos=center, size=radius, color=color, alpha=alpha) |
| |
|
| | @functools.lru_cache(maxsize=10000) |
| | def _setup_circle(hole): |
| | t = np.linspace(0, np.pi * 2, 128) |
| | s = np.sin(t); c = np.cos(t) |
| | v = np.stack([c, s, c * hole, s * hole], axis=-1).reshape(-1, 2) |
| | return v.astype('float32') |
| |
|
| | |
| |
|