Skip to content

Instantly share code, notes, and snippets.

@carlocaione
Created February 26, 2015 17:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carlocaione/b1224406c8d971531432 to your computer and use it in GitHub Desktop.
Save carlocaione/b1224406c8d971531432 to your computer and use it in GitHub Desktop.
/* plugin.c - drm backend renderer plugin
*
* Copyright (C) 2006-2009 Red Hat, Inc.
* 2008 Charlie Brej <cbrej@cs.man.ac.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by: Charlie Brej <cbrej@cs.man.ac.uk>
* Kristian Høgsberg <krh@redhat.com>
* Peter Jones <pjones@redhat.com>
* Ray Strode <rstrode@redhat.com>
*/
#include "config.h"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <values.h>
#include <unistd.h>
#include <drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "ply-array.h"
#include "ply-buffer.h"
#include "ply-event-loop.h"
#include "ply-list.h"
#include "ply-logger.h"
#include "ply-hashtable.h"
#include "ply-rectangle.h"
#include "ply-region.h"
#include "ply-terminal.h"
#include "ply-renderer.h"
#include "ply-renderer-plugin.h"
#include "ply-renderer-driver.h"
#include "ply-renderer-generic-driver.h"
#define BYTES_PER_PIXEL (4)
struct bo
{
int fd;
void *ptr;
size_t size;
size_t offset;
size_t pitch;
unsigned handle;
};
struct cursor {
int fd;
uint32_t bo_handle;
uint32_t crtc_id;
uint32_t crtc_w, crtc_h;
uint32_t w, h;
/* current state */
uint32_t enabled, x, y;
int32_t dx, dy;
};
struct _ply_renderer_head
{
ply_renderer_backend_t *backend;
ply_pixel_buffer_t *pixel_buffer;
ply_rectangle_t area;
unsigned long row_stride;
ply_array_t *connector_ids;
drmModeConnector *connector0;
int connector0_mode_index;
uint32_t controller_id;
uint32_t encoder_id;
uint32_t console_buffer_id;
uint32_t scan_out_buffer_id;
};
struct _ply_renderer_input_source
{
ply_renderer_backend_t *backend;
ply_fd_watch_t *terminal_input_watch;
ply_buffer_t *key_buffer;
ply_renderer_input_source_handler_t handler;
void *user_data;
};
struct _ply_renderer_backend
{
ply_event_loop_t *loop;
ply_terminal_t *terminal;
ply_renderer_driver_interface_t *driver_interface;
ply_renderer_driver_t *driver;
uint32_t driver_supports_mapping_console;
int device_fd;
char *device_name;
drmModeRes *resources;
ply_renderer_input_source_t input_source;
ply_list_t *heads;
ply_hashtable_t *heads_by_connector_id;
int32_t dither_red;
int32_t dither_green;
int32_t dither_blue;
uint32_t is_active : 1;
};
ply_renderer_plugin_interface_t *ply_renderer_backend_get_interface (void);
static void ply_renderer_head_redraw (ply_renderer_backend_t *backend,
ply_renderer_head_t *head);
static bool open_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source);
static bool reset_scan_out_buffer_if_needed (ply_renderer_backend_t *backend,
ply_renderer_head_t *head);
static void flush_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head);
static bool
ply_renderer_head_add_connector (ply_renderer_head_t *head,
drmModeConnector *connector,
int connector_mode_index)
{
drmModeModeInfo *mode;
mode = &connector->modes[connector_mode_index];
if (mode->hdisplay != head->area.width || mode->vdisplay != head->area.height)
{
ply_trace ("Tried to add connector with resolution %dx%d to %dx%d head",
(int) mode->hdisplay, (int) mode->vdisplay,
(int) head->area.width, (int) head->area.height);
return false;
}
else
{
ply_trace ("Adding connector with id %d to %dx%d head",
(int) connector->connector_id,
(int) head->area.width, (int) head->area.height);
}
ply_array_add_uint32_element (head->connector_ids, connector->connector_id);
return true;
}
static ply_renderer_head_t *
ply_renderer_head_new (ply_renderer_backend_t *backend,
drmModeConnector *connector,
int connector_mode_index,
uint32_t encoder_id,
uint32_t controller_id,
uint32_t console_buffer_id)
{
ply_renderer_head_t *head;
drmModeModeInfo *mode;
head = calloc (1, sizeof (ply_renderer_head_t));
head->backend = backend;
head->encoder_id = encoder_id;
head->connector_ids = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_UINT32);
head->controller_id = controller_id;
head->console_buffer_id = console_buffer_id;
assert (connector_mode_index < connector->count_modes);
mode = &connector->modes[connector_mode_index];
head->connector0 = connector;
head->connector0_mode_index = connector_mode_index;
head->area.x = 0;
head->area.y = 0;
head->area.width = mode->hdisplay;
head->area.height = mode->vdisplay;
ply_renderer_head_add_connector (head, connector, connector_mode_index);
assert (ply_array_get_size (head->connector_ids) > 0);
head->pixel_buffer = ply_pixel_buffer_new (head->area.width, head->area.height);
ply_trace ("Creating %ldx%ld renderer head", head->area.width, head->area.height);
ply_pixel_buffer_fill_with_color (head->pixel_buffer, NULL,
0.0, 0.0, 0.0, 1.0);
return head;
}
static void
ply_renderer_head_free (ply_renderer_head_t *head)
{
ply_trace ("freeing %ldx%ld renderer head", head->area.width, head->area.height);
ply_pixel_buffer_free (head->pixel_buffer);
drmModeFreeConnector (head->connector0);
ply_array_free (head->connector_ids);
free (head);
}
static bool
ply_renderer_head_set_scan_out_buffer (ply_renderer_backend_t *backend,
ply_renderer_head_t *head,
uint32_t buffer_id)
{
drmModeModeInfo *mode;
uint32_t *connector_ids;
int number_of_connectors;
connector_ids = (uint32_t *) ply_array_get_uint32_elements (head->connector_ids);
number_of_connectors = ply_array_get_size (head->connector_ids);
mode = &head->connector0->modes[head->connector0_mode_index];
ply_trace ("Setting scan out buffer of %ldx%ld head to our buffer",
head->area.width, head->area.height);
/* Tell the controller to use the allocated scan out buffer on each connectors
*/
if (drmModeSetCrtc (backend->device_fd, head->controller_id, buffer_id,
0, 0, connector_ids, number_of_connectors, mode) < 0)
{
ply_trace ("Couldn't set scan out buffer for head with controller id %d",
head->controller_id);
return false;
}
return true;
}
static bool
ply_renderer_head_map (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
bool scan_out_set;
assert (backend != NULL);
assert (backend->device_fd >= 0);
assert (backend->driver_interface != NULL);
assert (backend->driver != NULL);
assert (head != NULL);
{
uint32_t width = 64;
uint32_t height = 64;
unsigned int bpp = 32;
struct drm_mode_create_dumb c_arg;
struct drm_mode_map_dumb m_arg;
struct bo *bo;
void *map;
int ret;
struct cursor *cursor;
drmModeCrtc *controller;
controller = drmModeGetCrtc (backend->device_fd, head->controller_id);
bo = malloc(sizeof(*bo));
if (bo == NULL) {
ply_trace("failed to allocate buffer object");
return false;
}
cursor = malloc(sizeof(*cursor));
if (cursor == NULL) {
ply_trace("failed to allocate buffer object");
return false;
}
memset(&c_arg, 0, sizeof(c_arg));
c_arg.bpp = bpp;
c_arg.width = width;
c_arg.height = height;
ret = drmIoctl(backend->device_fd, DRM_IOCTL_MODE_CREATE_DUMB, &c_arg);
if (ret) {
ply_trace("failed to create dumb buffer");
return false;
}
bo->fd = backend->device_fd;
bo->handle = c_arg.handle;
bo->size = c_arg.size;
bo->pitch = c_arg.pitch;
memset(&m_arg, 0, sizeof(m_arg));
m_arg.handle = bo->handle;
ret = drmIoctl(bo->fd, DRM_IOCTL_MODE_MAP_DUMB, &m_arg);
if (ret) {
ply_trace("failed to map dumb buffer");
return false;
}
map = mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
bo->fd, m_arg.offset);
if (map == MAP_FAILED) {
ply_trace("failed to mmap the dumb buffer");
return false;
}
bo->ptr = map;
memset(bo->ptr, 0x77, height * bo->pitch);
cursor->fd = bo->fd;
cursor->bo_handle = bo->handle;
cursor->crtc_id = head->controller_id;
cursor->crtc_w = controller->mode.hdisplay;
cursor->crtc_h = controller->mode.vdisplay;
cursor->w = width;
cursor->h = height;
cursor->x = width/2;
cursor->y = height/2;
cursor->dx = 1;
cursor->dy = 1;
drmModeSetCursor(cursor->fd, cursor->crtc_id, cursor->bo_handle, cursor->w, cursor->h);
}
ply_trace ("Creating buffer for %ldx%ld renderer head", head->area.width, head->area.height);
head->scan_out_buffer_id =
backend->driver_interface->create_buffer (backend->driver,
head->area.width, head->area.height,
&head->row_stride);
if (head->scan_out_buffer_id == 0)
return false;
ply_trace ("Mapping buffer for %ldx%ld renderer head", head->area.width, head->area.height);
if (!backend->driver_interface->map_buffer (backend->driver,
head->scan_out_buffer_id))
{
backend->driver_interface->destroy_buffer (backend->driver,
head->scan_out_buffer_id);
head->scan_out_buffer_id = 0;
return false;
}
/* FIXME: Maybe we should blit the fbcon contents instead of the (blank)
* shadow buffer?
*/
ply_renderer_head_redraw (backend, head);
scan_out_set = reset_scan_out_buffer_if_needed (backend, head);
if (!scan_out_set && backend->is_active)
{
backend->driver_interface->destroy_buffer (backend->driver,
head->scan_out_buffer_id);
head->scan_out_buffer_id = 0;
return false;
}
return true;
}
static void
ply_renderer_head_unmap (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
ply_trace ("unmapping %ldx%ld renderer head", head->area.width, head->area.height);
backend->driver_interface->unmap_buffer (backend->driver,
head->scan_out_buffer_id);
backend->driver_interface->destroy_buffer (backend->driver,
head->scan_out_buffer_id);
head->scan_out_buffer_id = 0;
}
static void
flush_area (const char *src,
unsigned long src_row_stride,
char *dst,
unsigned long dst_row_stride,
ply_rectangle_t *area_to_flush)
{
unsigned long y1, y2, y;
y1 = area_to_flush->y;
y2 = y1 + area_to_flush->height;
if (area_to_flush->width * 4 == src_row_stride &&
area_to_flush->width * 4 == dst_row_stride)
{
memcpy (dst, src, area_to_flush->width * area_to_flush->height * 4);
return;
}
for (y = y1; y < y2; y++)
{
memcpy (dst, src, area_to_flush->width * 4);
dst += dst_row_stride;
src += src_row_stride;
}
}
static void
ply_renderer_head_flush_area (ply_renderer_head_t *head,
ply_rectangle_t *area_to_flush,
char *map_address)
{
uint32_t *shadow_buffer;
char *dst, *src;
shadow_buffer = ply_pixel_buffer_get_argb32_data (head->pixel_buffer);
dst = &map_address[area_to_flush->y * head->row_stride + area_to_flush->x * BYTES_PER_PIXEL];
src = (char *) &shadow_buffer[area_to_flush->y * head->area.width + area_to_flush->x];
flush_area (src, head->area.width * 4, dst, head->row_stride, area_to_flush);
}
static void
free_heads (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
node = ply_list_get_first_node (backend->heads);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_renderer_head_t *head;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
ply_renderer_head_free (head);
ply_list_remove_node (backend->heads, node);
node = next_node;
}
}
static ply_renderer_backend_t *
create_backend (const char *device_name,
ply_terminal_t *terminal)
{
ply_renderer_backend_t *backend;
backend = calloc (1, sizeof (ply_renderer_backend_t));
if (device_name != NULL)
backend->device_name = strdup (device_name);
else
backend->device_name = strdup ("/dev/dri/card0");
ply_trace ("creating renderer backend for device %s", backend->device_name);
backend->device_fd = -1;
backend->loop = ply_event_loop_get_default ();
backend->heads = ply_list_new ();
backend->input_source.key_buffer = ply_buffer_new ();
backend->terminal = terminal;
return backend;
}
static void
destroy_backend (ply_renderer_backend_t *backend)
{
ply_trace ("destroying renderer backend for device %s", backend->device_name);
free_heads (backend);
free (backend->device_name);
free (backend);
}
static void
activate (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
ply_trace ("taking master and scanning out");
backend->is_active = true;
drmSetMaster (backend->device_fd);
node = ply_list_get_first_node (backend->heads);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_renderer_head_t *head;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
if (head->scan_out_buffer_id != 0)
{
/* Flush out any pending drawing to the buffer
*/
flush_head (backend, head);
/* Then send the buffer to the monitor
*/
ply_renderer_head_set_scan_out_buffer (backend, head,
head->scan_out_buffer_id);
}
node = next_node;
}
}
static void
deactivate (ply_renderer_backend_t *backend)
{
FILE *file = fopen("/dev/kmsg","w");
fprintf(file, "<1>[plymouth] =========================== %s\n", __func__);
fclose(file);
ply_trace ("dropping master");
drmDropMaster (backend->device_fd);
backend->is_active = false;
}
static void
on_active_vt_changed (ply_renderer_backend_t *backend)
{
if (ply_terminal_is_active (backend->terminal))
{
ply_trace ("activating on vt change");
activate (backend);
}
else
{
ply_trace ("deactivating on vt change");
deactivate (backend);
}
}
static bool
load_driver (ply_renderer_backend_t *backend)
{
int device_fd;
ply_trace ("Opening '%s'", backend->device_name);
device_fd = open (backend->device_name, O_RDWR);
if (device_fd < 0)
{
ply_trace ("open failed: %m");
return false;
}
backend->driver_interface = ply_renderer_generic_driver_get_interface (device_fd);
backend->driver_supports_mapping_console = false;
if (backend->driver_interface == NULL)
{
close (device_fd);
return false;
}
backend->driver = backend->driver_interface->create_driver (device_fd);
if (backend->driver == NULL)
{
close (device_fd);
return false;
}
backend->device_fd = device_fd;
return true;
}
static void
unload_driver (ply_renderer_backend_t *backend)
{
if (backend->driver == NULL)
return;
ply_trace ("unloading driver");
assert (backend->driver_interface != NULL);
backend->driver_interface->destroy_driver (backend->driver);
backend->driver = NULL;
backend->driver_interface = NULL;
if (backend->device_fd >= 0)
{
drmClose (backend->device_fd);
backend->device_fd = -1;
}
}
static bool
open_device (ply_renderer_backend_t *backend)
{
assert (backend != NULL);
assert (backend->device_name != NULL);
if (!load_driver (backend))
return false;
if (backend->terminal == NULL)
return true;
if (!ply_terminal_open (backend->terminal))
{
ply_trace ("could not open terminal: %m");
return false;
}
if (!ply_terminal_is_vt (backend->terminal))
{
ply_trace ("terminal is not a VT");
ply_terminal_close (backend->terminal);
return false;
}
ply_terminal_watch_for_active_vt_change (backend->terminal,
(ply_terminal_active_vt_changed_handler_t)
on_active_vt_changed,
backend);
return true;
}
static void
close_device (ply_renderer_backend_t *backend)
{
ply_trace ("closing device");
free_heads (backend);
if (backend->terminal != NULL)
ply_terminal_stop_watching_for_active_vt_change (backend->terminal,
(ply_terminal_active_vt_changed_handler_t)
on_active_vt_changed,
backend);
unload_driver (backend);
}
static drmModeCrtc *
find_controller_for_encoder (ply_renderer_backend_t *backend,
drmModeEncoder *encoder)
{
drmModeCrtc *controller;
controller = NULL;
/* Monitor is already lit. We'll use the same controller.
*/
if (encoder->crtc_id != 0)
{
controller = drmModeGetCrtc (backend->device_fd, encoder->crtc_id);
if (controller != NULL)
{
ply_trace ("Found already lit monitor");
return controller;
}
}
return NULL;
}
static drmModeEncoder *
find_encoder_for_connector (ply_renderer_backend_t *backend,
drmModeConnector *connector)
{
int i;
drmModeEncoder *encoder;
assert (backend != NULL);
for (i = 0; i < connector->count_encoders; i++)
{
encoder = drmModeGetEncoder (backend->device_fd,
connector->encoders[i]);
if (encoder == NULL)
continue;
if (encoder->encoder_id == connector->encoder_id)
return encoder;
drmModeFreeEncoder (encoder);
}
return NULL;
}
static bool
modes_are_equal (drmModeModeInfo *a,
drmModeModeInfo *b)
{
return a->clock == b->clock &&
a->hdisplay == b->hdisplay &&
a->hsync_start == b->hsync_start &&
a->hsync_end == b->hsync_end &&
a->htotal == b->htotal &&
a->hskew == b->hskew &&
a->vdisplay == b->vdisplay &&
a->vsync_start == b->vsync_start &&
a->vsync_end == b->vsync_end &&
a->vtotal == b->vtotal &&
a->vscan == b->vscan &&
a->vrefresh == b->vrefresh &&
a->flags == b->flags &&
a->type == b->type;
}
static int
find_index_of_mode (ply_renderer_backend_t *backend,
drmModeConnector *connector,
drmModeModeInfo *mode)
{
int i;
for (i = 0; i < connector->count_modes; i++)
{
if (modes_are_equal (&connector->modes[i], mode))
{
ply_trace ("Found connector mode index %d for mode %dx%d",
i, mode->hdisplay, mode->vdisplay);
return i;
}
}
return -1;
}
static int
get_index_of_active_mode (ply_renderer_backend_t *backend,
drmModeCrtc *controller,
drmModeConnector *connector)
{
if (!controller->mode_valid)
{
ply_trace ("No valid mode currently active on monitor");
return -1;
}
ply_trace ("Looking for connector mode index of active mode %dx%d",
controller->mode.hdisplay, controller->mode.vdisplay);
return find_index_of_mode (backend, connector, &controller->mode);
}
static bool
create_heads_for_active_connectors (ply_renderer_backend_t *backend)
{
int i;
drmModeConnector *connector;
ply_hashtable_t *heads_by_controller_id;
heads_by_controller_id = ply_hashtable_new (NULL, NULL);
for (i = 0; i < backend->resources->count_connectors; i++)
{
ply_renderer_head_t *head;
drmModeEncoder *encoder;
uint32_t encoder_id;
drmModeCrtc *controller;
uint32_t controller_id;
uint32_t console_buffer_id;
int connector_mode_index;
connector = drmModeGetConnector (backend->device_fd,
backend->resources->connectors[i]);
if (connector == NULL)
continue;
if (connector->connection != DRM_MODE_CONNECTED)
{
drmModeFreeConnector (connector);
continue;
}
if (connector->count_modes <= 0)
{
drmModeFreeConnector (connector);
continue;
}
encoder = find_encoder_for_connector (backend, connector);
if (encoder == NULL)
{
drmModeFreeConnector (connector);
continue;
}
encoder_id = encoder->encoder_id;
controller = find_controller_for_encoder (backend, encoder);
drmModeFreeEncoder (encoder);
if (controller == NULL)
{
drmModeFreeConnector (connector);
continue;
}
controller_id = controller->crtc_id;
connector_mode_index = get_index_of_active_mode (backend, controller, connector);
/* If we couldn't find the current active mode, fall back to the first available.
*/
if (connector_mode_index < 0)
{
ply_trace ("falling back to first available mode");
connector_mode_index = 0;
}
console_buffer_id = controller->buffer_id;
drmModeFreeCrtc (controller);
head = ply_hashtable_lookup (heads_by_controller_id,
(void *) (intptr_t) controller_id);
if (head == NULL)
{
head = ply_renderer_head_new (backend, connector, connector_mode_index,
encoder_id, controller_id,
console_buffer_id);
ply_list_append_data (backend->heads, head);
ply_hashtable_insert (heads_by_controller_id,
(void *) (intptr_t) controller_id,
head);
}
else
{
if (!ply_renderer_head_add_connector (head, connector, connector_mode_index))
{
ply_trace ("couldn't connect monitor to existing head");
}
drmModeFreeConnector (connector);
}
}
ply_hashtable_free (heads_by_controller_id);
#ifdef PLY_ENABLE_DEPRECATED_GDM_TRANSITION
/* If the driver doesn't support mapping the fb console
* then we can't get a smooth crossfade transition to
* the display manager unless we use the /dev/fb interface
* or the plymouth deactivate interface.
*
* In multihead configurations, we'd rather have working
* multihead, but otherwise bail now.
*/
if (!backend->driver_supports_mapping_console &&
ply_list_get_length (backend->heads) == 1)
{
ply_list_node_t *node;
ply_renderer_head_t *head;
node = ply_list_get_first_node (backend->heads);
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
if (ply_array_get_size (head->connector_ids) == 1)
{
ply_trace ("Only one monitor configured, and driver doesn't "
"support mapping console, so letting frame-buffer "
"take over");
free_heads (backend);
return false;
}
}
#endif
return ply_list_get_length (backend->heads) > 0;
}
static bool
has_32bpp_support (ply_renderer_backend_t *backend)
{
uint32_t buffer_id;
unsigned long row_stride;
uint32_t min_width;
uint32_t min_height;
min_width = backend->resources->min_width;
min_height = backend->resources->min_height;
/* Some drivers set min_width/min_height to 0,
* but 0x0 sized buffers don't work.
*/
if (min_width == 0)
min_width = 1;
if (min_height == 0)
min_height = 1;
buffer_id = backend->driver_interface->create_buffer (backend->driver,
min_width,
min_height,
&row_stride);
if (buffer_id == 0)
{
ply_trace ("Could not create minimal (%ux%u) 32bpp dummy buffer",
backend->resources->min_width,
backend->resources->min_height);
return false;
}
backend->driver_interface->destroy_buffer (backend->driver,
buffer_id);
return true;
}
static bool
query_device (ply_renderer_backend_t *backend)
{
assert (backend != NULL);
assert (backend->device_fd >= 0);
backend->resources = drmModeGetResources (backend->device_fd);
if (backend->resources == NULL)
{
ply_trace ("Could not get card resources");
return false;
}
if (!create_heads_for_active_connectors (backend))
{
ply_trace ("Could not initialize heads");
return false;
}
if (!has_32bpp_support (backend))
{
ply_trace ("Device doesn't support 32bpp framebuffer");
return false;
}
return true;
}
static bool
map_to_device (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
bool head_mapped;
head_mapped = false;
node = ply_list_get_first_node (backend->heads);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_renderer_head_t *head;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
if (ply_renderer_head_map (backend, head))
head_mapped = true;
node = next_node;
}
if (backend->terminal != NULL)
{
if (ply_terminal_is_active (backend->terminal))
activate (backend);
else
ply_terminal_activate_vt (backend->terminal);
}
else
{
activate (backend);
}
return head_mapped;
}
static bool
ply_renderer_head_set_scan_out_buffer_to_console (ply_renderer_backend_t *backend,
ply_renderer_head_t *head,
bool should_set_to_black)
{
unsigned long width;
unsigned long height;
unsigned long row_stride;
uint32_t *shadow_buffer;
ply_pixel_buffer_t *pixel_buffer;
char *map_address;
ply_rectangle_t area;
if (!backend->driver_interface->fetch_buffer (backend->driver,
head->console_buffer_id,
&width, &height, &row_stride))
return false;
if (!backend->driver_interface->map_buffer (backend->driver,
head->console_buffer_id))
{
backend->driver_interface->destroy_buffer (backend->driver,
head->console_buffer_id);
return false;
}
if (head->area.width != width || head->area.height != height)
{
/* Force black if the fb console resolution doesn't match our resolution
*/
area.x = 0;
area.y = 0;
area.width = width;
area.height = height;
should_set_to_black = true;
ply_trace ("Console fb is %ldx%ld and screen contents are %ldx%ld. "
"They aren't the same dimensions; forcing black",
width, height, head->area.width, head->area.height);
}
else
area = head->area;
if (should_set_to_black)
{
pixel_buffer = ply_pixel_buffer_new (width, height);
shadow_buffer = ply_pixel_buffer_get_argb32_data (pixel_buffer);
}
else
{
pixel_buffer = NULL;
shadow_buffer = ply_pixel_buffer_get_argb32_data (head->pixel_buffer);
}
ply_trace ("Drawing %s to console fb", should_set_to_black? "black" : "screen contents");
map_address =
backend->driver_interface->begin_flush (backend->driver,
head->console_buffer_id);
flush_area ((char *) shadow_buffer, area.width * 4,
map_address, row_stride, &area);
backend->driver_interface->end_flush (backend->driver,
head->console_buffer_id);
backend->driver_interface->unmap_buffer (backend->driver,
head->console_buffer_id);
ply_trace ("Setting scan out hardware to console fb");
ply_renderer_head_set_scan_out_buffer (backend,
head, head->console_buffer_id);
backend->driver_interface->destroy_buffer (backend->driver,
head->console_buffer_id);
if (pixel_buffer != NULL)
ply_pixel_buffer_free (pixel_buffer);
return true;
}
static void
unmap_from_device (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
bool should_set_to_black;
/* We only copy what's on screen back to the fb console
* if there's one head (since in multihead set ups the fb console
* is cloned).
*/
should_set_to_black = ply_list_get_length (backend->heads) > 1;
node = ply_list_get_first_node (backend->heads);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_renderer_head_t *head;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
if (backend->is_active)
{
ply_trace ("scanning out %s directly to console",
should_set_to_black? "black" : "splash");
ply_renderer_head_set_scan_out_buffer_to_console (backend, head,
should_set_to_black);
}
ply_renderer_head_unmap (backend, head);
node = next_node;
}
}
static bool
reset_scan_out_buffer_if_needed (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
drmModeCrtc *controller;
bool did_reset = false;
if (backend->terminal != NULL)
{
if (!ply_terminal_is_active (backend->terminal))
return false;
}
controller = drmModeGetCrtc (backend->device_fd, head->controller_id);
if (controller == NULL)
return false;
if (controller->buffer_id != head->scan_out_buffer_id)
{
ply_renderer_head_set_scan_out_buffer (backend, head,
head->scan_out_buffer_id);
did_reset = true;
}
drmModeFreeCrtc (controller);
return did_reset;
}
static void
flush_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
ply_region_t *updated_region;
ply_list_t *areas_to_flush;
ply_list_node_t *node;
ply_pixel_buffer_t *pixel_buffer;
char *map_address;
assert (backend != NULL);
if (!backend->is_active)
return;
if (backend->terminal != NULL)
{
ply_terminal_set_mode (backend->terminal, PLY_TERMINAL_MODE_GRAPHICS);
ply_terminal_set_unbuffered_input (backend->terminal);
}
pixel_buffer = head->pixel_buffer;
updated_region = ply_pixel_buffer_get_updated_areas (pixel_buffer);
areas_to_flush = ply_region_get_sorted_rectangle_list (updated_region);
map_address =
backend->driver_interface->begin_flush (backend->driver,
head->scan_out_buffer_id);
node = ply_list_get_first_node (areas_to_flush);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_rectangle_t *area_to_flush;
area_to_flush = (ply_rectangle_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (areas_to_flush, node);
if (reset_scan_out_buffer_if_needed (backend, head))
ply_trace ("Needed to reset scan out buffer on %ldx%ld renderer head",
head->area.width, head->area.height);
ply_renderer_head_flush_area (head, area_to_flush, map_address);
node = next_node;
}
backend->driver_interface->end_flush (backend->driver,
head->scan_out_buffer_id);
ply_region_clear (updated_region);
}
static void
ply_renderer_head_redraw (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
ply_region_t *region;
ply_trace ("Redrawing %ldx%ld renderer head", head->area.width, head->area.height);
region = ply_pixel_buffer_get_updated_areas (head->pixel_buffer);
ply_region_add_rectangle (region, &head->area);
flush_head (backend, head);
}
static ply_list_t *
get_heads (ply_renderer_backend_t *backend)
{
return backend->heads;
}
static ply_pixel_buffer_t *
get_buffer_for_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
if (head->backend != backend)
return NULL;
return head->pixel_buffer;
}
static bool
has_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
return input_source == &backend->input_source;
}
static ply_renderer_input_source_t *
get_input_source (ply_renderer_backend_t *backend)
{
return &backend->input_source;
}
static void
on_key_event (ply_renderer_input_source_t *input_source,
int terminal_fd)
{
ply_buffer_append_from_fd (input_source->key_buffer,
terminal_fd);
if (input_source->handler != NULL)
input_source->handler (input_source->user_data, input_source->key_buffer, input_source);
}
static void
on_input_source_disconnected (ply_renderer_input_source_t *input_source)
{
ply_trace ("input source disconnected, reopening");
open_input_source (input_source->backend, input_source);
}
static bool
open_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
int terminal_fd;
assert (backend != NULL);
assert (has_input_source (backend, input_source));
if (backend->terminal == NULL)
return false;
terminal_fd = ply_terminal_get_fd (backend->terminal);
input_source->backend = backend;
input_source->terminal_input_watch = ply_event_loop_watch_fd (backend->loop, terminal_fd, PLY_EVENT_LOOP_FD_STATUS_HAS_DATA,
(ply_event_handler_t) on_key_event,
(ply_event_handler_t)
on_input_source_disconnected, input_source);
return true;
}
static void
set_handler_for_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source,
ply_renderer_input_source_handler_t handler,
void *user_data)
{
assert (backend != NULL);
assert (has_input_source (backend, input_source));
input_source->handler = handler;
input_source->user_data = user_data;
}
static void
close_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
assert (backend != NULL);
assert (has_input_source (backend, input_source));
if (backend->terminal == NULL)
return;
ply_event_loop_stop_watching_fd (backend->loop, input_source->terminal_input_watch);
input_source->terminal_input_watch = NULL;
input_source->backend = NULL;
}
ply_renderer_plugin_interface_t *
ply_renderer_backend_get_interface (void)
{
static ply_renderer_plugin_interface_t plugin_interface =
{
.create_backend = create_backend,
.destroy_backend = destroy_backend,
.open_device = open_device,
.close_device = close_device,
.query_device = query_device,
.map_to_device = map_to_device,
.unmap_from_device = unmap_from_device,
.activate = activate,
.deactivate = deactivate,
.flush_head = flush_head,
.get_heads = get_heads,
.get_buffer_for_head = get_buffer_for_head,
.get_input_source = get_input_source,
.open_input_source = open_input_source,
.set_handler_for_input_source = set_handler_for_input_source,
.close_input_source = close_input_source
};
return &plugin_interface;
}
/* vim: set ts=4 sw=4 et ai ci cino={.5s,^-2,+.5s,t0,g0,e-2,n-2,p2s,(0,=.5s,:.5s */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment