Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 257 additions & 13 deletions platform/drm/cog-drm-modeset-renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "../../core/cog.h"
#include "cog-drm-renderer.h"
#include "cursor-drm.h"
#include <errno.h>
#include <gbm.h>
#include <wayland-server.h>
Expand Down Expand Up @@ -74,6 +75,7 @@ struct buffer_object {
uint32_t fb_id;
struct gbm_bo *bo;
struct wl_resource *buffer_resource;
void *renderer; /* owning CogDrmModesetRenderer */

struct {
struct wl_resource *resource;
Expand Down Expand Up @@ -105,6 +107,22 @@ typedef struct {
drmModeObjectProperties *props;
drmModePropertyRes **props_info;
} connector_props, crtc_props, plane_props;

/* Software cursor: composited into the scanout dumb buffer.
* size is the (square) cursor edge in pixels - the 16x16 artwork
* times the device scale factor; buffers hold the maximum. */
struct {
bool enabled;
int x, y;
int size;
uint32_t image[COG_DRM_CURSOR_IMAGE_SIZE * COG_DRM_CURSOR_IMAGE_MAX_SCALE *
COG_DRM_CURSOR_IMAGE_SIZE * COG_DRM_CURSOR_IMAGE_MAX_SCALE];
uint32_t saved[COG_DRM_CURSOR_IMAGE_SIZE * COG_DRM_CURSOR_IMAGE_MAX_SCALE *
COG_DRM_CURSOR_IMAGE_SIZE * COG_DRM_CURSOR_IMAGE_MAX_SCALE];
struct gbm_bo *drawn_bo; /* cursor currently painted on this bo */
int drawn_x, drawn_y, drawn_w, drawn_h;
gint64 last_move_us;
} sw_cursor;
} CogDrmModesetRenderer;

static inline int
Expand All @@ -114,9 +132,177 @@ get_drm_fd(CogDrmModesetRenderer *self)
return s->pfd.fd;
}

static void
sw_cursor_restore(CogDrmModesetRenderer *self)
{
struct gbm_bo *bo = self->sw_cursor.drawn_bo;
if (!bo)
return;
self->sw_cursor.drawn_bo = NULL;

uint32_t stride = 0;
void *map = NULL;
gbm_bo_map(bo, 0, 0, gbm_bo_get_width(bo), gbm_bo_get_height(bo), GBM_BO_TRANSFER_WRITE, &stride, &map);
if (!map)
return;
for (int r = 0; r < self->sw_cursor.drawn_h; r++)
memcpy((uint8_t *) map + (self->sw_cursor.drawn_y + r) * stride + self->sw_cursor.drawn_x * 4,
self->sw_cursor.saved + r * self->sw_cursor.size,
self->sw_cursor.drawn_w * 4);
gbm_bo_unmap(bo, map);
}

static void
sw_cursor_paint(CogDrmModesetRenderer *self, struct gbm_bo *bo)
{
if (!self->sw_cursor.enabled || !bo)
return;

const int S = self->sw_cursor.size;
int bw = gbm_bo_get_width(bo), bh = gbm_bo_get_height(bo);
int x = self->sw_cursor.x, y = self->sw_cursor.y;
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= bw) x = bw - 1;
if (y >= bh) y = bh - 1;
int w = MIN(S, bw - x), h = MIN(S, bh - y);
if (w <= 0 || h <= 0)
return;

uint32_t stride = 0;
void *map = NULL;
gbm_bo_map(bo, 0, 0, bw, bh, GBM_BO_TRANSFER_READ_WRITE, &stride, &map);
if (!map)
return;
for (int r = 0; r < h; r++) {
uint32_t *dst = (uint32_t *) ((uint8_t *) map + (y + r) * stride) + x;
uint32_t *sav = self->sw_cursor.saved + r * S;
const uint32_t *img = self->sw_cursor.image + r * S;
for (int c = 0; c < w; c++) {
sav[c] = dst[c];
uint32_t px = img[c], a = px >> 24;
if (a == 0xff)
dst[c] = px;
else if (a) {
uint32_t d = dst[c];
uint32_t rr = ((px >> 16) & 0xff) + (((d >> 16) & 0xff) * (255 - a)) / 255;
uint32_t gg = ((px >> 8) & 0xff) + (((d >> 8) & 0xff) * (255 - a)) / 255;
uint32_t bb = (px & 0xff) + ((d & 0xff) * (255 - a)) / 255;
dst[c] = 0xff000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
gbm_bo_unmap(bo, map);

self->sw_cursor.drawn_bo = bo;
self->sw_cursor.drawn_x = x;
self->sw_cursor.drawn_y = y;
self->sw_cursor.drawn_w = w;
self->sw_cursor.drawn_h = h;
}

bool
cog_drm_modeset_renderer_sw_cursor_enable(CogDrmRenderer *renderer, unsigned scale, unsigned *screen_w,
unsigned *screen_h)
{
CogDrmModesetRenderer *self = (CogDrmModesetRenderer *) renderer;

scale = CLAMP(scale, 1, COG_DRM_CURSOR_IMAGE_MAX_SCALE);
self->sw_cursor.size = COG_DRM_CURSOR_IMAGE_SIZE * scale;
cog_drm_cursor_image_argb_premult(self->sw_cursor.image, scale);
self->sw_cursor.enabled = true;
self->sw_cursor.x = self->mode.hdisplay / 2;
self->sw_cursor.y = self->mode.vdisplay / 2;
if (screen_w)
*screen_w = self->mode.hdisplay;
if (screen_h)
*screen_h = self->mode.vdisplay;
return true;
}

void
cog_drm_modeset_renderer_sw_cursor_move(CogDrmRenderer *renderer, int x, int y)
{
CogDrmModesetRenderer *self = (CogDrmModesetRenderer *) renderer;

if (!self->sw_cursor.enabled)
return;
self->sw_cursor.x = x;
self->sw_cursor.y = y;

/* Throttle to display rate: input events arrive much faster than
* frames; repainting on every event multiplies the raster's chance
* to catch a half-updated cursor (flicker). The final position is
* never lost - each event updates x/y and the next repaint uses it. */
gint64 now = g_get_monotonic_time();
if (now - self->sw_cursor.last_move_us < 16000)
return;
self->sw_cursor.last_move_us = now;

/* Repaint in place ONLY on a bo the cursor was already baked into
* via the SHM frame path - those are CPU-writable dumb buffers.
* The committed buffer may be an IMPORTED DMABUF (hardware-rendered
* frames); mapping and scribbling on those crashes or corrupts -
* with no SHM frames the software cursor simply stays hidden. Use a
* single map session for restore + paint so the raster cannot catch
* the cursor-less gap between two separate map cycles. */
struct gbm_bo *bo = self->sw_cursor.drawn_bo;
if (!bo)
return;

const int S = self->sw_cursor.size;
int bw = gbm_bo_get_width(bo), bh = gbm_bo_get_height(bo);
uint32_t stride = 0;
void *map = NULL;
gbm_bo_map(bo, 0, 0, bw, bh, GBM_BO_TRANSFER_READ_WRITE, &stride, &map);
if (!map)
return;

if (self->sw_cursor.drawn_bo == bo) {
for (int r = 0; r < self->sw_cursor.drawn_h; r++)
memcpy((uint8_t *) map + (self->sw_cursor.drawn_y + r) * stride + self->sw_cursor.drawn_x * 4,
self->sw_cursor.saved + r * S, self->sw_cursor.drawn_w * 4);
self->sw_cursor.drawn_bo = NULL;
}

if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= bw) x = bw - 1;
if (y >= bh) y = bh - 1;
int w = MIN(S, bw - x), h = MIN(S, bh - y);
if (w > 0 && h > 0) {
for (int r = 0; r < h; r++) {
uint32_t *dst = (uint32_t *) ((uint8_t *) map + (y + r) * stride) + x;
uint32_t *sav = self->sw_cursor.saved + r * S;
const uint32_t *img = self->sw_cursor.image + r * S;
for (int c = 0; c < w; c++) {
sav[c] = dst[c];
uint32_t px = img[c], a = px >> 24;
if (a == 0xff)
dst[c] = px;
else if (a) {
uint32_t d = dst[c];
uint32_t rr = ((px >> 16) & 0xff) + (((d >> 16) & 0xff) * (255 - a)) / 255;
uint32_t gg = ((px >> 8) & 0xff) + (((d >> 8) & 0xff) * (255 - a)) / 255;
uint32_t bb = (px & 0xff) + ((d & 0xff) * (255 - a)) / 255;
dst[c] = 0xff000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
self->sw_cursor.drawn_bo = bo;
self->sw_cursor.drawn_x = x;
self->sw_cursor.drawn_y = y;
self->sw_cursor.drawn_w = w;
self->sw_cursor.drawn_h = h;
}
gbm_bo_unmap(bo, map);
}

static void
destroy_buffer(CogDrmModesetRenderer *renderer, struct buffer_object *buffer)
{
if (renderer->sw_cursor.drawn_bo == buffer->bo)
renderer->sw_cursor.drawn_bo = NULL;
drmModeRmFB(get_drm_fd(renderer), buffer->fb_id);
gbm_bo_destroy(buffer->bo);

Expand All @@ -138,14 +324,13 @@ static void
destroy_buffer_notify(struct wl_listener *listener, void *data)
{
struct buffer_object *buffer = wl_container_of(listener, buffer, destroy_listener);
CogDrmModesetRenderer *renderer = wl_resource_get_user_data(buffer->buffer_resource);
CogDrmModesetRenderer *renderer = buffer->renderer;

if (renderer->committed_buffer == buffer)
renderer->committed_buffer = NULL;

wl_list_remove(&buffer->link);

wl_resource_set_user_data(buffer->buffer_resource, NULL);
destroy_buffer(renderer, buffer);
}

Expand Down Expand Up @@ -216,7 +401,7 @@ drm_create_buffer_for_bo(CogDrmModesetRenderer *self,
wl_list_insert(&self->buffer_list, &buffer->link);
buffer->destroy_listener.notify = destroy_buffer_notify;
wl_resource_add_destroy_listener(buffer_resource, &buffer->destroy_listener);
wl_resource_set_user_data(buffer_resource, self);
buffer->renderer = self;

buffer->fb_id = fb_id;
buffer->bo = bo;
Expand Down Expand Up @@ -271,12 +456,14 @@ drm_create_buffer_for_shm_buffer(CogDrmModesetRenderer *self,
wl_list_insert(&self->buffer_list, &buffer->link);
buffer->destroy_listener.notify = destroy_buffer_notify;
wl_resource_add_destroy_listener(buffer_resource, &buffer->destroy_listener);
wl_resource_set_user_data(buffer_resource, self);
buffer->renderer = self;

buffer->fb_id = fb_id;
buffer->bo = bo;
buffer->buffer_resource = buffer_resource;

g_message("created shm bo %dx%d for resource %p", width, height, (void *) buffer_resource);

return buffer;
}

Expand All @@ -287,6 +474,19 @@ drm_copy_shm_buffer_into_bo(struct wl_shm_buffer *shm_buffer, struct gbm_bo *bo)
int32_t height = wl_shm_buffer_get_height(shm_buffer);
int32_t stride = wl_shm_buffer_get_stride(shm_buffer);

/* DEBUG/hardening: never write past the bo - clamp and report any
* size mismatch (stale buffer_object?) instead of crashing. */
uint32_t pre_bo_width = gbm_bo_get_width(bo);
uint32_t pre_bo_height = gbm_bo_get_height(bo);
if ((uint32_t) width > pre_bo_width || (uint32_t) height > pre_bo_height) {
g_warning("SHM->bo size mismatch: shm %dx%d (stride %d) vs bo %ux%u - clamping",
width, height, stride, pre_bo_width, pre_bo_height);
if ((uint32_t) width > pre_bo_width)
width = pre_bo_width;
if ((uint32_t) height > pre_bo_height)
height = pre_bo_height;
}

uint32_t bo_stride = 0;
void *map_data = NULL;
gbm_bo_map(bo, 0, 0, width, height, GBM_BO_TRANSFER_WRITE, &bo_stride, &map_data);
Expand All @@ -296,6 +496,11 @@ drm_copy_shm_buffer_into_bo(struct wl_shm_buffer *shm_buffer, struct gbm_bo *bo)
wl_shm_buffer_begin_access(shm_buffer);

uint8_t *src = wl_shm_buffer_get_data(shm_buffer);
if (!src) {
wl_shm_buffer_end_access(shm_buffer);
gbm_bo_unmap(bo, map_data);
return;
}
uint8_t *dst = map_data;

uint32_t bo_width = gbm_bo_get_width(bo);
Expand Down Expand Up @@ -522,6 +727,8 @@ on_export_dmabuf_resource(void *data, struct wpe_view_backend_exportable_fdo_dma
}
}

static unsigned bogus_streak = 0;

static void
on_export_shm_buffer(void *data, struct wpe_fdo_shm_exported_buffer *exported_buffer)
{
Expand All @@ -530,20 +737,57 @@ on_export_shm_buffer(void *data, struct wpe_fdo_shm_exported_buffer *exported_bu
struct wl_resource *exported_resource = wpe_fdo_shm_exported_buffer_get_resource(exported_buffer);
struct wl_shm_buffer *exported_shm_buffer = wpe_fdo_shm_exported_buffer_get_shm_buffer(exported_buffer);

struct buffer_object *buffer = drm_buffer_for_resource(self, exported_resource);
if (buffer) {
drm_copy_shm_buffer_into_bo(exported_shm_buffer, buffer->bo);

buffer->export.shm_buffer = exported_buffer;
drm_commit_buffer(self, buffer);
return;
/* Trace + guard: wpebackend-fdo (<= 1.16.1) never clears
* Surface::shmBuffer, so a commit without a fresh attach exports a
* stale - potentially dangling - wl_shm_buffer whose metadata reads
* as garbage (negative width/stride). Never touch buffers with
* impossible geometry; skip the frame instead of crashing. */
{
int32_t w = exported_shm_buffer ? wl_shm_buffer_get_width(exported_shm_buffer) : -1;
int32_t h = exported_shm_buffer ? wl_shm_buffer_get_height(exported_shm_buffer) : -1;
int32_t st = exported_shm_buffer ? wl_shm_buffer_get_stride(exported_shm_buffer) : -1;
g_debug("shm export: resource %p shm %p %dx%d stride %d",
(void *) exported_resource, (void *) exported_shm_buffer, w, h, st);
if (!exported_resource || w <= 0 || h <= 0 || w > 16384 || h > 16384 || st < w * 4) {
g_warning("shm export with bogus geometry (resource %p, shm %p, %dx%d stride %d) - frame skipped (streak %u)",
(void *) exported_resource, (void *) exported_shm_buffer, w, h, st, ++bogus_streak);
/* Do NOT release the wrapper: dispatch_release would send a
* wl_buffer.release through the (dangling) resource, poking
* freed protocol state on every skipped frame. Leaking the
* small wrapper is the lesser evil in an already-broken
* session. Keep pacing alive for a few frames in case the
* client recovers; after that, exit and let procd respawn a
* clean process (bounded by the respawn limit). */
if (bogus_streak >= 4) {
g_critical("persistent bogus SHM exports - exiting for a clean respawn");
exit(70);
}
wpe_view_backend_exportable_fdo_dispatch_frame_complete(self->exportable);
return;
}
bogus_streak = 0;
}

buffer = drm_create_buffer_for_shm_buffer(self, exported_resource, exported_shm_buffer);
struct buffer_object *buffer = drm_buffer_for_resource(self, exported_resource);
if (!buffer)
buffer = drm_create_buffer_for_shm_buffer(self, exported_resource, exported_shm_buffer);
if (buffer) {
drm_copy_shm_buffer_into_bo(exported_shm_buffer, buffer->bo);

buffer->export.shm_buffer = exported_buffer;
/* the copy replaced the whole bo content - the previously saved
* background patch is void; bake the cursor into the new frame */
if (self->sw_cursor.drawn_bo == buffer->bo)
self->sw_cursor.drawn_bo = NULL;
sw_cursor_paint(self, buffer->bo);

/* The dumb buffer owns the pixels now: release the client's SHM
* buffer right away instead of parking it until the frame retires.
* Holding it keeps an external reference on the wl_shm pool, which
* turns client-side pool resizes into deferred remaps - the next
* copy would then use a stale mapping (libwayland warns "Buffer
* address requested when its parent pool has an external reference
* and a deferred resize pending.") and walk off its end. */
wpe_view_backend_exportable_fdo_dispatch_release_shm_exported_buffer(self->exportable, exported_buffer);
drm_commit_buffer(self, buffer);
}
}
Expand Down
7 changes: 7 additions & 0 deletions platform/drm/cog-drm-renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ CogDrmRenderer *cog_drm_modeset_renderer_new(struct gbm_device *dev,
const drmModeModeInfo *mode,
bool atomic_modesetting);

/* Software cursor, modeset renderer only: composited into the scanout
* buffer (hardware cursors are broken on some display blocks, e.g.
* radeon DCE4.1 - see RADEON_DCE41_CURSOR_NOTES). */
bool cog_drm_modeset_renderer_sw_cursor_enable(CogDrmRenderer *, unsigned scale, unsigned *screen_w,
unsigned *screen_h);
void cog_drm_modeset_renderer_sw_cursor_move(CogDrmRenderer *, int x, int y);

CogDrmRenderer *cog_drm_gles_renderer_new(struct gbm_device *dev,
EGLDisplay display,
uint32_t plane_id,
Expand Down
Loading