RetroArch/gfx/drivers/sunxi_gfx.c

982 lines
27 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2015-2017 - Manuel Alfayate
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <rthreads/rthreads.h>
#include <string/stdstring.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#include "../font_driver.h"
#include "../../retroarch.h"
#define NUMPAGES 2
/* Lowlevel SunxiG2D functions block */
#define FBIOGET_LAYER_HDL_0 0x4700
#define FBIOGET_LAYER_HDL_1 0x4701
#define DISP_FORMAT_RGB565 0x5
#define DISP_FORMAT_ARGB8888 0xa
#define DISP_MOD_INTERLEAVED 0x1
#define DISP_SEQ_ARGB 0x0
#define DISP_SEQ_P10 0xd
#define DISP_BT601 = 0,
#define DISP_LAYER_WORK_MODE_NORMAL 0
#define DISP_LAYER_WORK_MODE_SCALER 4
#define DISP_CMD_VERSION 0x00
/* for tracking the ioctls API/ABI */
#define SUNXI_DISP_VERSION_MAJOR 1
#define SUNXI_DISP_VERSION_MINOR 0
#define SUNXI_DISP_VERSION ((SUNXI_DISP_VERSION_MAJOR << 16) | SUNXI_DISP_VERSION_MINOR)
#define SUNXI_DISP_VERSION_MAJOR_GET(x) (((x) >> 16) & 0x7FFF)
#define SUNXI_DISP_VERSION_MINOR_GET(x) ((x) & 0xFFFF)
typedef struct
{
__s32 x;
__s32 y;
__u32 width;
__u32 height;
} __disp_rect_t;
typedef struct
{
__u32 width;
__u32 height;
} __disp_rectsz_t;
typedef struct
{
__s32 x;
__s32 y;
} __disp_pos_t;
typedef enum tag_DISP_CMD
{
/* ----layer---- */
DISP_CMD_LAYER_REQUEST = 0x40,
DISP_CMD_LAYER_RELEASE = 0x41,
DISP_CMD_LAYER_OPEN = 0x42,
DISP_CMD_LAYER_CLOSE = 0x43,
DISP_CMD_LAYER_SET_FB = 0x44,
DISP_CMD_LAYER_GET_FB = 0x45,
DISP_CMD_LAYER_SET_SRC_WINDOW = 0x46,
DISP_CMD_LAYER_GET_SRC_WINDOW = 0x47,
DISP_CMD_LAYER_SET_SCN_WINDOW = 0x48,
DISP_CMD_LAYER_GET_SCN_WINDOW = 0x49,
DISP_CMD_LAYER_SET_PARA = 0x4a,
DISP_CMD_LAYER_GET_PARA = 0x4b
} __disp_cmd_t;
typedef struct
{
int fd_fb;
int fd_disp;
int fb_id; /* /dev/fb0 = 0, /dev/fb1 = 1 */
int xres, yres, bits_per_pixel;
uint8_t *framebuffer_addr; /* mmapped address */
uintptr_t framebuffer_paddr; /* physical address */
uint32_t framebuffer_size; /* total size of the framebuffer */
int framebuffer_height;/* virtual vertical resolution */
uint32_t gfx_layer_size; /* the size of the primary layer */
/* Layers support */
int gfx_layer_id;
int layer_id;
int layer_has_scaler;
int layer_buf_x, layer_buf_y, layer_buf_w, layer_buf_h;
int layer_win_x, layer_win_y;
int layer_scaler_is_enabled;
int layer_format;
} sunxi_disp_t;
typedef struct
{
/*
* the contents of the frame buffer address for rgb type only addr[0]
* valid
*/
__u32 addr[3];
__disp_rectsz_t size; /* unit is pixel */
unsigned int format;
unsigned int seq;
unsigned int mode;
/*
* blue red color swap flag, FALSE:RGB; TRUE:BGR,only used in rgb format
*/
signed char br_swap;
unsigned int cs_mode; /* color space */
signed char b_trd_src; /* if 3d source, used for scaler mode layer */
/* source 3d mode, used for scaler mode layer */
unsigned int trd_mode;
__u32 trd_right_addr[3]; /* used when in frame packing 3d mode */
} __disp_fb_t;
typedef struct
{
unsigned int mode; /* layer work mode */
signed char b_from_screen;
/*
* layer pipe,0/1,if in scaler mode, scaler0 must be pipe0,
* scaler1 must be pipe1
*/
__u8 pipe;
/*
* layer priority,can get layer prio,but never set layer prio.
* From bottom to top, priority from low to high
*/
__u8 prio;
signed char alpha_en; /* layer global alpha enable */
__u16 alpha_val; /* layer global alpha value */
signed char ck_enable; /* layer color key enable */
/* framebuffer source window,only care x,y if is not scaler mode */
__disp_rect_t src_win;
__disp_rect_t scn_win; /* screen window */
__disp_fb_t fb; /* framebuffer */
signed char b_trd_out; /* if output 3d mode, used for scaler mode layer */
unsigned int out_trd_mode; /* output 3d mode, used for scaler mode layer */
} __disp_layer_info_t;
/*****************************************************************************
* Support for scaled layers *
*****************************************************************************/
static int sunxi_layer_change_work_mode(sunxi_disp_t *ctx, int new_mode)
{
__disp_layer_info_t layer_info;
uint32_t tmp[4];
if (ctx->layer_id < 0)
return -1;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&layer_info;
if (ioctl(ctx->fd_disp, DISP_CMD_LAYER_GET_PARA, tmp) < 0)
return -1;
layer_info.mode = new_mode;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&layer_info;
return ioctl(ctx->fd_disp, DISP_CMD_LAYER_SET_PARA, tmp);
}
static int sunxi_layer_reserve(sunxi_disp_t *ctx)
{
__disp_layer_info_t layer_info;
uint32_t tmp[4];
/* try to allocate a layer */
tmp[0] = ctx->fb_id;
tmp[1] = DISP_LAYER_WORK_MODE_NORMAL;
ctx->layer_id = ioctl(ctx->fd_disp, DISP_CMD_LAYER_REQUEST, &tmp);
if (ctx->layer_id < 0)
return -1;
/* Initially set the layer configuration to something reasonable */
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&layer_info;
if (ioctl(ctx->fd_disp, DISP_CMD_LAYER_GET_PARA, tmp) < 0)
return -1;
/* the screen and overlay layers need to be in different pipes */
layer_info.pipe = 1;
layer_info.alpha_en = 1;
layer_info.alpha_val = 255;
layer_info.fb.addr[0] = ctx->framebuffer_paddr;
layer_info.fb.size.width = 1;
layer_info.fb.size.height = 1;
layer_info.fb.format = DISP_FORMAT_ARGB8888;
layer_info.fb.seq = DISP_SEQ_ARGB;
layer_info.fb.mode = DISP_MOD_INTERLEAVED;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&layer_info;
if (ioctl(ctx->fd_disp, DISP_CMD_LAYER_SET_PARA, tmp) < 0)
return -1;
/* Now probe the scaler mode to see if there is a free scaler available */
if (sunxi_layer_change_work_mode(ctx, DISP_LAYER_WORK_MODE_SCALER) == 0)
ctx->layer_has_scaler = 1;
/* Revert back to normal mode */
sunxi_layer_change_work_mode(ctx, DISP_LAYER_WORK_MODE_NORMAL);
ctx->layer_scaler_is_enabled = 0;
ctx->layer_format = DISP_FORMAT_ARGB8888;
return ctx->layer_id;
}
static int sunxi_layer_set_output_window(sunxi_disp_t *ctx, int x, int y, int w, int h)
{
__disp_rect_t win_rect = { x, y, w, h };
uint32_t tmp[4];
if (ctx->layer_id < 0 || w <= 0 || h <= 0)
return -1;
/* Save the new non-adjusted window position */
ctx->layer_win_x = x;
ctx->layer_win_y = y;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&win_rect;
return ioctl(ctx->fd_disp, DISP_CMD_LAYER_SET_SCN_WINDOW, &tmp);
}
static int sunxi_layer_show(sunxi_disp_t *ctx)
{
uint32_t tmp[4];
if (ctx->layer_id < 0)
return -1;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
return ioctl(ctx->fd_disp, DISP_CMD_LAYER_OPEN, &tmp);
}
static int sunxi_layer_release(sunxi_disp_t *ctx)
{
uint32_t tmp[4];
if (ctx->layer_id < 0)
return -1;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
ioctl(ctx->fd_disp, DISP_CMD_LAYER_RELEASE, &tmp);
ctx->layer_id = -1;
ctx->layer_has_scaler = 0;
return 0;
}
static int sunxi_layer_set_rgb_input_buffer(sunxi_disp_t *ctx,
int bpp,
uint32_t offset_in_framebuffer,
int width,
int height,
int stride)
{
__disp_fb_t fb;
uint32_t tmp[4];
__disp_rect_t rect = { 0, 0, width, height };
memset(&fb, 0, sizeof(fb));
if (ctx->layer_id < 0)
return -1;
if (!ctx->layer_scaler_is_enabled)
{
if (sunxi_layer_change_work_mode(ctx, DISP_LAYER_WORK_MODE_SCALER) == 0)
ctx->layer_scaler_is_enabled = 1;
else
return -1;
}
fb.addr[0] = ctx->framebuffer_paddr + offset_in_framebuffer;
fb.size.height = height;
if (bpp == 32)
{
fb.format = DISP_FORMAT_ARGB8888;
fb.seq = DISP_SEQ_ARGB;
fb.mode = DISP_MOD_INTERLEAVED;
fb.size.width = stride;
}
else if (bpp == 16)
{
fb.format = DISP_FORMAT_RGB565;
fb.seq = DISP_SEQ_P10;
fb.mode = DISP_MOD_INTERLEAVED;
fb.size.width = stride * 2;
}
else
return -1;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&fb;
if (ioctl(ctx->fd_disp, DISP_CMD_LAYER_SET_FB, &tmp) < 0)
return -1;
ctx->layer_buf_x = rect.x;
ctx->layer_buf_y = rect.y;
ctx->layer_buf_w = rect.width;
ctx->layer_buf_h = rect.height;
ctx->layer_format = fb.format;
tmp[0] = ctx->fb_id;
tmp[1] = ctx->layer_id;
tmp[2] = (uintptr_t)&rect;
return ioctl(ctx->fd_disp, DISP_CMD_LAYER_SET_SRC_WINDOW, &tmp);
}
static sunxi_disp_t *sunxi_disp_init(const char *device)
{
int tmp, version;
struct fb_var_screeninfo fb_var;
struct fb_fix_screeninfo fb_fix;
sunxi_disp_t *ctx = (sunxi_disp_t*)calloc(1, sizeof(sunxi_disp_t));
if (!ctx)
return NULL;
/* use /dev/fb0 by default */
if (!device)
device = "/dev/fb0";
if (string_is_equal(device, "/dev/fb0"))
ctx->fb_id = 0;
else if (string_is_equal(device, "/dev/fb1"))
ctx->fb_id = 1;
else
goto error;
ctx->fd_disp = open("/dev/disp", O_RDWR);
/* maybe it's even not a sunxi hardware */
if (ctx->fd_disp < 0)
goto error;
/* version check */
tmp = SUNXI_DISP_VERSION;
version = ioctl(ctx->fd_disp, DISP_CMD_VERSION, &tmp);
if (version < 0)
{
close(ctx->fd_disp);
goto error;
}
ctx->fd_fb = open(device, O_RDWR);
if (ctx->fd_fb < 0)
{
close(ctx->fd_disp);
goto error;
}
if (ioctl(ctx->fd_fb, FBIOGET_VSCREENINFO, &fb_var) < 0 ||
ioctl(ctx->fd_fb, FBIOGET_FSCREENINFO, &fb_fix) < 0)
{
close(ctx->fd_fb);
close(ctx->fd_disp);
goto error;
}
ctx->xres = fb_var.xres;
ctx->yres = fb_var.yres;
ctx->bits_per_pixel = fb_var.bits_per_pixel;
ctx->framebuffer_paddr = fb_fix.smem_start;
ctx->framebuffer_size = fb_fix.smem_len;
ctx->framebuffer_height = ctx->framebuffer_size /
(ctx->xres * ctx->bits_per_pixel / 8);
ctx->gfx_layer_size = ctx->xres * ctx->yres * fb_var.bits_per_pixel / 8;
if (ctx->framebuffer_size < ctx->gfx_layer_size)
{
close(ctx->fd_fb);
close(ctx->fd_disp);
goto error;
}
/* mmap framebuffer memory */
ctx->framebuffer_addr = (uint8_t *)mmap(0, ctx->framebuffer_size,
PROT_READ | PROT_WRITE,
MAP_SHARED, ctx->fd_fb, 0);
if (ctx->framebuffer_addr == MAP_FAILED)
{
close(ctx->fd_fb);
close(ctx->fd_disp);
goto error;
}
/* Get the id of the screen layer */
if (ioctl(ctx->fd_fb,
ctx->fb_id == 0 ? FBIOGET_LAYER_HDL_0 : FBIOGET_LAYER_HDL_1,
&ctx->gfx_layer_id))
{
close(ctx->fd_fb);
close(ctx->fd_disp);
goto error;
}
if (sunxi_layer_reserve(ctx) < 0)
{
close(ctx->fd_fb);
close(ctx->fd_disp);
goto error;
}
return ctx;
error:
if (ctx)
free(ctx);
return NULL;
}
static int sunxi_disp_close(sunxi_disp_t *ctx)
{
if (ctx->fd_disp >= 0)
{
/* release layer */
sunxi_layer_release(ctx);
/* close descriptors */
munmap(ctx->framebuffer_addr, ctx->framebuffer_size);
close(ctx->fd_fb);
close(ctx->fd_disp);
ctx->fd_disp = -1;
free(ctx);
}
return 0;
}
/* END of lowlevel SunxiG2D functions block */
void pixman_composite_src_0565_8888_asm_neon(int width,
int height,
uint32_t *dst,
int dst_stride_pixels,
uint16_t *src,
int src_stride_pixels);
void pixman_composite_src_8888_8888_asm_neon(int width,
int height,
uint32_t *dst,
int dst_stride_pixels,
uint16_t *src,
int src_stride_pixels);
/* Pointer to the blitting function. Will be asigned
* when we find out what bpp the core uses. */
void (*pixman_blit) (int width,
int height,
uint32_t *dst,
int dst_stride_pixels,
uint16_t *src,
int src_stride_pixels);
struct sunxi_page
{
uint32_t *address;
unsigned int offset;
};
struct sunxi_video
{
void *font;
const font_renderer_driver_t *font_driver;
uint8_t font_rgb[4];
/* Sunxi framebuffer information struct */
sunxi_disp_t *sunxi_disp;
/* current dimensions of the emulator fb */
unsigned int src_width;
unsigned int src_height;
unsigned int src_pitch;
unsigned int src_bpp;
unsigned int src_bytes_per_pixel;
unsigned int src_pixels_per_line;
unsigned int dst_pitch;
unsigned int dst_pixels_per_line;
unsigned int bytes_per_pixel;
struct sunxi_page *pages;
struct sunxi_page *nextPage;
bool pageflip_pending;
/* Keep the vsync while loop going. Set to false to exit. */
bool keep_vsync;
/* Variables to restore screen on exit */
unsigned int screensize;
char *screen_bck;
/* For threading */
sthread_t *vsync_thread;
scond_t *vsync_condition;
slock_t *pending_mutex;
/* menu data */
unsigned int menu_rotation;
bool menu_active;
unsigned int menu_width;
unsigned int menu_height;
unsigned int menu_pitch;
float aspect_ratio;
};
static void sunxi_blank_console(struct sunxi_video *_dispvars)
{
if (!_dispvars)
return;
/* Disable cursor blinking so it's not visible. */
system("setterm -cursor off");
/* Figure out the size of the screen in bytes. */
_dispvars->screensize = _dispvars->sunxi_disp->xres * _dispvars->sunxi_disp->yres * _dispvars->sunxi_disp->bits_per_pixel / 8;
/* Backup screen contents. */
_dispvars->screen_bck = (char*)malloc(_dispvars->screensize * sizeof(char));
if (!_dispvars->screen_bck)
return;
memcpy((char*)_dispvars->screen_bck, (char*)_dispvars->sunxi_disp->framebuffer_addr, _dispvars->screensize);
/* Blank screen. */
memset((char*)(_dispvars->sunxi_disp->framebuffer_addr), 0x00, _dispvars->screensize);
}
static void sunxi_restore_console(struct sunxi_video *_dispvars)
{
if (!_dispvars)
return;
system("setterm -cursor on");
memcpy((char*)_dispvars->sunxi_disp->framebuffer_addr, (char*)_dispvars->screen_bck, _dispvars->screensize);
free(_dispvars->screen_bck);
}
static void sunxi_vsync_thread_func(void *data)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
while (_dispvars->keep_vsync)
{
/* Wait for next vsync */
ioctl(_dispvars->sunxi_disp->fd_fb, FBIO_WAITFORVSYNC, 0);
/* Changing the page to write must be done before the signaling
* so we have the right page in nextPage when update_main continues */
if (_dispvars->nextPage == &_dispvars->pages[0])
_dispvars->nextPage = &_dispvars->pages[1];
else
_dispvars->nextPage = &_dispvars->pages[0];
/* These two things must be isolated "atomically" to avoid getting
* a false positive in the pending_mutex test in update_main. */
slock_lock(_dispvars->pending_mutex);
_dispvars->pageflip_pending = false;
scond_signal(_dispvars->vsync_condition);
slock_unlock(_dispvars->pending_mutex);
}
}
static void *sunxi_gfx_init(const video_info_t *video,
const input_driver_t **input, void **input_data)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)
calloc(1, sizeof(struct sunxi_video));
if (!_dispvars)
return NULL;
_dispvars->src_bytes_per_pixel = video->rgb32 ? 4 : 2;
_dispvars->sunxi_disp = sunxi_disp_init("/dev/fb0");
/* Blank text console and disable cursor blinking. */
sunxi_blank_console(_dispvars);
_dispvars->pages = (struct sunxi_page*)calloc(NUMPAGES, sizeof (struct sunxi_page));
if (!_dispvars->pages)
goto error;
_dispvars->dst_pitch = _dispvars->sunxi_disp->xres * _dispvars->sunxi_disp->bits_per_pixel / 8;
/* Considering 4 bytes per pixel since we will be in 32bpp on the CB/CB2/CT for hw scalers to work. */
_dispvars->dst_pixels_per_line = _dispvars->dst_pitch / 4;
_dispvars->pageflip_pending = false;
_dispvars->nextPage = &_dispvars->pages[0];
_dispvars->keep_vsync = true;
_dispvars->menu_active = false;
_dispvars->bytes_per_pixel = video->rgb32 ? 4 : 2;
/* It's very important that we set aspect here because the
* call seq when a core is loaded is gfx_init()->set_aspect()->gfx_frame()
* and we don't want the main surface to be setup in set_aspect()
* before we get to gfx_frame(). */
_dispvars->aspect_ratio = video_driver_get_aspect_ratio();
switch (_dispvars->bytes_per_pixel)
{
case 2:
pixman_blit = pixman_composite_src_0565_8888_asm_neon;
break;
case 4:
pixman_blit = pixman_composite_src_8888_8888_asm_neon;
break;
default:
goto error;
}
_dispvars->pending_mutex = slock_new();
_dispvars->vsync_condition = scond_new();
if (input && input_data)
*input = NULL;
/* Launching vsync thread */
_dispvars->vsync_thread = sthread_create(sunxi_vsync_thread_func, _dispvars);
return _dispvars;
error:
if (_dispvars)
free(_dispvars);
return NULL;
}
static void sunxi_gfx_free(void *data)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
/* Stop the vsync thread and wait for it to join. */
/* When menu is active, vsync thread has already been stopped. */
if (!_dispvars->menu_active)
{
_dispvars->keep_vsync = false;
sthread_join(_dispvars->vsync_thread);
}
slock_free(_dispvars->pending_mutex);
scond_free(_dispvars->vsync_condition);
free(_dispvars->pages);
/* Restore text console contents and reactivate cursor blinking. */
sunxi_restore_console(_dispvars);
sunxi_disp_close(_dispvars->sunxi_disp);
free(_dispvars);
}
static void sunxi_update_main(const void *frame, struct sunxi_video *_dispvars)
{
slock_lock(_dispvars->pending_mutex);
if (_dispvars->pageflip_pending)
scond_wait(_dispvars->vsync_condition, _dispvars->pending_mutex);
slock_unlock(_dispvars->pending_mutex);
/* Frame blitting */
pixman_blit(
_dispvars->src_width,
_dispvars->src_height,
_dispvars->nextPage->address,
_dispvars->dst_pixels_per_line,
(uint16_t*)frame,
_dispvars->src_pixels_per_line
);
/* Issue pageflip. Will flip on next vsync. */
sunxi_layer_set_rgb_input_buffer(_dispvars->sunxi_disp, _dispvars->sunxi_disp->bits_per_pixel,
_dispvars->nextPage->offset,
_dispvars->src_width, _dispvars->src_height, _dispvars->sunxi_disp->xres);
slock_lock(_dispvars->pending_mutex);
_dispvars->pageflip_pending = true;
slock_unlock(_dispvars->pending_mutex);
}
static void sunxi_setup_scale (void *data,
unsigned width, unsigned height, unsigned pitch)
{
int i;
unsigned int xpos, visible_width;
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
_dispvars->src_width = width;
_dispvars->src_height = height;
/* Total pitch, including things the
* cores render between "visible" scanlines. */
_dispvars->src_pitch = pitch;
/* Pixels per line */
_dispvars->src_pixels_per_line = _dispvars->src_pitch/_dispvars->bytes_per_pixel;
/* Incremental offset that sums up on
* each previous page offset.
* Total offset of each page has to
* be adjusted when internal resolution changes. */
for (i = 0; i < NUMPAGES; i++)
{
_dispvars->pages[i].offset = (_dispvars->sunxi_disp->yres + i * _dispvars->src_height) * _dispvars->sunxi_disp->xres * 4;
_dispvars->pages[i].address = ((uint32_t*) _dispvars->sunxi_disp->framebuffer_addr + (_dispvars->sunxi_disp->yres + i * _dispvars->src_height) * _dispvars->dst_pitch/4);
}
visible_width = _dispvars->sunxi_disp->yres * _dispvars->aspect_ratio;
xpos = (_dispvars->sunxi_disp->xres - visible_width) / 2;
/* setup layer window */
sunxi_layer_set_output_window(_dispvars->sunxi_disp, xpos, 0, visible_width, _dispvars->sunxi_disp->yres);
/* make the layer visible */
sunxi_layer_show(_dispvars->sunxi_disp);
}
static bool sunxi_gfx_frame(void *data, const void *frame, unsigned width,
unsigned height, uint64_t frame_count, unsigned pitch, const char *msg,
video_frame_info_t *video_info)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
if (_dispvars->src_width != width || _dispvars->src_height != height)
{
/* Sanity check on new dimensions */
if (width == 0 || height == 0)
return true;
RARCH_LOG("video_sunxi: internal resolution changed by core: %ux%u -> %ux%u\n",
_dispvars->src_width, _dispvars->src_height, width, height);
sunxi_setup_scale(_dispvars, width, height, pitch);
}
#ifdef HAVE_MENU
menu_driver_frame(video_info);
#endif
if (_dispvars->menu_active)
{
ioctl(_dispvars->sunxi_disp->fd_fb, FBIO_WAITFORVSYNC, 0);
return true;
}
sunxi_update_main(frame, _dispvars);
return true;
}
static void sunxi_gfx_set_nonblock_state(void *data, bool state)
{
struct sunxi_video *vid = data;
(void)vid;
(void)state;
}
static bool sunxi_gfx_alive(void *data)
{
(void)data;
return true; /* always alive */
}
static bool sunxi_gfx_focus(void *data)
{
(void)data;
return true; /* fb device always has focus */
}
static void sunxi_gfx_set_rotation(void *data, unsigned rotation)
{
(void)data;
(void)rotation;
}
static bool sunxi_gfx_suppress_screensaver(void *data, bool enable)
{
(void)data;
(void)enable;
return false;
}
static void sunxi_gfx_viewport_info(void *data, struct video_viewport *vp)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
if (!vp || !_dispvars)
return;
vp->x = vp->y = 0;
vp->width = vp->full_width = _dispvars->src_width;
vp->height = vp->full_height = _dispvars->src_height;
}
static bool sunxi_gfx_set_shader(void *data,
enum rarch_shader_type type, const char *path)
{
(void)data;
(void)type;
(void)path;
return false;
}
static void sunxi_set_texture_enable(void *data, bool state, bool full_screen)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
/* If it wasn't active and starts being active... */
if (!_dispvars->menu_active && state)
{
/* Stop the vsync thread. */
_dispvars->keep_vsync = false;
sthread_join(_dispvars->vsync_thread);
}
/* If it was active but now it isn't active anymore... */
if (_dispvars->menu_active && !state)
{
_dispvars->keep_vsync = true;
_dispvars->vsync_thread = sthread_create(sunxi_vsync_thread_func, _dispvars);
}
_dispvars->menu_active = state;
}
static void sunxi_set_texture_frame(void *data, const void *frame, bool rgb32,
unsigned width, unsigned height, float alpha)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
if (_dispvars->menu_active)
{
unsigned int i, j;
/* We have to go on a pixel format conversion adventure for now, until we can
* convince RGUI to output in an 8888 format. */
unsigned int src_pitch = width * 2;
unsigned int dst_pitch = _dispvars->sunxi_disp->xres * 4;
unsigned int dst_width = _dispvars->sunxi_disp->xres;
uint32_t line[dst_width];
/* Remember, memcpy() works with 8bits pointers for increments. */
char *dst_base_addr = (char*)(_dispvars->pages[0].address);
for (i = 0; i < height; i++)
{
for (j = 0; j < src_pitch / 2; j++)
{
uint16_t src_pix = *((uint16_t*)frame + (src_pitch / 2 * i) + j);
/* The hex AND is for keeping only the part we need for each component. */
uint32_t R = (src_pix << 8) & 0x00FF0000;
uint32_t G = (src_pix << 4) & 0x0000FF00;
uint32_t B = (src_pix << 0) & 0x000000FF;
line[j] = (0 | R | G | B);
}
memcpy(dst_base_addr + (dst_pitch * i), (char*)line, dst_pitch);
}
/* Issue pageflip. Will flip on next vsync. */
sunxi_layer_set_rgb_input_buffer(_dispvars->sunxi_disp,
_dispvars->sunxi_disp->bits_per_pixel,
_dispvars->pages[0].offset, width, height, _dispvars->sunxi_disp->xres);
}
}
static void sunxi_set_aspect_ratio (void *data, unsigned aspect_ratio_idx)
{
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
float new_aspect = aspectratio_lut[aspect_ratio_idx].value;
if (new_aspect != _dispvars->aspect_ratio)
{
/* Here we set the new aspect ratio. */
_dispvars->aspect_ratio = new_aspect;
sunxi_setup_scale(_dispvars, _dispvars->src_width, _dispvars->src_height, _dispvars->src_pitch);
}
}
static const video_poke_interface_t sunxi_poke_interface = {
NULL,
NULL,
NULL, /* set_video_mode */
NULL, /* set_filtering */
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
NULL, /* get_current_framebuffer */
NULL, /* get_proc_address */
sunxi_set_aspect_ratio,
NULL, /* sunxi_apply_state_changes */
#ifdef HAVE_MENU
sunxi_set_texture_frame,
sunxi_set_texture_enable,
#endif
NULL, /* sunxi_set_osd_msg */
NULL /* sunxi_show_mouse */
};
static void sunxi_gfx_get_poke_interface(void *data,
const video_poke_interface_t **iface)
{
(void)data;
*iface = &sunxi_poke_interface;
}
video_driver_t video_sunxi = {
sunxi_gfx_init,
sunxi_gfx_frame,
sunxi_gfx_set_nonblock_state,
sunxi_gfx_alive,
sunxi_gfx_focus,
sunxi_gfx_suppress_screensaver,
NULL, /* has_windowed */
sunxi_gfx_set_shader,
sunxi_gfx_free,
"sunxi",
NULL, /* set_viewport */
sunxi_gfx_set_rotation,
sunxi_gfx_viewport_info,
NULL, /* read_viewport */
NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
NULL, /* overlay_interface */
#endif
sunxi_gfx_get_poke_interface
};