diff options
author | Eric Anholt <eric@anholt.net> | 2013-12-30 17:23:38 -0800 |
---|---|---|
committer | Eric Anholt <eric@anholt.net> | 2014-10-10 11:15:31 +0200 |
commit | f9ac04ed0cc68f8a64426c734368a4f9330d74a2 (patch) | |
tree | 2f48a1157b7b35380248e12560ad10ad83703ed4 | |
parent | e362e1788fd2eeb6570eebea8b043227c17ee678 (diff) |
modesetting: Add support for DRI2 with glamor.modesetting-dri2
This is derived from the intel driver DRI2 code, with swapchain
dropped, functions renamed, and pageflipping shared code moved to a
pageflip.c for reuse by Present.
This allows AIGLX to load, which means that you get appropriate
visuals exposed in GL, along with many extensions under
direct-rendering that require presence in GLX (which aren't supported
in glxdriswrast.c).
v2: Drop unused header includes in pageflip.c, wrap in #ifdef GLAMOR.
Signed-off-by: Eric Anholt <eric@anholt.net>
-rw-r--r-- | hw/xfree86/drivers/modesetting/Makefile.am | 6 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/dri2.c | 1123 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/driver.c | 26 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/driver.h | 70 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/drmmode_display.c | 5 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/drmmode_display.h | 26 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/pageflip.c | 681 |
7 files changed, 1935 insertions, 2 deletions
diff --git a/hw/xfree86/drivers/modesetting/Makefile.am b/hw/xfree86/drivers/modesetting/Makefile.am index e6834e283..63fdd8faf 100644 --- a/hw/xfree86/drivers/modesetting/Makefile.am +++ b/hw/xfree86/drivers/modesetting/Makefile.am @@ -30,6 +30,7 @@ AM_CPPFLAGS = \ $(XORG_INCS) \ -I$(top_srcdir)/glamor \ -I$(srcdir)/../../ddc \ + -I$(srcdir)/../../dri2 \ -I$(srcdir)/../../i2c \ -I$(srcdir)/../../modes \ -I$(srcdir)/../../parser \ @@ -42,10 +43,13 @@ modesetting_drv_la_LIBADD = $(UDEV_LIBS) $(DRM_LIBS) modesetting_drv_ladir = @moduledir@/drivers modesetting_drv_la_SOURCES = \ + dri2.c \ driver.c \ driver.h \ drmmode_display.c \ - drmmode_display.h + drmmode_display.h \ + pageflip.c \ + $(NULL) drivermandir = $(DRIVER_MAN_DIR) driverman_PRE = modesetting.man diff --git a/hw/xfree86/drivers/modesetting/dri2.c b/hw/xfree86/drivers/modesetting/dri2.c new file mode 100644 index 000000000..12d7f915e --- /dev/null +++ b/hw/xfree86/drivers/modesetting/dri2.c @@ -0,0 +1,1123 @@ +/* + * Copyright © 2013 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. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include "dix-config.h" +#endif + +#include <time.h> +#include "list.h" +#include "xf86.h" +#include "driver.h" +#include "dri2.h" + +#ifdef GLAMOR +#define GLAMOR_FOR_XORG 1 +#include "glamor.h" + +enum ms_dri2_frame_event_type { + DRI2_SWAP, + DRI2_FLIP, + DRI2_WAITMSC, +}; + +typedef struct ms_dri2_frame_event { + ScreenPtr screen; + + DrawablePtr drawable; + ClientPtr client; + enum ms_dri2_frame_event_type type; + int frame; + int pipe; + + struct xorg_list drawable_resource, client_resource; + + /* for swaps & flips only */ + DRI2SwapEventPtr event_complete; + void *event_data; + DRI2BufferPtr front; + DRI2BufferPtr back; +} ms_dri2_frame_event_rec, *ms_dri2_frame_event_ptr; + +typedef struct { + int refcnt; + PixmapPtr pixmap; +} ms_dri2_buffer_private_rec, *ms_dri2_buffer_private_ptr; + +static DevPrivateKeyRec ms_dri2_client_key; +static RESTYPE frame_event_client_type, frame_event_drawable_type; +static int ms_dri2_server_generation; + +struct ms_dri2_resource { + XID id; + RESTYPE type; + struct xorg_list list; +}; + +static struct ms_dri2_resource * +ms_get_resource(XID id, RESTYPE type) +{ + struct ms_dri2_resource *resource; + void *ptr; + + ptr = NULL; + dixLookupResourceByType(&ptr, id, type, NULL, DixWriteAccess); + if (ptr) + return ptr; + + resource = malloc(sizeof(*resource)); + if (resource == NULL) + return NULL; + + if (!AddResource(id, type, resource)) { + free(resource); + return NULL; + } + + resource->id = id; + resource->type = type; + xorg_list_init(&resource->list); + return resource; +} + +static inline PixmapPtr +get_drawable_pixmap(DrawablePtr drawable) +{ + ScreenPtr screen = drawable->pScreen; + + if (drawable->type == DRAWABLE_PIXMAP) + return (PixmapPtr) drawable; + else + return screen->GetWindowPixmap((WindowPtr) drawable); +} + +static PixmapPtr +get_front_buffer(DrawablePtr drawable) +{ + PixmapPtr pixmap; + + pixmap = get_drawable_pixmap(drawable); + pixmap->refcnt++; + + return pixmap; +} + +static DRI2Buffer2Ptr +ms_dri2_create_buffer(DrawablePtr drawable, unsigned int attachment, + unsigned int format) +{ + ScreenPtr screen = drawable->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + DRI2Buffer2Ptr buffer; + PixmapPtr pixmap; + uint32_t size; + uint16_t pitch; + ms_dri2_buffer_private_ptr private; + + buffer = calloc(1, sizeof *buffer); + if (buffer == NULL) + return NULL; + + private = calloc(1, sizeof(*private)); + if (private == NULL) { + free(buffer); + return NULL; + } + + pixmap = NULL; + if (attachment == DRI2BufferFrontLeft) + pixmap = get_front_buffer(drawable); + + if (pixmap == NULL) { + int pixmap_width = drawable->width; + int pixmap_height = drawable->height; + int pixmap_cpp = (format != 0) ? format : drawable->depth; + + /* Assume that non-color-buffers require special + * device-specific handling. Mesa currently makes no requests + * for non-color aux buffers. + */ + switch (attachment) { + case DRI2BufferAccum: + case DRI2BufferBackLeft: + case DRI2BufferBackRight: + case DRI2BufferFakeFrontLeft: + case DRI2BufferFakeFrontRight: + case DRI2BufferFrontLeft: + case DRI2BufferFrontRight: + break; + + case DRI2BufferStencil: + case DRI2BufferDepth: + case DRI2BufferDepthStencil: + case DRI2BufferHiz: + default: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "Request for DRI2 buffer attachment %d unsupported\n", + attachment); + free(private); + free(buffer); + return NULL; + } + + pixmap = screen->CreatePixmap(screen, + pixmap_width, + pixmap_height, + pixmap_cpp, + 0); + if (pixmap == NULL) { + if (pixmap) + screen->DestroyPixmap(pixmap); + free(private); + free(buffer); + return NULL; + } + } + + buffer->attachment = attachment; + buffer->cpp = pixmap->drawable.bitsPerPixel / 8; + buffer->format = format; + /* The buffer's flags field is unused by the client drivers in + * Mesa currently. + */ + buffer->flags = 0; + + buffer->name = glamor_name_from_pixmap(pixmap, &pitch, &size); + buffer->pitch = pitch; + if (buffer->name == -1) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to get DRI2 name for pixmap\n"); + screen->DestroyPixmap(pixmap); + free(private); + free(buffer); + return NULL; + } + + buffer->driverPrivate = private; + private->refcnt = 1; + private->pixmap = pixmap; + + return buffer; +} + +static void +ms_dri2_reference_buffer(DRI2Buffer2Ptr buffer) +{ + if (buffer) { + ms_dri2_buffer_private_ptr private = buffer->driverPrivate; + private->refcnt++; + } +} + +static void ms_dri2_destroy_buffer(DrawablePtr drawable, DRI2Buffer2Ptr buffer) +{ + if (!buffer) + return; + + if (buffer->driverPrivate) { + ms_dri2_buffer_private_ptr private = buffer->driverPrivate; + if (--private->refcnt == 0) { + ScreenPtr screen = private->pixmap->drawable.pScreen; + screen->DestroyPixmap(private->pixmap); + free(private); + free(buffer); + } + } else { + free(buffer); + } +} + +static void +ms_dri2_copy_region(DrawablePtr drawable, RegionPtr pRegion, + DRI2BufferPtr destBuffer, DRI2BufferPtr sourceBuffer) +{ + ms_dri2_buffer_private_ptr src_priv = sourceBuffer->driverPrivate; + ms_dri2_buffer_private_ptr dst_priv = destBuffer->driverPrivate; + PixmapPtr src_pixmap = src_priv->pixmap; + PixmapPtr dst_pixmap = dst_priv->pixmap; + ScreenPtr screen = drawable->pScreen; + DrawablePtr src = (sourceBuffer->attachment == DRI2BufferFrontLeft) + ? drawable : &src_pixmap->drawable; + DrawablePtr dst = (destBuffer->attachment == DRI2BufferFrontLeft) + ? drawable : &dst_pixmap->drawable; + RegionPtr pCopyClip; + GCPtr gc; + + gc = GetScratchGC(dst->depth, screen); + if (!gc) + return; + + pCopyClip = REGION_CREATE(screen, NULL, 0); + REGION_COPY(screen, pCopyClip, pRegion); + (*gc->funcs->ChangeClip) (gc, CT_REGION, pCopyClip, 0); + ValidateGC(dst, gc); + + /* It's important that this copy gets submitted before the direct + * rendering client submits rendering for the next frame, but we + * don't actually need to submit right now. The client will wait + * for the DRI2CopyRegion reply or the swap buffer event before + * rendering, and we'll hit the flush callback chain before those + * messages are sent. We submit our batch buffers from the flush + * callback chain so we know that will happen before the client + * tries to render again. + */ + gc->ops->CopyArea(src, dst, gc, + 0, 0, + drawable->width, drawable->height, + 0, 0); + + FreeScratchGC(gc); +} + +static uint64_t +gettime_us(void) +{ + struct timespec tv; + + if (clock_gettime(CLOCK_MONOTONIC, &tv)) + return 0; + + return (uint64_t)tv.tv_sec * 1000000 + tv.tv_nsec / 1000; +} + +/** + * Get current frame count and frame count timestamp, based on drawable's + * crtc. + */ +static int +ms_dri2_get_msc(DrawablePtr draw, CARD64 *ust, CARD64 *msc) +{ + int ret; + xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw); + + /* Drawable not displayed, make up a *monotonic* value */ + if (crtc == NULL) { + *ust = gettime_us(); + *msc = 0; + return TRUE; + } + + ret = ms_get_crtc_ust_msc(crtc, ust, msc); + + if (ret) + return FALSE; + + return TRUE; +} + +static XID +get_client_id(ClientPtr client) +{ + XID *ptr = dixGetPrivateAddr(&client->devPrivates, &ms_dri2_client_key); + if (*ptr == 0) + *ptr = FakeClientID(client->index); + return *ptr; +} + +/* + * Hook this frame event into the server resource + * database so we can clean it up if the drawable or + * client exits while the swap is pending + */ +static Bool +ms_dri2_add_frame_event(ms_dri2_frame_event_ptr info) +{ + struct ms_dri2_resource *resource; + + resource = ms_get_resource(get_client_id(info->client), + frame_event_client_type); + if (resource == NULL) + return FALSE; + + xorg_list_add(&info->client_resource, &resource->list); + + resource = ms_get_resource(info->drawable->id, frame_event_drawable_type); + if (resource == NULL) { + xorg_list_del(&info->client_resource); + return FALSE; + } + + xorg_list_add(&info->drawable_resource, &resource->list); + + return TRUE; +} + +static void +ms_dri2_del_frame_event(ms_dri2_frame_event_rec *info) +{ + xorg_list_del(&info->client_resource); + xorg_list_del(&info->drawable_resource); + + if (info->front) + ms_dri2_destroy_buffer(NULL, info->front); + if (info->back) + ms_dri2_destroy_buffer(NULL, info->back); + + free(info); +} + +static void +ms_dri2_fallback_blit_swap(DrawablePtr drawable, + DRI2BufferPtr dst, + DRI2BufferPtr src) +{ + BoxRec box; + RegionRec region; + + box.x1 = 0; + box.y1 = 0; + box.x2 = drawable->width; + box.y2 = drawable->height; + REGION_INIT(pScreen, ®ion, &box, 0); + + ms_dri2_copy_region(drawable, ®ion, dst, src); +} + +static void +ms_exchange_pixmap_buffers(PixmapPtr front, PixmapPtr back, + uint32_t *front_name, uint32_t *back_name) +{ + uint32_t tmp_name; + + glamor_egl_exchange_buffers(front, back); + + tmp_name = *front_name; + *front_name = *back_name; + *back_name = tmp_name; +} + +static void +ms_dri2_exchange_buffers(modesettingPtr ms, + DRI2BufferPtr front, DRI2BufferPtr back) +{ + RegionRec region; + ms_dri2_buffer_private_ptr front_priv, back_priv; + DrawablePtr drawable; + front_priv = front->driverPrivate; + back_priv = back->driverPrivate; + + /* Swap pixmap bos */ + ms_exchange_pixmap_buffers(front_priv->pixmap, back_priv->pixmap, + &front->name, &back->name); + + /* Post damage on the front buffer so that listeners, such + * as DisplayLink know take a copy and shove it over the USB. + * also for sw cursors. + */ + drawable = &front_priv->pixmap->drawable; + region.extents.x1 = region.extents.y1 = 0; + region.extents.x2 = drawable->width; + region.extents.y2 = drawable->height; + region.data = NULL; + DamageRegionAppend(drawable, ®ion); + DamageRegionProcessPending(drawable); +} + +static void +ms_dri2_flip_handler(uint64_t msc, + uint64_t usec, + void *data) +{ + ms_dri2_frame_event_ptr flip_info = data; + ScreenPtr screen = flip_info->screen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + uint32_t frame = msc; + uint32_t tv_sec = usec / 1000000; + uint32_t tv_usec = usec % 1000000; + + switch (flip_info->type) { + case DRI2_SWAP: + if (!flip_info->drawable) + break; + + /* Check for too small vblank count of pageflip completion, + * taking wraparound into account. This usually means some + * defective kms pageflip completion, causing wrong (msc, ust) + * return values and possible visual corruption. + */ + if ((frame < flip_info->frame) && (flip_info->frame - frame < 5)) { + static int limit = 5; + + /* XXX we are currently hitting this path with older + * kernels, so make it quieter. + */ + if (limit) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: Pageflip completion has impossible msc %d < " + "target_msc %d\n", + __func__, frame, flip_info->frame); + limit--; + } + + /* All-0 values signal timestamping failure. */ + frame = tv_sec = tv_usec = 0; + } + + DRI2SwapComplete(flip_info->client, flip_info->drawable, + frame, tv_sec, tv_usec, + DRI2_FLIP_COMPLETE, + flip_info->client ? flip_info->event_complete : NULL, + flip_info->event_data); + break; + + default: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: unknown flip event (type %d) received\n", __func__, + flip_info->type); + break; + } + + ms_dri2_del_frame_event(flip_info); +} + +static void +ms_dri2_flip_abort(void *data) +{ + ms_dri2_frame_event_ptr flip_info = data; + + ms_dri2_del_frame_event(flip_info); +} + +/** + * Called either from ScheduleSwap when we just need to present the + * backbuffer at the next vblank, or from the vblank event handler if + * ScheduleSwap needed to wait for a later vblank before we could + * swap. + * + * When this call is done, there will be a flip request outstanding to + * the hardware which will start getting scanned out on the next + * vblank. + */ +static Bool +ms_dri2_do_pageflip(DrawablePtr draw, ms_dri2_frame_event_ptr info) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + ms_dri2_buffer_private_ptr back_priv = info->back->driverPrivate; + + if (!ms_do_pageflip(back_priv->pixmap, info->pipe, FALSE, info, + ms_dri2_flip_handler, ms_dri2_flip_abort)) { + return FALSE; + } + + /* Flip the front and back pointers. Now we have: + * front: backbuffer with flip scheduled to screen + * back: old front buffer still being scanned out + * triple: idle + */ + ms_dri2_exchange_buffers(ms, info->front, info->back); + DRI2SwapComplete(info->client, draw, 0, 0, 0, + DRI2_EXCHANGE_COMPLETE, + info->event_complete, + info->event_data); + + /* Try to create a third screen-size pixmap, to avoid stalling on + * the GPU. + * + * For a vblank-synchronized pageflip, there's a period when the + * pageflip is queued but the old front buffer is still being + * scanned out. To avoid blocking rendering to the backbuffer for + * the next frame (which the kernel would do so as to keep the WIP + * new frame from appearing on screen), we make a third + * screen-sized buffer and make *that* the new back. + */ + if (ms->drmmode.triple_buffer_pixmap == NULL) { + ms->drmmode.triple_buffer_pixmap = + screen->CreatePixmap(screen, + back_priv->pixmap->drawable.width, + back_priv->pixmap->drawable.height, + back_priv->pixmap->drawable.depth, + 0); + if (ms->drmmode.triple_buffer_pixmap) { + uint32_t size; + uint16_t pitch; + ms->drmmode.triple_buffer_name = + glamor_name_from_pixmap(ms->drmmode.triple_buffer_pixmap, + &pitch, &size); + + assert(pitch == info->back->pitch); + } + } + + if (ms->drmmode.triple_buffer_pixmap) { + /* Exchange the triple buffer pixmap in. Now we have: + * front: backbuffer with flip scheduled to screen + * back: idle + * triple: old front buffer still being scanned out + */ + ms_exchange_pixmap_buffers(back_priv->pixmap, + ms->drmmode.triple_buffer_pixmap, + &info->back->name, + &ms->drmmode.triple_buffer_name); + } + + return TRUE; +} + +static Bool +can_exchange(DrawablePtr drawable, DRI2BufferPtr front, DRI2BufferPtr back) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(drawable->pScreen); + modesettingPtr ms = modesettingPTR(scrn); + ms_dri2_buffer_private_ptr front_priv = front->driverPrivate; + ms_dri2_buffer_private_ptr back_priv = back->driverPrivate; + PixmapPtr front_pixmap = front_priv->pixmap; + PixmapPtr back_pixmap = back_priv->pixmap; + + if (!scrn->vtSema) + return FALSE; + + if (ms_dri2_crtc_covering_drawable(drawable) == NULL) + return FALSE; + + if (!DRI2CanFlip(drawable)) + return FALSE; + + if (ms->drmmode.shadow_enable) + return FALSE; + + if (front_pixmap->drawable.width != back_pixmap->drawable.width) + return FALSE; + + if (front_pixmap->drawable.height != back_pixmap->drawable.height) + return FALSE; + + /* XXX should we be checking depth instead of bpp? */ +#if 0 + if (front_pixmap->drawable.depth != back_pixmap->drawable.depth) + return FALSE; +#else + if (front_pixmap->drawable.bitsPerPixel != back_pixmap->drawable.bitsPerPixel) + return FALSE; +#endif + + return TRUE; +} + +static void +ms_dri2_vblank_handler(ScrnInfoPtr scrn, + xf86CrtcPtr crtc, + uint64_t msc, + uint64_t usec, + void *data) +{ + ms_dri2_frame_event_ptr swap_info = data; + DrawablePtr drawable = swap_info->drawable; + uint32_t tv_sec = usec / 1000000; + uint32_t tv_usec = usec % 1000000; + + if (!drawable) { + ms_dri2_del_frame_event(swap_info); + return; + } + + switch (swap_info->type) { + case DRI2_FLIP: + if (can_exchange(drawable, swap_info->front, swap_info->back) && + ms_dri2_do_pageflip(drawable, swap_info)) { + return; + } + + /* else fall through to exchange/blit */ + case DRI2_SWAP: { + ms_dri2_fallback_blit_swap(drawable, swap_info->front, swap_info->back); + DRI2SwapComplete(swap_info->client, drawable, msc, tv_sec, tv_usec, + DRI2_BLIT_COMPLETE, + swap_info->client ? swap_info->event_complete : NULL, + swap_info->event_data); + break; + } + case DRI2_WAITMSC: + if (swap_info->client) + DRI2WaitMSCComplete(swap_info->client, drawable, + msc, tv_sec, tv_usec); + break; + default: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: unknown vblank event (type %d) received\n", __func__, + swap_info->type); + break; + } + + ms_dri2_del_frame_event(swap_info); +} + +static void +ms_dri2_vblank_abort(ScrnInfoPtr scrn, xf86CrtcPtr crtc, void *data) +{ + ms_dri2_frame_event_ptr swap_info = data; + + ms_dri2_del_frame_event(swap_info); +} + +/** + * Request a DRM event when the requested conditions will be satisfied. + * + * We need to handle the event and ask the server to wake up the client when + * we receive it. + */ +static int +ms_dri2_schedule_wait_msc(ClientPtr client, DrawablePtr draw, CARD64 target_msc, + CARD64 divisor, CARD64 remainder) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + ms_dri2_frame_event_ptr wait_info; + drmVBlank vbl; + int ret; + xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw); + int pipe = crtc ? ms_crtc_pipe(crtc) : -1; + CARD64 current_msc, current_ust, request_msc; + uint32_t seq; + + /* Drawable not visible, return immediately */ + if (pipe == -1) + goto out_complete; + + wait_info = calloc(1, sizeof(*wait_info)); + if (!wait_info) + goto out_complete; + + wait_info->screen = screen; + wait_info->drawable = draw; + wait_info->client = client; + wait_info->type = DRI2_WAITMSC; + + if (!ms_dri2_add_frame_event(wait_info)) { + free(wait_info); + wait_info = NULL; + goto out_complete; + } + + /* Get current count */ + ret = ms_get_crtc_ust_msc(crtc, ¤t_ust, ¤t_msc); + + /* + * If divisor is zero, or current_msc is smaller than target_msc, + * we just need to make sure target_msc passes before waking up the + * client. + */ + if (divisor == 0 || current_msc < target_msc) { + /* If target_msc already reached or passed, set it to + * current_msc to ensure we return a reasonable value back + * to the caller. This keeps the client from continually + * sending us MSC targets from the past by forcibly updating + * their count on this call. + */ + seq = ms_drm_queue_alloc(crtc, wait_info, + ms_dri2_vblank_handler, + ms_dri2_vblank_abort); + if (!seq) + goto out_free; + + if (current_msc >= target_msc) + target_msc = current_msc; + vbl.request.type = (DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT | + ms_vblank_pipe_select(pipe)); + vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, target_msc); + vbl.request.signal = (unsigned long)seq; + + ret = drmWaitVBlank(ms->fd, &vbl); + if (ret) { + static int limit = 5; + if (limit) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s:%d get vblank counter failed: %s\n", + __FUNCTION__, __LINE__, + strerror(errno)); + limit--; + } + goto out_free; + } + + wait_info->frame = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence); + DRI2BlockClient(client, draw); + return TRUE; + } + + /* + * If we get here, target_msc has already passed or we don't have one, + * so we queue an event that will satisfy the divisor/remainder equation. + */ + vbl.request.type = + DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT | ms_vblank_pipe_select(pipe); + + request_msc = current_msc - (current_msc % divisor) + + remainder; + /* + * If calculated remainder is larger than requested remainder, + * it means we've passed the last point where + * seq % divisor == remainder, so we need to wait for the next time + * that will happen. + */ + if ((current_msc % divisor) >= remainder) + request_msc += divisor; + + seq = ms_drm_queue_alloc(crtc, wait_info, + ms_dri2_vblank_handler, + ms_dri2_vblank_abort); + if (!seq) + goto out_free; + + vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, request_msc); + vbl.request.signal = (unsigned long)seq; + + ret = drmWaitVBlank(ms->fd, &vbl); + if (ret) { + static int limit = 5; + if (limit) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s:%d get vblank counter failed: %s\n", + __FUNCTION__, __LINE__, + strerror(errno)); + limit--; + } + goto out_free; + } + + wait_info->frame = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence); + DRI2BlockClient(client, draw); + + return TRUE; + + out_free: + ms_dri2_del_frame_event(wait_info); + out_complete: + DRI2WaitMSCComplete(client, draw, target_msc, 0, 0); + return TRUE; +} + +/* + * ScheduleSwap is responsible for requesting a DRM vblank event for the + * appropriate frame. + * + * In the case of a blit (e.g. for a windowed swap) or buffer exchange, + * the vblank requested can simply be the last queued swap frame + the swap + * interval for the drawable. + * + * In the case of a page flip, we request an event for the last queued swap + * frame + swap interval - 1, since we'll need to queue the flip for the frame + * immediately following the received event. + * + * The client will be blocked if it tries to perform further GL commands + * after queueing a swap, though in the Intel case after queueing a flip, the + * client is free to queue more commands; they'll block in the kernel if + * they access buffers busy with the flip. + * + * When the swap is complete, the driver should call into the server so it + * can send any swap complete events that have been requested. + */ +static int +ms_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, + DRI2BufferPtr front, DRI2BufferPtr back, + CARD64 *target_msc, CARD64 divisor, + CARD64 remainder, DRI2SwapEventPtr func, void *data) +{ + ScreenPtr screen = draw->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + drmVBlank vbl; + int ret; + xf86CrtcPtr crtc = ms_dri2_crtc_covering_drawable(draw); + int pipe = crtc ? ms_crtc_pipe(crtc) : -1; + int flip = 0; + ms_dri2_frame_event_ptr swap_info = NULL; + enum ms_dri2_frame_event_type swap_type = DRI2_SWAP; + uint64_t current_msc, current_ust; + uint64_t request_msc; + uint32_t seq; + + /* Drawable not displayed... just complete the swap */ + if (pipe == -1) + goto blit_fallback; + + swap_info = calloc(1, sizeof(*swap_info)); + if (!swap_info) + goto blit_fallback; + + swap_info->screen = screen; + swap_info->drawable = draw; + swap_info->client = client; + swap_info->event_complete = func; + swap_info->event_data = data; + swap_info->front = front; + swap_info->back = back; + swap_info->pipe = pipe; + + if (!ms_dri2_add_frame_event(swap_info)) { + free(swap_info); + swap_info = NULL; + goto blit_fallback; + } + + ms_dri2_reference_buffer(front); + ms_dri2_reference_buffer(back); + + ret = ms_get_crtc_ust_msc(crtc, ¤t_ust, ¤t_msc); + + /* Flips need to be submitted one frame before */ + if (can_exchange(draw, front, back)) { + swap_type = DRI2_FLIP; + flip = 1; + } + + swap_info->type = swap_type; + + /* Correct target_msc by 'flip' if swap_type == DRI2_FLIP. + * Do it early, so handling of different timing constraints + * for divisor, remainder and msc vs. target_msc works. + */ + if (*target_msc > 0) + *target_msc -= flip; + + /* + * If divisor is zero, or current_msc is smaller than target_msc + * we just need to make sure target_msc passes before initiating + * the swap. + */ + if (divisor == 0 || current_msc < *target_msc) { + /* If we can, schedule the flip directly from here rather + * than waiting for an event from the kernel for the current + * (or a past) MSC. + */ + if (flip && divisor == 0 && current_msc >= *target_msc && + ms_dri2_do_pageflip(draw, swap_info)) + return TRUE; + + vbl.request.type = (DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT | + ms_vblank_pipe_select(pipe)); + + /* If non-pageflipping, but blitting/exchanging, we need to use + * DRM_VBLANK_NEXTONMISS to avoid unreliable timestamping later + * on. + */ + if (flip == 0) + vbl.request.type |= DRM_VBLANK_NEXTONMISS; + + /* If target_msc already reached or passed, set it to + * current_msc to ensure we return a reasonable value back + * to the caller. This makes swap_interval logic more robust. + */ + if (current_msc >= *target_msc) + *target_msc = current_msc; + + seq = ms_drm_queue_alloc(crtc, swap_info, + ms_dri2_vblank_handler, + ms_dri2_vblank_abort); + if (!seq) + goto blit_fallback; + + vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, *target_msc); + vbl.request.signal = (unsigned long)seq; + + ret = drmWaitVBlank(ms->fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "divisor 0 get vblank counter failed: %s\n", + strerror(errno)); + goto blit_fallback; + } + + *target_msc = ms_kernel_msc_to_crtc_msc(crtc, + vbl.reply.sequence + flip); + swap_info->frame = *target_msc; + + return TRUE; + } + + /* + * If we get here, target_msc has already passed or we don't have one, + * and we need to queue an event that will satisfy the divisor/remainder + * equation. + */ + vbl.request.type = (DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT | + ms_vblank_pipe_select(pipe)); + if (flip == 0) + vbl.request.type |= DRM_VBLANK_NEXTONMISS; + + request_msc = current_msc - (current_msc % divisor) + + remainder; + + /* + * If the calculated deadline vbl.request.sequence is smaller than + * or equal to current_msc, it means we've passed the last point + * when effective onset frame seq could satisfy + * seq % divisor == remainder, so we need to wait for the next time + * this will happen. + + * This comparison takes the 1 frame swap delay in pageflipping mode + * into account, as well as a potential DRM_VBLANK_NEXTONMISS delay + * if we are blitting/exchanging instead of flipping. + */ + if (request_msc <= current_msc) + request_msc += divisor; + + + seq = ms_drm_queue_alloc(crtc, swap_info, + ms_dri2_vblank_handler, ms_dri2_vblank_abort); + if (!seq) + goto blit_fallback; + + /* Account for 1 frame extra pageflip delay if flip > 0 */ + vbl.request.sequence = ms_crtc_msc_to_kernel_msc(crtc, request_msc) - flip; + vbl.request.signal = (unsigned long)seq; + + ret = drmWaitVBlank(ms->fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "final get vblank counter failed: %s\n", + strerror(errno)); + goto blit_fallback; + } + + /* Adjust returned value for 1 fame pageflip offset of flip > 0 */ + *target_msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence + flip); + swap_info->frame = *target_msc; + + return TRUE; + + blit_fallback: + ms_dri2_fallback_blit_swap(draw, front, back); + DRI2SwapComplete(client, draw, 0, 0, 0, DRI2_BLIT_COMPLETE, func, data); + if (swap_info) + ms_dri2_del_frame_event(swap_info); + *target_msc = 0; /* offscreen, so zero out target vblank count */ + return TRUE; +} + +static int +ms_dri2_frame_event_client_gone(void *data, XID id) +{ + struct ms_dri2_resource *resource = data; + + while (!xorg_list_is_empty(&resource->list)) { + ms_dri2_frame_event_ptr info = + xorg_list_first_entry(&resource->list, + ms_dri2_frame_event_rec, + client_resource); + + xorg_list_del(&info->client_resource); + info->client = NULL; + } + free(resource); + + return Success; +} + +static int +ms_dri2_frame_event_drawable_gone(void *data, XID id) +{ + struct ms_dri2_resource *resource = data; + + while (!xorg_list_is_empty(&resource->list)) { + ms_dri2_frame_event_ptr info = + xorg_list_first_entry(&resource->list, + ms_dri2_frame_event_rec, + drawable_resource); + + xorg_list_del(&info->drawable_resource); + info->drawable = NULL; + } + free(resource); + + return Success; +} + +static Bool +ms_dri2_register_frame_event_resource_types(void) +{ + frame_event_client_type = + CreateNewResourceType(ms_dri2_frame_event_client_gone, + "Frame Event Client"); + if (!frame_event_client_type) + return FALSE; + + frame_event_drawable_type = + CreateNewResourceType(ms_dri2_frame_event_drawable_gone, + "Frame Event Drawable"); + if (!frame_event_drawable_type) + return FALSE; + + return TRUE; +} + +Bool +ms_dri2_screen_init(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + DRI2InfoRec info; + + if (!glamor_supports_pixmap_import_export(screen)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "DRI2: glamor lacks support for pixmap import/export\n"); + } + + if (!xf86LoaderCheckSymbol("DRI2Version")) + return FALSE; + + if (!dixRegisterPrivateKey(&ms_dri2_client_key, + PRIVATE_CLIENT, sizeof(XID))) + return FALSE; + + if (serverGeneration != ms_dri2_server_generation) { + ms_dri2_server_generation = serverGeneration; + if (!ms_dri2_register_frame_event_resource_types()) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "Cannot register DRI2 frame event resources\n"); + return FALSE; + } + } + + memset(&info, '\0', sizeof(info)); + info.fd = ms->fd; + info.driverName = NULL; /* Compat field, unused. */ + info.deviceName = drmGetDeviceNameFromFd(ms->fd); + + info.version = 4; + info.CreateBuffer = ms_dri2_create_buffer; + info.DestroyBuffer = ms_dri2_destroy_buffer; + info.CopyRegion = ms_dri2_copy_region; + info.ScheduleSwap = ms_dri2_schedule_swap; + info.GetMSC = ms_dri2_get_msc; + info.ScheduleWaitMSC = ms_dri2_schedule_wait_msc; + + /* These two will be filled in by dri2.c */ + info.numDrivers = 0; + info.driverNames = NULL; + + return DRI2ScreenInit(screen, &info); +} + +void +ms_dri2_close_screen(ScreenPtr screen) +{ + DRI2CloseScreen(screen); +} + +#endif /* GLAMOR */ diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c index c62147a0f..d0b909557 100644 --- a/hw/xfree86/drivers/modesetting/driver.c +++ b/hw/xfree86/drivers/modesetting/driver.c @@ -788,7 +788,9 @@ PreInit(ScrnInfoPtr pScrn, int flags) try_enable_glamor(pScrn); - if (!ms->glamor) { + if (ms->glamor) { + xf86LoadSubModule(pScrn, "dri2"); + } else { Bool prefer_shadow = TRUE; ret = drmGetCap(ms->fd, DRM_CAP_DUMB_PREFER_SHADOW, &value); @@ -1107,6 +1109,21 @@ ScreenInit(ScreenPtr pScreen, int argc, char **argv) if (serverGeneration == 1) xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options); +#ifdef GLAMOR + if (ms->glamor) { + if (!ms_pageflip_screen_init(pScreen)) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to initialize pageflipping support.\n"); + return FALSE; + } + + if (!ms_dri2_screen_init(pScreen)) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to initialize the DRI2 extension.\n"); + } + } +#endif + return EnterVT(pScrn); } @@ -1172,6 +1189,13 @@ CloseScreen(ScreenPtr pScreen) ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); modesettingPtr ms = modesettingPTR(pScrn); +#ifdef GLAMOR + if (ms->glamor) { + ms_dri2_close_screen(pScreen); + ms_pageflip_close_screen(pScreen); + } +#endif + if (ms->damage) { DamageUnregister(ms->damage); DamageDestroy(ms->damage); diff --git a/hw/xfree86/drivers/modesetting/driver.h b/hw/xfree86/drivers/modesetting/driver.h index 35f24193a..c82c7de18 100644 --- a/hw/xfree86/drivers/modesetting/driver.h +++ b/hw/xfree86/drivers/modesetting/driver.h @@ -30,6 +30,7 @@ #include <errno.h> #include <drm.h> #include <xf86drm.h> +#include <xf86Crtc.h> #include <damage.h> #include "drmmode_display.h" @@ -42,6 +43,36 @@ typedef struct { ScrnInfoPtr pScrn_2; } EntRec, *EntPtr; +typedef void (*ms_drm_handler_proc)(ScrnInfoPtr scrn, + xf86CrtcPtr crtc, + uint64_t seq, + uint64_t usec, + void *data); + +typedef void (*ms_drm_abort_proc)(ScrnInfoPtr scrn, + xf86CrtcPtr crtc, + void *data); + +typedef void (*ms_pageflip_handler_proc)(uint64_t frame, + uint64_t usec, + void *data); + +typedef void (*ms_pageflip_abort_proc)(void *data); + +/** + * A tracked handler for an event that will hopefully be generated by + * the kernel, and what to do when it is encountered. + */ +struct ms_drm_queue { + struct xorg_list list; + xf86CrtcPtr crtc; + uint32_t seq; + void *data; + ScrnInfoPtr scrn; + ms_drm_handler_proc handler; + ms_drm_abort_proc abort; +}; + typedef struct _modesettingRec { int fd; @@ -70,6 +101,15 @@ typedef struct _modesettingRec { drmmode_rec drmmode; + drmEventContext event_context; + + int flip_count; + uint64_t fe_msc; + uint64_t fe_usec; + void *pageflip_data; + ms_pageflip_handler_proc pageflip_handler; + ms_pageflip_abort_proc pageflip_abort; + DamagePtr damage; Bool dirty_enabled; @@ -78,3 +118,33 @@ typedef struct _modesettingRec { } modesettingRec, *modesettingPtr; #define modesettingPTR(p) ((modesettingPtr)((p)->driverPrivate)) + +Bool ms_do_pageflip(PixmapPtr new_front, + int ref_crtc_hw_id, + Bool async, + void *pageflip_data, + ms_pageflip_handler_proc pageflip_handler, + ms_pageflip_abort_proc pageflip_abort); + +uint32_t ms_drm_queue_alloc(xf86CrtcPtr crtc, + void *data, + ms_drm_handler_proc handler, + ms_drm_abort_proc abort); + +int ms_crtc_pipe(xf86CrtcPtr randr_crtc); +xf86CrtcPtr ms_dri2_crtc_covering_drawable(DrawablePtr pDraw); +xf86CrtcPtr ms_covering_crtc(ScrnInfoPtr scrn, BoxPtr box, + xf86CrtcPtr desired, BoxPtr crtc_box_ret); +uint32_t ms_vblank_pipe_select(int pipe); + +int ms_get_crtc_ust_msc(xf86CrtcPtr crtc, CARD64 *ust, CARD64 *msc); + +uint32_t ms_crtc_msc_to_kernel_msc(xf86CrtcPtr crtc, uint64_t expect); +uint64_t ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint32_t sequence); + + +Bool ms_dri2_screen_init(ScreenPtr screen); +void ms_dri2_close_screen(ScreenPtr screen); + +Bool ms_pageflip_screen_init(ScreenPtr screen); +void ms_pageflip_close_screen(ScreenPtr screen); diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.c b/hw/xfree86/drivers/modesetting/drmmode_display.c index d8d1b4485..950088841 100644 --- a/hw/xfree86/drivers/modesetting/drmmode_display.c +++ b/hw/xfree86/drivers/modesetting/drmmode_display.c @@ -1183,6 +1183,11 @@ drmmode_xf86crtc_resize(ScrnInfoPtr scrn, int width, int height) xf86DrvMsg(scrn->scrnIndex, X_INFO, "Allocate new frame buffer %dx%d stride\n", width, height); + if (drmmode->triple_buffer_pixmap) { + screen->DestroyPixmap(drmmode->triple_buffer_pixmap); + drmmode->triple_buffer_pixmap = NULL; + } + old_width = scrn->virtualX; old_height = scrn->virtualY; old_pitch = drmmode->front_bo->pitch; diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.h b/hw/xfree86/drivers/modesetting/drmmode_display.h index c7e7ef0ca..f7e3c6bfa 100644 --- a/hw/xfree86/drivers/modesetting/drmmode_display.h +++ b/hw/xfree86/drivers/modesetting/drmmode_display.h @@ -43,6 +43,7 @@ struct dumb_bo { typedef struct { int fd; unsigned fb_id; + unsigned old_fb_id; drmModeResPtr mode_res; drmModeFBPtr mode_fb; int cpp; @@ -58,6 +59,19 @@ typedef struct { Bool shadow_enable; void *shadow_fb; + /** + * A screen-sized pixmap when we're doing triple-buffered DRI2 + * pageflipping. + * + * One is shared between all drawables that flip to the front + * buffer, and it only gets reallocated when root pixmap size + * changes. + */ + PixmapPtr triple_buffer_pixmap; + + /** The GEM name for triple_buffer_pixmap */ + uint32_t triple_buffer_name; + DevPrivateKeyRec pixmapPrivateKeyRec; } drmmode_rec, *drmmode_ptr; @@ -69,6 +83,18 @@ typedef struct { unsigned rotate_fb_id; uint16_t lut_r[256], lut_g[256], lut_b[256]; DamagePtr slave_damage; + + /** + * @{ MSC (vblank count) handling for the PRESENT extension. + * + * The kernel's vblank counters are 32 bits and apparently full of + * lies, and we need to give a reliable 64-bit msc for GL, so we + * have to track and convert to a userland-tracked 64-bit msc. + */ + int32_t vblank_offset; + uint32_t msc_prev; + uint64_t msc_high; + /** @} */ } drmmode_crtc_private_rec, *drmmode_crtc_private_ptr; typedef struct { diff --git a/hw/xfree86/drivers/modesetting/pageflip.c b/hw/xfree86/drivers/modesetting/pageflip.c new file mode 100644 index 000000000..23944b7a1 --- /dev/null +++ b/hw/xfree86/drivers/modesetting/pageflip.c @@ -0,0 +1,681 @@ +/* + * Copyright © 2013 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/** @file pageflip.c + * + * Support for tracking VBlank events, emitting requests for + * pageflips, and tracking the completion of pageflips. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include "dix-config.h" +#endif + +#ifdef GLAMOR + +#include <unistd.h> +#include <xf86.h> +#include <xf86Crtc.h> +#include <glamor.h> +#include <poll.h> +#include "driver.h" +#include "drmmode_display.h" + +/** + * Tracking for outstanding events queued to the kernel. + * + * Each list entry is a struct ms_drm_queue, which has a uint32_t + * value generated from drm_seq that identifies the event and a + * reference back to the crtc/screen associated with the event. It's + * done this way rather than in the screen because we want to be able + * to drain the list of event handlers that should be called at server + * regen time, even though we don't close the drm fd and have no way + * to actually drain the kernel events. + */ +static struct xorg_list ms_drm_queue; +static uint32_t ms_drm_seq; + +struct ms_pageflip { + ScreenPtr screen; + Bool crtc_for_msc_ust; +}; + +static void ms_box_intersect(BoxPtr dest, BoxPtr a, BoxPtr b) +{ + dest->x1 = a->x1 > b->x1 ? a->x1 : b->x1; + dest->x2 = a->x2 < b->x2 ? a->x2 : b->x2; + if (dest->x1 >= dest->x2) { + dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0; + return; + } + + dest->y1 = a->y1 > b->y1 ? a->y1 : b->y1; + dest->y2 = a->y2 < b->y2 ? a->y2 : b->y2; + if (dest->y1 >= dest->y2) + dest->x1 = dest->x2 = dest->y1 = dest->y2 = 0; +} + +static void ms_crtc_box(xf86CrtcPtr crtc, BoxPtr crtc_box) +{ + if (crtc->enabled) { + crtc_box->x1 = crtc->x; + crtc_box->x2 = + crtc->x + xf86ModeWidth(&crtc->mode, crtc->rotation); + crtc_box->y1 = crtc->y; + crtc_box->y2 = + crtc->y + xf86ModeHeight(&crtc->mode, crtc->rotation); + } else + crtc_box->x1 = crtc_box->x2 = crtc_box->y1 = crtc_box->y2 = 0; +} + +static int ms_box_area(BoxPtr box) +{ + return (int)(box->x2 - box->x1) * (int)(box->y2 - box->y1); +} + +/* + * Return the crtc covering 'box'. If two crtcs cover a portion of + * 'box', then prefer 'desired'. If 'desired' is NULL, then prefer the crtc + * with greater coverage + */ + +xf86CrtcPtr +ms_covering_crtc(ScrnInfoPtr scrn, + BoxPtr box, xf86CrtcPtr desired, BoxPtr crtc_box_ret) +{ + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); + xf86CrtcPtr crtc, best_crtc; + int coverage, best_coverage; + int c; + BoxRec crtc_box, cover_box; + + best_crtc = NULL; + best_coverage = 0; + crtc_box_ret->x1 = 0; + crtc_box_ret->x2 = 0; + crtc_box_ret->y1 = 0; + crtc_box_ret->y2 = 0; + for (c = 0; c < xf86_config->num_crtc; c++) { + crtc = xf86_config->crtc[c]; + + /* If the CRTC is off, treat it as not covering */ + if (!crtc->enabled) + continue; + + ms_crtc_box(crtc, &crtc_box); + ms_box_intersect(&cover_box, &crtc_box, box); + coverage = ms_box_area(&cover_box); + if (coverage && crtc == desired) { + *crtc_box_ret = crtc_box; + return crtc; + } + if (coverage > best_coverage) { + *crtc_box_ret = crtc_box; + best_crtc = crtc; + best_coverage = coverage; + } + } + return best_crtc; +} + +xf86CrtcPtr +ms_dri2_crtc_covering_drawable(DrawablePtr pDraw) +{ + ScreenPtr pScreen = pDraw->pScreen; + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + BoxRec box, crtcbox; + xf86CrtcPtr crtc; + + box.x1 = pDraw->x; + box.y1 = pDraw->y; + box.x2 = box.x1 + pDraw->width; + box.y2 = box.y1 + pDraw->height; + + crtc = ms_covering_crtc(pScrn, &box, NULL, &crtcbox); + + /* Make sure the CRTC is valid and this is the real front buffer */ + if (crtc != NULL && !crtc->rotatedData) + return crtc; + + return NULL; +} + +uint32_t +ms_vblank_pipe_select(int pipe) +{ + if (pipe > 1) + return pipe << DRM_VBLANK_HIGH_CRTC_SHIFT; + else if (pipe > 0) + return DRM_VBLANK_SECONDARY; + else + return 0; +} + +int +ms_crtc_pipe(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc; + + if (crtc == NULL) + return 0; + + drmmode_crtc = crtc->driver_private; + + return drmmode_crtc->hw_id; +} + +static Bool +ms_get_kernel_ust_msc(xf86CrtcPtr crtc, + uint32_t *msc, uint64_t *ust) +{ + ScreenPtr screen = crtc->randr_crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + drmVBlank vbl; + int pipe = ms_crtc_pipe(crtc); + int ret; + + /* Get current count */ + vbl.request.type = DRM_VBLANK_RELATIVE | ms_vblank_pipe_select(pipe); + vbl.request.sequence = 0; + vbl.request.signal = 0; + ret = drmWaitVBlank(ms->fd, &vbl); + if (ret) { + *msc = 0; + *ust = 0; + return FALSE; + } else { + *msc = vbl.reply.sequence; + *ust = (CARD64) vbl.reply.tval_sec * 1000000 + vbl.reply.tval_usec; + return TRUE; + } +} + +/** + * Convert a 32-bit kernel MSC sequence number to a 64-bit local sequence + * number, adding in the vblank_offset and high 32 bits, and dealing + * with 64-bit wrapping + */ +uint64_t +ms_kernel_msc_to_crtc_msc(xf86CrtcPtr crtc, uint32_t sequence) +{ + drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private; + sequence += drmmode_crtc->vblank_offset; + + if ((int32_t) (sequence - drmmode_crtc->msc_prev) < -0x40000000) + drmmode_crtc->msc_high += 0x100000000L; + drmmode_crtc->msc_prev = sequence; + return drmmode_crtc->msc_high + sequence; +} + +int +ms_get_crtc_ust_msc(xf86CrtcPtr crtc, CARD64 *ust, CARD64 *msc) +{ + uint32_t kernel_msc; + + if (!ms_get_kernel_ust_msc(crtc, &kernel_msc, ust)) + return BadMatch; + *msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_msc); + + return Success; +} + +#define MAX_VBLANK_OFFSET 1000 + +/** + * Convert a 64-bit adjusted MSC value into a 32-bit kernel sequence number, + * removing the high 32 bits and subtracting out the vblank_offset term. + * + * This also updates the vblank_offset when it notices that the value should + * change. + */ +uint32_t +ms_crtc_msc_to_kernel_msc(xf86CrtcPtr crtc, uint64_t expect) +{ + drmmode_crtc_private_rec *drmmode_crtc = crtc->driver_private; + uint64_t msc; + uint64_t ust; + int64_t diff; + + ms_get_crtc_ust_msc(crtc, &ust, &msc); + diff = expect - msc; + + /* We're way off here, assume that the kernel has lost its mind + * and smack the vblank back to something sensible + */ + if (diff < -MAX_VBLANK_OFFSET || MAX_VBLANK_OFFSET < diff) { + drmmode_crtc->vblank_offset += (int32_t) diff; + if (drmmode_crtc->vblank_offset > -MAX_VBLANK_OFFSET && + drmmode_crtc->vblank_offset < MAX_VBLANK_OFFSET) + drmmode_crtc->vblank_offset = 0; + } + return (uint32_t) (expect - drmmode_crtc->vblank_offset); +} + +/** + * Check for pending DRM events and process them. + */ +static void +ms_drm_wakeup_handler(void *data, int err, void *mask) +{ + ScreenPtr screen = data; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + fd_set *read_mask = mask; + + if (data == NULL || err < 0) + return; + + if (FD_ISSET(ms->fd, read_mask)) + drmHandleEvent(ms->fd, &ms->event_context); +} + +/* + * If there are any available, read drm_events + */ +static int +ms_read_drm_events(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + struct pollfd p = { + .fd = ms->fd, + .events = POLLIN + }; + int r; + + do { + r = poll(&p, 1, 0); + } while (r == -1 && (errno == EINTR || errno == EAGAIN)); + + if (r <= 0) + return 0; + + return drmHandleEvent(ms->fd, &ms->event_context); +} + +/* + * Enqueue a potential drm response; when the associated response + * appears, we've got data to pass to the handler from here + */ +uint32_t +ms_drm_queue_alloc(xf86CrtcPtr crtc, + void *data, + ms_drm_handler_proc handler, + ms_drm_abort_proc abort) +{ + ScreenPtr screen = crtc->randr_crtc->pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + struct ms_drm_queue *q; + + q = calloc(1, sizeof(struct ms_drm_queue)); + + if (!q) + return 0; + if (!ms_drm_seq) + ++ms_drm_seq; + q->seq = ms_drm_seq++; + q->scrn = scrn; + q->crtc = crtc; + q->data = data; + q->handler = handler; + q->abort = abort; + + xorg_list_add(&q->list, &ms_drm_queue); + + return q->seq; +} + +/** + * Abort one queued DRM entry, removing it + * from the list, calling the abort function and + * freeing the memory + */ +static void +ms_drm_abort_one(struct ms_drm_queue *q) +{ + xorg_list_del(&q->list); + q->abort(q->scrn, q->crtc, q->data); + free(q); +} + +/* + * Abort by drm queue sequence number + */ +static void +ms_drm_abort_seq(ScrnInfoPtr scrn, uint32_t seq) +{ + struct ms_drm_queue *q, *tmp; + + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->seq == seq) { + ms_drm_abort_one(q); + break; + } + } +} + +/** + * Abort all queued entries on a specific scrn, used + * when resetting the X server + */ +static void +ms_drm_abort_scrn(ScrnInfoPtr scrn) +{ + struct ms_drm_queue *q, *tmp; + + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->scrn == scrn) + ms_drm_abort_one(q); + } +} + +/** + * Notify the page flip caller that the flip is complete + */ +static void +ms_pageflip_complete(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + /* Release framebuffer */ + drmModeRmFB(ms->fd, ms->drmmode.old_fb_id); + + if (!ms->pageflip_handler) + return; + + ms->pageflip_handler(ms->fe_msc, ms->fe_usec, ms->pageflip_data); +} + +/** + * Called after processing a pageflip complete event from DRM. + * + * Update the saved msc/ust values as needed, then check to see if the + * whole set of events are complete and notify the application at that + * point. + */ +static Bool +ms_handle_pageflip(struct ms_pageflip *flip, uint64_t msc, uint64_t usec) +{ + ScreenPtr screen = flip->screen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + if (flip->crtc_for_msc_ust) { + /* Cache msc, ust for later delivery with a Present event or + * GLX reply. + */ + ms->fe_msc = msc; + ms->fe_usec = usec; + } + free(flip); + + ms->flip_count--; + + /* Tell the caller if this was the last DRM flip complete event expected. */ + return ms->flip_count == 0; +} + +/* + * Called from the DRM event queue when a single flip has completed + */ +static void +ms_pageflip_handler(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + uint64_t msc, uint64_t usec, void *data) +{ + struct ms_pageflip *flip = data; + ScreenPtr screen = flip->screen; + + if (ms_handle_pageflip(flip, msc, usec)) + ms_pageflip_complete(screen); +} + +/* + * Called from the DRM queue abort code when a flip has been aborted + */ +static void +ms_pageflip_abort(ScrnInfoPtr scrn, xf86CrtcPtr crtc, void *data) +{ + struct ms_pageflip *flip = data; + modesettingPtr ms = modesettingPTR(scrn); + + if (ms_handle_pageflip(flip, 0, 0)) { + /* Release framebuffer */ + drmModeRmFB(ms->fd, ms->drmmode.old_fb_id); + + if (!ms->pageflip_abort) + return; + + ms->pageflip_abort(ms->pageflip_data); + } +} + +Bool +ms_do_pageflip(PixmapPtr new_front, + int ref_crtc_hw_id, + Bool async, + void *pageflip_data, + ms_pageflip_handler_proc pageflip_handler, + ms_pageflip_abort_proc pageflip_abort) +{ + ScreenPtr screen = new_front->drawable.pScreen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); + uint16_t pitch; + struct ms_pageflip *flip; + uint32_t new_fb_id; + uint32_t flags; + uint32_t seq; + uint32_t size; + int i; + int fd; + struct dumb_bo *new_front_bo; + + glamor_block_handler(screen); + + /* Take a detour through a dmabuf fd to get a GEM handle for our + * new front, since there's no GL interface to get the GEM handle + * (just a dmabuf fd, or an old-style GEM name) + */ + fd = glamor_fd_from_pixmap(screen, new_front, &pitch, &size); + if (fd < 0) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to get fd for flip to new front.\n"); + return FALSE; + } + new_front_bo = dumb_get_bo_from_fd(ms->fd, fd, pitch, size); + close(fd); + if (!new_front_bo) { + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to open fd for new front.\n"); + return FALSE; + } + + /* + * Create a new handle for the back buffer + */ + if (drmModeAddFB(ms->fd, scrn->virtualX, scrn->virtualY, + scrn->depth, scrn->bitsPerPixel, pitch, + new_front_bo->handle, &new_fb_id)) + goto error_out; + + dumb_bo_destroy(ms->fd, new_front_bo); + + ms->pageflip_data = pageflip_data; + ms->pageflip_handler = pageflip_handler; + ms->pageflip_abort = pageflip_abort; + + /* + * Queue flips on all enabled CRTCs + * Note that if/when we get per-CRTC buffers, we'll have to update this. + * Right now it assumes a single shared fb across all CRTCs, with the + * kernel fixing up the offset of each CRTC as necessary. + * + * Also, flips queued on disabled or incorrectly configured displays + * may never complete; this is a configuration error. + */ + ms->fe_msc = 0; + ms->fe_usec = 0; + + flags = DRM_MODE_PAGE_FLIP_EVENT; + if (async) + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + for (i = 0; i < config->num_crtc; i++) { + xf86CrtcPtr crtc = config->crtc[i]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + int this_fb_id; + + if (!config->crtc[i]->enabled) + continue; + + flip = calloc(1, sizeof(struct ms_pageflip)); + if (flip == NULL) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue: carrier alloc failed.\n"); + goto error_undo; + } + + /* Only the reference crtc will finally deliver its page flip + * completion event. All other crtc's events will be discarded. + */ + flip->crtc_for_msc_ust = (drmmode_crtc->hw_id == ref_crtc_hw_id); + flip->screen = screen; + + seq = ms_drm_queue_alloc(crtc, flip, + ms_pageflip_handler, ms_pageflip_abort); + if (!seq) { + free(flip); + goto error_undo; + } + + this_fb_id = new_fb_id; + if (drmmode_crtc->rotate_fb_id) + this_fb_id = drmmode_crtc->rotate_fb_id; + again: + if (drmModePageFlip(ms->fd, + drmmode_crtc->mode_crtc->crtc_id, + this_fb_id, + flags, (void *) (uintptr_t) seq)) { + int err = errno; + if (ms_read_drm_events(screen)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue retry\n"); + goto again; + } + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue failed: %s\n", strerror(err)); + ms_drm_abort_seq(scrn, seq); + goto error_undo; + } + ms->flip_count++; + } + + ms->drmmode.old_fb_id = ms->drmmode.fb_id; + ms->drmmode.fb_id = new_fb_id; + + if (!ms->flip_count) + ms_pageflip_complete(screen); + + return TRUE; + + error_undo: + drmModeRmFB(ms->fd, new_fb_id); + for (i = 0; i < config->num_crtc; i++) { + if (config->crtc[i]->enabled) { + ErrorF("XXX: crtc apply\n"); + /*ms_crtc_apply(config->crtc[i]);*/ + } + } + + error_out: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, "Page flip failed: %s\n", + strerror(errno)); + + ms->flip_count = 0; + return FALSE; +} + +/* + * General DRM kernel handler. Looks for the matching sequence number in the + * drm event queue and calls the handler for it. + */ +static void +ms_drm_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec, + void *user_ptr) +{ + struct ms_drm_queue *q, *tmp; + uint32_t user_data = (uint32_t) (intptr_t) user_ptr; + + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->seq == user_data) { + uint64_t msc; + + msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame); + xorg_list_del(&q->list); + q->handler(q->scrn, q->crtc, msc, + (uint64_t) sec * 1000000 + usec, q->data); + free(q); + break; + } + } +} + +Bool +ms_pageflip_screen_init(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + xorg_list_init(&ms_drm_queue); + + ms->event_context.version = DRM_EVENT_CONTEXT_VERSION; + ms->event_context.vblank_handler = ms_drm_handler; + ms->event_context.page_flip_handler = ms_drm_handler; + ms->flip_count = 0; + + /* We need to re-register the DRM fd for the synchronisation + * feedback on every server generation, so perform the + * registration within ScreenInit and not PreInit. + */ + AddGeneralSocket(ms->fd); + RegisterBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA, + ms_drm_wakeup_handler, screen); + + return TRUE; +} + +void +ms_pageflip_close_screen(ScreenPtr screen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + + ms_drm_abort_scrn(scrn); + + RemoveBlockAndWakeupHandlers((BlockHandlerProcPtr)NoopDDA, + ms_drm_wakeup_handler, screen); + RemoveGeneralSocket(ms->fd); +} + +#endif /* GLAMOR */ |