Skia context failing with drm/gbm

14 views
Skip to first unread message

Richard Cooper

unread,
Oct 20, 2024, 6:27:51 PMOct 20
to skia-discuss
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()
```
Reply all
Reply to author
Forward
0 new messages