|
|
|
/*
|
Change remaining GPLv2 headers to MIT
The files in question are copyright Benjamin Franzke (who agrees),
Intel Corporation, Red Hat and myself. On behalf of Red Hat,
Richard Fontana says:
"Therefore, to the extent that Red Hat, Inc. has any copyright
interest in the files you cited as of this date (compositor-drm.c,
compositor.c, compositor.h, screenshooter.c in
http://cgit.freedesktop.org/wayland/wayland-demos/tree/compositor),
Red Hat hereby elects to apply the CC0 1.0 Universal Public Domain
Dedication to such copyrighted material. See:
http://creativecommons.org/publicdomain/zero/1.0/legalcode .
Thanks,
Richard E. Fontana
Open Source Licensing and Patent Counsel
Red Hat, Inc."
13 years ago
|
|
|
* Copyright © 2008-2011 Kristian Høgsberg
|
|
|
|
* Copyright © 2011 Intel Corporation
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice (including the
|
|
|
|
* next paragraph) shall be included in all copies or substantial
|
|
|
|
* portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
* SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <linux/vt.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
#include <xf86drm.h>
|
|
|
|
#include <xf86drmMode.h>
|
|
|
|
#include <drm_fourcc.h>
|
|
|
|
|
|
|
|
#include <gbm.h>
|
|
|
|
#include <libudev.h>
|
|
|
|
|
|
|
|
#include "compositor.h"
|
|
|
|
#include "compositor-drm.h"
|
|
|
|
#include "shared/helpers.h"
|
compositor-drm: Allow instant start of repaint loop. (v4)
drm_output_start_repaint_loop() incurred a delay of
one refresh cycle by using a no-op page-flip to get
an accurate vblank timestamp as reference. This causes
unwanted lag whenever Weston exited its repaint loop, e.g.,
whenever an application wants to repaint with less than
full video refresh rate but still minimum lag.
Try to use the drmWaitVblank ioctl to get a proper
timestamp instantaneously without lag. If that does
not work, fall back to the old method of idle page-flip.
This optimization will work on any drm/kms driver
which supports high precision vblank timestamping.
As of Linux 4.0 these would be intel, radeon and
nouveau on all their supported gpu's.
On kms drivers without instant high precision timestamping
support, the kernel is supposed to return a timestamp
of zero when calling drmWaitVblank() to query the current
vblank count and time iff vblank irqs are currently
disabled, because the only way to get a valid timestamp
on such kms drivers is to enable vblank interrupts and
then wait a bit for the next vblank irq to take a new valid
timestamp. The caller is supposed to poll until at next
vblank irq it gets a valid non-zero timestamp if it needs
a timestamp.
This zero-timestamp signalling works up to Linux 3.17, but
got broken due to a regression in Linux 3.18 and later. On
Linux 3.18+ with kms drivers that don't have high precision
timestamping, the kernel erroneously returns a stale timestamp
from an earlier vblank, ie. the vblank count and timestamp are
mismatched. A patch is under way to fix this, but to deal with
broken kernels, we also check non-zero timestamps if they are
more than one refresh duration in the past, as this indicates
a stale/invalid timestamp, so we need to take the page-flip
fallback for restarting the repaint loop.
v2: Implement review suggestions by Pekka Paalanen, especially
extend the commit message to describe when and why the
instant restart won't work due to missing Linux kernel
functionality or a Linux kernel regression.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
v3: Fix timespec_to_nsec() which was computing picoseconds,
use the new timespec-util.h helpers.
v4: Rebased to master, split long lines.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
10 years ago
|
|
|
#include "shared/timespec-util.h"
|
|
|
|
#include "gl-renderer.h"
|
|
|
|
#include "weston-egl-ext.h"
|
|
|
|
#include "pixman-renderer.h"
|
|
|
|
#include "pixel-formats.h"
|
|
|
|
#include "libbacklight.h"
|
|
|
|
#include "libinput-seat.h"
|
|
|
|
#include "launcher-util.h"
|
|
|
|
#include "vaapi-recorder.h"
|
|
|
|
#include "presentation-time-server-protocol.h"
|
|
|
|
#include "linux-dmabuf.h"
|
|
|
|
#include "linux-dmabuf-unstable-v1-server-protocol.h"
|
|
|
|
|
|
|
|
#ifndef DRM_CAP_TIMESTAMP_MONOTONIC
|
|
|
|
#define DRM_CAP_TIMESTAMP_MONOTONIC 0x6
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef DRM_CLIENT_CAP_UNIVERSAL_PLANES
|
|
|
|
#define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef DRM_CAP_CURSOR_WIDTH
|
|
|
|
#define DRM_CAP_CURSOR_WIDTH 0x8
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef DRM_CAP_CURSOR_HEIGHT
|
|
|
|
#define DRM_CAP_CURSOR_HEIGHT 0x9
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef GBM_BO_USE_CURSOR
|
|
|
|
#define GBM_BO_USE_CURSOR GBM_BO_USE_CURSOR_64X64
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of properties attached to DRM planes
|
|
|
|
*/
|
|
|
|
enum wdrm_plane_property {
|
|
|
|
WDRM_PLANE_TYPE = 0,
|
|
|
|
WDRM_PLANE__COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Possible values for the WDRM_PLANE_TYPE property.
|
|
|
|
*/
|
|
|
|
enum wdrm_plane_type {
|
|
|
|
WDRM_PLANE_TYPE_PRIMARY = 0,
|
|
|
|
WDRM_PLANE_TYPE_CURSOR,
|
|
|
|
WDRM_PLANE_TYPE_OVERLAY,
|
|
|
|
WDRM_PLANE_TYPE__COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of properties attached to a DRM connector
|
|
|
|
*/
|
|
|
|
enum wdrm_connector_property {
|
|
|
|
WDRM_CONNECTOR_EDID = 0,
|
|
|
|
WDRM_CONNECTOR_DPMS,
|
|
|
|
WDRM_CONNECTOR__COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents the values of an enum-type KMS property
|
|
|
|
*/
|
|
|
|
struct drm_property_enum_info {
|
|
|
|
const char *name; /**< name as string (static, not freed) */
|
|
|
|
bool valid; /**< true if value is supported; ignore if false */
|
|
|
|
uint64_t value; /**< raw value */
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds information on a DRM property, including its ID and the enum
|
|
|
|
* values it holds.
|
|
|
|
*
|
|
|
|
* DRM properties are allocated dynamically, and maintained as DRM objects
|
|
|
|
* within the normal object ID space; they thus do not have a stable ID
|
|
|
|
* to refer to. This includes enum values, which must be referred to by
|
|
|
|
* integer values, but these are not stable.
|
|
|
|
*
|
|
|
|
* drm_property_info allows a cache to be maintained where Weston can use
|
|
|
|
* enum values internally to refer to properties, with the mapping to DRM
|
|
|
|
* ID values being maintained internally.
|
|
|
|
*/
|
|
|
|
struct drm_property_info {
|
|
|
|
const char *name; /**< name as string (static, not freed) */
|
|
|
|
uint32_t prop_id; /**< KMS property object ID */
|
|
|
|
unsigned int num_enum_values; /**< number of enum values */
|
|
|
|
struct drm_property_enum_info *enum_values; /**< array of enum values */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_backend {
|
|
|
|
struct weston_backend base;
|
|
|
|
struct weston_compositor *compositor;
|
|
|
|
|
|
|
|
struct udev *udev;
|
|
|
|
struct wl_event_source *drm_source;
|
|
|
|
|
|
|
|
struct udev_monitor *udev_monitor;
|
|
|
|
struct wl_event_source *udev_drm_source;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
int id;
|
|
|
|
int fd;
|
|
|
|
char *filename;
|
|
|
|
} drm;
|
|
|
|
struct gbm_device *gbm;
|
|
|
|
struct wl_listener session_listener;
|
|
|
|
uint32_t gbm_format;
|
|
|
|
|
|
|
|
/* we need these parameters in order to not fail drmModeAddFB2()
|
|
|
|
* due to out of bounds dimensions, and then mistakenly set
|
|
|
|
* sprites_are_broken:
|
|
|
|
*/
|
|
|
|
int min_width, max_width;
|
|
|
|
int min_height, max_height;
|
|
|
|
int no_addfb2;
|
|
|
|
|
|
|
|
struct wl_list plane_list;
|
|
|
|
int sprites_are_broken;
|
|
|
|
int sprites_hidden;
|
|
|
|
|
|
|
|
void *repaint_data;
|
|
|
|
|
|
|
|
int cursors_are_broken;
|
|
|
|
|
|
|
|
bool universal_planes;
|
|
|
|
|
|
|
|
int use_pixman;
|
|
|
|
|
|
|
|
struct udev_input input;
|
|
|
|
|
|
|
|
int32_t cursor_width;
|
|
|
|
int32_t cursor_height;
|
|
|
|
|
|
|
|
uint32_t pageflip_timeout;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_mode {
|
Rename wayland-compositor to weston
This rename addresses a few problems around the split between core
Wayland and the wayland-demos repository.
1) Initially, we had one big repository with protocol code, sample
compositor and sample clients. We split that repository to make it
possible to implement the protocol without pulling in the sample/demo
code. At this point, the compositor is more than just a "demo" and
wayland-demos doesn't send the right message. The sample compositor
is a useful, self-contained project in it's own right, and we want to
move away from the "demos" label.
2) Another problem is that the wayland-demos compositor is often
called "the wayland compsitor", but it's really just one possible
compositor. Existing X11 compositors are expected to add Wayland
support and then gradually phase out/modularize the X11 support, for
example. Conversely, it's hard to talk about the wayland-demos
compositor specifically as opposed to, eg, the wayland protocol or a
wayland compositor in general.
We are also renaming the repo to weston, and the compositor
subdirectory to src/, to emphasize that the main "output" is the
compositor.
13 years ago
|
|
|
struct weston_mode base;
|
|
|
|
drmModeModeInfo mode_info;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum drm_fb_type {
|
|
|
|
BUFFER_INVALID = 0, /**< never used */
|
|
|
|
BUFFER_CLIENT, /**< directly sourced from client */
|
|
|
|
BUFFER_PIXMAN_DUMB, /**< internal Pixman rendering */
|
|
|
|
BUFFER_GBM_SURFACE, /**< internal EGL rendering */
|
|
|
|
BUFFER_CURSOR, /**< internal cursor buffer */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_fb {
|
|
|
|
enum drm_fb_type type;
|
|
|
|
|
|
|
|
int refcnt;
|
|
|
|
|
|
|
|
uint32_t fb_id, stride, handle, size;
|
|
|
|
const struct pixel_format_info *format;
|
|
|
|
int width, height;
|
|
|
|
int fd;
|
compositor: introduce weston_buffer_reference
The wl_buffer reference counting API has been inconsistent. You would
manually increment the refcount and register a destroy listener, as
opposed to calling weston_buffer_post_release(), which internally
decremented the refcount, and then removing a list item.
Replace both cases with a single function:
weston_buffer_reference(weston_buffer_reference *ref, wl_buffer *buffer)
Buffer is assigned to ref->buffer, while taking care of all the refcounting
and release posting. You take a reference by passing a non-NULL buffer, and
release a reference by passing NULL as buffer. The function uses an
internal wl_buffer destroy listener, so the pointer gets reset on
destruction automatically.
This is inspired by the pipe_resource_reference() of Mesa, and modified
by krh's suggestion to add struct weston_buffer_reference.
Additionally, when a surface gets destroyed, the associated wl_buffer
will send a release event. Often the buffer is already destroyed on
client side, so the event will be discarded by libwayland-client.
Compositor-drm.c is converted to use weston_buffer_reference.
Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
|
|
|
struct weston_buffer_reference buffer_ref;
|
|
|
|
|
|
|
|
/* Used by gbm fbs */
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
struct gbm_surface *gbm_surface;
|
|
|
|
|
|
|
|
/* Used by dumb fbs */
|
|
|
|
void *map;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_edid {
|
|
|
|
char eisa_id[13];
|
|
|
|
char monitor_name[13];
|
|
|
|
char pnp_id[5];
|
|
|
|
char serial_number[13];
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pending state holds one or more drm_output_state structures, collected from
|
|
|
|
* performing repaint. This pending state is transient, and only lives between
|
|
|
|
* beginning a repaint group and flushing the results: after flush, each
|
|
|
|
* output state will complete and be retired separately.
|
|
|
|
*/
|
|
|
|
struct drm_pending_state {
|
|
|
|
struct drm_backend *backend;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A plane represents one buffer, positioned within a CRTC, and stacked
|
|
|
|
* relative to other planes on the same CRTC.
|
|
|
|
*
|
|
|
|
* Each CRTC has a 'primary plane', which use used to display the classic
|
|
|
|
* framebuffer contents, as accessed through the legacy drmModeSetCrtc
|
|
|
|
* call (which combines setting the CRTC's actual physical mode, and the
|
|
|
|
* properties of the primary plane).
|
|
|
|
*
|
|
|
|
* The cursor plane also has its own alternate legacy API.
|
|
|
|
*
|
|
|
|
* Other planes are used opportunistically to display content we do not
|
|
|
|
* wish to blit into the primary plane. These non-primary/cursor planes
|
|
|
|
* are referred to as 'sprites'.
|
|
|
|
*/
|
|
|
|
struct drm_plane {
|
|
|
|
struct wl_list link;
|
|
|
|
|
|
|
|
struct weston_plane base;
|
|
|
|
|
|
|
|
struct drm_output *output;
|
|
|
|
struct drm_backend *backend;
|
|
|
|
|
|
|
|
enum wdrm_plane_type type;
|
|
|
|
|
|
|
|
uint32_t possible_crtcs;
|
|
|
|
uint32_t plane_id;
|
|
|
|
uint32_t count_formats;
|
|
|
|
|
|
|
|
struct drm_property_info props[WDRM_PLANE__COUNT];
|
|
|
|
|
|
|
|
/* The last framebuffer submitted to the kernel for this plane. */
|
|
|
|
struct drm_fb *fb_current;
|
|
|
|
/* The previously-submitted framebuffer, where the hardware has not
|
|
|
|
* yet acknowledged display of fb_current. */
|
|
|
|
struct drm_fb *fb_last;
|
|
|
|
/* Framebuffer we are going to submit to the kernel when the current
|
|
|
|
* repaint is flushed. */
|
|
|
|
struct drm_fb *fb_pending;
|
|
|
|
|
|
|
|
int32_t src_x, src_y;
|
|
|
|
uint32_t src_w, src_h;
|
|
|
|
uint32_t dest_x, dest_y;
|
|
|
|
uint32_t dest_w, dest_h;
|
|
|
|
|
|
|
|
uint32_t formats[];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct drm_output {
|
|
|
|
struct weston_output base;
|
|
|
|
drmModeConnector *connector;
|
|
|
|
|
|
|
|
uint32_t crtc_id; /* object ID to pass to DRM functions */
|
|
|
|
int pipe; /* index of CRTC in resource array / bitmasks */
|
|
|
|
uint32_t connector_id;
|
|
|
|
drmModeCrtcPtr original_crtc;
|
|
|
|
struct drm_edid edid;
|
|
|
|
|
|
|
|
/* Holds the properties for the connector */
|
|
|
|
struct drm_property_info props_conn[WDRM_CONNECTOR__COUNT];
|
|
|
|
|
|
|
|
enum dpms_enum dpms;
|
|
|
|
struct backlight *backlight;
|
|
|
|
|
|
|
|
bool state_invalid;
|
|
|
|
|
|
|
|
int vblank_pending;
|
|
|
|
int page_flip_pending;
|
|
|
|
int destroy_pending;
|
|
|
|
int disable_pending;
|
|
|
|
|
|
|
|
struct drm_fb *gbm_cursor_fb[2];
|
|
|
|
struct weston_plane cursor_plane;
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *cursor_view;
|
|
|
|
int current_cursor;
|
|
|
|
|
|
|
|
struct gbm_surface *gbm_surface;
|
|
|
|
uint32_t gbm_format;
|
|
|
|
|
|
|
|
/* Plane for a fullscreen direct scanout view */
|
|
|
|
struct weston_plane scanout_plane;
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
|
|
|
|
/* The last framebuffer submitted to the kernel for this CRTC. */
|
|
|
|
struct drm_fb *fb_current;
|
|
|
|
/* The previously-submitted framebuffer, where the hardware has not
|
|
|
|
* yet acknowledged display of fb_current. */
|
|
|
|
struct drm_fb *fb_last;
|
|
|
|
/* Framebuffer we are going to submit to the kernel when the current
|
|
|
|
* repaint is flushed. */
|
|
|
|
struct drm_fb *fb_pending;
|
|
|
|
|
|
|
|
struct drm_fb *dumb[2];
|
|
|
|
pixman_image_t *image[2];
|
|
|
|
int current_image;
|
|
|
|
pixman_region32_t previous_damage;
|
|
|
|
|
|
|
|
struct vaapi_recorder *recorder;
|
|
|
|
struct wl_listener recorder_frame_listener;
|
|
|
|
|
|
|
|
struct wl_event_source *pageflip_timer;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct gl_renderer_interface *gl_renderer;
|
|
|
|
|
|
|
|
static const char default_seat[] = "seat0";
|
|
|
|
|
|
|
|
static inline struct drm_output *
|
|
|
|
to_drm_output(struct weston_output *base)
|
|
|
|
{
|
|
|
|
return container_of(base, struct drm_output, base);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct drm_backend *
|
|
|
|
to_drm_backend(struct weston_compositor *base)
|
|
|
|
{
|
|
|
|
return container_of(base->backend, struct drm_backend, base);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pageflip_timeout(void *data) {
|
|
|
|
/*
|
|
|
|
* Our timer just went off, that means we're not receiving drm
|
|
|
|
* page flip events anymore for that output. Let's gracefully exit
|
|
|
|
* weston with a return value so devs can debug what's going on.
|
|
|
|
*/
|
|
|
|
struct drm_output *output = data;
|
|
|
|
struct weston_compositor *compositor = output->base.compositor;
|
|
|
|
|
|
|
|
weston_log("Pageflip timeout reached on output %s, your "
|
|
|
|
"driver is probably buggy! Exiting.\n",
|
|
|
|
output->base.name);
|
|
|
|
weston_compositor_exit_with_code(compositor, EXIT_FAILURE);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Creates the pageflip timer. Note that it isn't armed by default */
|
|
|
|
static int
|
|
|
|
drm_output_pageflip_timer_create(struct drm_output *output)
|
|
|
|
{
|
|
|
|
struct wl_event_loop *loop = NULL;
|
|
|
|
struct weston_compositor *ec = output->base.compositor;
|
|
|
|
|
|
|
|
loop = wl_display_get_event_loop(ec->wl_display);
|
|
|
|
assert(loop);
|
|
|
|
output->pageflip_timer = wl_event_loop_add_timer(loop,
|
|
|
|
pageflip_timeout,
|
|
|
|
output);
|
|
|
|
|
|
|
|
if (output->pageflip_timer == NULL) {
|
|
|
|
weston_log("creating drm pageflip timer failed: %m\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current value of a KMS property
|
|
|
|
*
|
|
|
|
* Given a drmModeObjectGetProperties return, as well as the drm_property_info
|
|
|
|
* for the target property, return the current value of that property,
|
|
|
|
* with an optional default. If the property is a KMS enum type, the return
|
|
|
|
* value will be translated into the appropriate internal enum.
|
|
|
|
*
|
|
|
|
* If the property is not present, the default value will be returned.
|
|
|
|
*
|
|
|
|
* @param info Internal structure for property to look up
|
|
|
|
* @param props Raw KMS properties for the target object
|
|
|
|
* @param def Value to return if property is not found
|
|
|
|
*/
|
|
|
|
static uint64_t
|
|
|
|
drm_property_get_value(struct drm_property_info *info,
|
|
|
|
drmModeObjectPropertiesPtr props,
|
|
|
|
uint64_t def)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if (info->prop_id == 0)
|
|
|
|
return def;
|
|
|
|
|
|
|
|
for (i = 0; i < props->count_props; i++) {
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
if (props->props[i] != info->prop_id)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Simple (non-enum) types can return the value directly */
|
|
|
|
if (info->num_enum_values == 0)
|
|
|
|
return props->prop_values[i];
|
|
|
|
|
|
|
|
/* Map from raw value to enum value */
|
|
|
|
for (j = 0; j < info->num_enum_values; j++) {
|
|
|
|
if (!info->enum_values[j].valid)
|
|
|
|
continue;
|
|
|
|
if (info->enum_values[j].value != props->prop_values[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return j;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We don't have a mapping for this enum; return default. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cache DRM property values
|
|
|
|
*
|
|
|
|
* Update a per-object array of drm_property_info structures, given the
|
|
|
|
* DRM properties of the object.
|
|
|
|
*
|
|
|
|
* Call this every time an object newly appears (note that only connectors
|
|
|
|
* can be hotplugged), the first time it is seen, or when its status changes
|
|
|
|
* in a way which invalidates the potential property values (currently, the
|
|
|
|
* only case for this is connector hotplug).
|
|
|
|
*
|
|
|
|
* This updates the property IDs and enum values within the drm_property_info
|
|
|
|
* array.
|
|
|
|
*
|
|
|
|
* DRM property enum values are dynamic at runtime; the user must query the
|
|
|
|
* property to find out the desired runtime value for a requested string
|
|
|
|
* name. Using the 'type' field on planes as an example, there is no single
|
|
|
|
* hardcoded constant for primary plane types; instead, the property must be
|
|
|
|
* queried at runtime to find the value associated with the string "Primary".
|
|
|
|
*
|
|
|
|
* This helper queries and caches the enum values, to allow us to use a set
|
|
|
|
* of compile-time-constant enums portably across various implementations.
|
|
|
|
* The values given in enum_names are searched for, and stored in the
|
|
|
|
* same-indexed field of the map array.
|
|
|
|
*
|
|
|
|
* @param b DRM backend object
|
|
|
|
* @param src DRM property info array to source from
|
|
|
|
* @param info DRM property info array to copy into
|
|
|
|
* @param num_infos Number of entries in the source array
|
|
|
|
* @param props DRM object properties for the object
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_property_info_populate(struct drm_backend *b,
|
|
|
|
const struct drm_property_info *src,
|
|
|
|
struct drm_property_info *info,
|
|
|
|
unsigned int num_infos,
|
|
|
|
drmModeObjectProperties *props)
|
|
|
|
{
|
|
|
|
drmModePropertyRes *prop;
|
|
|
|
unsigned i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < num_infos; i++) {
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
info[i].name = src[i].name;
|
|
|
|
info[i].prop_id = 0;
|
|
|
|
info[i].num_enum_values = src[i].num_enum_values;
|
|
|
|
|
|
|
|
if (src[i].num_enum_values == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
info[i].enum_values =
|
|
|
|
malloc(src[i].num_enum_values *
|
|
|
|
sizeof(*info[i].enum_values));
|
|
|
|
assert(info[i].enum_values);
|
|
|
|
for (j = 0; j < info[i].num_enum_values; j++) {
|
|
|
|
info[i].enum_values[j].name = src[i].enum_values[j].name;
|
|
|
|
info[i].enum_values[j].valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < props->count_props; i++) {
|
|
|
|
unsigned int k;
|
|
|
|
|
|
|
|
prop = drmModeGetProperty(b->drm.fd, props->props[i]);
|
|
|
|
if (!prop)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (j = 0; j < num_infos; j++) {
|
|
|
|
if (!strcmp(prop->name, info[j].name))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We don't know/care about this property. */
|
|
|
|
if (j == num_infos) {
|
|
|
|
#ifdef DEBUG
|
|
|
|
weston_log("DRM debug: unrecognized property %u '%s'\n",
|
|
|
|
prop->prop_id, prop->name);
|
|
|
|
#endif
|
|
|
|
drmModeFreeProperty(prop);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info[j].num_enum_values == 0 &&
|
|
|
|
(prop->flags & DRM_MODE_PROP_ENUM)) {
|
|
|
|
weston_log("DRM: expected property %s to not be an"
|
|
|
|
" enum, but it is; ignoring\n", prop->name);
|
|
|
|
drmModeFreeProperty(prop);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
info[j].prop_id = props->props[i];
|
|
|
|
|
|
|
|
if (info[j].num_enum_values == 0) {
|
|
|
|
drmModeFreeProperty(prop);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(prop->flags & DRM_MODE_PROP_ENUM)) {
|
|
|
|
weston_log("DRM: expected property %s to be an enum,"
|
|
|
|
" but it is not; ignoring\n", prop->name);
|
|
|
|
drmModeFreeProperty(prop);
|
|
|
|
info[j].prop_id = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (k = 0; k < info[j].num_enum_values; k++) {
|
|
|
|
int l;
|
|
|
|
|
|
|
|
for (l = 0; l < prop->count_enums; l++) {
|
|
|
|
if (!strcmp(prop->enums[l].name,
|
|
|
|
info[j].enum_values[k].name))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (l == prop->count_enums)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
info[j].enum_values[k].valid = true;
|
|
|
|
info[j].enum_values[k].value = prop->enums[l].value;
|
|
|
|
}
|
|
|
|
|
|
|
|
drmModeFreeProperty(prop);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
for (i = 0; i < num_infos; i++) {
|
|
|
|
if (info[i].prop_id == 0)
|
|
|
|
weston_log("DRM warning: property '%s' missing\n",
|
|
|
|
info[i].name);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Free DRM property information
|
|
|
|
*
|
|
|
|
* Frees all memory associated with a DRM property info array.
|
|
|
|
*
|
|
|
|
* @param info DRM property info array
|
|
|
|
* @param num_props Number of entries in array to free
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_property_info_free(struct drm_property_info *info, int num_props)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_props; i++)
|
|
|
|
free(info[i].enum_values);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_set_cursor(struct drm_output *output);
|
|
|
|
|
compositor-drm: Allow instant start of repaint loop. (v4)
drm_output_start_repaint_loop() incurred a delay of
one refresh cycle by using a no-op page-flip to get
an accurate vblank timestamp as reference. This causes
unwanted lag whenever Weston exited its repaint loop, e.g.,
whenever an application wants to repaint with less than
full video refresh rate but still minimum lag.
Try to use the drmWaitVblank ioctl to get a proper
timestamp instantaneously without lag. If that does
not work, fall back to the old method of idle page-flip.
This optimization will work on any drm/kms driver
which supports high precision vblank timestamping.
As of Linux 4.0 these would be intel, radeon and
nouveau on all their supported gpu's.
On kms drivers without instant high precision timestamping
support, the kernel is supposed to return a timestamp
of zero when calling drmWaitVblank() to query the current
vblank count and time iff vblank irqs are currently
disabled, because the only way to get a valid timestamp
on such kms drivers is to enable vblank interrupts and
then wait a bit for the next vblank irq to take a new valid
timestamp. The caller is supposed to poll until at next
vblank irq it gets a valid non-zero timestamp if it needs
a timestamp.
This zero-timestamp signalling works up to Linux 3.17, but
got broken due to a regression in Linux 3.18 and later. On
Linux 3.18+ with kms drivers that don't have high precision
timestamping, the kernel erroneously returns a stale timestamp
from an earlier vblank, ie. the vblank count and timestamp are
mismatched. A patch is under way to fix this, but to deal with
broken kernels, we also check non-zero timestamps if they are
more than one refresh duration in the past, as this indicates
a stale/invalid timestamp, so we need to take the page-flip
fallback for restarting the repaint loop.
v2: Implement review suggestions by Pekka Paalanen, especially
extend the commit message to describe when and why the
instant restart won't work due to missing Linux kernel
functionality or a Linux kernel regression.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
v3: Fix timespec_to_nsec() which was computing picoseconds,
use the new timespec-util.h helpers.
v4: Rebased to master, split long lines.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
10 years ago
|
|
|
static void
|
|
|
|
drm_output_update_msc(struct drm_output *output, unsigned int seq);
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_plane_crtc_supported(struct drm_output *output, struct drm_plane *plane)
|
|
|
|
{
|
|
|
|
return !!(plane->possible_crtcs & (1 << output->pipe));
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_output *
|
|
|
|
drm_output_find_by_crtc(struct drm_backend *b, uint32_t crtc_id)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->output_list, base.link) {
|
|
|
|
if (output->crtc_id == crtc_id)
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->pending_output_list,
|
|
|
|
base.link) {
|
|
|
|
if (output->crtc_id == crtc_id)
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_output *
|
|
|
|
drm_output_find_by_connector(struct drm_backend *b, uint32_t connector_id)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->output_list, base.link) {
|
|
|
|
if (output->connector_id == connector_id)
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->pending_output_list,
|
|
|
|
base.link) {
|
|
|
|
if (output->connector_id == connector_id)
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_fb_destroy(struct drm_fb *fb)
|
|
|
|
{
|
|
|
|
if (fb->fb_id != 0)
|
|
|
|
drmModeRmFB(fb->fd, fb->fb_id);
|
compositor: introduce weston_buffer_reference
The wl_buffer reference counting API has been inconsistent. You would
manually increment the refcount and register a destroy listener, as
opposed to calling weston_buffer_post_release(), which internally
decremented the refcount, and then removing a list item.
Replace both cases with a single function:
weston_buffer_reference(weston_buffer_reference *ref, wl_buffer *buffer)
Buffer is assigned to ref->buffer, while taking care of all the refcounting
and release posting. You take a reference by passing a non-NULL buffer, and
release a reference by passing NULL as buffer. The function uses an
internal wl_buffer destroy listener, so the pointer gets reset on
destruction automatically.
This is inspired by the pipe_resource_reference() of Mesa, and modified
by krh's suggestion to add struct weston_buffer_reference.
Additionally, when a surface gets destroyed, the associated wl_buffer
will send a release event. Often the buffer is already destroyed on
client side, so the event will be discarded by libwayland-client.
Compositor-drm.c is converted to use weston_buffer_reference.
Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
|
|
|
weston_buffer_reference(&fb->buffer_ref, NULL);
|
|
|
|
free(fb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_fb_destroy_dumb(struct drm_fb *fb)
|
|
|
|
{
|
|
|
|
struct drm_mode_destroy_dumb destroy_arg;
|
|
|
|
|
|
|
|
assert(fb->type == BUFFER_PIXMAN_DUMB);
|
|
|
|
|
|
|
|
if (fb->map && fb->size > 0)
|
|
|
|
munmap(fb->map, fb->size);
|
|
|
|
|
|
|
|
memset(&destroy_arg, 0, sizeof(destroy_arg));
|
|
|
|
destroy_arg.handle = fb->handle;
|
|
|
|
drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg);
|
|
|
|
|
|
|
|
drm_fb_destroy(fb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_fb_destroy_gbm(struct gbm_bo *bo, void *data)
|
|
|
|
{
|
|
|
|
struct drm_fb *fb = data;
|
|
|
|
|
|
|
|
assert(fb->type == BUFFER_GBM_SURFACE || fb->type == BUFFER_CLIENT ||
|
|
|
|
fb->type == BUFFER_CURSOR);
|
|
|
|
drm_fb_destroy(fb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_fb *
|
|
|
|
drm_fb_create_dumb(struct drm_backend *b, int width, int height,
|
|
|
|
uint32_t format)
|
|
|
|
{
|
|
|
|
struct drm_fb *fb;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
struct drm_mode_create_dumb create_arg;
|
|
|
|
struct drm_mode_destroy_dumb destroy_arg;
|
|
|
|
struct drm_mode_map_dumb map_arg;
|
|
|
|
|
|
|
|
fb = zalloc(sizeof *fb);
|
|
|
|
if (!fb)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
fb->refcnt = 1;
|
|
|
|
|
|
|
|
fb->format = pixel_format_get_info(format);
|
|
|
|
if (!fb->format) {
|
|
|
|
weston_log("failed to look up format 0x%lx\n",
|
|
|
|
(unsigned long) format);
|
|
|
|
goto err_fb;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fb->format->depth || !fb->format->bpp) {
|
|
|
|
weston_log("format 0x%lx is not compatible with dumb buffers\n",
|
|
|
|
(unsigned long) format);
|
|
|
|
goto err_fb;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&create_arg, 0, sizeof create_arg);
|
|
|
|
create_arg.bpp = fb->format->bpp;
|
|
|
|
create_arg.width = width;
|
|
|
|
create_arg.height = height;
|
|
|
|
|
|
|
|
ret = drmIoctl(b->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg);
|
|
|
|
if (ret)
|
|
|
|
goto err_fb;
|
|
|
|
|
|
|
|
fb->type = BUFFER_PIXMAN_DUMB;
|
|
|
|
fb->handle = create_arg.handle;
|
|
|
|
fb->stride = create_arg.pitch;
|
|
|
|
fb->size = create_arg.size;
|
|
|
|
fb->width = width;
|
|
|
|
fb->height = height;
|
|
|
|
fb->fd = b->drm.fd;
|
|
|
|
|
|
|
|
ret = -1;
|
|
|
|
|
|
|
|
if (!b->no_addfb2) {
|
|
|
|
uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 };
|
|
|
|
|
|
|
|
handles[0] = fb->handle;
|
|
|
|
pitches[0] = fb->stride;
|
|
|
|
offsets[0] = 0;
|
|
|
|
|
|
|
|
ret = drmModeAddFB2(b->drm.fd, width, height,
|
|
|
|
fb->format->format,
|
|
|
|
handles, pitches, offsets,
|
|
|
|
&fb->fb_id, 0);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("addfb2 failed: %m\n");
|
|
|
|
b->no_addfb2 = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
ret = drmModeAddFB(b->drm.fd, width, height,
|
|
|
|
fb->format->depth, fb->format->bpp,
|
|
|
|
fb->stride, fb->handle, &fb->fb_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
goto err_bo;
|
|
|
|
|
|
|
|
memset(&map_arg, 0, sizeof map_arg);
|
|
|
|
map_arg.handle = fb->handle;
|
|
|
|
ret = drmIoctl(fb->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg);
|
|
|
|
if (ret)
|
|
|
|
goto err_add_fb;
|
|
|
|
|
|
|
|
fb->map = mmap(NULL, fb->size, PROT_WRITE,
|
|
|
|
MAP_SHARED, b->drm.fd, map_arg.offset);
|
|
|
|
if (fb->map == MAP_FAILED)
|
|
|
|
goto err_add_fb;
|
|
|
|
|
|
|
|
return fb;
|
|
|
|
|
|
|
|
err_add_fb:
|
|
|
|
drmModeRmFB(b->drm.fd, fb->fb_id);
|
|
|
|
err_bo:
|
|
|
|
memset(&destroy_arg, 0, sizeof(destroy_arg));
|
|
|
|
destroy_arg.handle = create_arg.handle;
|
|
|
|
drmIoctl(b->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg);
|
|
|
|
err_fb:
|
|
|
|
free(fb);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_fb *
|
|
|
|
drm_fb_ref(struct drm_fb *fb)
|
|
|
|
{
|
|
|
|
fb->refcnt++;
|
|
|
|
return fb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_fb *
|
|
|
|
drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend,
|
|
|
|
uint32_t format, enum drm_fb_type type)
|
|
|
|
{
|
|
|
|
struct drm_fb *fb = gbm_bo_get_user_data(bo);
|
|
|
|
uint32_t handles[4] = { 0 }, pitches[4] = { 0 }, offsets[4] = { 0 };
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (fb) {
|
|
|
|
assert(fb->type == type);
|
|
|
|
return drm_fb_ref(fb);
|
|
|
|
}
|
|
|
|
|
|
|
|
fb = zalloc(sizeof *fb);
|
|
|
|
if (fb == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
fb->type = type;
|
|
|
|
fb->refcnt = 1;
|
|
|
|
fb->bo = bo;
|
|
|
|
|
|
|
|
fb->width = gbm_bo_get_width(bo);
|
|
|
|
fb->height = gbm_bo_get_height(bo);
|
|
|
|
fb->stride = gbm_bo_get_stride(bo);
|
|
|
|
fb->handle = gbm_bo_get_handle(bo).u32;
|
|
|
|
fb->format = pixel_format_get_info(format);
|
|
|
|
fb->size = fb->stride * fb->height;
|
|
|
|
fb->fd = backend->drm.fd;
|
|
|
|
|
|
|
|
if (!fb->format) {
|
|
|
|
weston_log("couldn't look up format 0x%lx\n",
|
|
|
|
(unsigned long) format);
|
|
|
|
goto err_free;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (backend->min_width > fb->width ||
|
|
|
|
fb->width > backend->max_width ||
|
|
|
|
backend->min_height > fb->height ||
|
|
|
|
fb->height > backend->max_height) {
|
|
|
|
weston_log("bo geometry out of bounds\n");
|
|
|
|
goto err_free;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = -1;
|
|
|
|
|
|
|
|
if (format && !backend->no_addfb2) {
|
|
|
|
handles[0] = fb->handle;
|
|
|
|
pitches[0] = fb->stride;
|
|
|
|
offsets[0] = 0;
|
|
|
|
|
|
|
|
ret = drmModeAddFB2(backend->drm.fd, fb->width, fb->height,
|
|
|
|
format, handles, pitches, offsets,
|
|
|
|
&fb->fb_id, 0);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("addfb2 failed: %m\n");
|
|
|
|
backend->no_addfb2 = 1;
|
|
|
|
backend->sprites_are_broken = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret && fb->format->depth && fb->format->bpp)
|
|
|
|
ret = drmModeAddFB(backend->drm.fd, fb->width, fb->height,
|
|
|
|
fb->format->depth, fb->format->bpp,
|
|
|
|
fb->stride, fb->handle, &fb->fb_id);
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
weston_log("failed to create kms fb: %m\n");
|
|
|
|
goto err_free;
|
|
|
|
}
|
|
|
|
|
|
|
|
gbm_bo_set_user_data(bo, fb, drm_fb_destroy_gbm);
|
|
|
|
|
|
|
|
return fb;
|
|
|
|
|
|
|
|
err_free:
|
|
|
|
free(fb);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer)
|
|
|
|
{
|
compositor: introduce weston_buffer_reference
The wl_buffer reference counting API has been inconsistent. You would
manually increment the refcount and register a destroy listener, as
opposed to calling weston_buffer_post_release(), which internally
decremented the refcount, and then removing a list item.
Replace both cases with a single function:
weston_buffer_reference(weston_buffer_reference *ref, wl_buffer *buffer)
Buffer is assigned to ref->buffer, while taking care of all the refcounting
and release posting. You take a reference by passing a non-NULL buffer, and
release a reference by passing NULL as buffer. The function uses an
internal wl_buffer destroy listener, so the pointer gets reset on
destruction automatically.
This is inspired by the pipe_resource_reference() of Mesa, and modified
by krh's suggestion to add struct weston_buffer_reference.
Additionally, when a surface gets destroyed, the associated wl_buffer
will send a release event. Often the buffer is already destroyed on
client side, so the event will be discarded by libwayland-client.
Compositor-drm.c is converted to use weston_buffer_reference.
Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
|
|
|
assert(fb->buffer_ref.buffer == NULL);
|
|
|
|
assert(fb->type == BUFFER_CLIENT);
|
compositor: introduce weston_buffer_reference
The wl_buffer reference counting API has been inconsistent. You would
manually increment the refcount and register a destroy listener, as
opposed to calling weston_buffer_post_release(), which internally
decremented the refcount, and then removing a list item.
Replace both cases with a single function:
weston_buffer_reference(weston_buffer_reference *ref, wl_buffer *buffer)
Buffer is assigned to ref->buffer, while taking care of all the refcounting
and release posting. You take a reference by passing a non-NULL buffer, and
release a reference by passing NULL as buffer. The function uses an
internal wl_buffer destroy listener, so the pointer gets reset on
destruction automatically.
This is inspired by the pipe_resource_reference() of Mesa, and modified
by krh's suggestion to add struct weston_buffer_reference.
Additionally, when a surface gets destroyed, the associated wl_buffer
will send a release event. Often the buffer is already destroyed on
client side, so the event will be discarded by libwayland-client.
Compositor-drm.c is converted to use weston_buffer_reference.
Signed-off-by: Pekka Paalanen <ppaalanen@gmail.com>
12 years ago
|
|
|
weston_buffer_reference(&fb->buffer_ref, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_fb_unref(struct drm_fb *fb)
|
|
|
|
{
|
|
|
|
if (!fb)
|
|
|
|
return;
|
|
|
|
|
|
|
|
assert(fb->refcnt > 0);
|
|
|
|
if (--fb->refcnt > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (fb->type) {
|
|
|
|
case BUFFER_PIXMAN_DUMB:
|
|
|
|
drm_fb_destroy_dumb(fb);
|
|
|
|
break;
|
|
|
|
case BUFFER_CURSOR:
|
|
|
|
case BUFFER_CLIENT:
|
|
|
|
gbm_bo_destroy(fb->bo);
|
|
|
|
break;
|
|
|
|
case BUFFER_GBM_SURFACE:
|
|
|
|
gbm_surface_release_buffer(fb->gbm_surface, fb->bo);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_view_transform_supported(struct weston_view *ev)
|
|
|
|
{
|
|
|
|
return !ev->transform.enabled ||
|
|
|
|
(ev->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allocate a new drm_pending_state
|
|
|
|
*
|
|
|
|
* Allocate a new, empty, 'pending state' structure to be used across a
|
|
|
|
* repaint cycle or similar.
|
|
|
|
*
|
|
|
|
* @param backend DRM backend
|
|
|
|
* @returns Newly-allocated pending state structure
|
|
|
|
*/
|
|
|
|
static struct drm_pending_state *
|
|
|
|
drm_pending_state_alloc(struct drm_backend *backend)
|
|
|
|
{
|
|
|
|
struct drm_pending_state *ret;
|
|
|
|
|
|
|
|
ret = calloc(1, sizeof(*ret));
|
|
|
|
if (!ret)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret->backend = backend;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Free a drm_pending_state structure
|
|
|
|
*
|
|
|
|
* Frees a pending_state structure.
|
|
|
|
*
|
|
|
|
* @param pending_state Pending state structure to free
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_pending_state_free(struct drm_pending_state *pending_state)
|
|
|
|
{
|
|
|
|
if (!pending_state)
|
|
|
|
return;
|
|
|
|
|
|
|
|
free(pending_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
drm_output_check_scanout_format(struct drm_output *output,
|
|
|
|
struct weston_surface *es, struct gbm_bo *bo)
|
|
|
|
{
|
|
|
|
uint32_t format;
|
|
|
|
pixman_region32_t r;
|
|
|
|
|
|
|
|
format = gbm_bo_get_format(bo);
|
|
|
|
|
|
|
|
if (format == GBM_FORMAT_ARGB8888) {
|
|
|
|
/* We can scanout an ARGB buffer if the surface's
|
|
|
|
* opaque region covers the whole output, but we have
|
|
|
|
* to use XRGB as the KMS format code. */
|
|
|
|
pixman_region32_init_rect(&r, 0, 0,
|
|
|
|
output->base.width,
|
|
|
|
output->base.height);
|
|
|
|
pixman_region32_subtract(&r, &r, &es->opaque);
|
|
|
|
|
|
|
|
if (!pixman_region32_not_empty(&r))
|
|
|
|
format = GBM_FORMAT_XRGB8888;
|
|
|
|
|
|
|
|
pixman_region32_fini(&r);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->gbm_format == format)
|
|
|
|
return format;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct weston_plane *
|
|
|
|
drm_output_prepare_scanout_view(struct drm_output *output,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(output->base.compositor);
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
|
|
|
|
struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
uint32_t format;
|
|
|
|
|
|
|
|
/* Don't import buffers which span multiple outputs. */
|
|
|
|
if (ev->output_mask != (1u << output->base.id))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* We use GBM to import buffers. */
|
|
|
|
if (b->gbm == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (buffer == NULL)
|
|
|
|
return NULL;
|
|
|
|
if (wl_shm_buffer_get(buffer->resource))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Make sure our view is exactly compatible with the output. */
|
|
|
|
if (ev->geometry.x != output->base.x ||
|
|
|
|
ev->geometry.y != output->base.y)
|
|
|
|
return NULL;
|
|
|
|
if (buffer->width != output->base.current_mode->width ||
|
|
|
|
buffer->height != output->base.current_mode->height)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->transform.enabled)
|
|
|
|
return NULL;
|
|
|
|
if (ev->geometry.scissor_enabled)
|
|
|
|
return NULL;
|
|
|
|
if (viewport->buffer.transform != output->base.transform)
|
|
|
|
return NULL;
|
|
|
|
if (viewport->buffer.scale != output->base.current_scale)
|
|
|
|
return NULL;
|
|
|
|
if (!drm_view_transform_supported(ev))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->alpha != 1.0f)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER,
|
|
|
|
buffer->resource, GBM_BO_USE_SCANOUT);
|
|
|
|
|
|
|
|
/* Unable to use the buffer for scanout */
|
|
|
|
if (!bo)
|
|
|
|
return NULL;
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
format = drm_output_check_scanout_format(output, ev->surface, bo);
|
|
|
|
if (format == 0) {
|
|
|
|
gbm_bo_destroy(bo);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
output->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT);
|
|
|
|
if (!output->fb_pending) {
|
|
|
|
gbm_bo_destroy(bo);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm_fb_set_buffer(output->fb_pending, buffer);
|
|
|
|
|
|
|
|
return &output->scanout_plane;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_fb *
|
|
|
|
drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(output->base.compositor);
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
struct drm_fb *ret;
|
|
|
|
|
|
|
|
output->base.compositor->renderer->repaint_output(&output->base,
|
|
|
|
damage);
|
|
|
|
|
|
|
|
bo = gbm_surface_lock_front_buffer(output->gbm_surface);
|
|
|
|
if (!bo) {
|
|
|
|
weston_log("failed to lock front buffer: %m\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = drm_fb_get_from_bo(bo, b, output->gbm_format, BUFFER_GBM_SURFACE);
|
|
|
|
if (!ret) {
|
|
|
|
weston_log("failed to get drm_fb for bo\n");
|
|
|
|
gbm_surface_release_buffer(output->gbm_surface, bo);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ret->gbm_surface = output->gbm_surface;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_fb *
|
|
|
|
drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage)
|
|
|
|
{
|
|
|
|
struct weston_compositor *ec = output->base.compositor;
|
|
|
|
pixman_region32_t total_damage, previous_damage;
|
|
|
|
|
|
|
|
pixman_region32_init(&total_damage);
|
|
|
|
pixman_region32_init(&previous_damage);
|
|
|
|
|
|
|
|
pixman_region32_copy(&previous_damage, damage);
|
|
|
|
|
|
|
|
pixman_region32_union(&total_damage, damage, &output->previous_damage);
|
|
|
|
pixman_region32_copy(&output->previous_damage, &previous_damage);
|
|
|
|
|
|
|
|
output->current_image ^= 1;
|
|
|
|
|
|
|
|
pixman_renderer_output_set_buffer(&output->base,
|
|
|
|
output->image[output->current_image]);
|
|
|
|
|
|
|
|
ec->renderer->repaint_output(&output->base, &total_damage);
|
|
|
|
|
|
|
|
pixman_region32_fini(&total_damage);
|
|
|
|
pixman_region32_fini(&previous_damage);
|
|
|
|
|
|
|
|
return drm_fb_ref(output->dumb[output->current_image]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_render(struct drm_output *output, pixman_region32_t *damage)
|
|
|
|
{
|
|
|
|
struct weston_compositor *c = output->base.compositor;
|
|
|
|
struct drm_backend *b = to_drm_backend(c);
|
|
|
|
struct drm_fb *fb;
|
|
|
|
|
|
|
|
/* If we already have a client buffer promoted to scanout, then we don't
|
|
|
|
* want to render. */
|
|
|
|
if (output->fb_pending)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (b->use_pixman)
|
|
|
|
fb = drm_output_render_pixman(output, damage);
|
|
|
|
else
|
|
|
|
fb = drm_output_render_gl(output, damage);
|
|
|
|
|
|
|
|
if (!fb)
|
|
|
|
return;
|
|
|
|
output->fb_pending = fb;
|
|
|
|
|
|
|
|
pixman_region32_subtract(&c->primary_plane.damage,
|
|
|
|
&c->primary_plane.damage, damage);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_set_gamma(struct weston_output *output_base,
|
|
|
|
uint16_t size, uint16_t *r, uint16_t *g, uint16_t *b)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
|
|
|
struct drm_backend *backend =
|
|
|
|
to_drm_backend(output->base.compositor);
|
|
|
|
|
|
|
|
/* check */
|
|
|
|
if (output_base->gamma_size != size)
|
|
|
|
return;
|
|
|
|
if (!output->original_crtc)
|
|
|
|
return;
|
|
|
|
|
|
|
|
rc = drmModeCrtcSetGamma(backend->drm.fd,
|
|
|
|
output->crtc_id,
|
|
|
|
size, r, g, b);
|
|
|
|
if (rc)
|
|
|
|
weston_log("set gamma failed: %m\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine the type of vblank synchronization to use for the output.
|
|
|
|
*
|
|
|
|
* The pipe parameter indicates which CRTC is in use. Knowing this, we
|
|
|
|
* can determine which vblank sequence type to use for it. Traditional
|
|
|
|
* cards had only two CRTCs, with CRTC 0 using no special flags, and
|
|
|
|
* CRTC 1 using DRM_VBLANK_SECONDARY. The first bit of the pipe
|
|
|
|
* parameter indicates this.
|
|
|
|
*
|
|
|
|
* Bits 1-5 of the pipe parameter are 5 bit wide pipe number between
|
|
|
|
* 0-31. If this is non-zero it indicates we're dealing with a
|
|
|
|
* multi-gpu situation and we need to calculate the vblank sync
|
|
|
|
* using DRM_BLANK_HIGH_CRTC_MASK.
|
|
|
|
*/
|
|
|
|
static unsigned int
|
|
|
|
drm_waitvblank_pipe(struct drm_output *output)
|
|
|
|
{
|
|
|
|
if (output->pipe > 1)
|
|
|
|
return (output->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) &
|
|
|
|
DRM_VBLANK_HIGH_CRTC_MASK;
|
|
|
|
else if (output->pipe > 0)
|
|
|
|
return DRM_VBLANK_SECONDARY;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_repaint(struct weston_output *output_base,
|
|
|
|
pixman_region32_t *damage,
|
|
|
|
void *repaint_data)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
|
|
|
struct drm_backend *backend =
|
|
|
|
to_drm_backend(output->base.compositor);
|
|
|
|
struct drm_plane *p;
|
|
|
|
struct drm_mode *mode;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (output->disable_pending || output->destroy_pending)
|
|
|
|
return -1;
|
|
|
|
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
assert(!output->fb_last);
|
|
|
|
|
|
|
|
/* If disable_planes is set then assign_planes() wasn't
|
|
|
|
* called for this render, so we could still have a stale
|
|
|
|
* cursor plane set up.
|
|
|
|
*/
|
|
|
|
if (output->base.disable_planes) {
|
|
|
|
output->cursor_view = NULL;
|
|
|
|
output->cursor_plane.x = INT32_MIN;
|
|
|
|
output->cursor_plane.y = INT32_MIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm_output_render(output, damage);
|
|
|
|
if (!output->fb_pending)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
mode = container_of(output->base.current_mode, struct drm_mode, base);
|
|
|
|
if (output->state_invalid || !output->fb_current ||
|
|
|
|
output->fb_current->stride != output->fb_pending->stride) {
|
|
|
|
ret = drmModeSetCrtc(backend->drm.fd, output->crtc_id,
|
|
|
|
output->fb_pending->fb_id, 0, 0,
|
|
|
|
&output->connector_id, 1,
|
|
|
|
&mode->mode_info);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("set mode failed: %m\n");
|
|
|
|
goto err_pageflip;
|
|
|
|
}
|
|
|
|
output_base->set_dpms(output_base, WESTON_DPMS_ON);
|
|
|
|
|
|
|
|
output->state_invalid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (drmModePageFlip(backend->drm.fd, output->crtc_id,
|
|
|
|
output->fb_pending->fb_id,
|
|
|
|
DRM_MODE_PAGE_FLIP_EVENT, output) < 0) {
|
|
|
|
weston_log("queueing pageflip failed: %m\n");
|
|
|
|
goto err_pageflip;
|
|
|
|
}
|
|
|
|
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
output->fb_last = output->fb_current;
|
|
|
|
output->fb_current = output->fb_pending;
|
|
|
|
output->fb_pending = NULL;
|
|
|
|
|
|
|
|
assert(!output->page_flip_pending);
|
|
|
|
output->page_flip_pending = 1;
|
|
|
|
|
|
|
|
if (output->pageflip_timer)
|
|
|
|
wl_event_source_timer_update(output->pageflip_timer,
|
|
|
|
backend->pageflip_timeout);
|
|
|
|
|
|
|
|
drm_output_set_cursor(output);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now, update all the sprite surfaces
|
|
|
|
*/
|
|
|
|
wl_list_for_each(p, &backend->plane_list, link) {
|
|
|
|
uint32_t flags = 0, fb_id = 0;
|
|
|
|
drmVBlank vbl = {
|
|
|
|
.request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT,
|
|
|
|
.request.sequence = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (p->type != WDRM_PLANE_TYPE_OVERLAY)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((!p->fb_current && !p->fb_pending) ||
|
|
|
|
!drm_plane_crtc_supported(output, p))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (p->fb_pending && !backend->sprites_hidden)
|
|
|
|
fb_id = p->fb_pending->fb_id;
|
|
|
|
|
|
|
|
ret = drmModeSetPlane(backend->drm.fd, p->plane_id,
|
|
|
|
output->crtc_id, fb_id, flags,
|
|
|
|
p->dest_x, p->dest_y,
|
|
|
|
p->dest_w, p->dest_h,
|
|
|
|
p->src_x, p->src_y,
|
|
|
|
p->src_w, p->src_h);
|
|
|
|
if (ret)
|
|
|
|
weston_log("setplane failed: %d: %s\n",
|
|
|
|
ret, strerror(errno));
|
|
|
|
|
|
|
|
vbl.request.type |= drm_waitvblank_pipe(output);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Queue a vblank signal so we know when the surface
|
|
|
|
* becomes active on the display or has been replaced.
|
|
|
|
*/
|
|
|
|
vbl.request.signal = (unsigned long) p;
|
|
|
|
ret = drmWaitVBlank(backend->drm.fd, &vbl);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("vblank event request failed: %d: %s\n",
|
|
|
|
ret, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
p->output = output;
|
|
|
|
p->fb_last = p->fb_current;
|
|
|
|
p->fb_current = p->fb_pending;
|
|
|
|
p->fb_pending = NULL;
|
|
|
|
output->vblank_pending++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_pageflip:
|
|
|
|
output->cursor_view = NULL;
|
|
|
|
if (output->fb_pending) {
|
|
|
|
drm_fb_unref(output->fb_pending);
|
|
|
|
output->fb_pending = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_start_repaint_loop(struct weston_output *output_base)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
|
|
|
struct drm_backend *backend =
|
|
|
|
to_drm_backend(output_base->compositor);
|
|
|
|
uint32_t fb_id;
|
compositor-drm: Allow instant start of repaint loop. (v4)
drm_output_start_repaint_loop() incurred a delay of
one refresh cycle by using a no-op page-flip to get
an accurate vblank timestamp as reference. This causes
unwanted lag whenever Weston exited its repaint loop, e.g.,
whenever an application wants to repaint with less than
full video refresh rate but still minimum lag.
Try to use the drmWaitVblank ioctl to get a proper
timestamp instantaneously without lag. If that does
not work, fall back to the old method of idle page-flip.
This optimization will work on any drm/kms driver
which supports high precision vblank timestamping.
As of Linux 4.0 these would be intel, radeon and
nouveau on all their supported gpu's.
On kms drivers without instant high precision timestamping
support, the kernel is supposed to return a timestamp
of zero when calling drmWaitVblank() to query the current
vblank count and time iff vblank irqs are currently
disabled, because the only way to get a valid timestamp
on such kms drivers is to enable vblank interrupts and
then wait a bit for the next vblank irq to take a new valid
timestamp. The caller is supposed to poll until at next
vblank irq it gets a valid non-zero timestamp if it needs
a timestamp.
This zero-timestamp signalling works up to Linux 3.17, but
got broken due to a regression in Linux 3.18 and later. On
Linux 3.18+ with kms drivers that don't have high precision
timestamping, the kernel erroneously returns a stale timestamp
from an earlier vblank, ie. the vblank count and timestamp are
mismatched. A patch is under way to fix this, but to deal with
broken kernels, we also check non-zero timestamps if they are
more than one refresh duration in the past, as this indicates
a stale/invalid timestamp, so we need to take the page-flip
fallback for restarting the repaint loop.
v2: Implement review suggestions by Pekka Paalanen, especially
extend the commit message to describe when and why the
instant restart won't work due to missing Linux kernel
functionality or a Linux kernel regression.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
v3: Fix timespec_to_nsec() which was computing picoseconds,
use the new timespec-util.h helpers.
v4: Rebased to master, split long lines.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
10 years ago
|
|
|
struct timespec ts, tnow;
|
|
|
|
struct timespec vbl2now;
|
|
|
|
int64_t refresh_nsec;
|
|
|
|
int ret;
|
|
|
|
drmVBlank vbl = {
|
|
|
|
.request.type = DRM_VBLANK_RELATIVE,
|
|
|
|
.request.sequence = 0,
|
|
|
|
.request.signal = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (output->disable_pending || output->destroy_pending)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!output->fb_current) {
|
|
|
|
/* We can't page flip if there's no mode set */
|
|
|
|
goto finish_frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Need to smash all state in from scratch; current timings might not
|
|
|
|
* be what we want, page flip might not work, etc.
|
|
|
|
*/
|
|
|
|
if (output->state_invalid)
|
|
|
|
goto finish_frame;
|
|
|
|
|
compositor-drm: Allow instant start of repaint loop. (v4)
drm_output_start_repaint_loop() incurred a delay of
one refresh cycle by using a no-op page-flip to get
an accurate vblank timestamp as reference. This causes
unwanted lag whenever Weston exited its repaint loop, e.g.,
whenever an application wants to repaint with less than
full video refresh rate but still minimum lag.
Try to use the drmWaitVblank ioctl to get a proper
timestamp instantaneously without lag. If that does
not work, fall back to the old method of idle page-flip.
This optimization will work on any drm/kms driver
which supports high precision vblank timestamping.
As of Linux 4.0 these would be intel, radeon and
nouveau on all their supported gpu's.
On kms drivers without instant high precision timestamping
support, the kernel is supposed to return a timestamp
of zero when calling drmWaitVblank() to query the current
vblank count and time iff vblank irqs are currently
disabled, because the only way to get a valid timestamp
on such kms drivers is to enable vblank interrupts and
then wait a bit for the next vblank irq to take a new valid
timestamp. The caller is supposed to poll until at next
vblank irq it gets a valid non-zero timestamp if it needs
a timestamp.
This zero-timestamp signalling works up to Linux 3.17, but
got broken due to a regression in Linux 3.18 and later. On
Linux 3.18+ with kms drivers that don't have high precision
timestamping, the kernel erroneously returns a stale timestamp
from an earlier vblank, ie. the vblank count and timestamp are
mismatched. A patch is under way to fix this, but to deal with
broken kernels, we also check non-zero timestamps if they are
more than one refresh duration in the past, as this indicates
a stale/invalid timestamp, so we need to take the page-flip
fallback for restarting the repaint loop.
v2: Implement review suggestions by Pekka Paalanen, especially
extend the commit message to describe when and why the
instant restart won't work due to missing Linux kernel
functionality or a Linux kernel regression.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
v3: Fix timespec_to_nsec() which was computing picoseconds,
use the new timespec-util.h helpers.
v4: Rebased to master, split long lines.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
10 years ago
|
|
|
/* Try to get current msc and timestamp via instant query */
|
|
|
|
vbl.request.type |= drm_waitvblank_pipe(output);
|
|
|
|
ret = drmWaitVBlank(backend->drm.fd, &vbl);
|
|
|
|
|
|
|
|
/* Error ret or zero timestamp means failure to get valid timestamp */
|
|
|
|
if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) {
|
|
|
|
ts.tv_sec = vbl.reply.tval_sec;
|
|
|
|
ts.tv_nsec = vbl.reply.tval_usec * 1000;
|
|
|
|
|
|
|
|
/* Valid timestamp for most recent vblank - not stale?
|
|
|
|
* Stale ts could happen on Linux 3.17+, so make sure it
|
|
|
|
* is not older than 1 refresh duration since now.
|
|
|
|
*/
|
|
|
|
weston_compositor_read_presentation_clock(backend->compositor,
|
|
|
|
&tnow);
|
|
|
|
timespec_sub(&vbl2now, &tnow, &ts);
|
|
|
|
refresh_nsec =
|
|
|
|
millihz_to_nsec(output->base.current_mode->refresh);
|
|
|
|
if (timespec_to_nsec(&vbl2now) < refresh_nsec) {
|
|
|
|
drm_output_update_msc(output, vbl.reply.sequence);
|
|
|
|
weston_output_finish_frame(output_base, &ts,
|
|
|
|
WP_PRESENTATION_FEEDBACK_INVALID);
|
compositor-drm: Allow instant start of repaint loop. (v4)
drm_output_start_repaint_loop() incurred a delay of
one refresh cycle by using a no-op page-flip to get
an accurate vblank timestamp as reference. This causes
unwanted lag whenever Weston exited its repaint loop, e.g.,
whenever an application wants to repaint with less than
full video refresh rate but still minimum lag.
Try to use the drmWaitVblank ioctl to get a proper
timestamp instantaneously without lag. If that does
not work, fall back to the old method of idle page-flip.
This optimization will work on any drm/kms driver
which supports high precision vblank timestamping.
As of Linux 4.0 these would be intel, radeon and
nouveau on all their supported gpu's.
On kms drivers without instant high precision timestamping
support, the kernel is supposed to return a timestamp
of zero when calling drmWaitVblank() to query the current
vblank count and time iff vblank irqs are currently
disabled, because the only way to get a valid timestamp
on such kms drivers is to enable vblank interrupts and
then wait a bit for the next vblank irq to take a new valid
timestamp. The caller is supposed to poll until at next
vblank irq it gets a valid non-zero timestamp if it needs
a timestamp.
This zero-timestamp signalling works up to Linux 3.17, but
got broken due to a regression in Linux 3.18 and later. On
Linux 3.18+ with kms drivers that don't have high precision
timestamping, the kernel erroneously returns a stale timestamp
from an earlier vblank, ie. the vblank count and timestamp are
mismatched. A patch is under way to fix this, but to deal with
broken kernels, we also check non-zero timestamps if they are
more than one refresh duration in the past, as this indicates
a stale/invalid timestamp, so we need to take the page-flip
fallback for restarting the repaint loop.
v2: Implement review suggestions by Pekka Paalanen, especially
extend the commit message to describe when and why the
instant restart won't work due to missing Linux kernel
functionality or a Linux kernel regression.
Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
v3: Fix timespec_to_nsec() which was computing picoseconds,
use the new timespec-util.h helpers.
v4: Rebased to master, split long lines.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
10 years ago
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Immediate query didn't provide valid timestamp.
|
|
|
|
* Use pageflip fallback.
|
|
|
|
*/
|
|
|
|
fb_id = output->fb_current->fb_id;
|
|
|
|
|
|
|
|
assert(!output->page_flip_pending);
|
|
|
|
assert(!output->fb_last);
|
|
|
|
|
|
|
|
if (drmModePageFlip(backend->drm.fd, output->crtc_id, fb_id,
|
|
|
|
DRM_MODE_PAGE_FLIP_EVENT, output) < 0) {
|
|
|
|
weston_log("queueing pageflip failed: %m\n");
|
|
|
|
goto finish_frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->pageflip_timer)
|
|
|
|
wl_event_source_timer_update(output->pageflip_timer,
|
|
|
|
backend->pageflip_timeout);
|
|
|
|
|
|
|
|
output->fb_last = drm_fb_ref(output->fb_current);
|
|
|
|
output->page_flip_pending = 1;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
finish_frame:
|
|
|
|
/* if we cannot page-flip, immediately finish frame */
|
|
|
|
weston_output_finish_frame(output_base, NULL,
|
|
|
|
WP_PRESENTATION_FEEDBACK_INVALID);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_update_msc(struct drm_output *output, unsigned int seq)
|
|
|
|
{
|
|
|
|
uint64_t msc_hi = output->base.msc >> 32;
|
|
|
|
|
|
|
|
if (seq < (output->base.msc & 0xffffffff))
|
|
|
|
msc_hi++;
|
|
|
|
|
|
|
|
output->base.msc = (msc_hi << 32) + seq;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct drm_plane *s = (struct drm_plane *)data;
|
|
|
|
struct drm_output *output = s->output;
|
|
|
|
struct timespec ts;
|
|
|
|
uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION |
|
|
|
|
WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
|
|
|
|
|
|
|
|
drm_output_update_msc(output, frame);
|
|
|
|
output->vblank_pending--;
|
|
|
|
assert(output->vblank_pending >= 0);
|
|
|
|
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
assert(s->fb_last || s->fb_current);
|
|
|
|
drm_fb_unref(s->fb_last);
|
|
|
|
s->fb_last = NULL;
|
|
|
|
|
|
|
|
if (!output->page_flip_pending && !output->vblank_pending) {
|
|
|
|
/* Stop the pageflip timer instead of rearming it here */
|
|
|
|
if (output->pageflip_timer)
|
|
|
|
wl_event_source_timer_update(output->pageflip_timer, 0);
|
|
|
|
|
|
|
|
ts.tv_sec = sec;
|
|
|
|
ts.tv_nsec = usec * 1000;
|
compositor: set presentation.presented flags
Change weston_output_finish_frame() signature so that backends are
required to set the flags, that will be reported on the Presentation
'presented' event. This is meant for output-wide feedback flags. Flags
that vary per wl_surface are subject for the following patch.
All start_repaint_loop functions use the special private flag
PRESENTATION_FEEDBACK_INVALID to mark, that this call of
weston_output_finish_frame() cannot trigger the 'presented' event. If it
does, we now hit an assert, and should then investigate why a fake update
triggered Presentation feedback.
DRM:
Page flip is always vsync'd, and always gets the completion timestamp
from the kernel which should correspond well to hardware. Completion is
triggered by the kernel/hardware.
Vblank handler is only used with the broken planes path, therefore do
not report VSYNC, because we cannot guarantee all the planes updated at
the same time. We cannot set the INVALID, because it would abort the
compositor if the broken planes path was ever used. This is a hack that
will get fixed with nuclear pageflip support in the future.
fbdev:
No vsync, update done by copy, no completion event from hardware, and
completion time is totally fake.
headless:
No real output to update.
RDP:
Guessing that maybe no vsync, fake time, and copy make sense (pixels
sent over network). Also no event that the pixels have been shown?
RPI:
Presumably Dispmanx updates are vsync'd. We get a completion event from
the driver, but need to read the clock ourselves, so the completion time
is somewhat unreliable. Zero-copy flag not implemented though it would
be theoretically possible with EGL clients (zero-copy is a per-surface
flag anyway, so in this patch).
Wayland:
No information how the host compositor is doing updates, so make a safe
guess without assuming vsync or hardware completion event. While we do
get some timestamp from the host compositor, it is not the completion
time. Would need to hook to the Presentation extension of the host
compositor to get more accurate flags.
X11:
No idea about vsync, completion event, or copying. Also the timestamp is
a fake.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Tested-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Acked-by: Mario Kleiner <mario.kleiner.de@gmail.com>
10 years ago
|
|
|
weston_output_finish_frame(&output->base, &ts, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_destroy(struct weston_output *base);
|
|
|
|
|
|
|
|
static void
|
|
|
|
page_flip_handler(int fd, unsigned int frame,
|
|
|
|
unsigned int sec, unsigned int usec, void *data)
|
|
|
|
{
|
|
|
|
struct drm_output *output = data;
|
|
|
|
struct timespec ts;
|
|
|
|
uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC |
|
|
|
|
WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION |
|
|
|
|
WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
|
|
|
|
|
|
|
|
drm_output_update_msc(output, frame);
|
|
|
|
|
|
|
|
assert(output->page_flip_pending);
|
|
|
|
output->page_flip_pending = 0;
|
|
|
|
|
|
|
|
drm_fb_unref(output->fb_last);
|
|
|
|
output->fb_last = NULL;
|
|
|
|
|
|
|
|
if (output->destroy_pending)
|
|
|
|
drm_output_destroy(&output->base);
|
|
|
|
else if (output->disable_pending)
|
|
|
|
weston_output_disable(&output->base);
|
|
|
|
else if (!output->vblank_pending) {
|
|
|
|
/* Stop the pageflip timer instead of rearming it here */
|
|
|
|
if (output->pageflip_timer)
|
|
|
|
wl_event_source_timer_update(output->pageflip_timer, 0);
|
|
|
|
|
|
|
|
ts.tv_sec = sec;
|
|
|
|
ts.tv_nsec = usec * 1000;
|
compositor: set presentation.presented flags
Change weston_output_finish_frame() signature so that backends are
required to set the flags, that will be reported on the Presentation
'presented' event. This is meant for output-wide feedback flags. Flags
that vary per wl_surface are subject for the following patch.
All start_repaint_loop functions use the special private flag
PRESENTATION_FEEDBACK_INVALID to mark, that this call of
weston_output_finish_frame() cannot trigger the 'presented' event. If it
does, we now hit an assert, and should then investigate why a fake update
triggered Presentation feedback.
DRM:
Page flip is always vsync'd, and always gets the completion timestamp
from the kernel which should correspond well to hardware. Completion is
triggered by the kernel/hardware.
Vblank handler is only used with the broken planes path, therefore do
not report VSYNC, because we cannot guarantee all the planes updated at
the same time. We cannot set the INVALID, because it would abort the
compositor if the broken planes path was ever used. This is a hack that
will get fixed with nuclear pageflip support in the future.
fbdev:
No vsync, update done by copy, no completion event from hardware, and
completion time is totally fake.
headless:
No real output to update.
RDP:
Guessing that maybe no vsync, fake time, and copy make sense (pixels
sent over network). Also no event that the pixels have been shown?
RPI:
Presumably Dispmanx updates are vsync'd. We get a completion event from
the driver, but need to read the clock ourselves, so the completion time
is somewhat unreliable. Zero-copy flag not implemented though it would
be theoretically possible with EGL clients (zero-copy is a per-surface
flag anyway, so in this patch).
Wayland:
No information how the host compositor is doing updates, so make a safe
guess without assuming vsync or hardware completion event. While we do
get some timestamp from the host compositor, it is not the completion
time. Would need to hook to the Presentation extension of the host
compositor to get more accurate flags.
X11:
No idea about vsync, completion event, or copying. Also the timestamp is
a fake.
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
Reviewed-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Tested-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Acked-by: Mario Kleiner <mario.kleiner.de@gmail.com>
10 years ago
|
|
|
weston_output_finish_frame(&output->base, &ts, flags);
|
|
|
|
|
|
|
|
/* We can't call this from frame_notify, because the output's
|
|
|
|
* repaint needed flag is cleared just after that */
|
|
|
|
if (output->recorder)
|
|
|
|
weston_output_schedule_repaint(&output->base);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Begin a new repaint cycle
|
|
|
|
*
|
|
|
|
* Called by the core compositor at the beginning of a repaint cycle.
|
|
|
|
*/
|
|
|
|
static void *
|
|
|
|
drm_repaint_begin(struct weston_compositor *compositor)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(compositor);
|
|
|
|
struct drm_pending_state *ret;
|
|
|
|
|
|
|
|
ret = drm_pending_state_alloc(b);
|
|
|
|
b->repaint_data = ret;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flush a repaint set
|
|
|
|
*
|
|
|
|
* Called by the core compositor when a repaint cycle has been completed
|
|
|
|
* and should be flushed.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(compositor);
|
|
|
|
struct drm_pending_state *pending_state = repaint_data;
|
|
|
|
|
|
|
|
drm_pending_state_free(pending_state);
|
|
|
|
b->repaint_data = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cancel a repaint set
|
|
|
|
*
|
|
|
|
* Called by the core compositor when a repaint has finished, so the data
|
|
|
|
* held across the repaint cycle should be discarded.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(compositor);
|
|
|
|
struct drm_pending_state *pending_state = repaint_data;
|
|
|
|
|
|
|
|
drm_pending_state_free(pending_state);
|
|
|
|
b->repaint_data = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
|
|
drm_output_check_plane_format(struct drm_plane *p,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev, struct gbm_bo *bo)
|
|
|
|
{
|
|
|
|
uint32_t i, format;
|
|
|
|
|
|
|
|
format = gbm_bo_get_format(bo);
|
|
|
|
|
|
|
|
if (format == GBM_FORMAT_ARGB8888) {
|
|
|
|
pixman_region32_t r;
|
|
|
|
|
|
|
|
pixman_region32_init_rect(&r, 0, 0,
|
|
|
|
ev->surface->width,
|
|
|
|
ev->surface->height);
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
pixman_region32_subtract(&r, &r, &ev->surface->opaque);
|
|
|
|
|
|
|
|
if (!pixman_region32_not_empty(&r))
|
|
|
|
format = GBM_FORMAT_XRGB8888;
|
|
|
|
|
|
|
|
pixman_region32_fini(&r);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < p->count_formats; i++)
|
|
|
|
if (p->formats[i] == format)
|
|
|
|
return format;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct weston_plane *
|
|
|
|
drm_output_prepare_overlay_view(struct drm_output *output,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev)
|
|
|
|
{
|
|
|
|
struct weston_compositor *ec = output->base.compositor;
|
|
|
|
struct drm_backend *b = to_drm_backend(ec);
|
|
|
|
struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
|
|
|
|
struct wl_resource *buffer_resource;
|
|
|
|
struct drm_plane *p;
|
|
|
|
struct linux_dmabuf_buffer *dmabuf;
|
|
|
|
int found = 0;
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
pixman_region32_t dest_rect, src_rect;
|
|
|
|
pixman_box32_t *box, tbox;
|
|
|
|
uint32_t format;
|
|
|
|
wl_fixed_t sx1, sy1, sx2, sy2;
|
|
|
|
|
|
|
|
if (b->sprites_are_broken)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Don't import buffers which span multiple outputs. */
|
|
|
|
if (ev->output_mask != (1u << output->base.id))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* We can only import GBM buffers. */
|
|
|
|
if (b->gbm == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->surface->buffer_ref.buffer == NULL)
|
|
|
|
return NULL;
|
|
|
|
buffer_resource = ev->surface->buffer_ref.buffer->resource;
|
|
|
|
if (wl_shm_buffer_get(buffer_resource))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (viewport->buffer.transform != output->base.transform)
|
|
|
|
return NULL;
|
|
|
|
if (viewport->buffer.scale != output->base.current_scale)
|
|
|
|
return NULL;
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
if (!drm_view_transform_supported(ev))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->alpha != 1.0f)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
wl_list_for_each(p, &b->plane_list, link) {
|
|
|
|
if (p->type != WDRM_PLANE_TYPE_OVERLAY)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!drm_plane_crtc_supported(output, p))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!p->fb_pending) {
|
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No sprites available */
|
|
|
|
if (!found)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if ((dmabuf = linux_dmabuf_buffer_get(buffer_resource))) {
|
|
|
|
#ifdef HAVE_GBM_FD_IMPORT
|
|
|
|
/* XXX: TODO:
|
|
|
|
*
|
|
|
|
* Use AddFB2 directly, do not go via GBM.
|
|
|
|
* Add support for multiplanar formats.
|
|
|
|
* Both require refactoring in the DRM-backend to
|
|
|
|
* support a mix of gbm_bos and drmfbs.
|
|
|
|
*/
|
|
|
|
struct gbm_import_fd_data gbm_dmabuf = {
|
|
|
|
.fd = dmabuf->attributes.fd[0],
|
|
|
|
.width = dmabuf->attributes.width,
|
|
|
|
.height = dmabuf->attributes.height,
|
|
|
|
.stride = dmabuf->attributes.stride[0],
|
|
|
|
.format = dmabuf->attributes.format
|
|
|
|
};
|
|
|
|
|
|
|
|
/* XXX: TODO:
|
|
|
|
*
|
|
|
|
* Currently the buffer is rejected if any dmabuf attribute
|
|
|
|
* flag is set. This keeps us from passing an inverted /
|
|
|
|
* interlaced / bottom-first buffer (or any other type that may
|
|
|
|
* be added in the future) through to an overlay. Ultimately,
|
|
|
|
* these types of buffers should be handled through buffer
|
|
|
|
* transforms and not as spot-checks requiring specific
|
|
|
|
* knowledge. */
|
|
|
|
if (dmabuf->attributes.n_planes != 1 ||
|
|
|
|
dmabuf->attributes.offset[0] != 0 ||
|
|
|
|
dmabuf->attributes.flags)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_FD, &gbm_dmabuf,
|
|
|
|
GBM_BO_USE_SCANOUT);
|
|
|
|
#else
|
|
|
|
return NULL;
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER,
|
|
|
|
buffer_resource, GBM_BO_USE_SCANOUT);
|
|
|
|
}
|
|
|
|
if (!bo)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
format = drm_output_check_plane_format(p, ev, bo);
|
|
|
|
if (format == 0) {
|
|
|
|
gbm_bo_destroy(bo);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
p->fb_pending = drm_fb_get_from_bo(bo, b, format, BUFFER_CLIENT);
|
|
|
|
if (!p->fb_pending) {
|
|
|
|
gbm_bo_destroy(bo);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm_fb_set_buffer(p->fb_pending, ev->surface->buffer_ref.buffer);
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
box = pixman_region32_extents(&ev->transform.boundingbox);
|
|
|
|
p->base.x = box->x1;
|
|
|
|
p->base.y = box->y1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate the source & dest rects properly based on actual
|
|
|
|
* position (note the caller has called weston_view_update_transform()
|
|
|
|
* for us already).
|
|
|
|
*/
|
|
|
|
pixman_region32_init(&dest_rect);
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox,
|
|
|
|
&output->base.region);
|
|
|
|
pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y);
|
|
|
|
box = pixman_region32_extents(&dest_rect);
|
|
|
|
tbox = weston_transformed_rect(output->base.width,
|
|
|
|
output->base.height,
|
|
|
|
output->base.transform,
|
|
|
|
output->base.current_scale,
|
|
|
|
*box);
|
|
|
|
p->dest_x = tbox.x1;
|
|
|
|
p->dest_y = tbox.y1;
|
|
|
|
p->dest_w = tbox.x2 - tbox.x1;
|
|
|
|
p->dest_h = tbox.y2 - tbox.y1;
|
|
|
|
pixman_region32_fini(&dest_rect);
|
|
|
|
|
|
|
|
pixman_region32_init(&src_rect);
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
pixman_region32_intersect(&src_rect, &ev->transform.boundingbox,
|
|
|
|
&output->base.region);
|
|
|
|
box = pixman_region32_extents(&src_rect);
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
weston_view_from_global_fixed(ev,
|
|
|
|
wl_fixed_from_int(box->x1),
|
|
|
|
wl_fixed_from_int(box->y1),
|
|
|
|
&sx1, &sy1);
|
|
|
|
weston_view_from_global_fixed(ev,
|
|
|
|
wl_fixed_from_int(box->x2),
|
|
|
|
wl_fixed_from_int(box->y2),
|
|
|
|
&sx2, &sy2);
|
|
|
|
|
|
|
|
if (sx1 < 0)
|
|
|
|
sx1 = 0;
|
|
|
|
if (sy1 < 0)
|
|
|
|
sy1 = 0;
|
|
|
|
if (sx2 > wl_fixed_from_int(ev->surface->width))
|
|
|
|
sx2 = wl_fixed_from_int(ev->surface->width);
|
|
|
|
if (sy2 > wl_fixed_from_int(ev->surface->height))
|
|
|
|
sy2 = wl_fixed_from_int(ev->surface->height);
|
|
|
|
|
|
|
|
tbox.x1 = sx1;
|
|
|
|
tbox.y1 = sy1;
|
|
|
|
tbox.x2 = sx2;
|
|
|
|
tbox.y2 = sy2;
|
|
|
|
|
|
|
|
tbox = weston_transformed_rect(wl_fixed_from_int(ev->surface->width),
|
|
|
|
wl_fixed_from_int(ev->surface->height),
|
|
|
|
viewport->buffer.transform,
|
|
|
|
viewport->buffer.scale,
|
|
|
|
tbox);
|
|
|
|
|
|
|
|
p->src_x = tbox.x1 << 8;
|
|
|
|
p->src_y = tbox.y1 << 8;
|
|
|
|
p->src_w = (tbox.x2 - tbox.x1) << 8;
|
|
|
|
p->src_h = (tbox.y2 - tbox.y1) << 8;
|
|
|
|
pixman_region32_fini(&src_rect);
|
|
|
|
|
|
|
|
return &p->base;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct weston_plane *
|
|
|
|
drm_output_prepare_cursor_view(struct drm_output *output,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(output->base.compositor);
|
|
|
|
struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport;
|
|
|
|
struct wl_shm_buffer *shmbuf;
|
|
|
|
float x, y;
|
|
|
|
|
|
|
|
if (b->cursors_are_broken)
|
|
|
|
return NULL;
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
if (output->cursor_view)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Don't import buffers which span multiple outputs. */
|
|
|
|
if (ev->output_mask != (1u << output->base.id))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* We use GBM to import SHM buffers. */
|
|
|
|
if (b->gbm == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->surface->buffer_ref.buffer == NULL)
|
|
|
|
return NULL;
|
|
|
|
shmbuf = wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource);
|
|
|
|
if (!shmbuf)
|
|
|
|
return NULL;
|
|
|
|
if (wl_shm_buffer_get_format(shmbuf) != WL_SHM_FORMAT_ARGB8888)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL)
|
|
|
|
return NULL;
|
|
|
|
if (ev->transform.enabled &&
|
|
|
|
(ev->transform.matrix.type > WESTON_MATRIX_TRANSFORM_TRANSLATE))
|
|
|
|
return NULL;
|
|
|
|
if (viewport->buffer.scale != output->base.current_scale)
|
|
|
|
return NULL;
|
|
|
|
if (ev->geometry.scissor_enabled)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (ev->surface->width > b->cursor_width ||
|
|
|
|
ev->surface->height > b->cursor_height)
|
|
|
|
return NULL;
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
output->cursor_view = ev;
|
|
|
|
weston_view_to_global_float(ev, 0, 0, &x, &y);
|
|
|
|
output->cursor_plane.x = x;
|
|
|
|
output->cursor_plane.y = y;
|
|
|
|
|
|
|
|
return &output->cursor_plane;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the image for the current cursor surface
|
|
|
|
*
|
|
|
|
* @param b DRM backend structure
|
|
|
|
* @param bo GBM buffer object to write into
|
|
|
|
* @param ev View to use for cursor image
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
cursor_bo_update(struct drm_backend *b, struct gbm_bo *bo,
|
|
|
|
struct weston_view *ev)
|
|
|
|
{
|
|
|
|
struct weston_buffer *buffer = ev->surface->buffer_ref.buffer;
|
|
|
|
uint32_t buf[b->cursor_width * b->cursor_height];
|
|
|
|
int32_t stride;
|
|
|
|
uint8_t *s;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
assert(buffer && buffer->shm_buffer);
|
|
|
|
assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource));
|
|
|
|
assert(ev->surface->width <= b->cursor_width);
|
|
|
|
assert(ev->surface->height <= b->cursor_height);
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof buf);
|
|
|
|
stride = wl_shm_buffer_get_stride(buffer->shm_buffer);
|
|
|
|
s = wl_shm_buffer_get_data(buffer->shm_buffer);
|
|
|
|
|
|
|
|
wl_shm_buffer_begin_access(buffer->shm_buffer);
|
|
|
|
for (i = 0; i < ev->surface->height; i++)
|
|
|
|
memcpy(buf + i * b->cursor_width,
|
|
|
|
s + i * stride,
|
|
|
|
ev->surface->width * 4);
|
|
|
|
wl_shm_buffer_end_access(buffer->shm_buffer);
|
|
|
|
|
|
|
|
if (gbm_bo_write(bo, buf, sizeof buf) < 0)
|
|
|
|
weston_log("failed update cursor: %m\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_set_cursor(struct drm_output *output)
|
|
|
|
{
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev = output->cursor_view;
|
|
|
|
struct drm_backend *b = to_drm_backend(output->base.compositor);
|
|
|
|
EGLint handle;
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
float x, y;
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
if (ev == NULL) {
|
|
|
|
drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pixman_region32_not_empty(&output->cursor_plane.damage)) {
|
|
|
|
pixman_region32_fini(&output->cursor_plane.damage);
|
|
|
|
pixman_region32_init(&output->cursor_plane.damage);
|
|
|
|
output->current_cursor ^= 1;
|
|
|
|
bo = output->gbm_cursor_fb[output->current_cursor]->bo;
|
|
|
|
|
|
|
|
cursor_bo_update(b, bo, ev);
|
|
|
|
handle = gbm_bo_get_handle(bo).s32;
|
|
|
|
if (drmModeSetCursor(b->drm.fd, output->crtc_id, handle,
|
|
|
|
b->cursor_width, b->cursor_height)) {
|
|
|
|
weston_log("failed to set cursor: %m\n");
|
|
|
|
b->cursors_are_broken = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
x = (output->cursor_plane.x - output->base.x) *
|
|
|
|
output->base.current_scale;
|
|
|
|
y = (output->cursor_plane.y - output->base.y) *
|
|
|
|
output->base.current_scale;
|
|
|
|
|
|
|
|
if (drmModeMoveCursor(b->drm.fd, output->crtc_id, x, y)) {
|
|
|
|
weston_log("failed to move cursor: %m\n");
|
|
|
|
b->cursors_are_broken = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_assign_planes(struct weston_output *output_base, void *repaint_data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(output_base->compositor);
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
struct weston_view *ev, *next;
|
|
|
|
pixman_region32_t overlap, surface_overlap;
|
|
|
|
struct weston_plane *primary, *next_plane;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find a surface for each sprite in the output using some heuristics:
|
|
|
|
* 1) size
|
|
|
|
* 2) frequency of update
|
|
|
|
* 3) opacity (though some hw might support alpha blending)
|
|
|
|
* 4) clipping (this can be fixed with color keys)
|
|
|
|
*
|
|
|
|
* The idea is to save on blitting since this should save power.
|
|
|
|
* If we can get a large video surface on the sprite for example,
|
|
|
|
* the main display surface may not need to update at all, and
|
|
|
|
* the client buffer can be used directly for the sprite surface
|
|
|
|
* as we do for flipping full screen surfaces.
|
|
|
|
*/
|
|
|
|
pixman_region32_init(&overlap);
|
|
|
|
primary = &output_base->compositor->primary_plane;
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
|
|
|
|
output->cursor_view = NULL;
|
|
|
|
output->cursor_plane.x = INT32_MIN;
|
|
|
|
output->cursor_plane.y = INT32_MIN;
|
|
|
|
|
|
|
|
wl_list_for_each_safe(ev, next, &output_base->compositor->view_list, link) {
|
|
|
|
struct weston_surface *es = ev->surface;
|
|
|
|
|
|
|
|
/* Test whether this buffer can ever go into a plane:
|
|
|
|
* non-shm, or small enough to be a cursor.
|
|
|
|
*
|
|
|
|
* Also, keep a reference when using the pixman renderer.
|
|
|
|
* That makes it possible to do a seamless switch to the GL
|
|
|
|
* renderer and since the pixman renderer keeps a reference
|
|
|
|
* to the buffer anyway, there is no side effects.
|
|
|
|
*/
|
|
|
|
if (b->use_pixman ||
|
|
|
|
(es->buffer_ref.buffer &&
|
|
|
|
(!wl_shm_buffer_get(es->buffer_ref.buffer->resource) ||
|
|
|
|
(ev->surface->width <= b->cursor_width &&
|
|
|
|
ev->surface->height <= b->cursor_height))))
|
|
|
|
es->keep_buffer = true;
|
|
|
|
else
|
|
|
|
es->keep_buffer = false;
|
|
|
|
|
|
|
|
pixman_region32_init(&surface_overlap);
|
|
|
|
pixman_region32_intersect(&surface_overlap, &overlap,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
&ev->transform.boundingbox);
|
|
|
|
|
|
|
|
next_plane = NULL;
|
|
|
|
if (pixman_region32_not_empty(&surface_overlap))
|
|
|
|
next_plane = primary;
|
|
|
|
if (next_plane == NULL)
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
next_plane = drm_output_prepare_cursor_view(output, ev);
|
|
|
|
if (next_plane == NULL)
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
next_plane = drm_output_prepare_scanout_view(output, ev);
|
|
|
|
if (next_plane == NULL)
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
next_plane = drm_output_prepare_overlay_view(output, ev);
|
|
|
|
if (next_plane == NULL)
|
|
|
|
next_plane = primary;
|
|
|
|
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
weston_view_move_to_plane(ev, next_plane);
|
|
|
|
|
|
|
|
if (next_plane == primary)
|
|
|
|
pixman_region32_union(&overlap, &overlap,
|
Split the geometry information from weston_surface out into weston_view
The weston_surface structure is split into two structures:
* The weston_surface structure storres everything required for a
client-side or server-side surface. This includes buffers; callbacks;
backend private data; input, damage, and opaque regions; and a few other
bookkeeping bits.
* The weston_view structure represents an entity in the scenegraph and
storres all of the geometry information. This includes clip region,
alpha, position, and the transformation list as well as all of the
temporary information derived from the geometry state. Because a view,
and not a surface, is a scenegraph element, the view is what is placed
in layers and planes.
There are a few things worth noting about the surface/view split:
1. This is *not* a modification to the protocol. It is, instead, a
modification to Weston's internal scenegraph to allow a single surface
to exist in multiple places at a time. Clients are completely unaware
of how many views to a particular surface exist.
2. A view is considered a direct child of a surface and is destroyed when
the surface is destroyed. Because of this, the view.surface pointer is
always valid and non-null.
3. The compositor's surface_list is replaced with a view_list. Due to
subsurfaces, building the view list is a little more complicated than
it used to be and involves building a tree of views on the fly whenever
subsurfaces are used. However, this means that backends can remain
completely subsurface-agnostic.
4. Surfaces and views both keep track of which outputs they are on.
5. The weston_surface structure now has width and height fields. These
are populated when a new buffer is attached before surface.configure
is called. This is because there are many surface-based operations
that really require the width and height and digging through the views
didn't work well.
Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
11 years ago
|
|
|
&ev->transform.boundingbox);
|
|
|
|
|
|
|
|
if (next_plane == primary ||
|
|
|
|
next_plane == &output->cursor_plane) {
|
|
|
|
/* cursor plane involves a copy */
|
|
|
|
ev->psf_flags = 0;
|
|
|
|
} else {
|
|
|
|
/* All other planes are a direct scanout of a
|
|
|
|
* single client buffer.
|
|
|
|
*/
|
|
|
|
ev->psf_flags = WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY;
|
|
|
|
}
|
|
|
|
|
|
|
|
pixman_region32_fini(&surface_overlap);
|
|
|
|
}
|
|
|
|
pixman_region32_fini(&overlap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the closest-matching mode for a given target
|
|
|
|
*
|
|
|
|
* Given a target mode, find the most suitable mode amongst the output's
|
|
|
|
* current mode list to use, preferring the current mode if possible, to
|
|
|
|
* avoid an expensive mode switch.
|
|
|
|
*
|
|
|
|
* @param output DRM output
|
|
|
|
* @param target_mode Mode to attempt to match
|
|
|
|
* @returns Pointer to a mode from the output's mode list
|
|
|
|
*/
|
|
|
|
static struct drm_mode *
|
|
|
|
choose_mode (struct drm_output *output, struct weston_mode *target_mode)
|
|
|
|
{
|
|
|
|
struct drm_mode *tmp_mode = NULL, *mode;
|
|
|
|
|
|
|
|
if (output->base.current_mode->width == target_mode->width &&
|
|
|
|
output->base.current_mode->height == target_mode->height &&
|
|
|
|
(output->base.current_mode->refresh == target_mode->refresh ||
|
|
|
|
target_mode->refresh == 0))
|
|
|
|
return (struct drm_mode *)output->base.current_mode;
|
|
|
|
|
|
|
|
wl_list_for_each(mode, &output->base.mode_list, base.link) {
|
|
|
|
if (mode->mode_info.hdisplay == target_mode->width &&
|
|
|
|
mode->mode_info.vdisplay == target_mode->height) {
|
|
|
|
if (mode->base.refresh == target_mode->refresh ||
|
|
|
|
target_mode->refresh == 0) {
|
|
|
|
return mode;
|
|
|
|
} else if (!tmp_mode)
|
|
|
|
tmp_mode = mode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmp_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_init_egl(struct drm_output *output, struct drm_backend *b);
|
|
|
|
static void
|
|
|
|
drm_output_fini_egl(struct drm_output *output);
|
|
|
|
static int
|
|
|
|
drm_output_init_pixman(struct drm_output *output, struct drm_backend *b);
|
|
|
|
static void
|
|
|
|
drm_output_fini_pixman(struct drm_output *output);
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
struct drm_mode *drm_mode;
|
|
|
|
struct drm_backend *b;
|
|
|
|
|
|
|
|
if (output_base == NULL) {
|
|
|
|
weston_log("output is NULL.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == NULL) {
|
|
|
|
weston_log("mode is NULL.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
b = to_drm_backend(output_base->compositor);
|
|
|
|
output = to_drm_output(output_base);
|
|
|
|
drm_mode = choose_mode (output, mode);
|
|
|
|
|
|
|
|
if (!drm_mode) {
|
|
|
|
weston_log("%s, invalid resolution:%dx%d\n", __func__, mode->width, mode->height);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (&drm_mode->base == output->base.current_mode)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
output->base.current_mode->flags = 0;
|
|
|
|
|
|
|
|
output->base.current_mode = &drm_mode->base;
|
|
|
|
output->base.current_mode->flags =
|
|
|
|
WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED;
|
|
|
|
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
/* XXX: This drops our current buffer too early, before we've started
|
|
|
|
* displaying it. Ideally this should be much more atomic and
|
|
|
|
* integrated with a full repaint cycle, rather than doing a
|
|
|
|
* sledgehammer modeswitch first, and only later showing new
|
|
|
|
* content.
|
|
|
|
*/
|
|
|
|
drm_fb_unref(output->fb_current);
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
assert(!output->fb_last);
|
|
|
|
assert(!output->fb_pending);
|
|
|
|
output->fb_last = output->fb_current = NULL;
|
|
|
|
|
|
|
|
if (b->use_pixman) {
|
|
|
|
drm_output_fini_pixman(output);
|
|
|
|
if (drm_output_init_pixman(output, b) < 0) {
|
|
|
|
weston_log("failed to init output pixman state with "
|
|
|
|
"new mode\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
drm_output_fini_egl(output);
|
|
|
|
if (drm_output_init_egl(output, b) < 0) {
|
|
|
|
weston_log("failed to init output egl state with "
|
|
|
|
"new mode");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
on_drm_input(int fd, uint32_t mask, void *data)
|
|
|
|
{
|
|
|
|
drmEventContext evctx;
|
|
|
|
|
|
|
|
memset(&evctx, 0, sizeof evctx);
|
|
|
|
evctx.version = 2;
|
|
|
|
evctx.page_flip_handler = page_flip_handler;
|
|
|
|
evctx.vblank_handler = vblank_handler;
|
|
|
|
drmHandleEvent(fd, &evctx);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init_kms_caps(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
uint64_t cap;
|
|
|
|
int ret;
|
|
|
|
clockid_t clk_id;
|
|
|
|
|
|
|
|
weston_log("using %s\n", b->drm.filename);
|
|
|
|
|
|
|
|
ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap);
|
|
|
|
if (ret == 0 && cap == 1)
|
|
|
|
clk_id = CLOCK_MONOTONIC;
|
|
|
|
else
|
|
|
|
clk_id = CLOCK_REALTIME;
|
|
|
|
|
|
|
|
if (weston_compositor_set_presentation_clock(b->compositor, clk_id) < 0) {
|
|
|
|
weston_log("Error: failed to set presentation clock %d.\n",
|
|
|
|
clk_id);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap);
|
|
|
|
if (ret == 0)
|
|
|
|
b->cursor_width = cap;
|
|
|
|
else
|
|
|
|
b->cursor_width = 64;
|
|
|
|
|
|
|
|
ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap);
|
|
|
|
if (ret == 0)
|
|
|
|
b->cursor_height = cap;
|
|
|
|
else
|
|
|
|
b->cursor_height = 64;
|
|
|
|
|
|
|
|
if (!getenv("WESTON_DISABLE_UNIVERSAL_PLANES")) {
|
|
|
|
ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
|
|
|
b->universal_planes = (ret == 0);
|
|
|
|
}
|
|
|
|
weston_log("DRM: %s universal planes\n",
|
|
|
|
b->universal_planes ? "supports" : "does not support");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct gbm_device *
|
|
|
|
create_gbm_device(int fd)
|
|
|
|
{
|
|
|
|
struct gbm_device *gbm;
|
|
|
|
|
|
|
|
gl_renderer = weston_load_module("gl-renderer.so",
|
|
|
|
"gl_renderer_interface");
|
|
|
|
if (!gl_renderer)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* GBM will load a dri driver, but even though they need symbols from
|
|
|
|
* libglapi, in some version of Mesa they are not linked to it. Since
|
|
|
|
* only the gl-renderer module links to it, the call above won't make
|
|
|
|
* these symbols globally available, and loading the DRI driver fails.
|
|
|
|
* Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. */
|
|
|
|
dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL);
|
|
|
|
|
|
|
|
gbm = gbm_create_device(fd);
|
|
|
|
|
|
|
|
return gbm;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When initializing EGL, if the preferred buffer format isn't available
|
|
|
|
* we may be able to substitute an ARGB format for an XRGB one.
|
|
|
|
*
|
|
|
|
* This returns 0 if substitution isn't possible, but 0 might be a
|
|
|
|
* legitimate format for other EGL platforms, so the caller is
|
|
|
|
* responsible for checking for 0 before calling gl_renderer->create().
|
|
|
|
*
|
|
|
|
* This works around https://bugs.freedesktop.org/show_bug.cgi?id=89689
|
|
|
|
* but it's entirely possible we'll see this again on other implementations.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
fallback_format_for(uint32_t format)
|
|
|
|
{
|
|
|
|
switch (format) {
|
|
|
|
case GBM_FORMAT_XRGB8888:
|
|
|
|
return GBM_FORMAT_ARGB8888;
|
|
|
|
case GBM_FORMAT_XRGB2101010:
|
|
|
|
return GBM_FORMAT_ARGB2101010;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_backend_create_gl_renderer(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
EGLint format[3] = {
|
|
|
|
b->gbm_format,
|
|
|
|
fallback_format_for(b->gbm_format),
|
|
|
|
0,
|
|
|
|
};
|
|
|
|
int n_formats = 2;
|
|
|
|
|
|
|
|
if (format[1])
|
|
|
|
n_formats = 3;
|
|
|
|
if (gl_renderer->display_create(b->compositor,
|
|
|
|
EGL_PLATFORM_GBM_KHR,
|
|
|
|
(void *)b->gbm,
|
|
|
|
NULL,
|
|
|
|
gl_renderer->opaque_attribs,
|
|
|
|
format,
|
|
|
|
n_formats) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init_egl(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
b->gbm = create_gbm_device(b->drm.fd);
|
|
|
|
|
|
|
|
if (!b->gbm)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (drm_backend_create_gl_renderer(b) < 0) {
|
|
|
|
gbm_device_destroy(b->gbm);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
init_pixman(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
return pixman_renderer_init(b->compositor);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a drm_plane for a hardware plane
|
|
|
|
*
|
|
|
|
* Creates one drm_plane structure for a hardware plane, and initialises its
|
|
|
|
* properties and formats.
|
|
|
|
*
|
|
|
|
* This function does not add the plane to the list of usable planes in Weston
|
|
|
|
* itself; the caller is responsible for this.
|
|
|
|
*
|
|
|
|
* Call drm_plane_destroy to clean up the plane.
|
|
|
|
*
|
|
|
|
* @param b DRM compositor backend
|
|
|
|
* @param kplane DRM plane to create
|
|
|
|
*/
|
|
|
|
static struct drm_plane *
|
|
|
|
drm_plane_create(struct drm_backend *b, const drmModePlane *kplane)
|
|
|
|
{
|
|
|
|
struct drm_plane *plane;
|
|
|
|
drmModeObjectProperties *props;
|
|
|
|
|
|
|
|
static struct drm_property_enum_info plane_type_enums[] = {
|
|
|
|
[WDRM_PLANE_TYPE_PRIMARY] = {
|
|
|
|
.name = "Primary",
|
|
|
|
},
|
|
|
|
[WDRM_PLANE_TYPE_OVERLAY] = {
|
|
|
|
.name = "Overlay",
|
|
|
|
},
|
|
|
|
[WDRM_PLANE_TYPE_CURSOR] = {
|
|
|
|
.name = "Cursor",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
static const struct drm_property_info plane_props[] = {
|
|
|
|
[WDRM_PLANE_TYPE] = {
|
|
|
|
.name = "type",
|
|
|
|
.enum_values = plane_type_enums,
|
|
|
|
.num_enum_values = WDRM_PLANE_TYPE__COUNT,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
plane = zalloc(sizeof(*plane) + ((sizeof(uint32_t)) *
|
|
|
|
kplane->count_formats));
|
|
|
|
if (!plane) {
|
|
|
|
weston_log("%s: out of memory\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
plane->backend = b;
|
|
|
|
plane->possible_crtcs = kplane->possible_crtcs;
|
|
|
|
plane->plane_id = kplane->plane_id;
|
|
|
|
plane->count_formats = kplane->count_formats;
|
|
|
|
memcpy(plane->formats, kplane->formats,
|
|
|
|
kplane->count_formats * sizeof(kplane->formats[0]));
|
|
|
|
|
|
|
|
props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id,
|
|
|
|
DRM_MODE_OBJECT_PLANE);
|
|
|
|
if (!props) {
|
|
|
|
weston_log("couldn't get plane properties\n");
|
|
|
|
free(plane);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
drm_property_info_populate(b, plane_props, plane->props,
|
|
|
|
WDRM_PLANE__COUNT, props);
|
|
|
|
plane->type =
|
|
|
|
drm_property_get_value(&plane->props[WDRM_PLANE_TYPE],
|
|
|
|
props,
|
|
|
|
WDRM_PLANE_TYPE_OVERLAY);
|
|
|
|
drmModeFreeObjectProperties(props);
|
|
|
|
|
|
|
|
weston_plane_init(&plane->base, b->compositor, 0, 0);
|
|
|
|
wl_list_insert(&b->plane_list, &plane->link);
|
|
|
|
|
|
|
|
return plane;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy one DRM plane
|
|
|
|
*
|
|
|
|
* Destroy a DRM plane, removing it from screen and releasing its retained
|
|
|
|
* buffers in the process. The counterpart to drm_plane_create.
|
|
|
|
*
|
|
|
|
* @param plane Plane to deallocate (will be freed)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
drm_plane_destroy(struct drm_plane *plane)
|
|
|
|
{
|
|
|
|
drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0);
|
|
|
|
assert(!plane->fb_last);
|
|
|
|
assert(!plane->fb_pending);
|
|
|
|
drm_property_info_free(plane->props, WDRM_PLANE__COUNT);
|
|
|
|
drm_fb_unref(plane->fb_current);
|
|
|
|
weston_plane_release(&plane->base);
|
|
|
|
wl_list_remove(&plane->link);
|
|
|
|
free(plane);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialise sprites (overlay planes)
|
|
|
|
*
|
|
|
|
* Walk the list of provided DRM planes, and add overlay planes.
|
|
|
|
*
|
|
|
|
* Call destroy_sprites to free these planes.
|
|
|
|
*
|
|
|
|
* @param b DRM compositor backend
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
create_sprites(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
drmModePlaneRes *kplane_res;
|
|
|
|
drmModePlane *kplane;
|
|
|
|
struct drm_plane *drm_plane;
|
|
|
|
uint32_t i;
|
|
|
|
|
|
|
|
kplane_res = drmModeGetPlaneResources(b->drm.fd);
|
|
|
|
if (!kplane_res) {
|
|
|
|
weston_log("failed to get plane resources: %s\n",
|
|
|
|
strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < kplane_res->count_planes; i++) {
|
|
|
|
kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]);
|
|
|
|
if (!kplane)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
drm_plane = drm_plane_create(b, kplane);
|
|
|
|
drmModeFreePlane(kplane);
|
|
|
|
if (!drm_plane)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY)
|
|
|
|
weston_compositor_stack_plane(b->compositor,
|
|
|
|
&drm_plane->base,
|
|
|
|
&b->compositor->primary_plane);
|
|
|
|
}
|
|
|
|
|
|
|
|
drmModeFreePlaneResources(kplane_res);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up sprites (overlay planes)
|
|
|
|
*
|
|
|
|
* The counterpart to create_sprites.
|
|
|
|
*
|
|
|
|
* @param b DRM compositor backend
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
destroy_sprites(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
struct drm_plane *plane, *next;
|
|
|
|
|
|
|
|
wl_list_for_each_safe(plane, next, &b->plane_list, link)
|
|
|
|
drm_plane_destroy(plane);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a mode to output's mode list
|
|
|
|
*
|
|
|
|
* Copy the supplied DRM mode into a Weston mode structure, and add it to the
|
|
|
|
* output's mode list.
|
|
|
|
*
|
|
|
|
* @param output DRM output to add mode to
|
|
|
|
* @param info DRM mode structure to add
|
|
|
|
* @returns Newly-allocated Weston/DRM mode structure
|
|
|
|
*/
|
|
|
|
static struct drm_mode *
|
|
|
|
drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info)
|
|
|
|
{
|
|
|
|
struct drm_mode *mode;
|
|
|
|
uint64_t refresh;
|
|
|
|
|
|
|
|
mode = malloc(sizeof *mode);
|
|
|
|
if (mode == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
mode->base.flags = 0;
|
|
|
|
mode->base.width = info->hdisplay;
|
|
|
|
mode->base.height = info->vdisplay;
|
|
|
|
|
|
|
|
/* Calculate higher precision (mHz) refresh rate */
|
|
|
|
refresh = (info->clock * 1000000LL / info->htotal +
|
|
|
|
info->vtotal / 2) / info->vtotal;
|
|
|
|
|
|
|
|
if (info->flags & DRM_MODE_FLAG_INTERLACE)
|
|
|
|
refresh *= 2;
|
|
|
|
if (info->flags & DRM_MODE_FLAG_DBLSCAN)
|
|
|
|
refresh /= 2;
|
|
|
|
if (info->vscan > 1)
|
|
|
|
refresh /= info->vscan;
|
|
|
|
|
|
|
|
mode->base.refresh = refresh;
|
|
|
|
mode->mode_info = *info;
|
|
|
|
|
|
|
|
if (info->type & DRM_MODE_TYPE_PREFERRED)
|
|
|
|
mode->base.flags |= WL_OUTPUT_MODE_PREFERRED;
|
|
|
|
|
|
|
|
wl_list_insert(output->base.mode_list.prev, &mode->base.link);
|
|
|
|
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_subpixel_to_wayland(int drm_value)
|
|
|
|
{
|
|
|
|
switch (drm_value) {
|
|
|
|
default:
|
|
|
|
case DRM_MODE_SUBPIXEL_UNKNOWN:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_UNKNOWN;
|
|
|
|
case DRM_MODE_SUBPIXEL_NONE:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_NONE;
|
|
|
|
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB;
|
|
|
|
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR;
|
|
|
|
case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB;
|
|
|
|
case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
|
|
|
|
return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns a value between 0-255 range, where higher is brighter */
|
|
|
|
static uint32_t
|
|
|
|
drm_get_backlight(struct drm_output *output)
|
|
|
|
{
|
|
|
|
long brightness, max_brightness, norm;
|
|
|
|
|
|
|
|
brightness = backlight_get_brightness(output->backlight);
|
|
|
|
max_brightness = backlight_get_max_brightness(output->backlight);
|
|
|
|
|
|
|
|
/* convert it on a scale of 0 to 255 */
|
|
|
|
norm = (brightness * 255)/(max_brightness);
|
|
|
|
|
|
|
|
return (uint32_t) norm;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* values accepted are between 0-255 range */
|
|
|
|
static void
|
|
|
|
drm_set_backlight(struct weston_output *output_base, uint32_t value)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
|
|
|
long max_brightness, new_brightness;
|
|
|
|
|
|
|
|
if (!output->backlight)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (value > 255)
|
|
|
|
return;
|
|
|
|
|
|
|
|
max_brightness = backlight_get_max_brightness(output->backlight);
|
|
|
|
|
|
|
|
/* get denormalized value */
|
|
|
|
new_brightness = (value * max_brightness) / 255;
|
|
|
|
|
|
|
|
backlight_set_brightness(output->backlight, new_brightness);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_set_dpms(struct weston_output *output_base, enum dpms_enum level)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(output_base);
|
|
|
|
struct weston_compositor *ec = output_base->compositor;
|
|
|
|
struct drm_backend *b = to_drm_backend(ec);
|
|
|
|
struct drm_property_info *prop =
|
|
|
|
&output->props_conn[WDRM_CONNECTOR_DPMS];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!prop->prop_id)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = drmModeConnectorSetProperty(b->drm.fd, output->connector_id,
|
|
|
|
prop->prop_id, level);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("DRM: DPMS: failed property set for %s\n",
|
|
|
|
output->base.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
output->dpms = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char * const connector_type_names[] = {
|
|
|
|
[DRM_MODE_CONNECTOR_Unknown] = "Unknown",
|
|
|
|
[DRM_MODE_CONNECTOR_VGA] = "VGA",
|
|
|
|
[DRM_MODE_CONNECTOR_DVII] = "DVI-I",
|
|
|
|
[DRM_MODE_CONNECTOR_DVID] = "DVI-D",
|
|
|
|
[DRM_MODE_CONNECTOR_DVIA] = "DVI-A",
|
|
|
|
[DRM_MODE_CONNECTOR_Composite] = "Composite",
|
|
|
|
[DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO",
|
|
|
|
[DRM_MODE_CONNECTOR_LVDS] = "LVDS",
|
|
|
|
[DRM_MODE_CONNECTOR_Component] = "Component",
|
|
|
|
[DRM_MODE_CONNECTOR_9PinDIN] = "DIN",
|
|
|
|
[DRM_MODE_CONNECTOR_DisplayPort] = "DP",
|
|
|
|
[DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A",
|
|
|
|
[DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B",
|
|
|
|
[DRM_MODE_CONNECTOR_TV] = "TV",
|
|
|
|
[DRM_MODE_CONNECTOR_eDP] = "eDP",
|
|
|
|
#ifdef DRM_MODE_CONNECTOR_DSI
|
|
|
|
[DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual",
|
|
|
|
[DRM_MODE_CONNECTOR_DSI] = "DSI",
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Create a name given a DRM connector
|
|
|
|
*
|
|
|
|
* \param con The DRM connector whose type and id form the name.
|
|
|
|
* \return A newly allocate string, or NULL on error. Must be free()'d
|
|
|
|
* after use.
|
|
|
|
*
|
|
|
|
* The name does not identify the DRM display device.
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
make_connector_name(const drmModeConnector *con)
|
|
|
|
{
|
|
|
|
char *name;
|
|
|
|
const char *type_name = NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (con->connector_type < ARRAY_LENGTH(connector_type_names))
|
|
|
|
type_name = connector_type_names[con->connector_type];
|
|
|
|
|
|
|
|
if (!type_name)
|
|
|
|
type_name = "UNNAMED";
|
|
|
|
|
|
|
|
ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id);
|
|
|
|
if (ret < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
find_crtc_for_connector(struct drm_backend *b,
|
|
|
|
drmModeRes *resources, drmModeConnector *connector)
|
|
|
|
{
|
|
|
|
drmModeEncoder *encoder;
|
|
|
|
int i, j;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
for (j = 0; j < connector->count_encoders; j++) {
|
|
|
|
uint32_t possible_crtcs, encoder_id, crtc_id;
|
|
|
|
|
|
|
|
encoder = drmModeGetEncoder(b->drm.fd, connector->encoders[j]);
|
|
|
|
if (encoder == NULL) {
|
|
|
|
weston_log("Failed to get encoder.\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
encoder_id = encoder->encoder_id;
|
|
|
|
possible_crtcs = encoder->possible_crtcs;
|
|
|
|
crtc_id = encoder->crtc_id;
|
|
|
|
drmModeFreeEncoder(encoder);
|
|
|
|
|
|
|
|
for (i = 0; i < resources->count_crtcs; i++) {
|
|
|
|
if (!(possible_crtcs & (1 << i)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (drm_output_find_by_crtc(b, resources->crtcs[i]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Try to preserve the existing
|
|
|
|
* CRTC -> encoder -> connector routing; it makes
|
|
|
|
* initialisation faster, and also since we have a
|
|
|
|
* very dumb picking algorithm, may preserve a better
|
|
|
|
* choice. */
|
|
|
|
if (!connector->encoder_id ||
|
|
|
|
(encoder_id == connector->encoder_id &&
|
|
|
|
crtc_id == resources->crtcs[i]))
|
|
|
|
return i;
|
|
|
|
|
|
|
|
ret = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void drm_output_fini_cursor_egl(struct drm_output *output)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) {
|
|
|
|
drm_fb_unref(output->gbm_cursor_fb[i]);
|
|
|
|
output->gbm_cursor_fb[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) {
|
|
|
|
struct gbm_bo *bo;
|
|
|
|
|
|
|
|
bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height,
|
|
|
|
GBM_FORMAT_ARGB8888,
|
|
|
|
GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
|
|
|
|
if (!bo)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
output->gbm_cursor_fb[i] =
|
|
|
|
drm_fb_get_from_bo(bo, b, GBM_FORMAT_ARGB8888,
|
|
|
|
BUFFER_CURSOR);
|
|
|
|
if (!output->gbm_cursor_fb[i]) {
|
|
|
|
gbm_bo_destroy(bo);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
weston_log("cursor buffers unavailable, using gl cursors\n");
|
|
|
|
b->cursors_are_broken = 1;
|
|
|
|
drm_output_fini_cursor_egl(output);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Init output state that depends on gl or gbm */
|
|
|
|
static int
|
|
|
|
drm_output_init_egl(struct drm_output *output, struct drm_backend *b)
|
|
|
|
{
|
|
|
|
EGLint format[2] = {
|
|
|
|
output->gbm_format,
|
|
|
|
fallback_format_for(output->gbm_format),
|
|
|
|
};
|
|
|
|
int n_formats = 1;
|
|
|
|
|
|
|
|
output->gbm_surface = gbm_surface_create(b->gbm,
|
|
|
|
output->base.current_mode->width,
|
|
|
|
output->base.current_mode->height,
|
|
|
|
format[0],
|
|
|
|
GBM_BO_USE_SCANOUT |
|
|
|
|
GBM_BO_USE_RENDERING);
|
|
|
|
if (!output->gbm_surface) {
|
|
|
|
weston_log("failed to create gbm surface\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (format[1])
|
|
|
|
n_formats = 2;
|
|
|
|
if (gl_renderer->output_window_create(&output->base,
|
|
|
|
(EGLNativeWindowType)output->gbm_surface,
|
|
|
|
output->gbm_surface,
|
|
|
|
gl_renderer->opaque_attribs,
|
|
|
|
format,
|
|
|
|
n_formats) < 0) {
|
|
|
|
weston_log("failed to create gl renderer output state\n");
|
|
|
|
gbm_surface_destroy(output->gbm_surface);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
drm_output_init_cursor_egl(output, b);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_fini_egl(struct drm_output *output)
|
|
|
|
{
|
|
|
|
gl_renderer->output_destroy(&output->base);
|
|
|
|
gbm_surface_destroy(output->gbm_surface);
|
|
|
|
drm_output_fini_cursor_egl(output);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_init_pixman(struct drm_output *output, struct drm_backend *b)
|
|
|
|
{
|
|
|
|
int w = output->base.current_mode->width;
|
|
|
|
int h = output->base.current_mode->height;
|
|
|
|
uint32_t format = output->gbm_format;
|
|
|
|
uint32_t pixman_format;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
case GBM_FORMAT_XRGB8888:
|
|
|
|
pixman_format = PIXMAN_x8r8g8b8;
|
|
|
|
break;
|
|
|
|
case GBM_FORMAT_RGB565:
|
|
|
|
pixman_format = PIXMAN_r5g6b5;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
weston_log("Unsupported pixman format 0x%x\n", format);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME error checking */
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) {
|
|
|
|
output->dumb[i] = drm_fb_create_dumb(b, w, h, format);
|
|
|
|
if (!output->dumb[i])
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
output->image[i] =
|
|
|
|
pixman_image_create_bits(pixman_format, w, h,
|
|
|
|
output->dumb[i]->map,
|
|
|
|
output->dumb[i]->stride);
|
|
|
|
if (!output->image[i])
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pixman_renderer_output_create(&output->base) < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
pixman_region32_init_rect(&output->previous_damage,
|
|
|
|
output->base.x, output->base.y, output->base.width, output->base.height);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) {
|
|
|
|
if (output->dumb[i])
|
|
|
|
drm_fb_unref(output->dumb[i]);
|
|
|
|
if (output->image[i])
|
|
|
|
pixman_image_unref(output->image[i]);
|
|
|
|
|
|
|
|
output->dumb[i] = NULL;
|
|
|
|
output->image[i] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_fini_pixman(struct drm_output *output)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
pixman_renderer_output_destroy(&output->base);
|
|
|
|
pixman_region32_fini(&output->previous_damage);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) {
|
|
|
|
pixman_image_unref(output->image[i]);
|
|
|
|
drm_fb_unref(output->dumb[i]);
|
|
|
|
output->dumb[i] = NULL;
|
|
|
|
output->image[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
edid_parse_string(const uint8_t *data, char text[])
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int replaced = 0;
|
|
|
|
|
|
|
|
/* this is always 12 bytes, but we can't guarantee it's null
|
|
|
|
* terminated or not junk. */
|
|
|
|
strncpy(text, (const char *) data, 12);
|
|
|
|
|
|
|
|
/* guarantee our new string is null-terminated */
|
|
|
|
text[12] = '\0';
|
|
|
|
|
|
|
|
/* remove insane chars */
|
|
|
|
for (i = 0; text[i] != '\0'; i++) {
|
|
|
|
if (text[i] == '\n' ||
|
|
|
|
text[i] == '\r') {
|
|
|
|
text[i] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ensure string is printable */
|
|
|
|
for (i = 0; text[i] != '\0'; i++) {
|
|
|
|
if (!isprint(text[i])) {
|
|
|
|
text[i] = '-';
|
|
|
|
replaced++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if the string is random junk, ignore the string */
|
|
|
|
if (replaced > 4)
|
|
|
|
text[0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
|
|
|
|
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
|
|
|
|
#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
|
|
|
|
#define EDID_OFFSET_DATA_BLOCKS 0x36
|
|
|
|
#define EDID_OFFSET_LAST_BLOCK 0x6c
|
|
|
|
#define EDID_OFFSET_PNPID 0x08
|
|
|
|
#define EDID_OFFSET_SERIAL 0x0c
|
|
|
|
|
|
|
|
static int
|
|
|
|
edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
uint32_t serial_number;
|
|
|
|
|
|
|
|
/* check header */
|
|
|
|
if (length < 128)
|
|
|
|
return -1;
|
|
|
|
if (data[0] != 0x00 || data[1] != 0xff)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* decode the PNP ID from three 5 bit words packed into 2 bytes
|
|
|
|
* /--08--\/--09--\
|
|
|
|
* 7654321076543210
|
|
|
|
* |\---/\---/\---/
|
|
|
|
* R C1 C2 C3 */
|
|
|
|
edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
|
|
|
|
edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
|
|
|
|
edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
|
|
|
|
edid->pnp_id[3] = '\0';
|
|
|
|
|
|
|
|
/* maybe there isn't a ASCII serial number descriptor, so use this instead */
|
|
|
|
serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
|
|
|
|
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
|
|
|
|
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
|
|
|
|
serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
|
|
|
|
if (serial_number > 0)
|
|
|
|
sprintf(edid->serial_number, "%lu", (unsigned long) serial_number);
|
|
|
|
|
|
|
|
/* parse EDID data */
|
|
|
|
for (i = EDID_OFFSET_DATA_BLOCKS;
|
|
|
|
i <= EDID_OFFSET_LAST_BLOCK;
|
|
|
|
i += 18) {
|
|
|
|
/* ignore pixel clock data */
|
|
|
|
if (data[i] != 0)
|
|
|
|
continue;
|
|
|
|
if (data[i+2] != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* any useful blocks? */
|
|
|
|
if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
|
|
|
|
edid_parse_string(&data[i+5],
|
|
|
|
edid->monitor_name);
|
|
|
|
} else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
|
|
|
|
edid_parse_string(&data[i+5],
|
|
|
|
edid->serial_number);
|
|
|
|
} else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
|
|
|
|
edid_parse_string(&data[i+5],
|
|
|
|
edid->eisa_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Parse monitor make, model and serial from EDID
|
|
|
|
*
|
|
|
|
* \param b The backend instance.
|
|
|
|
* \param output The output whose \c drm_edid to fill in.
|
|
|
|
* \param props The DRM connector properties to get the EDID from.
|
|
|
|
* \param make[out] The monitor make (PNP ID).
|
|
|
|
* \param model[out] The monitor model (name).
|
|
|
|
* \param serial_number[out] The monitor serial number.
|
|
|
|
*
|
|
|
|
* Each of \c *make, \c *model and \c *serial_number are set only if the
|
|
|
|
* information is found in the EDID. The pointers they are set to must not
|
|
|
|
* be free()'d explicitly, instead they get implicitly freed when the
|
|
|
|
* \c drm_output is destroyed.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
find_and_parse_output_edid(struct drm_backend *b, struct drm_output *output,
|
|
|
|
drmModeObjectPropertiesPtr props,
|
|
|
|
const char **make,
|
|
|
|
const char **model,
|
|
|
|
const char **serial_number)
|
|
|
|
{
|
|
|
|
drmModePropertyBlobPtr edid_blob = NULL;
|
|
|
|
uint32_t blob_id;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
blob_id =
|
|
|
|
drm_property_get_value(&output->props_conn[WDRM_CONNECTOR_EDID],
|
|
|
|
props, 0);
|
|
|
|
if (!blob_id)
|
|
|
|
return;
|
|
|
|
|
|
|
|
edid_blob = drmModeGetPropertyBlob(b->drm.fd, blob_id);
|
|
|
|
if (!edid_blob)
|
|
|
|
return;
|
|
|
|
|
|
|
|
rc = edid_parse(&output->edid,
|
|
|
|
edid_blob->data,
|
|
|
|
edid_blob->length);
|
|
|
|
if (!rc) {
|
|
|
|
weston_log("EDID data '%s', '%s', '%s'\n",
|
|
|
|
output->edid.pnp_id,
|
|
|
|
output->edid.monitor_name,
|
|
|
|
output->edid.serial_number);
|
|
|
|
if (output->edid.pnp_id[0] != '\0')
|
|
|
|
*make = output->edid.pnp_id;
|
|
|
|
if (output->edid.monitor_name[0] != '\0')
|
|
|
|
*model = output->edid.monitor_name;
|
|
|
|
if (output->edid.serial_number[0] != '\0')
|
|
|
|
*serial_number = output->edid.serial_number;
|
|
|
|
}
|
|
|
|
drmModeFreePropertyBlob(edid_blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
parse_modeline(const char *s, drmModeModeInfo *mode)
|
|
|
|
{
|
|
|
|
char hsync[16];
|
|
|
|
char vsync[16];
|
|
|
|
float fclock;
|
|
|
|
|
|
|
|
mode->type = DRM_MODE_TYPE_USERDEF;
|
|
|
|
mode->hskew = 0;
|
|
|
|
mode->vscan = 0;
|
|
|
|
mode->vrefresh = 0;
|
|
|
|
mode->flags = 0;
|
|
|
|
|
|
|
|
if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
|
|
|
|
&fclock,
|
|
|
|
&mode->hdisplay,
|
|
|
|
&mode->hsync_start,
|
|
|
|
&mode->hsync_end,
|
|
|
|
&mode->htotal,
|
|
|
|
&mode->vdisplay,
|
|
|
|
&mode->vsync_start,
|
|
|
|
&mode->vsync_end,
|
|
|
|
&mode->vtotal, hsync, vsync) != 11)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
mode->clock = fclock * 1000;
|
|
|
|
if (strcmp(hsync, "+hsync") == 0)
|
|
|
|
mode->flags |= DRM_MODE_FLAG_PHSYNC;
|
|
|
|
else if (strcmp(hsync, "-hsync") == 0)
|
|
|
|
mode->flags |= DRM_MODE_FLAG_NHSYNC;
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (strcmp(vsync, "+vsync") == 0)
|
|
|
|
mode->flags |= DRM_MODE_FLAG_PVSYNC;
|
|
|
|
else if (strcmp(vsync, "-vsync") == 0)
|
|
|
|
mode->flags |= DRM_MODE_FLAG_NVSYNC;
|
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
snprintf(mode->name, sizeof mode->name, "%dx%d@%.3f",
|
|
|
|
mode->hdisplay, mode->vdisplay, fclock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
setup_output_seat_constraint(struct drm_backend *b,
|
|
|
|
struct weston_output *output,
|
|
|
|
const char *s)
|
|
|
|
{
|
|
|
|
if (strcmp(s, "") != 0) {
|
|
|
|
struct weston_pointer *pointer;
|
|
|
|
struct udev_seat *seat;
|
|
|
|
|
|
|
|
seat = udev_seat_get_named(&b->input, s);
|
|
|
|
if (!seat)
|
|
|
|
return;
|
|
|
|
|
|
|
|
seat->base.output = output;
|
|
|
|
|
|
|
|
pointer = weston_seat_get_pointer(&seat->base);
|
|
|
|
if (pointer)
|
|
|
|
weston_pointer_clamp(pointer,
|
|
|
|
&pointer->x,
|
|
|
|
&pointer->y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (s == NULL)
|
|
|
|
*gbm_format = default_value;
|
|
|
|
else if (strcmp(s, "xrgb8888") == 0)
|
|
|
|
*gbm_format = GBM_FORMAT_XRGB8888;
|
|
|
|
else if (strcmp(s, "rgb565") == 0)
|
|
|
|
*gbm_format = GBM_FORMAT_RGB565;
|
|
|
|
else if (strcmp(s, "xrgb2101010") == 0)
|
|
|
|
*gbm_format = GBM_FORMAT_XRGB2101010;
|
|
|
|
else {
|
|
|
|
weston_log("fatal: unrecognized pixel format: %s\n", s);
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Choose suitable mode for an output
|
|
|
|
*
|
|
|
|
* Find the most suitable mode to use for initial setup (or reconfiguration on
|
|
|
|
* hotplug etc) for a DRM output.
|
|
|
|
*
|
|
|
|
* @param output DRM output to choose mode for
|
|
|
|
* @param kind Strategy and preference to use when choosing mode
|
|
|
|
* @param width Desired width for this output
|
|
|
|
* @param height Desired height for this output
|
|
|
|
* @param current_mode Mode currently being displayed on this output
|
|
|
|
* @param modeline Manually-entered mode (may be NULL)
|
|
|
|
* @returns A mode from the output's mode list, or NULL if none available
|
|
|
|
*/
|
|
|
|
static struct drm_mode *
|
|
|
|
drm_output_choose_initial_mode(struct drm_backend *backend,
|
|
|
|
struct drm_output *output,
|
|
|
|
enum weston_drm_backend_output_mode mode,
|
|
|
|
const char *modeline,
|
|
|
|
const drmModeModeInfo *current_mode)
|
|
|
|
{
|
|
|
|
struct drm_mode *preferred = NULL;
|
|
|
|
struct drm_mode *current = NULL;
|
|
|
|
struct drm_mode *configured = NULL;
|
|
|
|
struct drm_mode *best = NULL;
|
|
|
|
struct drm_mode *drm_mode;
|
|
|
|
drmModeModeInfo drm_modeline;
|
|
|
|
int32_t width = 0;
|
|
|
|
int32_t height = 0;
|
|
|
|
uint32_t refresh = 0;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) {
|
|
|
|
n = sscanf(modeline, "%dx%d@%d", &width, &height, &refresh);
|
|
|
|
if (n != 2 && n != 3) {
|
|
|
|
width = -1;
|
|
|
|
|
|
|
|
if (parse_modeline(modeline, &drm_modeline) == 0) {
|
|
|
|
configured = drm_output_add_mode(output, &drm_modeline);
|
|
|
|
if (!configured)
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
weston_log("Invalid modeline \"%s\" for output %s\n",
|
|
|
|
modeline, output->base.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) {
|
|
|
|
if (width == drm_mode->base.width &&
|
|
|
|
height == drm_mode->base.height &&
|
|
|
|
(refresh == 0 || refresh == drm_mode->mode_info.vrefresh))
|
|
|
|
configured = drm_mode;
|
|
|
|
|
|
|
|
if (memcmp(current_mode, &drm_mode->mode_info,
|
|
|
|
sizeof *current_mode) == 0)
|
|
|
|
current = drm_mode;
|
|
|
|
|
|
|
|
if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED)
|
|
|
|
preferred = drm_mode;
|
|
|
|
|
|
|
|
best = drm_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current == NULL && current_mode->clock != 0) {
|
|
|
|
current = drm_output_add_mode(output, current_mode);
|
|
|
|
if (!current)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == WESTON_DRM_BACKEND_OUTPUT_CURRENT)
|
|
|
|
configured = current;
|
|
|
|
|
|
|
|
if (configured)
|
|
|
|
return configured;
|
|
|
|
|
|
|
|
if (preferred)
|
|
|
|
return preferred;
|
|
|
|
|
|
|
|
if (current)
|
|
|
|
return current;
|
|
|
|
|
|
|
|
if (best)
|
|
|
|
return best;
|
|
|
|
|
|
|
|
weston_log("no available modes for %s\n", output->base.name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
connector_get_current_mode(drmModeConnector *connector, int drm_fd,
|
|
|
|
drmModeModeInfo *mode)
|
|
|
|
{
|
|
|
|
drmModeEncoder *encoder;
|
|
|
|
drmModeCrtc *crtc;
|
|
|
|
|
|
|
|
/* Get the current mode on the crtc that's currently driving
|
|
|
|
* this connector. */
|
|
|
|
encoder = drmModeGetEncoder(drm_fd, connector->encoder_id);
|
|
|
|
memset(mode, 0, sizeof *mode);
|
|
|
|
if (encoder != NULL) {
|
|
|
|
crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id);
|
|
|
|
drmModeFreeEncoder(encoder);
|
|
|
|
if (crtc == NULL)
|
|
|
|
return -1;
|
|
|
|
if (crtc->mode_valid)
|
|
|
|
*mode = crtc->mode;
|
|
|
|
drmModeFreeCrtc(crtc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_set_mode(struct weston_output *base,
|
|
|
|
enum weston_drm_backend_output_mode mode,
|
|
|
|
const char *modeline)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
|
|
|
|
struct drm_mode *current;
|
|
|
|
drmModeModeInfo crtc_mode;
|
|
|
|
|
|
|
|
if (connector_get_current_mode(output->connector, b->drm.fd, &crtc_mode) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
current = drm_output_choose_initial_mode(b, output, mode, modeline, &crtc_mode);
|
|
|
|
if (!current)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
output->base.current_mode = ¤t->base;
|
|
|
|
output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT;
|
|
|
|
|
|
|
|
/* Set native_ fields, so weston_output_mode_switch_to_native() works */
|
|
|
|
output->base.native_mode = output->base.current_mode;
|
|
|
|
output->base.native_scale = output->base.current_scale;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_set_gbm_format(struct weston_output *base,
|
|
|
|
const char *gbm_format)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
|
|
|
|
if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1)
|
|
|
|
output->gbm_format = b->gbm_format;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_set_seat(struct weston_output *base,
|
|
|
|
const char *seat)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
|
|
|
|
setup_output_seat_constraint(b, &output->base,
|
|
|
|
seat ? seat : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_enable(struct weston_output *base)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
struct weston_mode *m;
|
|
|
|
|
|
|
|
if (b->pageflip_timeout)
|
|
|
|
drm_output_pageflip_timer_create(output);
|
|
|
|
|
|
|
|
if (b->use_pixman) {
|
|
|
|
if (drm_output_init_pixman(output, b) < 0) {
|
|
|
|
weston_log("Failed to init output pixman state\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
} else if (drm_output_init_egl(output, b) < 0) {
|
|
|
|
weston_log("Failed to init output gl state\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->backlight) {
|
|
|
|
weston_log("Initialized backlight, device %s\n",
|
|
|
|
output->backlight->path);
|
|
|
|
output->base.set_backlight = drm_set_backlight;
|
|
|
|
output->base.backlight_current = drm_get_backlight(output);
|
|
|
|
} else {
|
|
|
|
weston_log("Failed to initialize backlight\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
output->base.start_repaint_loop = drm_output_start_repaint_loop;
|
|
|
|
output->base.repaint = drm_output_repaint;
|
|
|
|
output->base.assign_planes = drm_assign_planes;
|
|
|
|
output->base.set_dpms = drm_set_dpms;
|
|
|
|
output->base.switch_mode = drm_output_switch_mode;
|
|
|
|
|
|
|
|
output->base.gamma_size = output->original_crtc->gamma_size;
|
|
|
|
output->base.set_gamma = drm_output_set_gamma;
|
|
|
|
|
|
|
|
weston_plane_init(&output->cursor_plane, b->compositor,
|
|
|
|
INT32_MIN, INT32_MIN);
|
|
|
|
weston_plane_init(&output->scanout_plane, b->compositor, 0, 0);
|
|
|
|
|
|
|
|
weston_compositor_stack_plane(b->compositor, &output->cursor_plane, NULL);
|
|
|
|
weston_compositor_stack_plane(b->compositor, &output->scanout_plane,
|
|
|
|
&b->compositor->primary_plane);
|
|
|
|
|
|
|
|
weston_log("Output %s, (connector %d, crtc %d)\n",
|
|
|
|
output->base.name, output->connector_id, output->crtc_id);
|
|
|
|
wl_list_for_each(m, &output->base.mode_list, link)
|
|
|
|
weston_log_continue(STAMP_SPACE "mode %dx%d@%.1f%s%s%s\n",
|
|
|
|
m->width, m->height, m->refresh / 1000.0,
|
|
|
|
m->flags & WL_OUTPUT_MODE_PREFERRED ?
|
|
|
|
", preferred" : "",
|
|
|
|
m->flags & WL_OUTPUT_MODE_CURRENT ?
|
|
|
|
", current" : "",
|
|
|
|
output->connector->count_modes == 0 ?
|
|
|
|
", built-in" : "");
|
|
|
|
|
|
|
|
output->state_invalid = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_deinit(struct weston_output *base)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
/* output->fb_last and output->fb_pending must not be set here;
|
|
|
|
* destroy_pending/disable_pending exist to guarantee exactly this. */
|
compositor-drm: Introduce fb_last member
Previously, framebuffers were stored as fb_current and fb_pending.
In this scheme, current was the last buffer that the kernel/hardware had
acknowledged displaying: a framebuffer would be created, set as
fb_pending, and Weston would request the kernel display it. When the
kernel signals that the request was completed and the hardware had made
the buffer current (i.e. page_flip_handler / vblank_handler), we would
unreference the old fb_current, and promote fb_pending to fb_current.
In other words, the view is 'which buffer has turned to light?'.
This patch changes them to a tristate of fb_last, fb_current and
fb_pending, based around the kernel's view of the current state.
fb_pending is used purely as a staging area for request construction;
when the kernel acknowledges a request (e.g. drmModePageFlip returns 0),
the previous buffer is moved to fb_last, and this new buffer to
fb_current. When the kernel signals that the request has completed and
the hardware has made the buffer current, we simply unreference and
clear fb_last, without touching fb_current/fb_pending.
The view here is now 'which state is current in the kernel?'.
As all state changes are incremental on the last state submitted to the
kernel, even if the hardware has not yet been able to make it current,
this simplifies state tracking: all state submissions will always be
relative to fb_current, rather than the previous
(fb_pending) ? fb_pending : fb_current.
The use of fb_pending is strictly bounded between a repaint cycle
(including a grouped set of repaints) beginning, and those repaints
being flushed to the kernel.
fb_current will always be valid between an output's first repaint
flush, and when a disable/destroy request has been processed. For a
plane, it will be valid when a repaint cycle enabling that plane has
been flushed, and when a repaint cycle disabling that plane has been
flushed.
fb_last is only present when a repaint request for the output/plane has
been submitted, but not yet completed by the hardware.
This is the same set of constructs which will be used for storing
plane/output state objects in future patches.
Signed-off-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
8 years ago
|
|
|
assert(!output->fb_last);
|
|
|
|
assert(!output->fb_pending);
|
|
|
|
drm_fb_unref(output->fb_current);
|
|
|
|
output->fb_current = NULL;
|
|
|
|
|
|
|
|
if (b->use_pixman)
|
|
|
|
drm_output_fini_pixman(output);
|
|
|
|
else
|
|
|
|
drm_output_fini_egl(output);
|
|
|
|
|
|
|
|
weston_plane_release(&output->scanout_plane);
|
|
|
|
weston_plane_release(&output->cursor_plane);
|
|
|
|
|
|
|
|
/* Turn off hardware cursor */
|
|
|
|
drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_output_destroy(struct weston_output *base)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
struct drm_mode *drm_mode, *next;
|
|
|
|
drmModeCrtcPtr origcrtc = output->original_crtc;
|
|
|
|
|
|
|
|
if (output->page_flip_pending) {
|
|
|
|
output->destroy_pending = 1;
|
|
|
|
weston_log("destroy output while page flip pending\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->base.enabled)
|
|
|
|
drm_output_deinit(&output->base);
|
|
|
|
|
|
|
|
wl_list_for_each_safe(drm_mode, next, &output->base.mode_list,
|
|
|
|
base.link) {
|
|
|
|
wl_list_remove(&drm_mode->base.link);
|
|
|
|
free(drm_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (origcrtc) {
|
|
|
|
/* Restore original CRTC state */
|
|
|
|
drmModeSetCrtc(b->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id,
|
|
|
|
origcrtc->x, origcrtc->y,
|
|
|
|
&output->connector_id, 1, &origcrtc->mode);
|
|
|
|
drmModeFreeCrtc(origcrtc);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->pageflip_timer)
|
|
|
|
wl_event_source_remove(output->pageflip_timer);
|
|
|
|
|
|
|
|
weston_output_release(&output->base);
|
|
|
|
|
|
|
|
drm_property_info_free(output->props_conn, WDRM_CONNECTOR__COUNT);
|
|
|
|
|
|
|
|
drmModeFreeConnector(output->connector);
|
|
|
|
|
|
|
|
if (output->backlight)
|
|
|
|
backlight_destroy(output->backlight);
|
|
|
|
|
|
|
|
free(output);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drm_output_disable(struct weston_output *base)
|
|
|
|
{
|
|
|
|
struct drm_output *output = to_drm_output(base);
|
|
|
|
struct drm_backend *b = to_drm_backend(base->compositor);
|
|
|
|
|
|
|
|
if (output->page_flip_pending) {
|
|
|
|
output->disable_pending = 1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output->base.enabled)
|
|
|
|
drm_output_deinit(&output->base);
|
|
|
|
|
|
|
|
output->disable_pending = 0;
|
|
|
|
|
|
|
|
weston_log("Disabling output %s\n", output->base.name);
|
|
|
|
drmModeSetCrtc(b->drm.fd, output->crtc_id,
|
|
|
|
0, 0, 0, 0, 0, NULL);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a Weston output structure
|
|
|
|
*
|
|
|
|
* Given a DRM connector, create a matching drm_output structure and add it
|
|
|
|
* to Weston's output list. It also takes ownership of the connector, which
|
|
|
|
* is released when output is destroyed.
|
|
|
|
*
|
|
|
|
* @param b Weston backend structure
|
|
|
|
* @param resources DRM resources for this device
|
|
|
|
* @param connector DRM connector to use for this new output
|
|
|
|
* @param drm_device udev device pointer
|
|
|
|
* @returns 0 on success, or -1 on failure
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
create_output_for_connector(struct drm_backend *b,
|
|
|
|
drmModeRes *resources,
|
|
|
|
drmModeConnector *connector,
|
|
|
|
struct udev_device *drm_device)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
drmModeObjectPropertiesPtr props;
|
|
|
|
struct drm_mode *drm_mode;
|
|
|
|
char *name;
|
|
|
|
const char *make = "unknown";
|
|
|
|
const char *model = "unknown";
|
|
|
|
const char *serial_number = "unknown";
|
|
|
|
int i;
|
|
|
|
|
|
|
|
static const struct drm_property_info connector_props[] = {
|
|
|
|
[WDRM_CONNECTOR_EDID] = { .name = "EDID" },
|
|
|
|
[WDRM_CONNECTOR_DPMS] = { .name = "DPMS" },
|
|
|
|
};
|
|
|
|
|
|
|
|
i = find_crtc_for_connector(b, resources, connector);
|
|
|
|
if (i < 0) {
|
|
|
|
weston_log("No usable crtc/encoder pair for connector.\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
output = zalloc(sizeof *output);
|
|
|
|
if (output == NULL)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
output->connector = connector;
|
|
|
|
output->crtc_id = resources->crtcs[i];
|
|
|
|
output->pipe = i;
|
|
|
|
output->connector_id = connector->connector_id;
|
|
|
|
|
|
|
|
output->backlight = backlight_init(drm_device,
|
|
|
|
connector->connector_type);
|
|
|
|
|
|
|
|
output->original_crtc = drmModeGetCrtc(b->drm.fd, output->crtc_id);
|
|
|
|
|
|
|
|
name = make_connector_name(connector);
|
|
|
|
weston_output_init(&output->base, b->compositor, name);
|
|
|
|
free(name);
|
|
|
|
|
|
|
|
output->base.enable = drm_output_enable;
|
|
|
|
output->base.destroy = drm_output_destroy;
|
|
|
|
output->base.disable = drm_output_disable;
|
|
|
|
|
|
|
|
output->destroy_pending = 0;
|
|
|
|
output->disable_pending = 0;
|
|
|
|
|
|
|
|
props = drmModeObjectGetProperties(b->drm.fd, connector->connector_id,
|
|
|
|
DRM_MODE_OBJECT_CONNECTOR);
|
|
|
|
if (!props) {
|
|
|
|
weston_log("failed to get connector properties\n");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
drm_property_info_populate(b, connector_props, output->props_conn,
|
|
|
|
WDRM_CONNECTOR__COUNT, props);
|
|
|
|
find_and_parse_output_edid(b, output, props,
|
|
|
|
&make, &model, &serial_number);
|
|
|
|
output->base.make = (char *)make;
|
|
|
|
output->base.model = (char *)model;
|
|
|
|
output->base.serial_number = (char *)serial_number;
|
|
|
|
output->base.subpixel = drm_subpixel_to_wayland(output->connector->subpixel);
|
|
|
|
|
|
|
|
if (output->connector->connector_type == DRM_MODE_CONNECTOR_LVDS ||
|
|
|
|
output->connector->connector_type == DRM_MODE_CONNECTOR_eDP)
|
|
|
|
output->base.connection_internal = true;
|
|
|
|
|
|
|
|
output->base.mm_width = output->connector->mmWidth;
|
|
|
|
output->base.mm_height = output->connector->mmHeight;
|
|
|
|
|
|
|
|
drmModeFreeObjectProperties(props);
|
|
|
|
|
|
|
|
for (i = 0; i < output->connector->count_modes; i++) {
|
|
|
|
drm_mode = drm_output_add_mode(output, &output->connector->modes[i]);
|
|
|
|
if (!drm_mode) {
|
|
|
|
drm_output_destroy(&output->base);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
weston_compositor_add_pending_output(&output->base, b->compositor);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
drmModeFreeConnector(connector);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
create_outputs(struct drm_backend *b, struct udev_device *drm_device)
|
|
|
|
{
|
|
|
|
drmModeConnector *connector;
|
|
|
|
drmModeRes *resources;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
resources = drmModeGetResources(b->drm.fd);
|
|
|
|
if (!resources) {
|
|
|
|
weston_log("drmModeGetResources failed\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
b->min_width = resources->min_width;
|
|
|
|
b->max_width = resources->max_width;
|
|
|
|
b->min_height = resources->min_height;
|
|
|
|
b->max_height = resources->max_height;
|
|
|
|
|
|
|
|
for (i = 0; i < resources->count_connectors; i++) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
connector = drmModeGetConnector(b->drm.fd,
|
|
|
|
resources->connectors[i]);
|
|
|
|
if (connector == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (connector->connection == DRM_MODE_CONNECTED) {
|
|
|
|
ret = create_output_for_connector(b, resources,
|
|
|
|
connector, drm_device);
|
|
|
|
if (ret < 0)
|
|
|
|
weston_log("failed to create new connector\n");
|
|
|
|
} else {
|
|
|
|
drmModeFreeConnector(connector);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wl_list_empty(&b->compositor->output_list) &&
|
|
|
|
wl_list_empty(&b->compositor->pending_output_list))
|
|
|
|
weston_log("No currently active connector found.\n");
|
|
|
|
|
|
|
|
drmModeFreeResources(resources);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
update_outputs(struct drm_backend *b, struct udev_device *drm_device)
|
|
|
|
{
|
|
|
|
drmModeConnector *connector;
|
|
|
|
drmModeRes *resources;
|
|
|
|
struct drm_output *output, *next;
|
|
|
|
uint32_t *connected;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
resources = drmModeGetResources(b->drm.fd);
|
|
|
|
if (!resources) {
|
|
|
|
weston_log("drmModeGetResources failed\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
connected = calloc(resources->count_connectors, sizeof(uint32_t));
|
|
|
|
if (!connected) {
|
|
|
|
drmModeFreeResources(resources);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* collect new connects */
|
|
|
|
for (i = 0; i < resources->count_connectors; i++) {
|
|
|
|
uint32_t connector_id = resources->connectors[i];
|
|
|
|
|
|
|
|
connector = drmModeGetConnector(b->drm.fd, connector_id);
|
|
|
|
if (connector == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (connector->connection != DRM_MODE_CONNECTED) {
|
|
|
|
drmModeFreeConnector(connector);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
connected[i] = connector_id;
|
|
|
|
|
|
|
|
if (drm_output_find_by_connector(b, connector_id)) {
|
|
|
|
drmModeFreeConnector(connector);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
create_output_for_connector(b, resources,
|
|
|
|
connector, drm_device);
|
|
|
|
weston_log("connector %d connected\n", connector_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each_safe(output, next, &b->compositor->output_list,
|
|
|
|
base.link) {
|
|
|
|
bool disconnected = true;
|
|
|
|
|
|
|
|
for (i = 0; i < resources->count_connectors; i++) {
|
|
|
|
if (connected[i] == output->connector_id) {
|
|
|
|
disconnected = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!disconnected)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
weston_log("connector %d disconnected\n", output->connector_id);
|
|
|
|
drm_output_destroy(&output->base);
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each_safe(output, next, &b->compositor->pending_output_list,
|
|
|
|
base.link) {
|
|
|
|
bool disconnected = true;
|
|
|
|
|
|
|
|
for (i = 0; i < resources->count_connectors; i++) {
|
|
|
|
if (connected[i] == output->connector_id) {
|
|
|
|
disconnected = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!disconnected)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
weston_log("connector %d disconnected\n", output->connector_id);
|
|
|
|
drm_output_destroy(&output->base);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(connected);
|
|
|
|
drmModeFreeResources(resources);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device)
|
|
|
|
{
|
|
|
|
const char *sysnum;
|
|
|
|
const char *val;
|
|
|
|
|
|
|
|
sysnum = udev_device_get_sysnum(device);
|
|
|
|
if (!sysnum || atoi(sysnum) != b->drm.id)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
val = udev_device_get_property_value(device, "HOTPLUG");
|
|
|
|
if (!val)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return strcmp(val, "1") == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
udev_drm_event(int fd, uint32_t mask, void *data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = data;
|
|
|
|
struct udev_device *event;
|
|
|
|
|
|
|
|
event = udev_monitor_receive_device(b->udev_monitor);
|
|
|
|
|
|
|
|
if (udev_event_is_hotplug(b, event))
|
|
|
|
update_outputs(b, event);
|
|
|
|
|
|
|
|
udev_device_unref(event);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drm_restore(struct weston_compositor *ec)
|
|
|
|
{
|
|
|
|
weston_launcher_restore(ec->launcher);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
Rename wayland-compositor to weston
This rename addresses a few problems around the split between core
Wayland and the wayland-demos repository.
1) Initially, we had one big repository with protocol code, sample
compositor and sample clients. We split that repository to make it
possible to implement the protocol without pulling in the sample/demo
code. At this point, the compositor is more than just a "demo" and
wayland-demos doesn't send the right message. The sample compositor
is a useful, self-contained project in it's own right, and we want to
move away from the "demos" label.
2) Another problem is that the wayland-demos compositor is often
called "the wayland compsitor", but it's really just one possible
compositor. Existing X11 compositors are expected to add Wayland
support and then gradually phase out/modularize the X11 support, for
example. Conversely, it's hard to talk about the wayland-demos
compositor specifically as opposed to, eg, the wayland protocol or a
wayland compositor in general.
We are also renaming the repo to weston, and the compositor
subdirectory to src/, to emphasize that the main "output" is the
compositor.
13 years ago
|
|
|
drm_destroy(struct weston_compositor *ec)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = to_drm_backend(ec);
|
|
|
|
|
|
|
|
udev_input_destroy(&b->input);
|
|
|
|
|
|
|
|
wl_event_source_remove(b->udev_drm_source);
|
|
|
|
wl_event_source_remove(b->drm_source);
|
|
|
|
|
|
|
|
destroy_sprites(b);
|
|
|
|
|
|
|
|
weston_compositor_shutdown(ec);
|
|
|
|
|
|
|
|
if (b->gbm)
|
|
|
|
gbm_device_destroy(b->gbm);
|
|
|
|
|
|
|
|
udev_unref(b->udev);
|
|
|
|
|
|
|
|
weston_launcher_destroy(ec->launcher);
|
|
|
|
|
|
|
|
close(b->drm.fd);
|
|
|
|
free(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
session_notify(struct wl_listener *listener, void *data)
|
|
|
|
{
|
|
|
|
struct weston_compositor *compositor = data;
|
|
|
|
struct drm_backend *b = to_drm_backend(compositor);
|
|
|
|
struct drm_plane *plane;
|
|
|
|
struct drm_output *output;
|
|
|
|
|
|
|
|
if (compositor->session_active) {
|
|
|
|
weston_log("activating session\n");
|
|
|
|
weston_compositor_wake(compositor);
|
Rename wayland-compositor to weston
This rename addresses a few problems around the split between core
Wayland and the wayland-demos repository.
1) Initially, we had one big repository with protocol code, sample
compositor and sample clients. We split that repository to make it
possible to implement the protocol without pulling in the sample/demo
code. At this point, the compositor is more than just a "demo" and
wayland-demos doesn't send the right message. The sample compositor
is a useful, self-contained project in it's own right, and we want to
move away from the "demos" label.
2) Another problem is that the wayland-demos compositor is often
called "the wayland compsitor", but it's really just one possible
compositor. Existing X11 compositors are expected to add Wayland
support and then gradually phase out/modularize the X11 support, for
example. Conversely, it's hard to talk about the wayland-demos
compositor specifically as opposed to, eg, the wayland protocol or a
wayland compositor in general.
We are also renaming the repo to weston, and the compositor
subdirectory to src/, to emphasize that the main "output" is the
compositor.
13 years ago
|
|
|
weston_compositor_damage_all(compositor);
|
|
|
|
|
|
|
|
wl_list_for_each(output, &compositor->output_list, base.link)
|
|
|
|
output->state_invalid = true;
|
|
|
|
|
|
|
|
udev_input_enable(&b->input);
|
|
|
|
} else {
|
|
|
|
weston_log("deactivating session\n");
|
|
|
|
udev_input_disable(&b->input);
|
|
|
|
|
|
|
|
weston_compositor_offscreen(compositor);
|
|
|
|
|
|
|
|
/* If we have a repaint scheduled (either from a
|
|
|
|
* pending pageflip or the idle handler), make sure we
|
|
|
|
* cancel that so we don't try to pageflip when we're
|
|
|
|
* vt switched away. The OFFSCREEN state will prevent
|
|
|
|
* further attempts at repainting. When we switch
|
|
|
|
* back, we schedule a repaint, which will process
|
|
|
|
* pending frame callbacks. */
|
|
|
|
|
|
|
|
wl_list_for_each(output, &compositor->output_list, base.link) {
|
|
|
|
output->base.repaint_needed = false;
|
|
|
|
drmModeSetCursor(b->drm.fd, output->crtc_id, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
output = container_of(compositor->output_list.next,
|
|
|
|
struct drm_output, base.link);
|
|
|
|
|
|
|
|
wl_list_for_each(plane, &b->plane_list, link) {
|
|
|
|
if (plane->type != WDRM_PLANE_TYPE_OVERLAY)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
drmModeSetPlane(b->drm.fd,
|
|
|
|
plane->plane_id,
|
|
|
|
output->crtc_id, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether or not a device is capable of modesetting. If successful,
|
|
|
|
* sets b->drm.fd and b->drm.filename to the opened device.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
drm_device_is_kms(struct drm_backend *b, struct udev_device *device)
|
|
|
|
{
|
|
|
|
const char *filename = udev_device_get_devnode(device);
|
|
|
|
const char *sysnum = udev_device_get_sysnum(device);
|
|
|
|
drmModeRes *res;
|
|
|
|
int id, fd;
|
|
|
|
|
|
|
|
if (!filename)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR);
|
|
|
|
if (fd < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
res = drmModeGetResources(fd);
|
|
|
|
if (!res)
|
|
|
|
goto out_fd;
|
|
|
|
|
|
|
|
if (res->count_crtcs <= 0 || res->count_connectors <= 0 ||
|
|
|
|
res->count_encoders <= 0)
|
|
|
|
goto out_res;
|
|
|
|
|
|
|
|
if (sysnum)
|
|
|
|
id = atoi(sysnum);
|
|
|
|
if (!sysnum || id < 0) {
|
|
|
|
weston_log("couldn't get sysnum for device %s\n", filename);
|
|
|
|
goto out_res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We can be called successfully on multiple devices; if we have,
|
|
|
|
* clean up old entries. */
|
|
|
|
if (b->drm.fd >= 0)
|
|
|
|
weston_launcher_close(b->compositor->launcher, b->drm.fd);
|
|
|
|
free(b->drm.filename);
|
|
|
|
|
|
|
|
b->drm.fd = fd;
|
|
|
|
b->drm.id = id;
|
|
|
|
b->drm.filename = strdup(filename);
|
|
|
|
|
|
|
|
drmModeFreeResources(res);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
out_res:
|
|
|
|
drmModeFreeResources(res);
|
|
|
|
out_fd:
|
|
|
|
weston_launcher_close(b->compositor->launcher, fd);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find primary GPU
|
|
|
|
* Some systems may have multiple DRM devices attached to a single seat. This
|
|
|
|
* function loops over all devices and tries to find a PCI device with the
|
|
|
|
* boot_vga sysfs attribute set to 1.
|
|
|
|
* If no such device is found, the first DRM device reported by udev is used.
|
|
|
|
* Devices are also vetted to make sure they are are capable of modesetting,
|
|
|
|
* rather than pure render nodes (GPU with no display), or pure
|
|
|
|
* memory-allocation devices (VGEM).
|
|
|
|
*/
|
|
|
|
static struct udev_device*
|
|
|
|
find_primary_gpu(struct drm_backend *b, const char *seat)
|
|
|
|
{
|
|
|
|
struct udev_enumerate *e;
|
|
|
|
struct udev_list_entry *entry;
|
|
|
|
const char *path, *device_seat, *id;
|
|
|
|
struct udev_device *device, *drm_device, *pci;
|
|
|
|
|
|
|
|
e = udev_enumerate_new(b->udev);
|
|
|
|
udev_enumerate_add_match_subsystem(e, "drm");
|
|
|
|
udev_enumerate_add_match_sysname(e, "card[0-9]*");
|
|
|
|
|
|
|
|
udev_enumerate_scan_devices(e);
|
|
|
|
drm_device = NULL;
|
|
|
|
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
|
|
|
|
bool is_boot_vga = false;
|
|
|
|
|
|
|
|
path = udev_list_entry_get_name(entry);
|
|
|
|
device = udev_device_new_from_syspath(b->udev, path);
|
|
|
|
if (!device)
|
|
|
|
continue;
|
|
|
|
device_seat = udev_device_get_property_value(device, "ID_SEAT");
|
|
|
|
if (!device_seat)
|
|
|
|
device_seat = default_seat;
|
|
|
|
if (strcmp(device_seat, seat)) {
|
|
|
|
udev_device_unref(device);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pci = udev_device_get_parent_with_subsystem_devtype(device,
|
|
|
|
"pci", NULL);
|
|
|
|
if (pci) {
|
|
|
|
id = udev_device_get_sysattr_value(pci, "boot_vga");
|
|
|
|
if (id && !strcmp(id, "1"))
|
|
|
|
is_boot_vga = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we already have a modesetting-capable device, and this
|
|
|
|
* device isn't our boot-VGA device, we aren't going to use
|
|
|
|
* it. */
|
|
|
|
if (!is_boot_vga && drm_device) {
|
|
|
|
udev_device_unref(device);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make sure this device is actually capable of modesetting;
|
|
|
|
* if this call succeeds, b->drm.{fd,filename} will be set,
|
|
|
|
* and any old values freed. */
|
|
|
|
if (!drm_device_is_kms(b, device)) {
|
|
|
|
udev_device_unref(device);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* There can only be one boot_vga device, and we try to use it
|
|
|
|
* at all costs. */
|
|
|
|
if (is_boot_vga) {
|
|
|
|
if (drm_device)
|
|
|
|
udev_device_unref(drm_device);
|
|
|
|
drm_device = device;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Per the (!is_boot_vga && drm_device) test above, we only
|
|
|
|
* trump existing saved devices with boot-VGA devices, so if
|
|
|
|
* we end up here, this must be the first device we've seen. */
|
|
|
|
assert(!drm_device);
|
|
|
|
drm_device = device;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we're returning a device to use, we must have an open FD for
|
|
|
|
* it. */
|
|
|
|
assert(!!drm_device == (b->drm.fd >= 0));
|
|
|
|
|
|
|
|
udev_enumerate_unref(e);
|
|
|
|
return drm_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
planes_binding(struct weston_keyboard *keyboard, const struct timespec *time,
|
|
|
|
uint32_t key, void *data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = data;
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case KEY_C:
|
|
|
|
b->cursors_are_broken ^= 1;
|
|
|
|
break;
|
|
|
|
case KEY_V:
|
|
|
|
b->sprites_are_broken ^= 1;
|
|
|
|
break;
|
|
|
|
case KEY_O:
|
|
|
|
b->sprites_hidden ^= 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef BUILD_VAAPI_RECORDER
|
|
|
|
static void
|
|
|
|
recorder_destroy(struct drm_output *output)
|
|
|
|
{
|
|
|
|
vaapi_recorder_destroy(output->recorder);
|
|
|
|
output->recorder = NULL;
|
|
|
|
|
|
|
|
output->base.disable_planes--;
|
|
|
|
|
|
|
|
wl_list_remove(&output->recorder_frame_listener.link);
|
|
|
|
weston_log("[libva recorder] done\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
recorder_frame_notify(struct wl_listener *listener, void *data)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
struct drm_backend *b;
|
|
|
|
int fd, ret;
|
|
|
|
|
|
|
|
output = container_of(listener, struct drm_output,
|
|
|
|
recorder_frame_listener);
|
|
|
|
b = to_drm_backend(output->base.compositor);
|
|
|
|
|
|
|
|
if (!output->recorder)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = drmPrimeHandleToFD(b->drm.fd, output->fb_current->handle,
|
|
|
|
DRM_CLOEXEC, &fd);
|
|
|
|
if (ret) {
|
|
|
|
weston_log("[libva recorder] "
|
|
|
|
"failed to create prime fd for front buffer\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = vaapi_recorder_frame(output->recorder, fd,
|
|
|
|
output->fb_current->stride);
|
|
|
|
if (ret < 0) {
|
|
|
|
weston_log("[libva recorder] aborted: %m\n");
|
|
|
|
recorder_destroy(output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *
|
|
|
|
create_recorder(struct drm_backend *b, int width, int height,
|
|
|
|
const char *filename)
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
drm_magic_t magic;
|
|
|
|
|
|
|
|
fd = open(b->drm.filename, O_RDWR | O_CLOEXEC);
|
|
|
|
if (fd < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
drmGetMagic(fd, &magic);
|
|
|
|
drmAuthMagic(b->drm.fd, magic);
|
|
|
|
|
|
|
|
return vaapi_recorder_create(fd, width, height, filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time,
|
|
|
|
uint32_t key, void *data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b = data;
|
|
|
|
struct drm_output *output;
|
|
|
|
int width, height;
|
|
|
|
|
|
|
|
output = container_of(b->compositor->output_list.next,
|
|
|
|
struct drm_output, base.link);
|
|
|
|
|
|
|
|
if (!output->recorder) {
|
|
|
|
if (output->gbm_format != GBM_FORMAT_XRGB8888) {
|
|
|
|
weston_log("failed to start vaapi recorder: "
|
|
|
|
"output format not supported\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
width = output->base.current_mode->width;
|
|
|
|
height = output->base.current_mode->height;
|
|
|
|
|
|
|
|
output->recorder =
|
|
|
|
create_recorder(b, width, height, "capture.h264");
|
|
|
|
if (!output->recorder) {
|
|
|
|
weston_log("failed to create vaapi recorder\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
output->base.disable_planes++;
|
|
|
|
|
|
|
|
output->recorder_frame_listener.notify = recorder_frame_notify;
|
|
|
|
wl_signal_add(&output->base.frame_signal,
|
|
|
|
&output->recorder_frame_listener);
|
|
|
|
|
|
|
|
weston_output_schedule_repaint(&output->base);
|
|
|
|
|
|
|
|
weston_log("[libva recorder] initialized\n");
|
|
|
|
} else {
|
|
|
|
recorder_destroy(output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static void
|
|
|
|
recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time,
|
|
|
|
uint32_t key, void *data)
|
|
|
|
{
|
|
|
|
weston_log("Compiled without libva support\n");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void
|
|
|
|
switch_to_gl_renderer(struct drm_backend *b)
|
|
|
|
{
|
|
|
|
struct drm_output *output;
|
|
|
|
bool dmabuf_support_inited;
|
|
|
|
|
|
|
|
if (!b->use_pixman)
|
|
|
|
return;
|
|
|
|
|
|
|
|
dmabuf_support_inited = !!b->compositor->renderer->import_dmabuf;
|
|
|
|
|
|
|
|
weston_log("Switching to GL renderer\n");
|
|
|
|
|
|
|
|
b->gbm = create_gbm_device(b->drm.fd);
|
|
|
|
if (!b->gbm) {
|
|
|
|
weston_log("Failed to create gbm device. "
|
|
|
|
"Aborting renderer switch\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->output_list, base.link)
|
|
|
|
pixman_renderer_output_destroy(&output->base);
|
|
|
|
|
|
|
|
b->compositor->renderer->destroy(b->compositor);
|
|
|
|
|
|
|
|
if (drm_backend_create_gl_renderer(b) < 0) {
|
|
|
|
gbm_device_destroy(b->gbm);
|
|
|
|
weston_log("Failed to create GL renderer. Quitting.\n");
|
|
|
|
/* FIXME: we need a function to shutdown cleanly */
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_for_each(output, &b->compositor->output_list, base.link)
|
|
|
|
drm_output_init_egl(output, b);
|
|
|
|
|
|
|
|
b->use_pixman = 0;
|
|
|
|
|
|
|
|
if (!dmabuf_support_inited && b->compositor->renderer->import_dmabuf) {
|
|
|
|
if (linux_dmabuf_setup(b->compositor) < 0)
|
|
|
|
weston_log("Error: initializing dmabuf "
|
|
|
|
"support failed.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
renderer_switch_binding(struct weston_keyboard *keyboard,
|
|
|
|
const struct timespec *time, uint32_t key, void *data)
|
|
|
|
{
|
|
|
|
struct drm_backend *b =
|
|
|
|
to_drm_backend(keyboard->seat->compositor);
|
|
|
|
|
|
|
|
switch_to_gl_renderer(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct weston_drm_output_api api = {
|
|
|
|
drm_output_set_mode,
|
|
|
|
drm_output_set_gbm_format,
|
|
|
|
drm_output_set_seat,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct drm_backend *
|
|
|
|
drm_backend_create(struct weston_compositor *compositor,
|
|
|
|
struct weston_drm_backend_config *config)
|
|
|
|
{
|
|
|
|
struct drm_backend *b;
|
|
|
|
struct udev_device *drm_device;
|
|
|
|
struct wl_event_loop *loop;
|
|
|
|
const char *seat_id = default_seat;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
weston_log("initializing drm backend\n");
|
|
|
|
|
|
|
|
b = zalloc(sizeof *b);
|
|
|
|
if (b == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
b->drm.fd = -1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* KMS support for hardware planes cannot properly synchronize
|
|
|
|
* without nuclear page flip. Without nuclear/atomic, hw plane
|
|
|
|
* and cursor plane updates would either tear or cause extra
|
|
|
|
* waits for vblanks which means dropping the compositor framerate
|
|
|
|
* to a fraction. For cursors, it's not so bad, so they are
|
|
|
|
* enabled.
|
|
|
|
*
|
|
|
|
* These can be enabled again when nuclear/atomic support lands.
|
|
|
|
*/
|
|
|
|
b->sprites_are_broken = 1;
|
|
|
|
b->compositor = compositor;
|
|
|
|
b->use_pixman = config->use_pixman;
|
|
|
|
b->pageflip_timeout = config->pageflip_timeout;
|
|
|
|
|
|
|
|
compositor->backend = &b->base;
|
|
|
|
|
|
|
|
if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0)
|
|
|
|
goto err_compositor;
|
|
|
|
|
|
|
|
if (config->seat_id)
|
|
|
|
seat_id = config->seat_id;
|
|
|
|
|
|
|
|
/* Check if we run drm-backend using weston-launch */
|
|
|
|
compositor->launcher = weston_launcher_connect(compositor, config->tty,
|
|
|
|
seat_id, true);
|
|
|
|
if (compositor->launcher == NULL) {
|
|
|
|
weston_log("fatal: drm backend should be run using "
|
|
|
|
"weston-launch binary, or your system should "
|
|
|
|
"provide the logind D-Bus API.\n");
|
|
|
|
goto err_compositor;
|
|
|
|
}
|
|
|
|
|
|
|
|
b->udev = udev_new();
|
|
|
|
if (b->udev == NULL) {
|
|
|
|
weston_log("failed to initialize udev context\n");
|
|
|
|
goto err_launcher;
|
|
|
|
}
|
|
|
|
|
|
|
|
b->session_listener.notify = session_notify;
|
|
|
|
wl_signal_add(&compositor->session_signal, &b->session_listener);
|
|
|
|
|
|
|
|
drm_device = find_primary_gpu(b, seat_id);
|
|
|
|
if (drm_device == NULL) {
|
|
|
|
weston_log("no drm device found\n");
|
|
|
|
goto err_udev;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (init_kms_caps(b) < 0) {
|
|
|
|
weston_log("failed to initialize kms\n");
|
|
|
|
goto err_udev_dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b->use_pixman) {
|
|
|
|
if (init_pixman(b) < 0) {
|
|
|
|
weston_log("failed to initialize pixman renderer\n");
|
|
|
|
goto err_udev_dev;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (init_egl(b) < 0) {
|
|
|
|
weston_log("failed to initialize egl\n");
|
|
|
|
goto err_udev_dev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b->base.destroy = drm_destroy;
|
|
|
|
b->base.restore = drm_restore;
|
|
|
|
b->base.repaint_begin = drm_repaint_begin;
|
|
|
|
b->base.repaint_flush = drm_repaint_flush;
|
|
|
|
b->base.repaint_cancel = drm_repaint_cancel;
|
|
|
|
|
|
|
|
weston_setup_vt_switch_bindings(compositor);
|
|
|
|
|
|
|
|
wl_list_init(&b->plane_list);
|
|
|
|
create_sprites(b);
|
|
|
|
|
|
|
|
if (udev_input_init(&b->input,
|
|
|
|
compositor, b->udev, seat_id,
|
|
|
|
config->configure_device) < 0) {
|
|
|
|
weston_log("failed to create input devices\n");
|
|
|
|
goto err_sprite;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (create_outputs(b, drm_device) < 0) {
|
|
|
|
weston_log("failed to create output for %s\n", b->drm.filename);
|
|
|
|
goto err_udev_input;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A this point we have some idea of whether or not we have a working
|
|
|
|
* cursor plane. */
|
|
|
|
if (!b->cursors_are_broken)
|
|
|
|
compositor->capabilities |= WESTON_CAP_CURSOR_PLANE;
|
|
|
|
|
|
|
|
loop = wl_display_get_event_loop(compositor->wl_display);
|
|
|
|
b->drm_source =
|
|
|
|
wl_event_loop_add_fd(loop, b->drm.fd,
|
|
|
|
WL_EVENT_READABLE, on_drm_input, b);
|
|
|
|
|
|
|
|
b->udev_monitor = udev_monitor_new_from_netlink(b->udev, "udev");
|
|
|
|
if (b->udev_monitor == NULL) {
|
|
|
|
weston_log("failed to initialize udev monitor\n");
|
|
|
|
goto err_drm_source;
|
|
|
|
}
|
|
|
|
udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor,
|
|
|
|
"drm", NULL);
|
|
|
|
b->udev_drm_source =
|
|
|
|
wl_event_loop_add_fd(loop,
|
|
|
|
udev_monitor_get_fd(b->udev_monitor),
|
|
|
|
WL_EVENT_READABLE, udev_drm_event, b);
|
|
|
|
|
|
|
|
if (udev_monitor_enable_receiving(b->udev_monitor) < 0) {
|
|
|
|
weston_log("failed to enable udev-monitor receiving\n");
|
|
|
|
goto err_udev_monitor;
|
|
|
|
}
|
|
|
|
|
|
|
|
udev_device_unref(drm_device);
|
|
|
|
|
|
|
|
weston_compositor_add_debug_binding(compositor, KEY_O,
|
|
|
|
planes_binding, b);
|
|
|
|
weston_compositor_add_debug_binding(compositor, KEY_C,
|
|
|
|
planes_binding, b);
|
|
|
|
weston_compositor_add_debug_binding(compositor, KEY_V,
|
|
|
|
planes_binding, b);
|
|
|
|
weston_compositor_add_debug_binding(compositor, KEY_Q,
|
|
|
|
recorder_binding, b);
|
|
|
|
weston_compositor_add_debug_binding(compositor, KEY_W,
|
|
|
|
renderer_switch_binding, b);
|
|
|
|
|
|
|
|
if (compositor->renderer->import_dmabuf) {
|
|
|
|
if (linux_dmabuf_setup(compositor) < 0)
|
|
|
|
weston_log("Error: initializing dmabuf "
|
|
|
|
"support failed.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME,
|
|
|
|
&api, sizeof(api));
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
weston_log("Failed to register output API.\n");
|
|
|
|
goto err_udev_monitor;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
|
|
|
|
err_udev_monitor:
|
|
|
|
wl_event_source_remove(b->udev_drm_source);
|
|
|
|
udev_monitor_unref(b->udev_monitor);
|
|
|
|
err_drm_source:
|
|
|
|
wl_event_source_remove(b->drm_source);
|
|
|
|
err_udev_input:
|
|
|
|
udev_input_destroy(&b->input);
|
|
|
|
err_sprite:
|
|
|
|
if (b->gbm)
|
|
|
|
gbm_device_destroy(b->gbm);
|
|
|
|
destroy_sprites(b);
|
|
|
|
err_udev_dev:
|
|
|
|
udev_device_unref(drm_device);
|
|
|
|
err_launcher:
|
|
|
|
weston_launcher_destroy(compositor->launcher);
|
|
|
|
err_udev:
|
|
|
|
udev_unref(b->udev);
|
|
|
|
err_compositor:
|
|
|
|
weston_compositor_shutdown(compositor);
|
|
|
|
free(b);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
config_init_to_defaults(struct weston_drm_backend_config *config)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
WL_EXPORT int
|
|
|
|
weston_backend_init(struct weston_compositor *compositor,
|
|
|
|
struct weston_backend_config *config_base)
|
|
|
|
{
|
|
|
|
struct drm_backend *b;
|
|
|
|
struct weston_drm_backend_config config = {{ 0, }};
|
|
|
|
|
|
|
|
if (config_base == NULL ||
|
|
|
|
config_base->struct_version != WESTON_DRM_BACKEND_CONFIG_VERSION ||
|
|
|
|
config_base->struct_size > sizeof(struct weston_drm_backend_config)) {
|
|
|
|
weston_log("drm backend config structure is invalid\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
config_init_to_defaults(&config);
|
|
|
|
memcpy(&config, config_base, config_base->struct_size);
|
|
|
|
|
|
|
|
b = drm_backend_create(compositor, &config);
|
|
|
|
if (b == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|