summaryrefslogtreecommitdiff
path: root/fb.c
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@gnugeneration.com>2017-01-18 17:14:52 -0800
committerVito Caputo <vcaputo@gnugeneration.com>2017-01-18 17:31:44 -0800
commit524db0cf19648e3c7c78d3e73103b7a0bdcd6bfc (patch)
tree6fd682629904a210927797c92d956c208666b03a /fb.c
parentee2073d4e411555aba878277131b56f7eb562c84 (diff)
*: move source into src/ subdir
Restoring some organizational sanity since adopting autotools.
Diffstat (limited to 'fb.c')
-rw-r--r--fb.c347
1 files changed, 0 insertions, 347 deletions
diff --git a/fb.c b/fb.c
deleted file mode 100644
index 4d1a562..0000000
--- a/fb.c
+++ /dev/null
@@ -1,347 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <xf86drm.h>
-#include <xf86drmMode.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-
-#include "fb.h"
-#include "util.h"
-
-/* Copyright (C) 2016 Vito Caputo <vcaputo@pengaru.com> */
-
-
-/* I've used a separate thread for page-flipping duties because the libdrm api
- * (and related kernel ioctl) for page flips doesn't appear to support queueing
- * multiple flip requests. In this use case we aren't interactive and wish to
- * just accumulate rendered pages until we run out of spare pages, allowing the
- * renderer to get as far ahead of vsync as possible, and certainly never
- * blocked waiting for vsync unless there's no spare page available for drawing
- * into.
- *
- * In lieu of a queueing mechanism on the drm fd, we must submit the next page
- * once the currently submitted page is flipped to - it's at that moment we
- * won't get EBUSY from the ioctl any longer. Without a dedicated thread
- * submitting flip requests and synchronously consuming their flip events,
- * we're liable to introduce latency in the page flip submission if implemented
- * in a more opportunistic manner whenever the fb api is entered from the
- * render loop.
- *
- * If the kernel simply let us queue multiple flip requests we could maintain
- * our submission queue entirely in the drm fd, and get available pages from
- * the drm event handler once our pool of pages is depleted. The kernel on
- * vsync could check the fd to see if another flip is queued and there would be
- * the least latency possible in submitting the flips - the least likely to
- * miss a vsync. This would also elide the need for synchronization in
- * userspace between the renderer and the flipper thread, since there would no
- * longer be a flipper thread.
- *
- * Let me know if you're aware of a better way with existing mainline drm!
- */
-
-
-/* Most of fb_page_t is kept private, the public part is
- * just an fb_fragment_t describing the whole page.
- */
-typedef struct _fb_page_t _fb_page_t;
-struct _fb_page_t {
- _fb_page_t *next;
- uint32_t *mmap;
- size_t mmap_size;
- uint32_t drm_dumb_handle;
- uint32_t drm_fb_id;
- fb_page_t public_page;
-};
-
-typedef struct fb_t {
- pthread_t thread;
- int drm_fd;
- uint32_t drm_crtc_id;
-
- _fb_page_t *active_page; /* page currently displayed */
-
- pthread_mutex_t ready_mutex;
- pthread_cond_t ready_cond;
- _fb_page_t *ready_pages_head; /* next pages to flip to */
- _fb_page_t *ready_pages_tail;
-
- pthread_mutex_t inactive_mutex;
- pthread_cond_t inactive_cond;
- _fb_page_t *inactive_pages; /* finished pages available for (re)use */
-
- unsigned put_pages_count;
-} fb_t;
-
-#ifndef container_of
-#define container_of(_ptr, _type, _member) \
- (_type *)((void *)(_ptr) - offsetof(_type, _member))
-#endif
-
-
-/* Synchronize with the page flip by waiting for its event. */
-static inline void fb_drm_flip_wait(fb_t *fb)
-{
- drmEventContext drm_ev_ctx = {
- .version = DRM_EVENT_CONTEXT_VERSION,
- .vblank_handler = NULL,
- .page_flip_handler = NULL
- };
- drmHandleEvent(fb->drm_fd, &drm_ev_ctx);
-}
-
-
-/* Consumes ready pages queued via fb_page_put(), submits them to drm to flip
- * on vsync. Produces inactive pages from those replaced, making them
- * available to fb_page_get(). */
-static void * fb_flipper_thread(void *_fb)
-{
- fb_t *fb = _fb;
-
- for (;;) {
- _fb_page_t *next_active_page;
- /* wait for a flip req, submit the req page for flip on vsync, wait for it to flip before making the
- * active page inactive/available, repeat.
- */
- pthread_mutex_lock(&fb->ready_mutex);
- while (!fb->ready_pages_head)
- pthread_cond_wait(&fb->ready_cond, &fb->ready_mutex);
-
- next_active_page = fb->ready_pages_head;
- fb->ready_pages_head = next_active_page->next;
- if (!fb->ready_pages_head)
- fb->ready_pages_tail = NULL;
- pthread_mutex_unlock(&fb->ready_mutex);
-
- /* submit the next active page for page flip on vsync, and wait for it. */
- pexit_if(drmModePageFlip(fb->drm_fd, fb->drm_crtc_id, next_active_page->drm_fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL) < 0,
- "unable to flip page");
- fb_drm_flip_wait(fb);
-
- /* now that we're displaying a new page, make the previously active one inactive so rendering can reuse it */
- pthread_mutex_lock(&fb->inactive_mutex);
- fb->active_page->next = fb->inactive_pages;
- fb->inactive_pages = fb->active_page;
- pthread_cond_signal(&fb->inactive_cond);
- pthread_mutex_unlock(&fb->inactive_mutex);
-
- fb->active_page = next_active_page;
- }
-}
-
-
-/* creates a framebuffer page, which is a coupled drm_fb object and mmap region of memory */
-static void fb_page_new(fb_t *fb, drmModeModeInfoPtr mode)
-{
- _fb_page_t *page;
- struct drm_mode_create_dumb create_dumb = {
- .width = mode->hdisplay,
- .height = mode->vdisplay,
- .bpp = 32,
- .flags = 0, // unused,
- };
- struct drm_mode_map_dumb map_dumb = {
- .pad = 0, // unused
- };
- uint32_t *map, fb_id;
-
- page = calloc(1, sizeof(_fb_page_t));
-
- pexit_if(ioctl(fb->drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) < 0,
- "unable to create dumb buffer");
-
- map_dumb.handle = create_dumb.handle;
- pexit_if(ioctl(fb->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) < 0,
- "unable to prepare dumb buffer for mmap");
- pexit_if(!(map = mmap(NULL, create_dumb.size, PROT_READ|PROT_WRITE, MAP_SHARED, fb->drm_fd, map_dumb.offset)),
- "unable to mmap dumb buffer");
- pexit_if(drmModeAddFB(fb->drm_fd, mode->hdisplay, mode->vdisplay, 24, 32, create_dumb.pitch, create_dumb.handle, &fb_id) < 0,
- "unable to add dumb buffer");
-
- page->mmap = map;
- page->mmap_size = create_dumb.size;
- page->drm_dumb_handle = map_dumb.handle;
- page->drm_fb_id = fb_id;
-
- page->public_page.fragment.buf = map;
- page->public_page.fragment.width = mode->hdisplay;
- page->public_page.fragment.height = mode->vdisplay;
- page->public_page.fragment.stride = create_dumb.pitch - (mode->hdisplay * 4);
-
- page->next = fb->inactive_pages;
- fb->inactive_pages = page;
-}
-
-
-static void _fb_page_free(fb_t *fb, _fb_page_t *page)
-{
- struct drm_mode_destroy_dumb destroy_dumb = {
- .handle = page->drm_dumb_handle,
- };
-
- drmModeRmFB(fb->drm_fd, page->drm_fb_id);
- munmap(page->mmap, page->mmap_size);
- ioctl(fb->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); // XXX: errors?
- free(page);
-}
-
-
-/* get the next inactive page from the fb, waiting if necessary. */
-static inline _fb_page_t * _fb_page_get(fb_t *fb)
-{
- _fb_page_t *page;
-
- /* As long as n_pages is >= 3 this won't block unless we're submitting
- * pages faster than vhz.
- */
- pthread_mutex_lock(&fb->inactive_mutex);
- while (!(page = fb->inactive_pages))
- pthread_cond_wait(&fb->inactive_cond, &fb->inactive_mutex);
- fb->inactive_pages = page->next;
- pthread_mutex_unlock(&fb->inactive_mutex);
-
- page->next = NULL;
-
- return page;
-}
-
-
-/* public interface */
-fb_page_t * fb_page_get(fb_t *fb)
-{
- return &(_fb_page_get(fb)->public_page);
-}
-
-
-/* put a page into the fb, queueing for display */
-static inline void _fb_page_put(fb_t *fb, _fb_page_t *page)
-{
- pthread_mutex_lock(&fb->ready_mutex);
- if (fb->ready_pages_tail)
- fb->ready_pages_tail->next = page;
- else
- fb->ready_pages_head = page;
-
- fb->ready_pages_tail = page;
- pthread_cond_signal(&fb->ready_cond);
- pthread_mutex_unlock(&fb->ready_mutex);
-}
-
-
-/* public interface */
-
-/* put a page into the fb, queueing for display */
-void fb_page_put(fb_t *fb, fb_page_t *page)
-{
- fb->put_pages_count++;
-
- _fb_page_put(fb, container_of(page, _fb_page_t, public_page));
-}
-
-
-/* get (and reset) the current count of put pages */
-void fb_get_put_pages_count(fb_t *fb, unsigned *count)
-{
- *count = fb->put_pages_count;
- fb->put_pages_count = 0;
-}
-
-
-/* free the fb and associated resources */
-void fb_free(fb_t *fb)
-{
- if (fb->active_page) {
- pthread_cancel(fb->thread);
- pthread_join(fb->thread, NULL);
- }
-
- /* TODO: free all the pages */
-
- pthread_mutex_destroy(&fb->ready_mutex);
- pthread_cond_destroy(&fb->ready_cond);
- pthread_mutex_destroy(&fb->inactive_mutex);
- pthread_cond_destroy(&fb->inactive_cond);
-
- free(fb);
-}
-
-
-/* create a new fb instance */
-fb_t * fb_new(int drm_fd, uint32_t crtc_id, uint32_t *connectors, int n_connectors, drmModeModeInfoPtr mode, int n_pages)
-{
- int i;
- _fb_page_t *page;
- fb_t *fb;
-
- /* XXX: page-flipping is the only supported rendering model, requiring 2+ pages. */
- if (n_pages < 2)
- return NULL;
-
- fb = calloc(1, sizeof(fb_t));
- if (!fb)
- return NULL;
-
- fb->drm_fd = drm_fd;
- fb->drm_crtc_id = crtc_id;
-
- for (i = 0; i < n_pages; i++)
- fb_page_new(fb, mode);
-
- pthread_mutex_init(&fb->ready_mutex, NULL);
- pthread_cond_init(&fb->ready_cond, NULL);
- pthread_mutex_init(&fb->inactive_mutex, NULL);
- pthread_cond_init(&fb->inactive_cond, NULL);
-
- page = _fb_page_get(fb);
-
- /* set the video mode, pinning this page, set it as the active page. */
- if (drmModeSetCrtc(drm_fd, crtc_id, page->drm_fb_id, 0, 0, connectors, n_connectors, mode) < 0) {
- _fb_page_free(fb, page);
- fb_free(fb);
- return NULL;
- }
-
- fb->active_page = page;
-
- /* start up the page flipper thread */
- pthread_create(&fb->thread, NULL, fb_flipper_thread, fb);
-
- return fb;
-}
-
-
-/* divide a fragment into n_fragments, storing their values into fragments[],
- * which is expected to have n_fragments of space. */
-void fb_fragment_divide(fb_fragment_t *fragment, unsigned n_fragments, fb_fragment_t fragments[])
-{
- unsigned slice = fragment->height / n_fragments;
- unsigned i;
- void *buf = fragment->buf;
- unsigned pitch = (fragment->width * 4) + fragment->stride;
- unsigned y = fragment->y;
-
- /* This just splits the supplied fragment into even horizontal slices */
- /* TODO: It probably makes sense to add an fb_fragment_tile() as well, since some rendering
- * algorithms benefit from the locality of a tiled fragment.
- */
-
- for (i = 0; i < n_fragments; i++) {
- fragments[i].buf = buf;
- fragments[i].x = fragment->x;
- fragments[i].y = y;
- fragments[i].width = fragment->width;
- fragments[i].height = slice;
- fragments[i].stride = fragment->stride;
-
- buf += pitch * slice;
- y += slice;
- }
- /* TODO: handle potential fractional tail slice? */
-}
© All Rights Reserved