libcamera/src/py/cam/cam_qtgl.py
Tomi Valkeinen 7a0a464dd1 py: Rename 'efd' to 'event_fd'
Perhaps it's better to have a more descriptive name here. I also
considered just renaming 'efd' to 'fd', but 'event_fd' won.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2022-05-27 22:03:41 +03:00

363 lines
10 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
import math
import os
import sys
os.environ['PYOPENGL_PLATFORM'] = 'egl'
from OpenGL.EGL.EXT.image_dma_buf_import import *
from OpenGL.EGL.KHR.image import *
from OpenGL.EGL.VERSION.EGL_1_0 import *
from OpenGL.EGL.VERSION.EGL_1_2 import *
from OpenGL.EGL.VERSION.EGL_1_3 import *
from OpenGL.GLES2.OES.EGL_image import *
from OpenGL.GLES2.OES.EGL_image_external import *
from OpenGL.GLES2.VERSION.GLES2_2_0 import *
from OpenGL.GLES3.VERSION.GLES3_3_0 import *
from OpenGL.GL import shaders
from gl_helpers import *
class EglState:
def __init__(self):
self.create_display()
self.choose_config()
self.create_context()
self.check_extensions()
def create_display(self):
xdpy = getEGLNativeDisplay()
dpy = eglGetDisplay(xdpy)
self.display = dpy
def choose_config(self):
dpy = self.display
major, minor = EGLint(), EGLint()
b = eglInitialize(dpy, major, minor)
assert(b)
print('EGL {} {}'.format(
eglQueryString(dpy, EGL_VENDOR).decode(),
eglQueryString(dpy, EGL_VERSION).decode()))
check_egl_extensions(dpy, ['EGL_EXT_image_dma_buf_import'])
b = eglBindAPI(EGL_OPENGL_ES_API)
assert(b)
def print_config(dpy, cfg):
def getconf(a):
value = ctypes.c_long()
eglGetConfigAttrib(dpy, cfg, a, value)
return value.value
print('EGL Config {}: color buf {}/{}/{}/{} = {}, depth {}, stencil {}, native visualid {}, native visualtype {}'.format(
getconf(EGL_CONFIG_ID),
getconf(EGL_ALPHA_SIZE),
getconf(EGL_RED_SIZE),
getconf(EGL_GREEN_SIZE),
getconf(EGL_BLUE_SIZE),
getconf(EGL_BUFFER_SIZE),
getconf(EGL_DEPTH_SIZE),
getconf(EGL_STENCIL_SIZE),
getconf(EGL_NATIVE_VISUAL_ID),
getconf(EGL_NATIVE_VISUAL_TYPE)))
if False:
num_configs = ctypes.c_long()
eglGetConfigs(dpy, None, 0, num_configs)
print('{} configs'.format(num_configs.value))
configs = (EGLConfig * num_configs.value)()
eglGetConfigs(dpy, configs, num_configs.value, num_configs)
for config_id in configs:
print_config(dpy, config_id)
config_attribs = [
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 0,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE,
]
n = EGLint()
configs = (EGLConfig * 1)()
b = eglChooseConfig(dpy, config_attribs, configs, 1, n)
assert(b and n.value == 1)
config = configs[0]
print('Chosen Config:')
print_config(dpy, config)
self.config = config
def create_context(self):
dpy = self.display
context_attribs = [
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE,
]
context = eglCreateContext(dpy, self.config, EGL_NO_CONTEXT, context_attribs)
assert(context)
b = eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context)
assert(b)
self.context = context
def check_extensions(self):
check_gl_extensions(['GL_OES_EGL_image'])
assert(eglCreateImageKHR)
assert(eglDestroyImageKHR)
assert(glEGLImageTargetTexture2DOES)
class QtRenderer:
def __init__(self, state):
self.state = state
def setup(self):
self.app = QtWidgets.QApplication([])
window = MainWindow(self.state)
window.show()
self.window = window
def run(self):
camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Read)
camnotif.activated.connect(lambda _: self.readcam())
keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)
keynotif.activated.connect(lambda _: self.readkey())
print('Capturing...')
self.app.exec()
print('Exiting...')
def readcam(self):
running = self.state.event_handler()
if not running:
self.app.quit()
def readkey(self):
sys.stdin.readline()
self.app.quit()
def request_handler(self, ctx, req):
self.window.handle_request(ctx, req)
def cleanup(self):
self.window.close()
class MainWindow(QtWidgets.QWidget):
def __init__(self, state):
super().__init__()
self.setAttribute(Qt.WA_PaintOnScreen)
self.setAttribute(Qt.WA_NativeWindow)
self.state = state
self.textures = {}
self.reqqueue = {}
self.current = {}
for ctx in self.state.contexts:
self.reqqueue[ctx.idx] = []
self.current[ctx.idx] = []
for stream in ctx.streams:
self.textures[stream] = None
num_tiles = len(self.textures)
self.num_columns = math.ceil(math.sqrt(num_tiles))
self.num_rows = math.ceil(num_tiles / self.num_columns)
self.egl = EglState()
self.surface = None
def paintEngine(self):
return None
def create_surface(self):
native_surface = c_void_p(self.winId().__int__())
surface = eglCreateWindowSurface(self.egl.display, self.egl.config,
native_surface, None)
b = eglMakeCurrent(self.egl.display, self.surface, self.surface, self.egl.context)
assert(b)
self.surface = surface
def init_gl(self):
self.create_surface()
vertShaderSrc = '''
attribute vec2 aPosition;
varying vec2 texcoord;
void main()
{
gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);
texcoord.x = aPosition.x;
texcoord.y = 1.0 - aPosition.y;
}
'''
fragShaderSrc = '''
#extension GL_OES_EGL_image_external : enable
precision mediump float;
varying vec2 texcoord;
uniform samplerExternalOES texture;
void main()
{
gl_FragColor = texture2D(texture, texcoord);
}
'''
program = shaders.compileProgram(
shaders.compileShader(vertShaderSrc, GL_VERTEX_SHADER),
shaders.compileShader(fragShaderSrc, GL_FRAGMENT_SHADER)
)
glUseProgram(program)
glClearColor(0.5, 0.8, 0.7, 1.0)
vertPositions = [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
]
inputAttrib = glGetAttribLocation(program, 'aPosition')
glVertexAttribPointer(inputAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertPositions)
glEnableVertexAttribArray(inputAttrib)
def create_texture(self, stream, fb):
cfg = stream.configuration
fmt = cfg.pixel_format.fourcc
w = cfg.size.width
h = cfg.size.height
attribs = [
EGL_WIDTH, w,
EGL_HEIGHT, h,
EGL_LINUX_DRM_FOURCC_EXT, fmt,
EGL_DMA_BUF_PLANE0_FD_EXT, fb.fd(0),
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
EGL_DMA_BUF_PLANE0_PITCH_EXT, cfg.stride,
EGL_NONE,
]
image = eglCreateImageKHR(self.egl.display,
EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT,
None,
attribs)
assert(image)
textures = glGenTextures(1)
glBindTexture(GL_TEXTURE_EXTERNAL_OES, textures)
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image)
return textures
def resizeEvent(self, event):
size = event.size()
print('Resize', size)
super().resizeEvent(event)
if self.surface is None:
return
glViewport(0, 0, size.width() // 2, size.height())
def paintEvent(self, event):
if self.surface is None:
self.init_gl()
for ctx_idx, queue in self.reqqueue.items():
if len(queue) == 0:
continue
ctx = next(ctx for ctx in self.state.contexts if ctx.idx == ctx_idx)
if self.current[ctx_idx]:
old = self.current[ctx_idx]
self.current[ctx_idx] = None
self.state.request_processed(ctx, old)
next_req = queue.pop(0)
self.current[ctx_idx] = next_req
stream, fb = next(iter(next_req.buffers.items()))
self.textures[stream] = self.create_texture(stream, fb)
self.paint_gl()
def paint_gl(self):
b = eglMakeCurrent(self.egl.display, self.surface, self.surface, self.egl.context)
assert(b)
glClear(GL_COLOR_BUFFER_BIT)
size = self.size()
for idx, ctx in enumerate(self.state.contexts):
for stream in ctx.streams:
if self.textures[stream] is None:
continue
w = size.width() // self.num_columns
h = size.height() // self.num_rows
x = idx % self.num_columns
y = idx // self.num_columns
x *= w
y *= h
glViewport(x, y, w, h)
glBindTexture(GL_TEXTURE_EXTERNAL_OES, self.textures[stream])
glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
b = eglSwapBuffers(self.egl.display, self.surface)
assert(b)
def handle_request(self, ctx, req):
self.reqqueue[ctx.idx].append(req)
self.update()