Merge pull request #34 from nongio/wayland-shm-surface

Wayland shm surface
This commit is contained in:
Fred Kiefer 2021-12-27 13:29:36 +01:00 committed by GitHub
commit 06be91b267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 313 additions and 262 deletions

View file

@ -1,5 +1,5 @@
/*
WaylandCairoSurface.h
WaylandCairoShmSurface.h
Copyright (C) 2020 Free Software Foundation, Inc.
@ -25,14 +25,15 @@
Boston, MA 02110-1301, USA.
*/
#ifndef WaylandCairoSurface_h
#define WaylandCairoSurface_h
#ifndef WaylandCairoShmSurface_h
#define WaylandCairoShmSurface_h
#include "cairo/CairoSurface.h"
@interface WaylandCairoSurface : CairoSurface
@interface WaylandCairoShmSurface : CairoSurface
{
}
- (void)destroySurface;
@end
#endif

View file

@ -37,7 +37,8 @@
#include <cairo/cairo.h>
#include <xkbcommon/xkbcommon.h>
#include "cairo/WaylandCairoSurface.h"
#include "cairo/CairoSurface.h"
#include "wayland/xdg-shell-client-protocol.h"
#include "wayland/wlr-layer-shell-client-protocol.h"
@ -129,8 +130,6 @@ struct window
int is_out;
int level;
unsigned char *data;
struct wl_buffer *buffer;
struct wl_surface *surface;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *toplevel;
@ -138,7 +137,7 @@ struct window
struct xdg_positioner *positioner;
struct zwlr_layer_surface_v1 *layer_surface;
struct output *output;
WaylandCairoSurface *wcs;
CairoSurface *wcs;
};
@interface WaylandServer : GSDisplayServer

View file

@ -73,9 +73,9 @@
#elif BUILD_SERVER == SERVER_wayland
# include "wayland/WaylandServer.h"
# include "cairo/CairoGState.h"
# include "cairo/WaylandCairoSurface.h"
# include "cairo/WaylandCairoShmSurface.h"
# define _CAIRO_GSTATE_CLASSNAME CairoGState
# define _CAIRO_SURFACE_CLASSNAME WaylandCairoSurface
# define _CAIRO_SURFACE_CLASSNAME WaylandCairoShmSurface
#else
# error Invalid server for Cairo backend : non implemented
#endif /* BUILD_SERVER */

View file

@ -50,7 +50,7 @@ ifeq ($(BUILD_SERVER),x11)
endif
else
ifeq ($(BUILD_SERVER),wayland)
cairo_OBJC_FILES += WaylandCairoSurface.m
cairo_OBJC_FILES += WaylandCairoShmSurface.m
else
ifeq ($(BUILD_GRAPHICS),cairo)
ifeq ($(WITH_GLITZ),yes)

View file

@ -0,0 +1,275 @@
/* WaylandCairoSurface
WaylandCairoShmSurface - A cairo surface backed by a wayland
shared memory buffer.
After the wayland surface is configured, the buffer needs to be
attached to the surface. Subsequent changes to the cairo surface
needs to be notified to the wayland server using wl_surface_damage
and wl_surface_commit. The buffer is freed after the compositor
releases it and the cairo surface is not in use.
Copyright (C) 2020 Free Software Foundation, Inc.
Author: Sergio L. Pascual <slp@sinrega.org>
Rewrite: Riccardo Canalicchio <riccardo.canalicchio(at)gmail.com>
Date: November 2021
This file is part of the GNU Objective C Backend Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#define _GNU_SOURCE
#include "wayland/WaylandServer.h"
#include "cairo/WaylandCairoShmSurface.h"
#include <cairo/cairo.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
struct pool_buffer
{
int poolfd;
struct wl_shm_pool *pool;
struct wl_buffer *buffer;
cairo_surface_t *surface;
uint32_t width, height;
void *data;
size_t size;
bool busy;
};
static const enum wl_shm_format wl_fmt = WL_SHM_FORMAT_ARGB8888;
static const cairo_format_t cairo_fmt = CAIRO_FORMAT_ARGB32;
static void
finishBuffer(struct pool_buffer *buf)
{
// The buffer can be deleted if it has been released by the compositor
// and if not used by the cairo surface
if(buf == NULL || buf->busy || buf->surface != NULL)
{
return;
}
if (buf->buffer)
{
wl_buffer_destroy(buf->buffer);
}
if (buf->data)
{
munmap(buf->data, buf->size);
}
free(buf);
return;
}
static void
buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
{
struct pool_buffer *buffer = data;
buffer->busy = false;
// If the buffer was not released before dealloc
finishBuffer(buffer);
}
static const struct wl_buffer_listener buffer_listener = {
// Sent by the compositor when it's no longer using a buffer
.release = buffer_handle_release,
};
// Creates a file descriptor for the compositor to share pixel buffers
static int
createPoolFile(off_t size)
{
static const char template[] = "/gnustep-shared-XXXXXX";
const char *path;
char *name;
int fd;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = malloc(strlen(path) + sizeof(template));
if (!name)
{
return -1;
}
strcpy(name, path);
strcat(name, template);
fd = memfd_create(name, MFD_CLOEXEC);
free(name);
if (fd < 0)
return -1;
if (ftruncate(fd, size) != 0)
{
close(fd);
return -1;
}
return fd;
}
static struct pool_buffer *
createBufferForWindow(struct window * window, struct wl_shm *shm)
{
uint32_t stride = cairo_format_stride_for_width(cairo_fmt, window->width);
size_t size = stride * window->height;
struct pool_buffer * buf = malloc(sizeof(struct pool_buffer));
void *data = NULL;
if (size > 0)
{
buf->poolfd = createPoolFile(size);
if (buf->poolfd == -1)
{
return NULL;
}
data
= mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, buf->poolfd, 0);
if (data == MAP_FAILED)
{
return NULL;
}
buf->pool = wl_shm_create_pool(shm, buf->poolfd, size);
buf->buffer = wl_shm_pool_create_buffer(buf->pool, 0, window->width, window->height,
stride, wl_fmt);
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
}
buf->data = data;
buf->size = size;
buf->width = window->width;
buf->height = window->height;
buf->surface = cairo_image_surface_create_for_data(data, cairo_fmt, window->width, window->height, stride);
wl_shm_pool_destroy(buf->pool);
return buf;
}
@implementation WaylandCairoShmSurface
{
struct pool_buffer *pbuffer;
}
- (id)initWithDevice:(void *)device
{
struct window *window = (struct window *) device;
NSDebugLog(@"WaylandCairoShmSurface: initWithDevice win=%d",
window->window_id);
gsDevice = device;
pbuffer = createBufferForWindow(window, window->wlconfig->shm);
if (pbuffer == NULL)
{
NSDebugLog(@"failed to obtain buffer");
return nil;
}
_surface = pbuffer->surface;
window->buffer_needs_attach = YES;
if (_surface == NULL)
{
NSDebugLog(@"can't create cairo surface");
return nil;
}
if (window->configured)
{
// we can attach a buffer to the surface only if the surface is configured
// this is usually done in the configure event handler
// in case of resize of an already configured surface
// we should reattach the new allocated buffer
NSDebugLog(@"wl_surface_attach: win=%d",
window->window_id);
window->buffer_needs_attach = NO;
wl_surface_attach(window->surface, pbuffer->buffer, 0, 0);
wl_surface_commit(window->surface);
}
window->wcs = self;
return self;
}
- (void)dealloc
{
struct window *window = (struct window *) gsDevice;
NSDebugLog(@"WaylandCairoSurface: dealloc win=%d", window->window_id);
cairo_surface_destroy(_surface);
_surface = NULL;
pbuffer->surface = NULL;
// try to free the buffer if already released by the compositor
finishBuffer(pbuffer);
[super dealloc];
}
- (NSSize)size
{
if (_surface == NULL)
{
return NSZeroSize;
}
return NSMakeSize(cairo_image_surface_get_width(_surface),
cairo_image_surface_get_height(_surface));
}
- (void)handleExposeRect:(NSRect)rect
{
struct window *window = (struct window *) gsDevice;
NSDebugLog(@"[CairoSurface handleExposeRect] %d", window->window_id);
window->buffer_needs_attach = YES;
if (window->configured)
{
window->buffer_needs_attach = NO;
wl_surface_attach(window->surface, pbuffer->buffer, 0, 0);
NSDebugLog(@"[%d] updating region: %d,%d %fx%f", window->window_id, 0, 0,
window->width, window->height);
// FIXME we should update only the damaged area defined as x,y,width,
// height at the moment it doesnt work
wl_surface_damage(window->surface, 0, 0, window->width, window->height);
wl_surface_commit(window->surface);
wl_display_dispatch_pending(window->wlconfig->display);
wl_display_flush(window->wlconfig->display);
}
}
- (void)destroySurface
{
// noop this is an offscreen surface
// no need to destroy it when not visible
}
@end

View file

@ -1,236 +0,0 @@
/* WaylandCairoSurface
WaylandCairoSurface - Draw with Cairo onto Wayland surfaces
Copyright (C) 2020 Free Software Foundation, Inc.
Author: Sergio L. Pascual <slp@sinrega.org>
Rewrite: Riccardo Canalicchio <riccardo.canalicchio(at)gmail.com>
Date: November 2021
This file is part of the GNU Objective C Backend Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#define _GNU_SOURCE
#include "wayland/WaylandServer.h"
#include "cairo/WaylandCairoSurface.h"
#include <cairo/cairo.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define GSWINDEVICE ((struct window *) gsDevice)
/* Linux specific version */
static int
os_create_anonymous_file(off_t size)
{
static const char template[] = "/gnustep-shared-XXXXXX";
const char *path;
char *name;
int fd;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = malloc(strlen(path) + sizeof(template));
if (!name)
return -1;
strcpy(name, path);
strcat(name, template);
fd = memfd_create(name, MFD_CLOEXEC);
free(name);
if (fd < 0)
return -1;
if (ftruncate(fd, size) != 0)
{
close(fd);
return -1;
}
return fd;
}
static inline size_t
shm_buffer_stride(struct window *window)
{
return window->width * 4;
}
static inline size_t
shm_buffer_size(struct window *window)
{
return shm_buffer_stride(window) * window->height;
}
static cairo_surface_t *
create_shm_buffer(struct window *window)
{
struct wl_shm_pool *pool;
cairo_surface_t *surface;
int fd, size, stride;
stride = shm_buffer_stride(window);
size = shm_buffer_size(window);
NSDebugLog(@"WaylandCairoSurface: creating shm buffer of %d bytes", size);
fd = os_create_anonymous_file(size);
if (fd < 0)
{
NSLog(@"creating a buffer file for surface failed");
return NULL;
}
window->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (window->data == MAP_FAILED)
{
NSLog(@"error mapping anonymous file");
close(fd);
return NULL;
}
pool = wl_shm_create_pool(window->wlconfig->shm, fd, size);
surface
= cairo_image_surface_create_for_data(window->data, CAIRO_FORMAT_ARGB32,
window->width, window->height,
stride);
window->buffer
= wl_shm_pool_create_buffer(pool, 0, window->width, window->height, stride,
WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
return surface;
}
@implementation WaylandCairoSurface
- (id)initWithDevice:(void *)device
{
struct window *window = (struct window *) device;
NSDebugLog(@"WaylandCairoSurface: initWithDevice win=%d", window->window_id);
gsDevice = device;
_surface = create_shm_buffer(window);
window->buffer_needs_attach = YES;
if (_surface == NULL)
{
NSDebugLog(@"can't create cairo surface");
return 0;
}
if (window->configured)
{
// we can attach a buffer to the surface only if the surface is configured
// this is usually done in the configure event handler
// in case of resize of an already configured surface
// we should reattach the new allocated buffer
NSDebugLog(@"wl_surface_attach: win=%d toplevel_surface",
window->window_id);
window->buffer_needs_attach = NO;
wl_surface_attach(window->surface, window->buffer, 0, 0);
wl_surface_commit(window->surface);
}
window->wcs = self;
return self;
}
- (void)dealloc
{
struct window *window = (struct window *) gsDevice;
NSDebugLog(@"WaylandCairoSurface: dealloc win=%d", window->window_id);
// FIXME: This is leaking memory. We need to *correctly* implement
// the counterpart to create_shm_buffer.
//
// For instance, this is the wrong place to destroy the cairo surface:
// cairo_surface_destroy(window->surface);
// window->surface = NULL;
// and likely to unmap the data, and destroy/release the buffer.
// "Destroying the wl_buffer after wl_buffer.release does not change
// the surface contents. However, if the client destroys the wl_buffer
// before receiving the wl_buffer.release event, the surface contents
// become undefined immediately."
// Hence also skipping:
// munmap(window->data, shm_buffer_size(window));
[super dealloc];
}
- (NSSize)size
{
// NSDebugLog(@"WaylandCairoSurface: size");
struct window *window = (struct window *) gsDevice;
return NSMakeSize(window->width, window->height);
}
- (void)setSurface:(cairo_surface_t *)surface
{
// NSDebugLog(@"WaylandCairoSurface: setSurface");
_surface = surface;
}
- (void)handleExposeRect:(NSRect)rect
{
struct window *window = (struct window *) gsDevice;
NSDebugLog(@"[CairoSurface handleExposeRect] %d", window->window_id);
int x = NSMinX(rect);
int y = NSMinY(rect);
int width = NSWidth(rect);
int height = NSHeight(rect);
window->buffer_needs_attach = YES;
if (window->configured)
{
window->buffer_needs_attach = NO;
wl_surface_attach(window->surface, window->buffer, 0, 0);
NSDebugLog(@"[%d] updating region: %d,%d %dx%d", window->window_id, 0, 0,
window->width, window->height);
// FIXME we should update only the damaged area define as x,y,width,height
// at the moment it doesnt work
wl_surface_damage(window->surface, 0, 0, window->width, window->height);
wl_surface_commit(window->surface);
wl_display_dispatch_pending(window->wlconfig->display);
wl_display_flush(window->wlconfig->display);
}
// NSDebugLog(@"[CairoSurface handleExposeRect end]");
}
@end

6
Source/wayland/WaylandServer+Layershell.m Normal file → Executable file
View file

@ -40,9 +40,9 @@ layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
window->configured = YES;
if (window->buffer_needs_attach)
{
NSDebugLog(@"attach: win=%d layer", window->window_id);
wl_surface_attach(window->surface, window->buffer, 0, 0);
wl_surface_commit(window->surface);
[window->instance flushwindowrect:NSMakeRect(window->pos_x, window->pos_y,
window->width, window->height
):window->window_id];
}
}

9
Source/wayland/WaylandServer+Xdgshell.m Executable file → Normal file
View file

@ -56,9 +56,9 @@ xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface,
if (window->buffer_needs_attach)
{
NSDebugLog(@"attach: win=%d toplevel", window->window_id);
wl_surface_attach(window->surface, window->buffer, 0, 0);
wl_surface_commit(window->surface);
[window->instance flushwindowrect:NSMakeRect(window->pos_x, window->pos_y,
window->width, window->height
):window->window_id];
}
if (wlconfig->pointer.focus
@ -129,7 +129,8 @@ xdg_popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x,
struct window *window = data;
WaylandConfig *wlconfig = window->wlconfig;
NSDebugLog(@"xdg_popup_configure");
NSDebugLog(@"[%d] xdg_popup_configure [%d,%d %dx%d]", window->window_id, x, y,
width, height);
}
static void

View file

@ -487,6 +487,8 @@ WaylandServer (WindowOps)
if (window->toplevel)
{
xdg_toplevel_set_title(window->toplevel, cString);
wl_surface_commit(window->surface);
wl_display_flush(window->wlconfig->display);
}
}
@ -511,7 +513,11 @@ WaylandServer (WindowOps)
struct window *window;
window = get_window_with_id(wlconfig, winId);
// FIXME we could resize the current surface instead of creating a new one
if (window->wcs)
{
NSDebugLog(@"[%d] window has already a surface", winId);
}
GSSetDevice(ctxt, window, 0.0, window->height);
DPSinitmatrix(ctxt);
DPSinitclip(ctxt);
@ -970,7 +976,6 @@ WaylandServer (SurfaceRoles)
}
if (!parentwindow)
{
//[self createTopLevel: window];
return;
}
NSDebugLog(@"new popup: %d parent id: %d", win, parentwindow->window_id);
@ -991,7 +996,7 @@ WaylandServer (SurfaceRoles)
{
NSDebugLog(@"createPopupShell");
if (parent->xdg_surface == NULL || parent->toplevel == NULL)
if (parent->toplevel == NULL && parent->layer_surface == NULL)
{
NSDebugLog(@"parent surface %d is not toplevel", parent->window_id);
return;
@ -1025,12 +1030,14 @@ WaylandServer (SurfaceRoles)
xdg_positioner_set_offset(positioner, (child->pos_x - parent->pos_x),
(child->pos_y - parent->pos_y));
// NSDebugLog(@"xdg_positioner offset: %f,%f",
// (child->pos_x - parent->pos_x), (child->pos_y - parent->pos_y));
child->popup = xdg_surface_get_popup(child->xdg_surface, parent->xdg_surface,
positioner);
if (parent->layer_surface)
{
zwlr_layer_surface_v1_get_popup(parent->layer_surface, child->popup);
}
xdg_popup_add_listener(child->popup, &xdg_popup_listener, child);
xdg_surface_add_listener(child->xdg_surface, &xdg_surface_listener, child);
@ -1040,8 +1047,8 @@ WaylandServer (SurfaceRoles)
NSDebugLog(@"child_geometry : %f,%f %fx%f", child->pos_x - parent->pos_x,
child->pos_y - parent->pos_y, child->width, child->height);
wl_surface_commit(child->surface);
wl_display_dispatch_pending(wlconfig->display);
wl_display_flush(wlconfig->display);
wl_display_roundtrip(wlconfig->display);
xdg_positioner_destroy(positioner);
}
- (void)destroySurfaceRole:(struct window *)window
@ -1072,6 +1079,10 @@ WaylandServer (SurfaceRoles)
xdg_surface_destroy(window->xdg_surface);
window->xdg_surface = NULL;
}
if (window->wcs)
{
[window->wcs destroySurface];
}
window->configured = NO;
}