mirror of
https://github.com/libretro/RetroArch
synced 2025-01-31 15:32:59 +00:00
51d238875e
ever implemented for OpenGL2 driver, lots of code debt, best to instead just keep improving the overlay system instead which is already available for most video drivers
990 lines
28 KiB
C
990 lines
28 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"
|
|
#include "../../verbosity.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 */
|
|
float refresh_rate;
|
|
|
|
/* 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)▭
|
|
|
|
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;
|
|
ctx->refresh_rate = 1000000.0f / fb_var.pixclock * 1000000.0f /
|
|
(fb_var.yres + fb_var.upper_margin + fb_var.lower_margin + fb_var.vsync_len) /
|
|
(fb_var.xres + fb_var.left_margin + fb_var.right_margin + fb_var.hsync_len);
|
|
|
|
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,
|
|
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;
|
|
#ifdef HAVE_MENU
|
|
bool menu_is_alive = video_info->menu_is_alive;
|
|
#endif
|
|
|
|
if (_dispvars->src_width != width || _dispvars->src_height != height)
|
|
{
|
|
/* Sanity check on new dimensions */
|
|
if (width == 0 || height == 0)
|
|
return true;
|
|
|
|
sunxi_setup_scale(_dispvars, width, height, pitch);
|
|
}
|
|
|
|
#ifdef HAVE_MENU
|
|
menu_driver_frame(menu_is_alive, 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 *a, bool b, bool c, unsigned d) { }
|
|
|
|
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 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 float sunxi_get_refresh_rate (void *data)
|
|
{
|
|
struct sunxi_video *_dispvars = (struct sunxi_video*)data;
|
|
|
|
return _dispvars->sunxi_disp->refresh_rate;
|
|
}
|
|
|
|
static const video_poke_interface_t sunxi_poke_interface = {
|
|
NULL, /* get_flags */
|
|
NULL,
|
|
NULL,
|
|
NULL, /* set_video_mode */
|
|
NULL, /* get_refresh_rate */
|
|
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 */
|
|
sunxi_set_texture_frame,
|
|
sunxi_set_texture_enable,
|
|
NULL, /* set_osd_msg */
|
|
NULL, /* show_mouse */
|
|
NULL, /* grab_mouse_toggle */
|
|
NULL, /* get_current_shader */
|
|
NULL, /* get_current_software_framebuffer */
|
|
NULL, /* get_hw_render_interface */
|
|
NULL, /* set_hdr_max_nits */
|
|
NULL, /* set_hdr_paper_white_nits */
|
|
NULL, /* set_hdr_contrast */
|
|
NULL /* set_hdr_expand_gamut */
|
|
};
|
|
|
|
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 */
|
|
NULL, /* 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
|
|
};
|