I'm trying to get Skia working with drm/gbm on Linux (Raspbian on a Rpi4).
My setup of DRM, GBM, EGL and OpenGL appears ok - I can clear the screen, swap the buffers etc. But `skia.GrDirectContext.MakeGL()` returns None (I'm using skia-python). I assume this means something about the current context is wrong - but I can't see how I'm supposed to find out what's wrong.
Can anyone advise what I'm doing wrong, or how I can get errors to help figure out what's wrong?
```
import os
from ctypes import *
from OpenGL.GL import *
from OpenGL.EGL import *
from OpenGL.GLU import *
# Initialize DRM
drm = CDLL('libdrm.so', mode=RTLD_GLOBAL)
gbm = CDLL('libgbm.so', mode=RTLD_GLOBAL)
DRM_DISPLAY_INFO_LEN = 32
DRM_CONNECTOR_NAME_LEN = 32
DRM_DISPLAY_MODE_LEN = 32
DRM_PROP_NAME_LEN = 32
# drmModeConnection
DRM_MODE_CONNECTED = 1
DRM_MODE_DISCONNECTED = 2
DRM_MODE_UNKNOWNCONNECTION = 3
# drmModeSubPixel
DRM_MODE_SUBPIXEL_UNKNOWN = 1
DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2
DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3
DRM_MODE_SUBPIXEL_VERTICAL_RGB = 4
DRM_MODE_SUBPIXEL_VERTICAL_BGR = 5
DRM_MODE_SUBPIXEL_NONE = 6
class drmModeRes(Structure):
_fields_ = [
('count_fbs', c_int),
('fbs', POINTER(c_uint32)),
('count_crtcs', c_int),
('crtcs', POINTER(c_uint32)),
('count_connectors', c_int),
('connectors', POINTER(c_uint32)),
('count_encoders', c_int),
('encoders', POINTER(c_uint32)),
('min_width', c_uint32),
('max_wuidth', c_uint32),
('min_height', c_uint32),
('max_height', c_uint32)
]
class drmModeModeInfo(Structure):
_fields_ = [
('clock;', c_uint32),
('hdisplay', c_uint16),
('hsync_start', c_uint16),
('hsync_end', c_uint16),
('htotal', c_uint16),
('hskew', c_uint16),
('vdisplay', c_uint16),
('vsync_start', c_uint16),
('vsync_end', c_uint16),
('vtotal', c_uint16),
('vscan', c_uint16),
('vrefresh', c_uint32),
('flags', c_uint32),
('type', c_uint32),
('name', c_char * DRM_DISPLAY_MODE_LEN)
]
class drmModeConnector(Structure):
_fields_ = [
('connector_id', c_uint32),
('encoder_id', c_uint32),
('connector_type', c_uint32),
('connector_type_id', c_uint32),
('connection', c_int), # drmModeConnection
('mmWidth', c_uint32),
('mmHeight', c_uint32),
('subpixel', c_int), # drmModeSubPixel
('count_modes', c_int),
('modes', POINTER(drmModeModeInfo)),
('count_props', c_int),
('props', POINTER(c_uint32)),
('prop_values', POINTER(c_uint64)),
('count_encoders', c_int),
('encoders', POINTER(c_uint32)),
]
class drmModeEncoder(Structure):
_fields_ = [
('encoder_id', c_uint32),
('encoder_type', c_uint32),
('crtc_id', c_uint32),
('possible_crtcs', c_uint32),
('possible_clones', c_uint32)
]
class drmModeCrtc(Structure):
_fields_ = [
('crtc_id', c_uint32),
('buffer_id', c_uint32),
('x', c_uint32),
('y', c_uint32),
('width', c_uint32),
('height', c_uint32),
('mode_valid', c_int),
('mode', drmModeModeInfo),
('gamma_size', c_int)
]
drm.drmModeGetResources.restype = POINTER(drmModeRes)
drm.drmModeGetConnector.restype = POINTER(drmModeConnector)
drm.drmModeGetEncoder.restype = POINTER(drmModeEncoder)
drm.drmModeGetCrtc.restype = POINTER(drmModeCrtc)
def __gbm_fourcc_code(a, b, c, d):
return ord(a) | ord(b) << 8 | ord(c) << 16 | ord(d) << 24
GBM_BO_USE_SCANOUT = 1 << 0
GBM_BO_USE_RENDERING = 1 << 2
GBM_FORMAT_XRGB8888 = __gbm_fourcc_code('X', 'R', '2', '4')
GBM_FORMAT_ARGB8888 = __gbm_fourcc_code('A', 'R', '2', '4')
CONFIG_ATTRIBUTES = (c_int * 13)(
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, # XXX: windows bit might be wrong surface_type
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE
)
class DRMDisplay:
FMT = GBM_FORMAT_ARGB8888
def __init__(self, device):
self.device = device
self.gbm_device = None
self.gbm_surface = None
self._previous_buffer = None
self._previous_fb = None
self.display = None
self.context = None
self.surface = None
self.mode, self.crtc, self.connector_id = self._get_display_info()
self.gbm_device = gbm.gbm_create_device(self.device)
self.gbm_surface = gbm.gbm_surface_create(self.gbm_device, self.mode.hdisplay, self.mode.vdisplay, DRMDisplay.FMT, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING)
self._init_egl()
def _init_egl(self):
self.display = eglGetDisplay(self.gbm_device)
major, minor = EGLint(), EGLint()
if eglInitialize(self.display, byref(major), byref(minor)) == EGL_FALSE:
self.close()
raise ValueError('Unable to initialise egl')
print('EGL version %d.%d' % (major.value, minor.value))
eglBindAPI(EGL_OPENGL_API)
count = c_int()
res = eglGetConfigs(self.display, None, 0, byref(count))
num_configs = c_int()
configs = (count.value * EGLConfig)()
if eglChooseConfig(self.display, CONFIG_ATTRIBUTES, configs, count, byref(num_configs)) == EGL_FALSE:
self.close()
raise ValueError('Unable to get egl config attributes')
# find the correct config
# config_idx = None
# ident = c_int()
# for i in range(0, num_configs.value):
# if eglGetConfigAttrib(self.display, configs[i], EGL_NATIVE_VISUAL_ID, byref(ident)) == EGL_FALSE:
# continue
# print('config', configs[i], '%x' % ident.value,
# chr((ident.value >> 0) & 0xff),
# chr((ident.value >> 8) & 0xff),
# chr((ident.value >> 16) & 0xff),
# chr((ident.value >> 24) & 0xff)
# )
# if ident.value == DRMDisplay.FMT:
# config_idx = i
# print('config matches', configs[i], i)
# break
# if config_idx is None:
# self.close()
# raise ValueError('Unable to get find suitable egl config')
# print('selecting idx', config_idx)
# config = configs[config_idx]
config = configs[0]
self.surface = eglCreateWindowSurface(self.display, config, self.gbm_surface, None)
if self.surface == EGL_NO_SURFACE:
self.close()
raise ValueError('Unable to create egl surface')
self.context = eglCreateContext(self.display, config, EGL_NO_CONTEXT, None)
if self.context == EGL_NO_CONTEXT:
self.close()
raise ValueError('Unable to create egl context')
if eglMakeCurrent(self.display, self.surface, self.surface, self.context) == EGL_FALSE:
self.close()
raise ValueError('Unable to make egl context current')
def _get_connector(self, resources):
for i in range(0, resources.count_connectors):
connector = drm.drmModeGetConnector(self.device, resources.connectors[i])
if connector.contents.connection == DRM_MODE_CONNECTED:
return connector
drm.drmModeFreeConnector(connector)
return None
def _get_display_info(self):
resources = drm.drmModeGetResources(self.device)
if resources is None:
raise ValueError('Invalid resources')
connector = self._get_connector(resources.contents)
if connector is None:
drm.drmModeFreeResources(resources)
raise ValueError('Invalid connector')
connector_id = connector.contents.connector_id
mode = drmModeModeInfo() # copy as we're freeing connector
memmove(byref(mode), connector.contents.modes, sizeof(drmModeModeInfo))
encoder_id = connector.contents.encoder_id
if encoder_id == 0:
drm.drmModeFreeConnector(connector)
drm.drmModeFreeResources(resources)
raise ValueError('Invalid encoder')
encoder = drm.drmModeGetEncoder(self.device, encoder_id)
crtc = drm.drmModeGetCrtc(self.device, encoder.contents.crtc_id)
drm.drmModeFreeEncoder(encoder)
drm.drmModeFreeConnector(connector)
drm.drmModeFreeResources(resources)
return mode, crtc, c_int(connector_id)
def close(self):
if self.context is not None:
eglDestroyContext(self.display, self.context)
if self.surface is not None:
eglDestroySurface(self.display, self.surface)
if self.display is not None:
eglTerminate(self.display)
drm.drmModeSetCrtc(self.device,
self.crtc.contents.crtc_id,
self.crtc.contents.buffer_id,
self.crtc.contents.x,
self.crtc.contents.y,
byref(self.connector_id),
1,
byref(self.crtc.contents.mode))
drm.drmModeFreeCrtc(self.crtc)
if self._previous_buffer is not None:
drm.drmModeRmFB(self.device, self._previous_buffer)
gbm.gbm_surface_release_buffer(self.gbm_surface, self._previous_buffer)
gbm.gbm_surface_destroy(self.gbm_surface)
gbm.gbm_device_destroy(self.gbm_device)
os.close(self.device)
def swap_buffer(self):
# XXX: this seems inefficient
eglSwapBuffers(self.display, self.surface)
bo = gbm.gbm_surface_lock_front_buffer(self.gbm_surface)
handle = gbm.gbm_bo_get_handle(bo)
pitch = gbm.gbm_bo_get_stride(bo)
fb = c_int32()
drm.drmModeAddFB(self.device, self.mode.hdisplay, self.mode.vdisplay, 24, 32, pitch, handle, byref(fb))
drm.drmModeSetCrtc(self.device, self.crtc.contents.crtc_id, fb.value, 0, 0, byref(self.connector_id), 1, byref(self.mode))
if self._previous_buffer is not None:
drm.drmModeRmFB(self.device, self._previous_buffer)
gbm.gbm_surface_release_buffer(self.gbm_surface, self._previous_buffer)
self._previous_buffer = bo
self._previous_fb = fb
import skia
import time
device = os.open('/dev/dri/card1', os.O_RDWR)
display = DRMDisplay(device)
width = display.mode.hdisplay
height = display.mode.vdisplay
print(f'wxh = {width}x{height}')
# this code works - flips screen to black - enters opengl
# glViewport(0, 0, width, height)
# glClearColor(0.0, 0.0, 0.0, 1.0)
# glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# display.swap_buffer()
# time.sleep(3)
color = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, color)
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height)
depth = glGenRenderbuffers(1)
glBindRenderbuffer(GL_RENDERBUFFER, depth)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height)
framebuffer = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color)
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth)
glBindRenderbuffer(GL_RENDERBUFFER, 0)
# # Create texture for Skia surface
# texture = glGenTextures(1)
# glBindTexture(GL_TEXTURE_2D, texture)
# glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, None)
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
# glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0)
if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE:
print("Framebuffer is not complete")
# this code doesn't as MakeGL returns None
# This runtime errors on GrGLInterface with "null pointer error"
# iface = skia.GrGLInterface()
# context = skia.GrDirectContext.MakeGL(iface)
context = skia.GrDirectContext.MakeGL()
print('context', context)
backend_render_target = skia.GrBackendRenderTarget(
width, height, 0, 0, #8,
skia.GrGLFramebufferInfo(0, GL_RGB8)
)
print('backend_render_target', backend_render_target)
surface = skia.Surface.MakeFromBackendRenderTarget(
context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin,
skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB())
print('surface', surface)
canvas = surface.getCanvas()
canvas.clear(skia.ColorWHITE)
# Draw something with Skia
paint = skia.Paint(Color=skia.Color(0xff, 0xff, 0x00))
canvas.drawRect(skia.Rect.MakeXYWH(100, 100, 200, 200), paint)
# Display the Skia surface on the OpenGL context
context.flush()
display.swap_buffer()
time.sleep(3)
print('closing display')
display.close()
```