mirror of
https://github.com/CTCaer/RetroArch.git
synced 2024-12-26 12:47:37 +00:00
955 lines
27 KiB
C
955 lines
27 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2013-2014 - Tobias Jakobi
|
|
* Copyright (C) 2013-2014 - Daniel Mehrwald
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <limare.h>
|
|
#include <GLES2/gl2.h>
|
|
|
|
#include "../general.h"
|
|
#include "gfx_common.h"
|
|
#include "fonts/fonts.h"
|
|
|
|
/* Rename to LIMA_GFX_DEBUG to enable debugging code. */
|
|
#define NO_LIMA_GFX_DEBUG 1
|
|
|
|
/* Current limare only natively supports a limited amount of formats for texture *
|
|
* data. We compensate for this limitation by swizzling the texture data in the *
|
|
* pixel shader. */
|
|
|
|
#define LIMA_TEXEL_FORMAT_BGR_565 0x0e
|
|
#define LIMA_TEXEL_FORMAT_RGBA_5551 0x0f
|
|
#define LIMA_TEXEL_FORMAT_RGBA_4444 0x10
|
|
#define LIMA_TEXEL_FORMAT_RGBA_8888 0x16
|
|
|
|
/* Limare is currently unable to deallocate individual texture objects and *
|
|
* only allows to destroy all objects at once. *
|
|
* We only create a maximum of 12 objects, before doing a full "reset", or *
|
|
* sooner, under the condition that limare's texture memory runs out. */
|
|
static const unsigned num_max_textures = 12;
|
|
|
|
typedef struct limare_state limare_state_t;
|
|
|
|
typedef struct limare_texture {
|
|
unsigned width;
|
|
unsigned height;
|
|
|
|
int handle;
|
|
unsigned format;
|
|
|
|
bool rgui;
|
|
} limare_texture_t;
|
|
|
|
typedef struct vec2f {
|
|
float x, y;
|
|
} vec2f_t;
|
|
|
|
typedef struct vec3f {
|
|
float x, y, z;
|
|
} vec3f_t;
|
|
|
|
/* Create three shader programs. One is for displaying only the emulator core pixel data. *
|
|
* The other two are for displaying the RGUI, where the pixel data can be provided in *
|
|
* two different formats. Current RetroArch only seems to ever use a single format, but *
|
|
* this is not set in stone, therefore making two programs necessary. */
|
|
|
|
typedef struct limare_data {
|
|
limare_state_t *state;
|
|
|
|
int program;
|
|
|
|
int program_rgui_rgba16;
|
|
int program_rgui_rgba32;
|
|
|
|
float screen_aspect;
|
|
float frame_aspect;
|
|
|
|
unsigned upload_format;
|
|
unsigned upload_bpp; /* bytes per pixel */
|
|
|
|
vec3f_t *vertices;
|
|
vec2f_t *coords;
|
|
|
|
/* Generic buffer to create contiguous pixel data for limare
|
|
* or to use for font blitting. */
|
|
void *buffer;
|
|
unsigned buffer_size;
|
|
|
|
limare_texture_t **textures;
|
|
unsigned texture_slots;
|
|
|
|
limare_texture_t *cur_texture;
|
|
limare_texture_t *cur_texture_rgui;
|
|
|
|
unsigned font_width;
|
|
unsigned font_height;
|
|
limare_texture_t *font_texture;
|
|
} limare_data_t;
|
|
|
|
/* Header for simple vertex shader. */
|
|
static const char *vshader_src =
|
|
"attribute vec4 in_vertex;\n"
|
|
"attribute vec2 in_coord;\n"
|
|
"\n"
|
|
"varying vec2 coord;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" gl_Position = in_vertex;\n"
|
|
" coord = in_coord;\n"
|
|
"}\n";
|
|
|
|
/* Header for simple fragment shader. */
|
|
static const char *fshader_header_src =
|
|
"precision highp float;\n"
|
|
"\n"
|
|
"varying vec2 coord;\n"
|
|
"\n"
|
|
"uniform sampler2D in_texture;\n"
|
|
"\n";
|
|
|
|
/* Main (template) for simple fragment shader. */
|
|
static const char *fshader_main_src =
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec3 pixel = texture2D(in_texture, coord)%s;\n"
|
|
" gl_FragColor = vec4(pixel, 1.0);\n"
|
|
"}\n";
|
|
|
|
/* Header for RGUI fragment shader. */
|
|
/* Use mediump, which makes uColor into a (single-precision) float[4]. */
|
|
static const char *fshader_rgui_header_src =
|
|
"precision mediump float;\n"
|
|
"\n"
|
|
"varying vec2 coord;\n"
|
|
"uniform vec4 uColor;\n"
|
|
"\n"
|
|
"uniform sampler2D in_texture;\n"
|
|
"\n";
|
|
|
|
/* Main (template) for RGUI fragment shader. */
|
|
static const char *fshader_rgui_main_src =
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec4 pixel = texture2D(in_texture, coord)%s;\n"
|
|
" gl_FragColor = pixel * uColor;\n"
|
|
"}\n";
|
|
|
|
static inline void put_pixel_rgba4444(uint16_t *p, unsigned r, unsigned g, unsigned b, unsigned a) {
|
|
*p = (a >> 4) | ((b >> 4) << 4) | ((g >> 4) << 8) | ((r >> 4) << 12);
|
|
}
|
|
|
|
static inline unsigned align_common(unsigned i, unsigned j) {
|
|
return (i + j - 1) & ~(j - 1);
|
|
}
|
|
|
|
static float get_screen_aspect(limare_state_t *state) {
|
|
unsigned w = 0, h = 0;
|
|
|
|
limare_buffer_size(state, &w, &h);
|
|
|
|
if (w != 0 && h != 0) {
|
|
return (float)w / (float)h;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
static void apply_aspect(limare_data_t *pdata, float ratio) {
|
|
vec3f_t *vertices = pdata->vertices;
|
|
float x, y;
|
|
|
|
if (fabsf(pdata->screen_aspect - pdata->frame_aspect) < 0.0001f) {
|
|
x = 1.0f;
|
|
y = 1.0f;
|
|
} else {
|
|
if (pdata->screen_aspect > pdata->frame_aspect) {
|
|
x = pdata->frame_aspect / pdata->screen_aspect;
|
|
y = 1.0f;
|
|
} else {
|
|
x = 1.0f;
|
|
y = pdata->screen_aspect / pdata->frame_aspect;
|
|
}
|
|
}
|
|
|
|
/* TODO: use ratio parameter */
|
|
|
|
vertices[0].x = vertices[2].x = -x;
|
|
vertices[1].x = vertices[3].x = x;
|
|
|
|
vertices[0].y = vertices[1].y = -y;
|
|
vertices[2].y = vertices[3].y = y;
|
|
}
|
|
|
|
static int destroy_textures(limare_data_t *pdata) {
|
|
unsigned i;
|
|
int ret;
|
|
|
|
pdata->cur_texture = NULL;
|
|
pdata->cur_texture_rgui = NULL;
|
|
|
|
for (i = 0; i < pdata->texture_slots; ++i) {
|
|
free(pdata->textures[i]);
|
|
pdata->textures[i] = NULL;
|
|
}
|
|
|
|
ret = limare_texture_cleanup(pdata->state);
|
|
pdata->texture_slots = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static limare_texture_t *get_texture_handle(limare_data_t *pdata,
|
|
unsigned width, unsigned height, unsigned format) {
|
|
unsigned i;
|
|
|
|
format = (format == 0) ? pdata->upload_format : format;
|
|
|
|
for (i = 0; i < pdata->texture_slots; ++i) {
|
|
if (pdata->textures[i]->width == width &&
|
|
pdata->textures[i]->height == height &&
|
|
pdata->textures[i]->format == format) return pdata->textures[i];
|
|
}
|
|
|
|
if (pdata->texture_slots == num_max_textures) {
|
|
/* All texture slots are used, do a reset. */
|
|
if (destroy_textures(pdata)) {
|
|
RARCH_ERR("video_lima: failed to reset texture storage\n");
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static limare_texture_t *add_texture(limare_data_t *pdata,
|
|
unsigned width, unsigned height,
|
|
const void *pixels, unsigned format) {
|
|
int texture = -1;
|
|
unsigned retries = 2;
|
|
const unsigned i = pdata->texture_slots;
|
|
|
|
format = (format == 0) ? pdata->upload_format : format;
|
|
|
|
/* limare_texture_upload returns -1 when the upload fails for some reason. */
|
|
while (texture == -1 && retries > 0) {
|
|
texture = limare_texture_upload(pdata->state, pixels, width, height, format, 0);
|
|
|
|
if (texture != -1) break;
|
|
|
|
destroy_textures(pdata);
|
|
retries--;
|
|
}
|
|
|
|
if (texture == -1) return NULL;
|
|
|
|
/* Set magnification to linear and minification to nearest, since we will *
|
|
* probably only ever scale the image to larger dimensions. Also set *
|
|
* wrap mode for both coords to clamp, which should eliminate some artifacts. */
|
|
limare_texture_parameters(pdata->state, texture, GL_LINEAR, GL_NEAREST,
|
|
GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
|
|
|
|
pdata->textures[i] = calloc(1, sizeof(limare_texture_t));
|
|
|
|
pdata->textures[i]->width = width;
|
|
pdata->textures[i]->height = height;
|
|
pdata->textures[i]->handle = texture;
|
|
pdata->textures[i]->format = format;
|
|
|
|
pdata->texture_slots++;
|
|
|
|
return pdata->textures[i];
|
|
}
|
|
|
|
static const void *make_contiguous(limare_data_t *pdata,
|
|
unsigned width, unsigned height,
|
|
const void *pixels, unsigned bpp,
|
|
unsigned pitch) {
|
|
unsigned i;
|
|
unsigned full_pitch;
|
|
|
|
bpp = (bpp == 0) ? pdata->upload_bpp : bpp;
|
|
full_pitch = width * bpp;
|
|
|
|
if (full_pitch == pitch) return pixels;
|
|
|
|
RARCH_LOG("video_lima: input buffer not contiguous\n");
|
|
|
|
/* Enlarge our buffer, if it is currently too small. */
|
|
if (pdata->buffer_size < full_pitch * height) {
|
|
const unsigned aligned_size = align_common(full_pitch * height, 16);
|
|
|
|
free(pdata->buffer);
|
|
pdata->buffer = NULL;
|
|
|
|
posix_memalign(&pdata->buffer, 16, aligned_size);
|
|
if (pdata->buffer == NULL) {
|
|
RARCH_ERR("video_lima: failed to allocate buffer to make pixel data contiguous\n");
|
|
return NULL;
|
|
}
|
|
|
|
pdata->buffer_size = aligned_size;
|
|
}
|
|
|
|
for (i = 0; i < height; ++i) {
|
|
memcpy(pdata->buffer + i * full_pitch, pixels + i * pitch, full_pitch);
|
|
}
|
|
|
|
return pdata->buffer;
|
|
}
|
|
|
|
#ifdef LIMA_GFX_DEBUG
|
|
static void print_status(limare_data_t *pdata) {
|
|
unsigned i;
|
|
|
|
RARCH_LOG("video_lima: upload format = 0x%x, upload bpp = %u\n", pdata->upload_format, pdata->upload_bpp);
|
|
RARCH_LOG("video_lima: buffer at %p, buffer size = %u\n", pdata->buffer, pdata->buffer_size);
|
|
RARCH_LOG("video_lima: used texture slots = %u (from %u)\n", pdata->texture_slots, num_max_textures);
|
|
|
|
for (i = 0; i < pdata->texture_slots; ++i) {
|
|
RARCH_LOG("video_lima: texture slot %u, width = %u, height = %u, handle = %u, format = 0x%x\n",
|
|
i, pdata->textures[i]->width, pdata->textures[i]->height,
|
|
pdata->textures[i]->handle, pdata->textures[i]->format);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void destroy_data(limare_data_t *pdata) {
|
|
free(pdata->vertices);
|
|
free(pdata->coords);
|
|
}
|
|
|
|
static int setup_data(limare_data_t *pdata) {
|
|
static const unsigned num_verts = 4;
|
|
static const unsigned num_coords = 4 * 4;
|
|
unsigned i;
|
|
|
|
static const vec3f_t vertices[4] = {
|
|
{-1.0f, -1.0f, 0.0f},
|
|
{ 1.0f, -1.0f, 0.0f},
|
|
{-1.0f, 1.0f, 0.0f},
|
|
{ 1.0f, 1.0f, 0.0f}
|
|
};
|
|
|
|
static const vec2f_t coords[16] = {
|
|
{0.0f, 1.0f}, {1.0f, 1.0f}, /* 0 degrees */
|
|
{0.0f, 0.0f}, {1.0f, 0.0f},
|
|
{0.0f, 0.0f}, {0.0f, 1.0f}, /* 90 degrees */
|
|
{1.0f, 0.0f}, {1.0f, 1.0f},
|
|
{1.0f, 0.0f}, {0.0f, 0.0f}, /* 180 degrees */
|
|
{1.0f, 1.0f}, {0.0f, 1.0f},
|
|
{1.0f, 1.0f}, {1.0f, 0.0f}, /* 270 degrees */
|
|
{0.0f, 1.0f}, {0.0f, 0.0f}
|
|
};
|
|
|
|
pdata->vertices = calloc(num_verts, sizeof(vec3f_t));
|
|
if (pdata->vertices == NULL) goto fail;
|
|
|
|
pdata->coords = calloc(num_coords, sizeof(vec2f_t));
|
|
if (pdata->coords == NULL) goto fail;
|
|
|
|
for (i = 0; i < num_verts; ++i) {
|
|
pdata->vertices[i] = vertices[i];
|
|
}
|
|
|
|
for (i = 0; i < num_coords; ++i) {
|
|
pdata->coords[i] = coords[i];
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static int create_programs(limare_data_t *pdata) {
|
|
char tmpbufm[1024]; /* temp buffer for main function */
|
|
char tmpbuf[1024]; /* temp buffer for whole program */
|
|
|
|
const char* swz = (pdata->upload_bpp == 4) ? ".bgr" : ".rgb";
|
|
|
|
/* Create shader program for regular operation first. */
|
|
pdata->program = limare_program_new(pdata->state);
|
|
if (pdata->program < 0) goto fail;
|
|
|
|
snprintf(tmpbufm, 1024, fshader_main_src, swz);
|
|
strncpy(tmpbuf, fshader_header_src, 1024);
|
|
strcat(tmpbuf, tmpbufm);
|
|
|
|
if (vertex_shader_attach(pdata->state, pdata->program, vshader_src)) goto fail;
|
|
if (fragment_shader_attach(pdata->state, pdata->program, tmpbuf)) goto fail;
|
|
if (limare_link(pdata->state)) goto fail;
|
|
|
|
/* Create shader program for RGUI with RGBA4444 pixel data. */
|
|
pdata->program_rgui_rgba16 = limare_program_new(pdata->state);
|
|
if (pdata->program_rgui_rgba16 < 0) goto fail;
|
|
|
|
snprintf(tmpbufm, 1024, fshader_rgui_main_src, ".abgr");
|
|
strncpy(tmpbuf, fshader_rgui_header_src, 1024);
|
|
strcat(tmpbuf, tmpbufm);
|
|
|
|
if (vertex_shader_attach(pdata->state, pdata->program_rgui_rgba16, vshader_src)) goto fail;
|
|
if (fragment_shader_attach(pdata->state, pdata->program_rgui_rgba16, tmpbuf)) goto fail;
|
|
if (limare_link(pdata->state)) goto fail;
|
|
|
|
/* Create shader program for RGUI with RGBA8888 pixel data. */
|
|
pdata->program_rgui_rgba32 = limare_program_new(pdata->state);
|
|
if (pdata->program_rgui_rgba32 < 0) goto fail;
|
|
|
|
snprintf(tmpbufm, 1024, fshader_rgui_main_src, ".abgr");
|
|
strncpy(tmpbuf, fshader_rgui_header_src, 1024);
|
|
strcat(tmpbuf, tmpbufm);
|
|
|
|
if (vertex_shader_attach(pdata->state, pdata->program_rgui_rgba32, vshader_src)) goto fail;
|
|
if (fragment_shader_attach(pdata->state, pdata->program_rgui_rgba32, tmpbuf)) goto fail;
|
|
if (limare_link(pdata->state)) goto fail;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static void put_glyph_rgba4444(limare_data_t *pdata, const uint8_t *src, uint8_t *f_rgb,
|
|
unsigned g_width, unsigned g_height, unsigned g_pitch,
|
|
unsigned dst_x, unsigned dst_y) {
|
|
unsigned x, y;
|
|
uint16_t *dst;
|
|
|
|
dst = (uint16_t*)pdata->buffer + dst_y * pdata->font_width + dst_x;
|
|
|
|
for (y = 0; y < g_height; ++y, src += g_pitch, dst += pdata->font_width) {
|
|
for (x = 0; x < g_width; ++x) {
|
|
const uint8_t blend = src[x];
|
|
|
|
if (blend != 0) put_pixel_rgba4444(&dst[x], f_rgb[0], f_rgb[1], f_rgb[2], blend);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct lima_video {
|
|
limare_data_t *lima;
|
|
|
|
void *font;
|
|
const font_renderer_driver_t *font_driver;
|
|
uint8_t font_rgb[4];
|
|
|
|
/* current dimensions */
|
|
unsigned width;
|
|
unsigned height;
|
|
|
|
/* RGUI data */
|
|
int rgui_rotation;
|
|
float rgui_alpha;
|
|
bool rgui_active;
|
|
bool rgui_rgb32;
|
|
|
|
bool aspect_changed;
|
|
|
|
} lima_video_t;
|
|
|
|
static void lima_gfx_free(void *data) {
|
|
lima_video_t *vid = data;
|
|
if (!vid) return;
|
|
|
|
if (vid->lima && vid->lima->state) limare_finish(vid->lima->state);
|
|
if (vid->font) vid->font_driver->free(vid->font);
|
|
|
|
destroy_data(vid->lima);
|
|
destroy_textures(vid->lima);
|
|
free(vid->lima->textures);
|
|
|
|
free(vid->lima);
|
|
free(vid);
|
|
}
|
|
|
|
static void lima_init_font(lima_video_t *vid, const char *font_path, unsigned font_size) {
|
|
if (!g_settings.video.font_enable) return;
|
|
|
|
if (font_renderer_create_default(&vid->font_driver, &vid->font)) {
|
|
int r = g_settings.video.msg_color_r * 255;
|
|
int g = g_settings.video.msg_color_g * 255;
|
|
int b = g_settings.video.msg_color_b * 255;
|
|
|
|
vid->font_rgb[0] = r < 0 ? 0 : (r > 255 ? 255 : r);
|
|
vid->font_rgb[1] = g < 0 ? 0 : (g > 255 ? 255 : g);
|
|
vid->font_rgb[2] = b < 0 ? 0 : (b > 255 ? 255 : b);
|
|
} else {
|
|
RARCH_LOG("video_lima: font init failed\n");
|
|
}
|
|
}
|
|
|
|
static void lima_render_msg(lima_video_t *vid, const char *msg) {
|
|
struct font_output_list out;
|
|
struct font_output *head;
|
|
|
|
unsigned req_size;
|
|
limare_data_t *lima = vid->lima;
|
|
|
|
const int msg_base_x = g_settings.video.msg_pos_x * lima->font_width;
|
|
const int msg_base_y = (1.0 - g_settings.video.msg_pos_y) * lima->font_height;
|
|
|
|
if (vid->font == NULL) return;
|
|
|
|
/* Font texture uses RGBA4444 pixel data (2 bytes per pixel). */
|
|
req_size = lima->font_width * lima->font_height * 2;
|
|
|
|
if (lima->buffer_size < req_size) {
|
|
const unsigned aligned_size = align_common(req_size, 16);
|
|
|
|
free(lima->buffer);
|
|
lima->buffer = NULL;
|
|
|
|
posix_memalign(&lima->buffer, 16, aligned_size);
|
|
if (lima->buffer == NULL) {
|
|
RARCH_ERR("video_lima: failed to allocate buffer to render fonts\n");
|
|
return;
|
|
}
|
|
|
|
lima->buffer_size = aligned_size;
|
|
}
|
|
|
|
memset(lima->buffer, 0, req_size);
|
|
|
|
vid->font_driver->render_msg(vid->font, msg, &out);
|
|
|
|
for (head = out.head; head; head = head->next) {
|
|
int base_x = msg_base_x + head->off_x;
|
|
int base_y = msg_base_y - head->off_y - head->height;
|
|
|
|
const int max_width = lima->font_width - base_x;
|
|
const int max_height = lima->font_height - base_y;
|
|
|
|
int glyph_width = head->width;
|
|
int glyph_height = head->height;
|
|
|
|
const uint8_t *src = head->output;
|
|
|
|
if (base_x < 0) {
|
|
src -= base_x;
|
|
glyph_width += base_x;
|
|
base_x = 0;
|
|
}
|
|
|
|
if (base_y < 0) {
|
|
src -= base_y * (int)head->pitch;
|
|
glyph_height += base_y;
|
|
base_y = 0;
|
|
}
|
|
|
|
if (max_width <= 0 || max_height <= 0) continue;
|
|
|
|
if (glyph_width > max_width) glyph_width = max_width;
|
|
if (glyph_height > max_height) glyph_height = max_height;
|
|
|
|
put_glyph_rgba4444(lima, src, vid->font_rgb,
|
|
glyph_width, glyph_height,
|
|
head->pitch, base_x, base_y);
|
|
}
|
|
|
|
vid->font_driver->free_output(vid->font, &out);
|
|
}
|
|
|
|
static void *lima_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data) {
|
|
lima_video_t *vid = NULL;
|
|
limare_data_t *lima = NULL;
|
|
void *lima_input = NULL;
|
|
struct limare_windowsys_drm limare_config = { 0 };
|
|
|
|
vid = calloc(1, sizeof(lima_video_t));
|
|
if (!vid) return NULL;
|
|
|
|
vid->rgui_alpha = 1.0f;
|
|
|
|
lima = calloc(1, sizeof(limare_data_t));
|
|
if (!lima) goto fail;
|
|
|
|
/* Request the Exynos DRM backend for rendering. */
|
|
limare_config.type = LIMARE_WINDOWSYS_DRM;
|
|
limare_config.connector_index = g_settings.video.monitor_index;
|
|
|
|
lima->state = limare_init(&limare_config);
|
|
|
|
if (!lima->state) {
|
|
RARCH_ERR("video_lima: limare initialization failed\n");
|
|
goto fail;
|
|
}
|
|
|
|
limare_buffer_clear(lima->state);
|
|
|
|
if (limare_state_setup(lima->state, g_settings.video.fullscreen_x,
|
|
g_settings.video.fullscreen_y, 0xff000000)) {
|
|
RARCH_ERR("video_lima: limare state setup failed\n");
|
|
goto fail_lima;
|
|
}
|
|
|
|
lima->screen_aspect = get_screen_aspect(lima->state);
|
|
|
|
lima->font_height = 368;
|
|
lima->font_width = align_common((unsigned)(lima->screen_aspect * (float)lima->font_height), 16);
|
|
|
|
lima->upload_format = video->rgb32 ?
|
|
LIMA_TEXEL_FORMAT_RGBA_8888 : LIMA_TEXEL_FORMAT_BGR_565;
|
|
lima->upload_bpp = video->rgb32 ? 4 : 2;
|
|
|
|
limare_enable(lima->state, GL_DEPTH_TEST);
|
|
limare_depth_func(lima->state, GL_ALWAYS);
|
|
limare_depth_mask(lima->state, GL_TRUE);
|
|
|
|
limare_enable(lima->state, GL_CULL_FACE);
|
|
|
|
limare_blend_func(lima->state, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
if (setup_data(lima)) {
|
|
RARCH_ERR("video_lima: data setup failed\n");
|
|
goto fail_lima;
|
|
}
|
|
|
|
if (create_programs(lima)) {
|
|
RARCH_ERR("video_lima: creating shader programs failed\n");
|
|
goto fail_lima;
|
|
}
|
|
|
|
lima->textures = calloc(num_max_textures, sizeof(limare_texture_t*));
|
|
|
|
if (input && input_data) {
|
|
*input = NULL;
|
|
*input_data = NULL;
|
|
}
|
|
|
|
vid->lima = lima;
|
|
|
|
lima_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);
|
|
|
|
return vid;
|
|
|
|
fail_lima:
|
|
limare_finish(lima->state);
|
|
fail:
|
|
free(lima);
|
|
free(vid);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool lima_gfx_frame(void *data, const void *frame,
|
|
unsigned width, unsigned height,
|
|
unsigned pitch, const char *msg) {
|
|
lima_video_t *vid;
|
|
const void *pixels;
|
|
limare_data_t *lima;
|
|
bool upload_frame = true;
|
|
|
|
vid = data;
|
|
|
|
/* Check if neither RGUI nor emulator framebuffer is to be displayed. */
|
|
if (!vid->rgui_active && frame == NULL) return true;
|
|
|
|
lima = vid->lima;
|
|
|
|
if (frame != NULL) {
|
|
|
|
/* Handle resolution changes from the emulation core. */
|
|
if (width != vid->width || height != vid->height) {
|
|
limare_texture_t *tex;
|
|
|
|
if (width == 0 || height == 0) return true;
|
|
|
|
RARCH_LOG("video_lima: resolution was changed by core to %ux%u\n", width, height);
|
|
tex = get_texture_handle(lima, width, height, 0);
|
|
|
|
if (tex == NULL) {
|
|
pixels = make_contiguous(lima, width, height, frame, 0, pitch);
|
|
|
|
tex = add_texture(lima, width, height, pixels, 0);
|
|
|
|
if (tex == NULL) {
|
|
RARCH_ERR("video_lima: failed to allocate new texture with dimensions %ux%u\n",
|
|
width, height);
|
|
return false;
|
|
}
|
|
|
|
upload_frame = false; /* pixel data already got uploaded during texture allocation */
|
|
}
|
|
|
|
lima->cur_texture = tex;
|
|
|
|
vid->width = width;
|
|
vid->height = height;
|
|
|
|
lima->frame_aspect = (float)width / (float)height;
|
|
vid->aspect_changed = true;
|
|
}
|
|
|
|
if (upload_frame) {
|
|
pixels = make_contiguous(lima, width, height, frame, 0, pitch);
|
|
limare_texture_mipmap_upload(lima->state, lima->cur_texture->handle, 0, pixels);
|
|
}
|
|
}
|
|
|
|
if (g_settings.fps_show) {
|
|
char buffer[128], buffer_fps[128];
|
|
|
|
gfx_get_fps(buffer, sizeof(buffer), g_settings.fps_show ? buffer_fps : NULL, sizeof(buffer_fps));
|
|
msg_queue_push(g_extern.msg_queue, buffer_fps, 1, 1);
|
|
}
|
|
|
|
if (vid->aspect_changed) {
|
|
apply_aspect(lima, g_extern.system.aspect_ratio);
|
|
vid->aspect_changed = false;
|
|
}
|
|
|
|
limare_frame_new(lima->state);
|
|
|
|
if (lima->cur_texture != NULL) {
|
|
limare_program_current(lima->state, lima->program);
|
|
|
|
limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
|
|
3, 0, 4, lima->vertices);
|
|
limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
|
|
2, 0, 4, lima->coords + vid->rgui_rotation * 4);
|
|
|
|
limare_texture_attach(lima->state, "in_texture", lima->cur_texture->handle);
|
|
|
|
if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
|
|
}
|
|
|
|
/* Handle font rendering. */
|
|
if (msg) {
|
|
bool upload_font = true;
|
|
|
|
/* Both font_vertices and font_color are constant, but we can't make them *
|
|
* const, since limare_attribute_pointer expects (non-const) void pointers. */
|
|
static vec3f_t font_vertices[4] = {
|
|
{-1.0f, -1.0f, 0.0f},
|
|
{ 1.0f, -1.0f, 0.0f},
|
|
{-1.0f, 1.0f, 0.0f},
|
|
{ 1.0f, 1.0f, 0.0f}
|
|
};
|
|
static float font_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
|
|
lima_render_msg(vid, msg);
|
|
|
|
if (lima->font_texture == NULL) {
|
|
lima->font_texture = add_texture(lima, lima->font_width, lima->font_height,
|
|
lima->buffer, LIMA_TEXEL_FORMAT_RGBA_4444);
|
|
upload_font = false;
|
|
}
|
|
|
|
if (upload_font)
|
|
limare_texture_mipmap_upload(lima->state, lima->font_texture->handle, 0, lima->buffer);
|
|
|
|
/* We re-use the RGBA16 RGUI program here. */
|
|
limare_program_current(lima->state, lima->program_rgui_rgba16);
|
|
|
|
limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
|
|
3, 0, 4, font_vertices);
|
|
limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
|
|
2, 0, 4, lima->coords + vid->rgui_rotation * 4);
|
|
|
|
limare_texture_attach(lima->state, "in_texture", lima->font_texture->handle);
|
|
limare_uniform_attach(lima->state, "uColor", 4, font_color);
|
|
|
|
limare_enable(lima->state, GL_BLEND);
|
|
if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
|
|
limare_disable(lima->state, GL_BLEND);
|
|
}
|
|
|
|
if (vid->rgui_active && lima->cur_texture_rgui != NULL) {
|
|
float color[4] = {1.0f, 1.0f, 1.0f, vid->rgui_alpha};
|
|
|
|
if (vid->rgui_rgb32)
|
|
limare_program_current(lima->state, lima->program_rgui_rgba32);
|
|
else
|
|
limare_program_current(lima->state, lima->program_rgui_rgba16);
|
|
|
|
limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT,
|
|
3, 0, 4, lima->vertices);
|
|
limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT,
|
|
2, 0, 4, lima->coords + vid->rgui_rotation * 4);
|
|
|
|
limare_texture_attach(lima->state, "in_texture", lima->cur_texture_rgui->handle);
|
|
limare_uniform_attach(lima->state, "uColor", 4, color);
|
|
|
|
limare_enable(lima->state, GL_BLEND);
|
|
if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false;
|
|
limare_disable(lima->state, GL_BLEND);
|
|
}
|
|
|
|
if (limare_frame_flush(lima->state)) return false;
|
|
|
|
limare_buffer_swap(lima->state);
|
|
|
|
g_extern.frame_count++;
|
|
|
|
#ifdef LIMA_GFX_DEBUG
|
|
print_status(lima);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static void lima_gfx_set_nonblock_state(void *data, bool state) {
|
|
(void)data; /* limare doesn't export vsync control yet */
|
|
(void)state;
|
|
}
|
|
|
|
static bool lima_gfx_alive(void *data) {
|
|
(void)data;
|
|
return true; /* always alive */
|
|
}
|
|
|
|
static bool lima_gfx_focus(void *data) {
|
|
(void)data;
|
|
return true; /* limare doesn't use windowing, so we always have focus */
|
|
}
|
|
|
|
static void lima_gfx_set_rotation(void *data, unsigned rotation) {
|
|
lima_video_t *vid = data;
|
|
|
|
vid->rgui_rotation = rotation;
|
|
}
|
|
|
|
static void lima_gfx_viewport_info(void *data, struct rarch_viewport *vp) {
|
|
lima_video_t *vid = data;
|
|
vp->x = vp->y = 0;
|
|
|
|
vp->width = vp->full_width = vid->width;
|
|
vp->height = vp->full_height = vid->height;
|
|
}
|
|
|
|
static void lima_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) {
|
|
lima_video_t *vid = data;
|
|
|
|
switch (aspect_ratio_idx) {
|
|
case ASPECT_RATIO_SQUARE:
|
|
gfx_set_square_pixel_viewport(g_extern.system.av_info.geometry.base_width, g_extern.system.av_info.geometry.base_height);
|
|
break;
|
|
|
|
case ASPECT_RATIO_CORE:
|
|
gfx_set_core_viewport();
|
|
break;
|
|
|
|
case ASPECT_RATIO_CONFIG:
|
|
gfx_set_config_viewport();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_extern.system.aspect_ratio = aspectratio_lut[aspect_ratio_idx].value;
|
|
vid->aspect_changed = true;
|
|
}
|
|
|
|
static void lima_apply_state_changes(void *data) {
|
|
(void)data;
|
|
}
|
|
|
|
static void lima_set_texture_frame(void *data, const void *frame, bool rgb32,
|
|
unsigned width, unsigned height, float alpha) {
|
|
lima_video_t *vid = data;
|
|
limare_texture_t* tex;
|
|
const unsigned format = rgb32 ? LIMA_TEXEL_FORMAT_RGBA_8888 :
|
|
LIMA_TEXEL_FORMAT_RGBA_4444;
|
|
|
|
vid->rgui_rgb32 = rgb32;
|
|
vid->rgui_alpha = alpha;
|
|
|
|
tex = vid->lima->cur_texture_rgui;
|
|
|
|
/* Current RGUI doesn't change dimensions, so we should hit this most of the time. */
|
|
if (tex != NULL && tex->width == width &&
|
|
tex->height == height && tex->format == format) goto upload;
|
|
|
|
if (tex == NULL) {
|
|
tex = get_texture_handle(vid->lima, width, height, format);
|
|
if (tex == NULL) {
|
|
tex = add_texture(vid->lima, width, height, frame, format);
|
|
|
|
if (tex != NULL) {
|
|
vid->lima->cur_texture_rgui = tex;
|
|
goto upload;
|
|
}
|
|
|
|
RARCH_ERR("video_lima: failed to allocate new RGUI texture with dimensions %ux%u\n",
|
|
width, height);
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
upload:
|
|
limare_texture_mipmap_upload(vid->lima->state, tex->handle, 0, frame);
|
|
}
|
|
|
|
static void lima_set_texture_enable(void *data, bool state, bool full_screen) {
|
|
lima_video_t *vid = data;
|
|
vid->rgui_active = state;
|
|
}
|
|
|
|
static void lima_set_osd_msg(void *data, const char *msg, void *userdata) {
|
|
lima_video_t *vid = data;
|
|
|
|
/* TODO: what does this do? */
|
|
(void)msg;
|
|
(void)userdata;
|
|
}
|
|
|
|
static void lima_show_mouse(void *data, bool state) {
|
|
(void)data;
|
|
}
|
|
|
|
static const video_poke_interface_t lima_poke_interface = {
|
|
NULL, /* set_filtering */
|
|
#ifdef HAVE_FBO
|
|
NULL, /* get_current_framebuffer */
|
|
NULL, /* get_proc_address */
|
|
#endif
|
|
lima_set_aspect_ratio,
|
|
lima_apply_state_changes,
|
|
#if defined(HAVE_RGUI) || defined(HAVE_RMENU) /* TODO: only HAVE_MENU i think */
|
|
lima_set_texture_frame,
|
|
lima_set_texture_enable,
|
|
#endif
|
|
lima_set_osd_msg,
|
|
lima_show_mouse
|
|
};
|
|
|
|
static void lima_gfx_get_poke_interface(void *data, const video_poke_interface_t **iface) {
|
|
(void)data;
|
|
*iface = &lima_poke_interface;
|
|
}
|
|
|
|
const video_driver_t video_lima = {
|
|
lima_gfx_init,
|
|
lima_gfx_frame,
|
|
lima_gfx_set_nonblock_state,
|
|
lima_gfx_alive,
|
|
lima_gfx_focus,
|
|
NULL, /* set_shader */
|
|
lima_gfx_free,
|
|
"lima",
|
|
|
|
lima_gfx_set_rotation,
|
|
lima_gfx_viewport_info,
|
|
NULL, /* read_viewport */
|
|
|
|
#ifdef HAVE_OVERLAY
|
|
NULL, /* overlay_interface */
|
|
#endif
|
|
lima_gfx_get_poke_interface
|
|
};
|