Commit b8614bd0 authored by Youness Alaoui's avatar Youness Alaoui
Browse files

Add DRI support

parent 8dfe128c
all : test-menu-gtk test-menu-fb fbwhiptail
all : test-menu-gtk test-menu-fb fbwhiptail test-dri
test-menu-gtk: test-menu-gtk.c cairo_menu.c cairo_utils.c
gcc -g -O0 -o $@ $^ \
......@@ -12,7 +12,9 @@ test-menu-fb: test-menu-fb.c cairo_menu.c cairo_utils.c cairo_linuxfb.c \
strip $@
fbwhiptail: fbwhiptail.c cairo_menu.c cairo_utils.c cairo_linuxfb.c \
fbwhiptail: fbwhiptail.c fbwhiptail_menu.c cairo_menu.c cairo_utils.c cairo_dri.c cairo_linuxfb.c \
libcairo.a libpixman-1.a libpng16.a libz.a
gcc -g -O0 -o $@ $^ -lm
strip $@
test-dri: test-dri.c
gcc -g -O0 -o $@ $^
/*
* cairo_fb.c : Cairo Linux Framebuffer surface implementation
* Based on the demo application at https://github.com/toradex/cairo-fb-examples
*
* Copyright (c) 2015, Toradex AG
* Copyright (c) 2018, Youness Alaoui (KaKaRoTo)
*
* This project is licensed under the terms of the MIT license (see
* LICENSE)
*/
#include "cairo_dri.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <time.h>
#ifdef DEBUG
#define PERROR perror
#define PRINTF printf
#else
#define PERROR(...)
#define PRINTF(...)
#endif
static cairo_user_data_key_t user_data_key;
typedef struct {
cairo_dri_t *dri;
uint8_t *fb_data;
uint32_t size;
uint32_t fb_id;
uint32_t handle;
dri_screen_t *screen;
} dri_surface_fb_t;
/* Create a cairo surface using the specified framebuffer
* can return an error if fb driver doesn't support double buffering
*/
cairo_dri_t *cairo_dri_open(const char *dri_filename)
{
cairo_surface_t *surface;
cairo_dri_t *device = NULL;
uint32_t *res_fb_ids = NULL;
uint32_t *res_crtc_ids = NULL;
uint32_t *res_conn_ids = NULL;
uint32_t *res_enc_ids = NULL;
struct drm_mode_card_res res = {0};
int i, j, k, l;
device = malloc(sizeof(cairo_dri_t));
if (device == NULL) {
PERROR ("Error: can't allocate structure");
return NULL;
}
memset (device, 0, sizeof(cairo_dri_t));
// Open the file for reading and writing
device->dri_fd = open(dri_filename, O_RDWR);
if (device->dri_fd == -1) {
PERROR ("Error: cannot open DRM device");
goto handle_open_error;
}
device->master_is_set = ioctl(device->dri_fd, DRM_IOCTL_SET_MASTER, 0);
//Get resource counts
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res) == -1) {
PERROR ("Error: reading resource count information");
goto handle_ioctl_error;
}
if (res.count_fbs)
res_fb_ids = malloc (sizeof(uint32_t) * res.count_fbs);
if (res.count_crtcs)
res_crtc_ids = malloc (sizeof(uint32_t) * res.count_crtcs);
if (res.count_connectors)
res_conn_ids = malloc (sizeof(uint32_t) * res.count_connectors);
if (res.count_encoders)
res_enc_ids = malloc (sizeof(uint32_t) * res.count_encoders);
res.fb_id_ptr = (uint64_t) res_fb_ids;
res.crtc_id_ptr = (uint64_t) res_crtc_ids;
res.connector_id_ptr = (uint64_t) res_conn_ids;
res.encoder_id_ptr = (uint64_t) res_enc_ids;
//Get resource IDs
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETRESOURCES, &res) == -1) {
PERROR ("Error: reading resource information");
goto handle_ioctl_error;
}
device->screens = malloc (sizeof(dri_screen_t) * res.count_connectors);
memset (device->screens, 0, sizeof(dri_screen_t) * res.count_connectors);
device->num_screens = 0;
for (i = 0; i < res.count_connectors; i++) {
struct drm_mode_get_connector conn = {0};
struct drm_mode_get_encoder enc = {0};
struct drm_mode_modeinfo *conn_modes = NULL;
uint32_t *conn_enc_ids = NULL;
int32_t crtc = -1;
conn.connector_id = res_conn_ids[i];
// Get connector resource counts
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn) == -1) {
PRINTF ("Can't get connector information for connector %d\n", conn.connector_id);
continue;
}
// Verify the connector has at least one encoder and one mode and is physically connected
if (conn.count_encoders < 1 || conn.count_modes < 1 || conn.connection != 1) {
PRINTF ("Connector %d is not connected\n", conn.connector_id);
continue;
}
if (conn.count_modes)
conn_modes = malloc (sizeof(struct drm_mode_modeinfo) * conn.count_modes);
if (conn.count_encoders)
conn_enc_ids = malloc (sizeof(uint32_t) * conn.count_modes);
// Reset count to 0 for the properties since we don't want to retreive them
conn.count_props = 0;
conn.modes_ptr = (uint64_t) conn_modes;
conn.encoders_ptr = (uint64_t) conn_enc_ids;
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn) == -1) {
free (conn_modes);
free (conn_enc_ids);
PRINTF ("Can't get connector information for connector %d\n", conn.connector_id);
continue;
}
crtc = -1;
if (conn.encoder_id) {
enc.encoder_id = conn.encoder_id;
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc) == 0)
crtc = enc.crtc_id;
for (j = 0; j < device->num_screens; j++) {
if (device->screens[j].crtc == crtc) {
crtc = -1;
break;
}
}
}
/* If crtc was not found, find one */
for (j = 0; j < conn.count_encoders && crtc == -1; j++) {
enc.encoder_id = conn_enc_ids[j];
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETENCODER, &enc) == -1) {
PRINTF ("Can't get encoder information for encoder %d\n", enc.encoder_id);
continue;
}
for (k = 0; k < res.count_crtcs && crtc == -1; k++) {
/* check whether this CRTC works with the encoder */
if (!(enc.possible_crtcs & (1 << k)))
continue;
crtc = res_crtc_ids[k];
for (l = 0; l < device->num_screens; l++) {
if (device->screens[l].crtc == crtc) {
crtc = -1;
break;
}
}
}
}
if (crtc != -1) {
struct drm_mode_crtc *saved_crtc = &(device->screens[device->num_screens].saved_crtc);
device->screens[device->num_screens].conn = conn.connector_id;
device->screens[device->num_screens].mode = conn_modes[0];
device->screens[device->num_screens].crtc = crtc;
saved_crtc->crtc_id = crtc;
if (ioctl(device->dri_fd, DRM_IOCTL_MODE_GETCRTC, saved_crtc) == 0)
device->num_screens++;
}
free (conn_modes);
free (conn_enc_ids);
}
return device;
handle_ioctl_error:
close(device->dri_fd);
if (res_fb_ids)
free (res_fb_ids);
if (res_crtc_ids)
free (res_crtc_ids);
if (res_conn_ids)
free (res_conn_ids);
if (res_enc_ids)
free (res_enc_ids);
handle_open_error:
free (device);
return NULL;
}
void cairo_dri_close(cairo_dri_t *dri)
{
struct drm_mode_crtc crtc = {0};
int i;
for (i = 0; i < dri->num_screens; i++) {
dri->screens[i].saved_crtc.set_connectors_ptr = (uint64_t)&dri->screens[i].conn;
dri->screens[i].saved_crtc.count_connectors = 1;
ioctl(dri->dri_fd, DRM_IOCTL_MODE_SETCRTC, &dri->screens[i].saved_crtc);
}
if (dri->master_is_set)
ioctl(dri->dri_fd, DRM_IOCTL_DROP_MASTER, 0);
close (dri->dri_fd);
free (dri);
}
/* Destroy a cairo surface */
static void cairo_dri_surface_destroy(void *data)
{
dri_surface_fb_t *fb = (dri_surface_fb_t *)data;
struct drm_mode_destroy_dumb destroy_dumb = {0};
struct drm_mode_crtc crtc = {0};
if (fb == NULL)
return;
/* If current fb is mapped, restored saved fb */
printf ("Destroying surface with fb : %d\n", fb->fb_id);
crtc.crtc_id = fb->screen->crtc;
if (ioctl(fb->dri->dri_fd, DRM_IOCTL_MODE_GETCRTC, &crtc) == 0) {
if (crtc.fb_id == fb->fb_id) {
printf ("Currently displayed FB, restoring saved one\n");
crtc = fb->screen->saved_crtc;
crtc.set_connectors_ptr = (uint64_t)&fb->screen->conn;
crtc.count_connectors = 1;
ioctl(fb->dri->dri_fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
}
}
munmap(fb->fb_data, fb->size);
ioctl(fb->dri->dri_fd, DRM_IOCTL_MODE_RMFB, &fb->fb_id);
destroy_dumb.handle = fb->handle;
ioctl(fb->dri->dri_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
free(fb);
}
cairo_surface_t *
cairo_dri_create_surface(cairo_dri_t *dri, dri_screen_t *screen)
{
struct drm_mode_create_dumb create_dumb = {0};
struct drm_mode_map_dumb map_dumb = {0};
struct drm_mode_fb_cmd cmd_dumb = {0};
struct drm_mode_destroy_dumb destroy_dumb = {0};
cairo_surface_t *surface;
dri_surface_fb_t *fb = NULL;
uint8_t *fb_data;
create_dumb.width = screen->mode.hdisplay;
create_dumb.height = screen->mode.vdisplay;
create_dumb.bpp = 32;
create_dumb.flags = 0;
if (ioctl(dri->dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) == -1) {
PERROR ("Can't create dumb buffer");
return NULL;
}
cmd_dumb.width = create_dumb.width;
cmd_dumb.height = create_dumb.height;
cmd_dumb.bpp = create_dumb.bpp;
cmd_dumb.pitch = create_dumb.pitch;
cmd_dumb.depth = 24;
cmd_dumb.handle = create_dumb.handle;
if (ioctl(dri->dri_fd, DRM_IOCTL_MODE_ADDFB, &cmd_dumb) == -1) {
PERROR ("Can't create dumb buffer");
goto destroy_dumb;
}
map_dumb.handle = create_dumb.handle;
if (ioctl(dri->dri_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) == -1) {
PERROR ("Can't map dumb buffer");
goto remove_fb;
}
fb_data = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED,
dri->dri_fd, map_dumb.offset);
if (fb_data == MAP_FAILED) {
PERROR ("Failed to mmap frambuffer");
goto remove_fb;
}
fb = malloc (sizeof(dri_surface_fb_t));
fb->dri = dri;
fb->fb_data = fb_data;
fb->size = create_dumb.size;
fb->handle = create_dumb.handle;
fb->fb_id = cmd_dumb.fb_id;
fb->screen = screen;
printf ("Created framebuffer %p of size %d (%dx%d) with id %d\n", fb->fb_data,
fb->size, fb->screen->mode.hdisplay, fb->screen->mode.vdisplay, fb->fb_id);
/* Create the cairo surface which will be used to draw to */
surface = cairo_image_surface_create_for_data(fb_data,
CAIRO_FORMAT_RGB24, cmd_dumb.width, cmd_dumb.height, cmd_dumb.pitch);
cairo_surface_set_user_data(surface, &user_data_key, fb,
&cairo_dri_surface_destroy);
return surface;
remove_fb:
ioctl(dri->dri_fd, DRM_IOCTL_MODE_RMFB, &cmd_dumb.fb_id);
destroy_dumb:
destroy_dumb.handle = create_dumb.handle;
ioctl(dri->dri_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
return NULL;
}
int cairo_dri_flip_buffer(cairo_surface_t *surface, int vsync)
{
dri_surface_fb_t *fb;
struct drm_mode_crtc crtc = {0};
fb = cairo_surface_get_user_data (surface, &user_data_key);
if (fb == NULL)
return -1;
crtc = fb->screen->saved_crtc;
crtc.crtc_id = fb->screen->crtc;
crtc.fb_id = fb->fb_id;
crtc.set_connectors_ptr = (uint64_t)&fb->screen->conn;
crtc.count_connectors = 1;
crtc.mode = fb->screen->mode;
crtc.mode_valid = 1;
if (ioctl(fb->dri->dri_fd, DRM_IOCTL_MODE_SETCRTC, &crtc) == -1) {
PERROR ("Can't set CRTC information");
return -1;
}
return 0;
}
/*
* cairo_dri.c : Cairo DRI surface implementation
*
* Copyright (c) 2018, Youness Alaoui (KaKaRoTo)
*
*/
#ifndef __CAIRO_DRI_H__
#define __CAIRO_DRI_H__
#include <cairo/cairo.h>
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <stdint.h>
typedef struct {
uint32_t conn;
struct drm_mode_modeinfo mode;
uint32_t crtc;
struct drm_mode_crtc saved_crtc;
} dri_screen_t;
typedef struct {
int dri_fd;
int master_is_set;
dri_screen_t *screens;
int num_screens;
} cairo_dri_t ;
/*
* Flip framebuffer, return the next buffer id which will be used or -1 if
* an operation failed
*/
int cairo_dri_flip_buffer(cairo_surface_t *surface, int vsync);
/* Create a cairo surface using the specified framebuffer
* can return an error if fb driver doesn't support double buffering
*/
cairo_dri_t *cairo_dri_open(const char *dri_filename);
void cairo_dri_close(cairo_dri_t *dri);
cairo_surface_t *cairo_dri_create_surface(cairo_dri_t *dri, dri_screen_t *screen);
#endif /* __CAIRO_DRI_H__ */
/*
* test-menu-gtk.c : CAIRO Menu testing application
* fbwhiptail.c : Framebuffer Cairo Menu
*
* Copyright (C) Youness Alaoui (KaKaRoTo)
* Written in 2011 for PS3, rewritten in 2018
*
* This software is distributed under the terms of the GNU General Public
* License ("GPL") version 3, as published by the Free Software Foundation.
......@@ -9,6 +10,7 @@
*
*/
//#define USE_LINUXFB
#include <stdio.h>
#include <assert.h>
......@@ -33,78 +35,33 @@
#include <linux/vt.h>
#include <signal.h>
#include "cairo_menu.h"
#include "fbwhiptail_menu.h"
#ifdef USE_LINUXFB
#include "cairo_linuxfb.h"
#include "cairo_utils.h"
#else
#include "cairo_dri.h"
#endif
static volatile sig_atomic_t cancel = 0;
typedef struct Menu_s Menu;
struct Menu_s {
cairo_surface_t *background;
CairoMenu *menu;
int width;
int height;
const char *title;
cairo_surface_t *frame;
void (*callback) (Menu *menu, int accepted);
void (*draw) (Menu *menu, cairo_t *cr);
};
#define STANDARD_MENU_ITEM_WIDTH (600)
#define STANDARD_MENU_ITEM_HEIGHT 60
#define STANDARD_MENU_PAD_X 10
#define STANDARD_MENU_PAD_Y 3
#define STANDARD_MENU_BOX_X 7
#define STANDARD_MENU_BOX_Y 7
#define STANDARD_MENU_ITEM_BOX_WIDTH (STANDARD_MENU_ITEM_WIDTH + \
(2 * STANDARD_MENU_BOX_X))
#define STANDARD_MENU_ITEM_BOX_HEIGHT (STANDARD_MENU_ITEM_HEIGHT + \
(2 * STANDARD_MENU_BOX_Y))
#define STANDARD_MENU_ITEM_TOTAL_WIDTH (STANDARD_MENU_ITEM_BOX_WIDTH + \
(2 * STANDARD_MENU_PAD_X))
#define STANDARD_MENU_ITEM_TOTAL_HEIGHT (STANDARD_MENU_ITEM_BOX_HEIGHT + \
(2 * STANDARD_MENU_PAD_Y))
#define STANDARD_MENU_ITEM_IPAD_X (STANDARD_MENU_BOX_X + CAIRO_MENU_DEFAULT_IPAD_X)
#define STANDARD_MENU_ITEM_IPAD_Y (STANDARD_MENU_BOX_Y + CAIRO_MENU_DEFAULT_IPAD_Y)
#define STANDARD_MENU_FRAME_SIDE 25
#define STANDARD_MENU_FRAME_TOP 60
#define STANDARD_MENU_FRAME_BOTTOM 40
#define STANDARD_MENU_FRAME_HEIGHT (STANDARD_MENU_FRAME_TOP + \
STANDARD_MENU_FRAME_BOTTOM) // + nitems * STANDARD_MENU_ITEM_TOTAL_HEIGHT
#define STANDARD_MENU_FRAME_WIDTH (STANDARD_MENU_ITEM_TOTAL_WIDTH + \
(2 * STANDARD_MENU_FRAME_SIDE))
#define STANDARD_MENU_WIDTH (STANDARD_MENU_ITEM_TOTAL_WIDTH)
#define STANDARD_MENU_HEIGHT (menu->height * 0.7)
#define STANDARD_MENU_BOX_CORNER_RADIUS 11
#define STANDARD_MENU_BOX_BORDER_WIDTH 1
#define STANDARD_MENU_FRAME_CORNER_RADIUS 32
#define STANDARD_MENU_FRAME_BORDER_WIDTH 2
#define STANDARD_MENU_TITLE_FONT_SIZE 25
#define MAIN_MENU_FONT_SIZE 15
void signal_handler(int signum)
{
cancel = 1;
}
static int handle_input(Menu *menu, short code)
{
CairoMenuInput input;
CairoMenuRectangle bbox = {0, 0, 0, 0};
if (code == KEY_UP)
if (code == 0x41)
input = CAIRO_MENU_INPUT_UP;
else if (code == KEY_DOWN)
else if (code == 0x42)
input = CAIRO_MENU_INPUT_DOWN;
else if (code == KEY_LEFT)
input = CAIRO_MENU_INPUT_LEFT;
else if (code == KEY_RIGHT)
else if (code == 0x43)
input = CAIRO_MENU_INPUT_RIGHT;
else if (code == 0x44)
input = CAIRO_MENU_INPUT_LEFT;
else
return 0;
......@@ -112,299 +69,60 @@ static int handle_input(Menu *menu, short code)
return (bbox.width * bbox.height) != 0;
}
static void
draw_background (Menu *menu, cairo_t *cr)
{
/* Pre-cache the gradient background pattern into a cairo image surface.
* The cairo_pattern is a vector basically, so when painting the gradient
* into our surface, it needs to rasterize it, which makes the FPS drop
* to 6 or 7 FPS and makes the game unusable (misses controller input, and
* animations don't work anymore). So by pre-rasterizing it into an
* image surface, we can do the background paint very quickly and FPS should
* stay at 60fps or if we miss the VSYNC, drop to 30fps.
*/
if (menu->background == NULL) {
cairo_pattern_t *linpat = NULL;
cairo_t *grad_cr = NULL;
menu->background = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
menu->width, menu->height);
linpat = cairo_pattern_create_linear (0, 0,
menu->width, menu->height);
cairo_pattern_add_color_stop_rgb (linpat, 0, 0, 0.3, 0.8);
cairo_pattern_add_color_stop_rgb (linpat, 1, 0, 0.8, 0.3);
grad_cr = cairo_create (menu->background);
cairo_set_source (grad_cr, linpat);
cairo_paint (grad_cr);
cairo_destroy (grad_cr);
cairo_pattern_destroy (linpat);
cairo_surface_flush (menu->background);
}
cairo_set_source_surface (cr, menu->background, 0, 0);
cairo_paint (cr);
}
static void
create_standard_menu_frame (Menu *menu)
{
cairo_font_extents_t fex;
cairo_text_extents_t tex;
cairo_pattern_t *linpat = NULL;
cairo_surface_t *frame = NULL;
int width, height;
cairo_t *cr;
int x, y;
printf ("Creating %s frame\n", menu->title);
/* Adapt frame height depending on items in the menu */
width = STANDARD_MENU_FRAME_WIDTH;
height = menu->menu->nitems * STANDARD_MENU_ITEM_TOTAL_HEIGHT;
if (height > STANDARD_MENU_HEIGHT)
height = STANDARD_MENU_HEIGHT;
height += STANDARD_MENU_FRAME_HEIGHT;
frame = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height);
cr = cairo_create (frame);
cairo_utils_clip_round_edge (cr, width, height,
STANDARD_MENU_FRAME_CORNER_RADIUS, STANDARD_MENU_FRAME_CORNER_RADIUS,
STANDARD_MENU_FRAME_CORNER_RADIUS);
linpat = cairo_pattern_create_linear (width, 0, width, height);
cairo_pattern_add_color_stop_rgb (linpat, 0.0, 0.3, 0.3, 0.3);
cairo_pattern_add_color_stop_rgb (linpat, 1.0, 0.8, 0.8, 0.8);
cairo_set_source (cr, linpat);
cairo_paint (cr);
cairo_pattern_destroy (linpat);
linpat = cairo_pattern_create_linear (width, 0, width, height);
cairo_pattern_add_color_stop_rgb (linpat, 0.0, 0.03, 0.07, 0.10);
cairo_pattern_add_color_stop_rgb (linpat, 0.1, 0.04, 0.09, 0.16);
cairo_pattern_add_color_stop_rgb (linpat, 0.5, 0.05, 0.20, 0.35);
cairo_pattern_add_color_stop_rgb (linpat, 1.0, 0.06, 0.55, 0.75);
cairo_utils_clip_round_edge (cr, width, height,
STANDARD_MENU_FRAME_CORNER_RADIUS + STANDARD_MENU_FRAME_BORDER_WIDTH,
STANDARD_MENU_FRAME_CORNER_RADIUS + STANDARD_MENU_FRAME_BORDER_WIDTH,
STANDARD_MENU_FRAME_CORNER_RADIUS);
cairo_set_source (cr, linpat);
cairo_paint (cr);
cairo_pattern_destroy (linpat);
cairo_select_font_face(cr, "Arial",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, STANDARD_MENU_TITLE_FONT_SIZE);
cairo_font_extents (cr, &fex);
cairo_text_extents (cr, menu->title, &tex);
y = (STANDARD_MENU_FRAME_TOP / 2) + (fex.ascent / 2);
x = ((width - tex.width) / 2) - tex.x_bearing;
cairo_move_to(cr, x, y);
cairo_set_source_rgb (cr, 0.0, 0.3, 0.5);
cairo_show_text (cr, menu->title);
cairo_destroy (cr);
/* Create the frame