From 3a71d0437bc41969f39bd23619a5fb5a3e675212 Mon Sep 17 00:00:00 2001 From: Caleb Connolly Date: Tue, 21 Nov 2023 02:49:38 +0000 Subject: [PATCH] drm working Signed-off-by: Caleb Connolly --- include/framebuffer.h | 37 ++++ include/tfblib.h | 20 +- meson.build | 3 +- src/animate.c | 12 +- src/drawing.c | 20 +- src/drm.c | 488 ++++++++++++++++++++++++++++++++++++++++++ src/fb.c | 85 +++++++- src/meson.build | 1 + src/pbsplash.c | 62 +++--- 9 files changed, 665 insertions(+), 63 deletions(-) create mode 100644 include/framebuffer.h create mode 100644 src/drm.c diff --git a/include/framebuffer.h b/include/framebuffer.h new file mode 100644 index 0000000..e6b7da2 --- /dev/null +++ b/include/framebuffer.h @@ -0,0 +1,37 @@ +#ifndef _FRAMEBUFFER_H +#define _FRAMEBUFFER_H + +#include + +#include +#include +#include +#include + +struct modeset_buf { + uint32_t width; + uint32_t height; + uint32_t pitch; + uint32_t size; + uint32_t handle; + uint8_t *map; + uint32_t fb; +}; + +struct drm_framebuffer { + unsigned int front_buf; + struct modeset_buf bufs[2]; + + drmModeModeInfo mode; + uint32_t conn; + uint32_t mm_width; + uint32_t mm_height; + uint32_t crtc; + drmModeCrtc *saved_crtc; +}; + +extern struct drm_framebuffer *drm; + +int drm_framebuffer_init(int *handle, const char *card); +void drm_framebuffer_close(int handle); +#endif diff --git a/include/tfblib.h b/include/tfblib.h index 278a5af..2f809d8 100644 --- a/include/tfblib.h +++ b/include/tfblib.h @@ -85,6 +85,8 @@ */ int tfb_acquire_fb(u32 flags, const char *fb_device, const char *tty_device); +int tfb_acquire_drm(uint32_t flags, const char *device); + /** * Release the framebuffer device * @@ -97,23 +99,6 @@ int tfb_acquire_fb(u32 flags, const char *fb_device, const char *tty_device); */ void tfb_release_fb(void); -/** - * Limit the drawing to a window at (x, y) having size (w, h) - * - * In case the application does not want to use the whole screen, it can call - * this function to get the coordinate system shifted by (+x, +y) and everything - * outside of it just cut off. Using windows smaller than then screen could - * improve application's performance. - * - * @param[in] x X coordinate of the window, in pixels - * @param[in] y Y coordinate of the window, in pixels - * @param[in] w Width of the window, in pixels - * @param[in] h Height of the window, in pixels - * - * @return #TFB_SUCCESS in case of success or #TFB_ERR_INVALID_WINDOW. - */ -int tfb_set_window(u32 x, u32 y, u32 w, u32 h); - /** * Limit the drawing to a window having size (w, h) at the center of the screen * @@ -529,6 +514,7 @@ extern u8 __fb_g_pos; extern u8 __fb_b_pos; extern uint32_t tfb_red; +extern uint32_t tfb_green; extern uint32_t tfb_blue; extern uint32_t tfb_white; extern uint32_t tfb_gray; diff --git a/meson.build b/meson.build index 4f1f017..96b1cfd 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,8 @@ project('pbsplash', 'c') cc = meson.get_compiler('c') deps = [ - cc.find_library('m', required : false) + cc.find_library('m', required : false), + dependency('libdrm', required : true), ] inc = [ diff --git a/src/animate.c b/src/animate.c index 4cfc6c1..b80f2d1 100644 --- a/src/animate.c +++ b/src/animate.c @@ -1,8 +1,8 @@ #include "pbsplash.h" #include #include -#include -#include + +#include "tfblib.h" struct col color = { .r = 255, .g = 255, .b = 255, .a = 255 }; @@ -28,6 +28,14 @@ static void circles_wave(int frame, int w, int y_off, long dpi) amplitude * 2 + rad * 2 + 6, tfb_black); tfb_fill_circle(x, y, rad, t_col); } + + // tfb_draw_line(0, 0, 100, 500, tfb_red); + // tfb_draw_line(100, 0, 100, 500, tfb_green); + // tfb_draw_line(200, 0, 100, 500, tfb_blue); + + // tfb_fill_rect(400, 300, 200, 500, tfb_red); + // tfb_fill_rect(600, 300, 200, 500, tfb_green); + // tfb_fill_rect(800, 300, 200, 500, tfb_blue); } void animate_frame(int frame, int w, int y_off, long dpi) diff --git a/src/drawing.c b/src/drawing.c index 1f6d996..6f001cf 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -8,6 +8,7 @@ #include #include "pbsplash.h" +#include "framebuffer.h" #include "tfblib.h" extern inline uint32_t tfb_make_color(uint8_t red, uint8_t green, uint8_t blue); @@ -49,11 +50,6 @@ uint8_t __fb_r_pos; uint8_t __fb_g_pos; uint8_t __fb_b_pos; -int tfb_set_center_window_size(uint32_t w, uint32_t h) -{ - return tfb_set_window(__fb_screen_w / 2 - w / 2, __fb_screen_h / 2 - h / 2, w, h); -} - void tfb_clear_screen(uint32_t color) { if (__fb_pitch == (uint32_t)4 * __fb_screen_w) { @@ -141,10 +137,22 @@ void tfb_fill_rect(int x, int y, int w, int h, uint32_t color) if (w < 0 || h < 0) return; + /* + for (uint32_t cy = y; cy < yend; cy++) { + //memset(dest, color, w); + for (uint32_t cx = x; cx < x + w; cx++) + tfb_draw_pixel(cx, cy, color); + } + */ + w = MIN(w, MAX(0, (int)__fb_win_end_x - x)); yend = MIN(y + h, __fb_win_end_y); - dest = __fb_buffer + y * __fb_pitch + (x << 2); + dest = __fb_buffer + y * __fb_pitch + (x * 4); + + /* drm alignment weirdness */ + //if (drm) + w *= 4; for (uint32_t cy = y; cy < yend; cy++, dest += __fb_pitch) memset(dest, color, w); diff --git a/src/drm.c b/src/drm.c new file mode 100644 index 0000000..7129a68 --- /dev/null +++ b/src/drm.c @@ -0,0 +1,488 @@ +/* + * modeset - DRM Double-Buffered Modesetting Example + * + * Written 2012 by David Rheinsberg + * Dedicated to the Public Domain. + */ + +/* + * DRM Double-Buffered Modesetting Howto + * This example extends the modeset.c howto and introduces double-buffering. + * When drawing a new frame into a framebuffer, we should always draw into an + * unused buffer and not into the front buffer. If we draw into the front + * buffer, we might have drawn half the frame when the display-controller starts + * scanning out the next frame. Hence, we see flickering on the screen. + * The technique to avoid this is called double-buffering. We have two + * framebuffers, the front buffer which is currently used for scanout and a + * back-buffer that is used for drawing operations. When a frame is done, we + * simply swap both buffers. + * Swapping does not mean copying data, instead, only the pointers to the + * buffers are swapped. + * + * Please read modeset.c before reading this file as most of the functions stay + * the same. Only the differences are highlighted here. + * Also note that triple-buffering or any other number of buffers can be easily + * implemented by following the scheme here. However, in this example we limit + * the number of buffers to 2 so it is easier to follow. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "framebuffer.h" + +struct modeset_buf; +struct drm_framebuffer; +static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, + struct drm_framebuffer *dev); +static int modeset_create_fb(int fd, struct modeset_buf *buf); +static void modeset_destroy_fb(int fd, struct modeset_buf *buf); +static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, + struct drm_framebuffer *dev); +static int drm_init(int fd); + +/* + * modeset_open() stays the same as before. + */ + +static int drm_open(int *handle, const char *node) +{ + int fd, ret; + uint64_t has_dumb; + + fd = open(node, O_RDWR | O_CLOEXEC); + if (fd < 0) { + ret = -errno; + fprintf(stderr, "cannot open '%s': %m\n", node); + return ret; + } + + if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || + !has_dumb) { + fprintf(stderr, "drm device '%s' does not support dumb buffers\n", + node); + close(fd); + return -EOPNOTSUPP; + } + + ret = drm_init(fd); + if (ret) { + close(fd); + return ret; + } + + *handle = fd; + return 0; +} + +/* + * Previously, we used the modeset_dev objects to hold buffer informations, too. + * Technically, we could have split them but avoided this to make the + * example simpler. + * However, in this example we need 2 buffers. One back buffer and one front + * buffer. So we introduce a new structure modeset_buf which contains everything + * related to a single buffer. Each device now gets an array of two of these + * buffers. + * Each buffer consists of width, height, stride, size, handle, map and fb-id. + * They have the same meaning as before. + * + * Each device also gets a new integer field: front_buf. This field contains the + * index of the buffer that is currently used as front buffer / scanout buffer. + * In our example it can be 0 or 1. We flip it by using XOR: + * dev->front_buf ^= dev->front_buf + * + * Everything else stays the same. + */ + +struct drm_framebuffer *drm = NULL; + +/* + * modeset_prepare() stays the same. + */ + +static int drm_init(int fd) +{ + drmModeRes *res; + drmModeConnector *conn; + unsigned int i; + struct drm_framebuffer *dev; + struct modeset_buf *buf; + int ret; + + /* retrieve resources */ + res = drmModeGetResources(fd); + if (!res) { + fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", + errno); + return -errno; + } + + /* iterate all connectors */ + for (i = 0; i < res->count_connectors; ++i) { + /* get information for each connector */ + conn = drmModeGetConnector(fd, res->connectors[i]); + if (!conn) { + fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", + i, res->connectors[i], errno); + continue; + } + + /* create a device structure */ + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->conn = conn->connector_id; + dev->mm_width = conn->mmWidth; + dev->mm_height = conn->mmHeight; + + /* call helper function to prepare this connector */ + ret = modeset_setup_dev(fd, res, conn, dev); + if (ret) { + if (ret != -ENOENT) { + errno = -ret; + fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", + i, res->connectors[i], errno); + } + free(dev); + drmModeFreeConnector(conn); + continue; + } + + /* free connector data and link device into global list */ + drmModeFreeConnector(conn); + drm = dev; + break; + } + + /* free resources again */ + drmModeFreeResources(res); + + /* perform actual modesetting on each found connector+CRTC */ + dev->saved_crtc = drmModeGetCrtc(fd, dev->crtc); + buf = &dev->bufs[dev->front_buf]; + ret = drmModeSetCrtc(fd, dev->crtc, buf->fb, 0, 0, + &dev->conn, 1, &dev->mode); + if (ret) + fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", + dev->conn, errno); + + return 0; +} + +/* + * modeset_setup_dev() sets up all resources for a single device. It mostly + * stays the same, but one thing changes: We allocate two framebuffers instead + * of one. That is, we call modeset_create_fb() twice. + * We also copy the width/height information into both framebuffers so + * modeset_create_fb() can use them without requiring a pointer to modeset_dev. + */ + +static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, + struct drm_framebuffer *dev) +{ + int ret; + + /* check if a monitor is connected */ + if (conn->connection != DRM_MODE_CONNECTED) { + fprintf(stderr, "ignoring unused connector %u\n", + conn->connector_id); + return -ENOENT; + } + + /* check if there is at least one valid mode */ + if (conn->count_modes == 0) { + fprintf(stderr, "no valid mode for connector %u\n", + conn->connector_id); + return -EFAULT; + } + + /* copy the mode information into our device structure and into both + * buffers */ + memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); + dev->bufs[0].width = conn->modes[0].hdisplay; + dev->bufs[0].height = conn->modes[0].vdisplay; + dev->bufs[1].width = conn->modes[0].hdisplay; + dev->bufs[1].height = conn->modes[0].vdisplay; + fprintf(stderr, "mode for connector %u is %ux%u\n", + conn->connector_id, dev->bufs[0].width, dev->bufs[0].height); + + /* find a crtc for this connector */ + ret = modeset_find_crtc(fd, res, conn, dev); + if (ret) { + fprintf(stderr, "no valid crtc for connector %u\n", + conn->connector_id); + return ret; + } + + /* create framebuffer #1 for this CRTC */ + ret = modeset_create_fb(fd, &dev->bufs[0]); + if (ret) { + fprintf(stderr, "cannot create framebuffer for connector %u\n", + conn->connector_id); + return ret; + } + + /* create framebuffer #2 for this CRTC */ + ret = modeset_create_fb(fd, &dev->bufs[1]); + if (ret) { + fprintf(stderr, "cannot create framebuffer for connector %u\n", + conn->connector_id); + modeset_destroy_fb(fd, &dev->bufs[0]); + return ret; + } + + return 0; +} + +/* + * modeset_find_crtc() stays the same. + */ + +static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, + struct drm_framebuffer *dev) +{ + drmModeEncoder *enc; + unsigned int i, j; + int32_t crtc; + + /* first try the currently conected encoder+crtc */ + if (conn->encoder_id) + enc = drmModeGetEncoder(fd, conn->encoder_id); + else + enc = NULL; + + if (enc) { + if (enc->crtc_id) { + crtc = enc->crtc_id; + if (crtc >= 0) { + drmModeFreeEncoder(enc); + dev->crtc = crtc; + return 0; + } + } + + drmModeFreeEncoder(enc); + } + + /* If the connector is not currently bound to an encoder or if the + * encoder+crtc is already used by another connector (actually unlikely + * but lets be safe), iterate all other available encoders to find a + * matching CRTC. */ + for (i = 0; i < conn->count_encoders; ++i) { + enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) { + fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", + i, conn->encoders[i], errno); + continue; + } + + /* iterate all global CRTCs */ + for (j = 0; j < res->count_crtcs; ++j) { + /* check whether this CRTC works with the encoder */ + if (!(enc->possible_crtcs & (1 << j))) + continue; + + /* check that no other device already uses this CRTC */ + crtc = res->crtcs[j]; + + /* we have found a CRTC, so save it and return */ + if (crtc >= 0) { + drmModeFreeEncoder(enc); + dev->crtc = crtc; + return 0; + } + } + + drmModeFreeEncoder(enc); + } + + fprintf(stderr, "cannot find suitable CRTC for connector %u\n", + conn->connector_id); + return -ENOENT; +} + +/* + * modeset_create_fb() is mostly the same as before. Buf instead of writing the + * fields of a modeset_dev, we now require a buffer pointer passed as @buf. + * Please note that buf->width and buf->height are initialized by + * modeset_setup_dev() so we can use them here. + */ + +static int modeset_create_fb(int fd, struct modeset_buf *buf) +{ + struct drm_mode_create_dumb creq; + struct drm_mode_destroy_dumb dreq; + struct drm_mode_map_dumb mreq; + int ret; + + /* create dumb buffer */ + memset(&creq, 0, sizeof(creq)); + creq.width = buf->width; + creq.height = buf->height; + creq.bpp = 32; + ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); + if (ret < 0) { + fprintf(stderr, "cannot create dumb buffer (%d): %m\n", + errno); + return -errno; + } + buf->pitch = creq.pitch; + buf->size = creq.size; + buf->handle = creq.handle; + + /* create framebuffer object for the dumb-buffer */ + ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->pitch, + buf->handle, &buf->fb); + if (ret) { + fprintf(stderr, "cannot create framebuffer (%d): %m\n", + errno); + ret = -errno; + goto err_destroy; + } + + /* prepare buffer for memory mapping */ + memset(&mreq, 0, sizeof(mreq)); + mreq.handle = buf->handle; + ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); + if (ret) { + fprintf(stderr, "cannot map dumb buffer (%d): %m\n", + errno); + ret = -errno; + goto err_fb; + } + + /* perform actual memory mapping */ + buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, mreq.offset); + if (buf->map == MAP_FAILED) { + fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", + errno); + ret = -errno; + goto err_fb; + } + + /* clear the framebuffer to 0 */ + memset(buf->map, 0, buf->size); + + return 0; + +err_fb: + drmModeRmFB(fd, buf->fb); +err_destroy: + memset(&dreq, 0, sizeof(dreq)); + dreq.handle = buf->handle; + drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + return ret; +} + +/* + * modeset_destroy_fb() is a new function. It does exactly the reverse of + * modeset_create_fb() and destroys a single framebuffer. The modeset.c example + * used to do this directly in modeset_cleanup(). + * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer. + */ + +static void modeset_destroy_fb(int fd, struct modeset_buf *buf) +{ + struct drm_mode_destroy_dumb dreq; + + /* unmap buffer */ + munmap(buf->map, buf->size); + + /* delete framebuffer */ + drmModeRmFB(fd, buf->fb); + + /* delete dumb buffer */ + memset(&dreq, 0, sizeof(dreq)); + dreq.handle = buf->handle; + drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); +} + +int drm_framebuffer_init(int *handle, const char *card) +{ + int ret; + + card = card ?: "/dev/dri/card0"; + + printf("using card '%s'\n", card); + + /* open the DRM device */ + ret = drm_open(handle, card); + if (ret) { + errno = -ret; + fprintf(stderr, "drm_open failed with error %d: %m\n", errno); + return ret; + } + + printf("DRM mode %dx%d @ %dHz\n", drm->bufs[0].width, drm->bufs[0].height, + drm->mode.vrefresh); + + /* draw some colors for 5seconds */ + // modeset_draw(*handle); + + // /* cleanup everything */ + // drm_framebuffer_close(*handle); + + return ret; +} + +/* + * modeset_cleanup() stays the same as before. But it now calls + * modeset_destroy_fb() instead of accessing the framebuffers directly. + */ + +void drm_framebuffer_close(int handle) +{ + /* restore saved CRTC configuration */ + drmModeSetCrtc(handle, + drm->saved_crtc->crtc_id, + drm->saved_crtc->buffer_id, + drm->saved_crtc->x, + drm->saved_crtc->y, + &drm->conn, + 1, + &drm->saved_crtc->mode); + drmModeFreeCrtc(drm->saved_crtc); + + /* destroy framebuffers */ + modeset_destroy_fb(handle, &drm->bufs[1]); + modeset_destroy_fb(handle, &drm->bufs[0]); + + /* free allocated memory */ + free(drm); +} + +/* + * This was a very short extension to the basic modesetting example that shows + * how double-buffering is implemented. Double-buffering is the de-facto + * standard in any graphics application so any other example will be based on + * this. It is important to understand the ideas behind it as the code is pretty + * easy and short compared to modeset.c. + * + * Double-buffering doesn't solve all problems. Vsync'ed page-flips solve most + * of the problems that still occur, but has problems on it's own (see + * modeset-vsync.c for a discussion). + * + * If you want more code, I can recommend reading the source-code of: + * - plymouth (which uses dumb-buffers like this example; very easy to understand) + * - kmscon (which uses libuterm to do this) + * - wayland (very sophisticated DRM renderer; hard to understand fully as it + * uses more complicated techniques like DRM planes) + * - xserver (very hard to understand as it is split across many files/projects) + * + * Any feedback is welcome. Feel free to use this code freely for your own + * documentation or projects. + * + * - Hosted on http://github.com/dvdhrm/docs + * - Written by David Rheinsberg + */ \ No newline at end of file diff --git a/src/fb.c b/src/fb.c index 2c774f2..f115481 100644 --- a/src/fb.c +++ b/src/fb.c @@ -15,6 +15,7 @@ #include #include +#include "framebuffer.h" #include "pbsplash.h" #include "tfblib.h" @@ -23,15 +24,14 @@ struct fb_var_screeninfo __fbi; -#define ROTATE_SWAP_XY (__fbi.rotate == 1 || __fbi.rotate == 3) - int __tfb_ttyfd = -1; static int fbfd = -1; +static int drmfd = -1; static void tfb_init_colors(void); -int tfb_set_window(uint32_t x, uint32_t y, uint32_t w, uint32_t h) +static int tfb_set_window(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t xoffset, uint32_t yoffset) { if (x + w > (uint32_t)__fb_screen_w) { fprintf(stderr, "tfb_set_window: window exceeds screen width\n"); @@ -41,8 +41,8 @@ int tfb_set_window(uint32_t x, uint32_t y, uint32_t w, uint32_t h) fprintf(stderr, "tfb_set_window: window exceeds screen height\n"); } - __fb_off_x = __fbi.xoffset + x; - __fb_off_y = __fbi.yoffset + y; + __fb_off_x = xoffset + x; + __fb_off_y = yoffset + y; __fb_win_w = w; __fb_win_h = h; __fb_win_end_x = __fb_off_x + __fb_win_w; @@ -51,6 +51,42 @@ int tfb_set_window(uint32_t x, uint32_t y, uint32_t w, uint32_t h) return 0; } +int tfb_acquire_drm(uint32_t flags, const char *device) +{ + int ret; + ret = drm_framebuffer_init(&drmfd, device); + if (ret) { + fprintf(stderr, "Failed to get framebuffer\n"); + return ret; + } + + __fb_real_buffer = drm->bufs[0].map; + __fb_buffer = drm->bufs[1].map; + __fb_pitch = drm->bufs[0].pitch; + __fb_size = drm->bufs[0].size; + __fb_pitch_div4 = __fb_pitch >> 2; + + __fb_screen_w = drm->bufs[0].width; + __fb_screen_h = drm->bufs[0].height; + + __fb_r_pos = 16; + __fb_r_mask_size = 8; + __fb_r_mask = 0xff << __fb_r_pos; + + __fb_g_pos = 8; + __fb_g_mask_size = 8; + __fb_g_mask = 0xff << __fb_g_pos; + + __fb_b_pos = 0; + __fb_b_mask_size = 8; + __fb_b_mask = 0xff << __fb_b_pos; + + tfb_set_window(0, 0, __fb_screen_w, __fb_screen_h, 0, 0); + tfb_init_colors(); + + return 0; +} + int tfb_acquire_fb(uint32_t flags, const char *fb_device, const char *tty_device) { static struct fb_fix_screeninfo fb_fixinfo; @@ -134,8 +170,8 @@ int tfb_acquire_fb(uint32_t flags, const char *fb_device, const char *tty_device __fb_buffer = __fb_real_buffer; } - __fb_screen_w = ROTATE_SWAP_XY ? __fbi.yres : __fbi.xres; - __fb_screen_h = ROTATE_SWAP_XY ? __fbi.xres : __fbi.yres; + __fb_screen_w = __fbi.xres; + __fb_screen_h = __fbi.yres; __fb_r_pos = __fbi.red.offset; __fb_r_mask_size = __fbi.red.length; @@ -149,7 +185,7 @@ int tfb_acquire_fb(uint32_t flags, const char *fb_device, const char *tty_device __fb_b_mask_size = __fbi.blue.length; __fb_b_mask = ((1 << __fb_b_mask_size) - 1) << __fb_b_pos; - tfb_set_window(0, 0, __fb_screen_w, __fb_screen_h); + tfb_set_window(0, 0, __fb_screen_w, __fb_screen_h, __fbi.xoffset, __fbi.yoffset); tfb_init_colors(); return 0; @@ -157,6 +193,10 @@ int tfb_acquire_fb(uint32_t flags, const char *fb_device, const char *tty_device void tfb_release_fb(void) { + if (drmfd >= 0) { + drm_framebuffer_close(drmfd); + return; + } if (__fb_real_buffer) munmap(__fb_real_buffer, __fb_size); @@ -219,6 +259,23 @@ void tfb_flush_window(void) int tfb_flush_fb(void) { + int ret; + struct modeset_buf *buf; + if (drmfd >= 0) { + //printf("%s buffer %d\n", __func__, drm->front_buf); + buf = &drm->bufs[drm->front_buf ^ 1]; + ret = drmModeSetCrtc(drmfd, drm->crtc, buf->fb, 0, 0, + &drm->conn, 1, &drm->mode); + if (ret) + fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n", + drm->conn, errno); + else + drm->front_buf ^= 1; + + /* Swap the tfblib copies of the pointers */ + __fb_buffer = buf->map; //drm->bufs[drm->front_buf ^ 1].map; + return 0; + } __fbi.activate |= FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &__fbi) < 0) { perror("Couldn't flush framebuffer"); @@ -230,11 +287,17 @@ int tfb_flush_fb(void) uint32_t tfb_screen_width_mm(void) { - return ROTATE_SWAP_XY ? __fbi.height : __fbi.width; + if (drmfd >= 0) + return drm->mm_width; + + return __fbi.width; } uint32_t tfb_screen_height_mm(void) { - return ROTATE_SWAP_XY ? __fbi.width : __fbi.height; + if (drmfd >= 0) + return drm->mm_height; + + return __fbi.height; } /* @@ -246,6 +309,7 @@ uint32_t tfb_screen_height_mm(void) */ uint32_t tfb_red; +uint32_t tfb_green; uint32_t tfb_blue; uint32_t tfb_white; uint32_t tfb_gray; @@ -254,6 +318,7 @@ uint32_t tfb_black; static void tfb_init_colors(void) { tfb_red = tfb_make_color(255, 0, 0); + tfb_green = tfb_make_color(0, 255, 0); tfb_blue = tfb_make_color(0, 0, 255); tfb_white = tfb_make_color(255, 255, 255); tfb_gray = tfb_make_color(128, 128, 128); diff --git a/src/meson.build b/src/meson.build index 532e797..89879e9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,7 @@ src = [ 'timespec.c', 'pbsplash.c', 'fb.c', + 'drm.c', 'drawing.c', ] diff --git a/src/pbsplash.c b/src/pbsplash.c index 196c66d..e2b909c 100644 --- a/src/pbsplash.c +++ b/src/pbsplash.c @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include #include @@ -17,6 +15,7 @@ #include "nanosvg.h" #include "nanosvgrast.h" #include "pbsplash.h" +#include "tfblib.h" #include "timespec.h" #define MSG_MAX_LEN 4096 @@ -30,7 +29,7 @@ volatile sig_atomic_t terminate = 0; -bool debug = false; +bool debug = true; struct col bg = { .r = 0, .g = 0, .b = 0, .a = 255 }; static int screenWidth, screenHeight; @@ -78,9 +77,10 @@ static void draw_svg(NSVGimage *image, int x, int y, int w, int h) LOG("draw_svg: (%d, %d), %dx%d, %f\n", x, y, w, h, sz); NSVGrasterizer *rast = nsvgCreateRasterizer(); unsigned char *img = zalloc(w * h * 4); + struct col bg = { .r = 0, .g = 0, .b = 0, .a = 255 }; nsvgRasterize(rast, image, 0, 0, sz, img, w, h, w * 4); - blit_buf(img, x, y, w, h, false); + blit_buf(img, x, y, w, h, bg, false); free(img); nsvgDeleteRasterizer(rast); @@ -94,10 +94,11 @@ static void draw_text(const NSVGimage *font, const char *text, int x, int y, int NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text)); unsigned char *img = zalloc(width * height * 4); NSVGrasterizer *rast = nsvgCreateRasterizer(); + struct col bg = { .r = 0, .g = 0, .b = 0, .a = 255 }; nsvgRasterizeText(rast, font, 0, 0, scale, img, width, height, width * 4, text); - blit_buf(img, x, y, width, height, true); + blit_buf(img, x, y, width, height, bg, true); free(img); free(shapes); @@ -198,7 +199,7 @@ static void calculate_dpi_info(struct dpi_info *dpi_info) int h_mm = tfb_screen_height_mm(); if ((w_mm < 1 || h_mm < 1) && !dpi_info->dpi) { - fprintf(stderr, "ERROR!!!: Invalid screen size: %dx%d\n", w_mm, h_mm); + fprintf(stderr, "ERROR!!!: Invalid screen size: %dmmx%dmm\n", w_mm, h_mm); // Assume a dpi of 300 // This should be readable everywhere @@ -467,22 +468,28 @@ int main(int argc, char **argv) } } - { - FILE *fp = fopen("/sys/devices/virtual/tty/tty0/active", "r"); - int len = strlen(active_tty); - char *ptr = active_tty + len; - if (fp != NULL) { - fgets(ptr, TTY_PATH_LEN - len, fp); - *(ptr + strlen(ptr) - 1) = '\0'; - fclose(fp); - } - } + // { + // FILE *fp = fopen("/sys/devices/virtual/tty/tty0/active", "r"); + // int len = strlen(active_tty); + // char *ptr = active_tty + len; + // if (fp != NULL) { + // fgets(ptr, TTY_PATH_LEN - len, fp); + // *(ptr + strlen(ptr) - 1) = '\0'; + // fclose(fp); + // } + // } - LOG("active tty: '%s'\n", active_tty); + // LOG("active tty: '%s'\n", active_tty); - if ((rc = tfb_acquire_fb(/*TFB_FL_NO_TTY_KD_GRAPHICS */ 0, "/dev/fb0", active_tty)) != - TFB_SUCCESS) { - fprintf(stderr, "tfb_acquire_fb() failed with error code: %d\n", rc); + // if ((rc = tfb_acquire_fb(/*TFB_FL_NO_TTY_KD_GRAPHICS */ 0, "/dev/fb0", active_tty)) != + // TFB_SUCCESS) { + // fprintf(stderr, "tfb_acquire_fb() failed with error code: %d\n", rc); + // rc = 1; + // return rc; + // } + + if ((rc = tfb_acquire_drm(0, "/dev/dri/card0")) != 0) { + fprintf(stderr, "tfb_acquire_drm() failed with error code: %d\n", rc); rc = 1; return rc; } @@ -521,15 +528,16 @@ int main(int argc, char **argv) show_messages(&msgs, &dpi_info); no_messages: + /* This is necessary to copy the parts we draw once (like the logo) to the front buffer */ tfb_flush_window(); tfb_flush_fb(); int tick = 0; - int tty = open(active_tty, O_RDWR); - if (!tty) { - fprintf(stderr, "Failed to open tty %s (%d)\n", active_tty, errno); - goto out; - } + // int tty = open(active_tty, O_RDWR); + // if (!tty) { + // fprintf(stderr, "Failed to open tty %s (%d)\n", active_tty, errno); + // goto out; + // } struct timespec epoch, start, end, diff; int target_fps = 60; @@ -546,7 +554,7 @@ no_messages: tfb_flush_fb(); clock_gettime(CLOCK_REALTIME, &end); diff = timespec_sub(end, start); - // printf("%05d: %09ld\n", tick, diff.tv_nsec); + //printf("%05d: %09ld\n", tick, diff.tv_nsec); if (diff.tv_nsec < 1000000000 / target_fps) { struct timespec sleep_time = { .tv_sec = 0, @@ -559,7 +567,7 @@ no_messages: out: // Before we exit print the logo so it will persist if (image_info.image) { - ioctl(tty, KDSETMODE, KD_TEXT); + //ioctl(tty, KDSETMODE, KD_TEXT); draw_svg(image_info.image, image_info.x, image_info.y, image_info.width, image_info.height); }