mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-28 02:30:35 +00:00
430 lines
11 KiB
C
430 lines
11 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2011-2017 - Higor Euripedes
|
|
*
|
|
* 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 <gfx/video_frame.h>
|
|
#include <retro_assert.h>
|
|
#include "../../verbosity.h"
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
#include <drm/drm_fourcc.h>
|
|
|
|
#include "frontend/frontend_driver.h"
|
|
|
|
#include "../font_driver.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../../config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MENU
|
|
#include "../../menu/menu_driver.h"
|
|
#endif
|
|
|
|
#include "../../configuration.h"
|
|
#include "../../retroarch.h"
|
|
|
|
#include <go2/display.h>
|
|
#include <drm/drm_fourcc.h>
|
|
|
|
#define likely(x) __builtin_expect(!!(x), 1)
|
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
|
|
|
#define ALIGN(val, align) (((val) + (align) - 1) & ~((align) - 1))
|
|
|
|
#define NATIVE_WIDTH 480
|
|
#define NATIVE_HEIGHT 320
|
|
|
|
#define NUM_PAGES 2
|
|
|
|
typedef struct oga_video
|
|
{
|
|
go2_presenter_t* presenter;
|
|
go2_display_t* display;
|
|
go2_surface_t* frame;
|
|
|
|
go2_frame_buffer_t* frameBuffer[NUM_PAGES];
|
|
int cur_page;
|
|
|
|
const font_renderer_driver_t *font_driver;
|
|
void *font;
|
|
|
|
go2_surface_t* menu_surface;
|
|
const void* menu_frame;
|
|
unsigned menu_width;
|
|
unsigned menu_height;
|
|
unsigned menu_pitch;
|
|
} oga_video_t;
|
|
|
|
go2_rotation_t oga_rotation = GO2_ROTATION_DEGREES_0;
|
|
|
|
static void oga_gfx_free(void *data)
|
|
{
|
|
unsigned i;
|
|
oga_video_t *vid = (oga_video_t*)data;
|
|
|
|
if (!vid)
|
|
return;
|
|
|
|
if (vid->font)
|
|
{
|
|
vid->font_driver->free(vid->font);
|
|
vid->font_driver = NULL;
|
|
}
|
|
|
|
for (i = 0; i < NUM_PAGES; ++i)
|
|
{
|
|
go2_frame_buffer_t* frameBuffer = vid->frameBuffer[i];
|
|
go2_surface_t* surface = go2_frame_buffer_surface_get(frameBuffer);
|
|
|
|
go2_frame_buffer_destroy(frameBuffer);
|
|
go2_surface_destroy(surface);
|
|
}
|
|
|
|
go2_surface_destroy(vid->frame);
|
|
go2_surface_destroy(vid->menu_surface);
|
|
go2_presenter_destroy(vid->presenter);
|
|
go2_display_destroy(vid->display);
|
|
|
|
free(vid);
|
|
vid = NULL;
|
|
}
|
|
|
|
static void *oga_gfx_init(const video_info_t *video,
|
|
input_driver_t **input, void **input_data)
|
|
{
|
|
oga_video_t *vid = NULL;
|
|
settings_t *settings = config_get_ptr();
|
|
struct retro_system_av_info *av_info = video_viewport_get_system_av_info();
|
|
|
|
frontend_driver_install_signal_handler();
|
|
|
|
if (input && input_data)
|
|
{
|
|
void* udev = input_udev.init(settings->arrays.input_joypad_driver);
|
|
if (udev)
|
|
{
|
|
*input = &input_udev;
|
|
*input_data = udev;
|
|
}
|
|
else
|
|
*input = NULL;
|
|
}
|
|
|
|
vid = (oga_video_t*)calloc(1, sizeof(*vid));
|
|
|
|
vid->menu_frame = NULL;
|
|
vid->display = go2_display_create();
|
|
vid->presenter = go2_presenter_create(vid->display, DRM_FORMAT_RGB565, 0xff000000, false);
|
|
vid->menu_surface = go2_surface_create(vid->display, NATIVE_WIDTH, NATIVE_HEIGHT, DRM_FORMAT_RGB565);
|
|
vid->font = NULL;
|
|
vid->font_driver = NULL;
|
|
|
|
int aw = MAX(ALIGN(av_info->geometry.max_width, 32), NATIVE_WIDTH);
|
|
int ah = MAX(ALIGN(av_info->geometry.max_height, 32), NATIVE_HEIGHT);
|
|
|
|
printf("oga_gfx_init video %dx%d rgb32 %d smooth %d input_scale %u force_aspect %d fullscreen %d aw %d ah %d rgb %d\n",
|
|
video->width, video->height, video->rgb32, video->smooth, video->input_scale, video->force_aspect,
|
|
video->fullscreen, aw, ah, video->rgb32);
|
|
|
|
vid->frame = go2_surface_create(vid->display, aw, ah, video->rgb32 ? DRM_FORMAT_XRGB8888 : DRM_FORMAT_RGB565);
|
|
|
|
/* bitmap only for now */
|
|
if (settings->bools.video_font_enable)
|
|
{
|
|
vid->font_driver = &bitmap_font_renderer;
|
|
vid->font = vid->font_driver->init("", settings->floats.video_font_size);
|
|
}
|
|
|
|
for (int i = 0; i < NUM_PAGES; ++i)
|
|
{
|
|
go2_surface_t* surface = go2_surface_create(vid->display, NATIVE_HEIGHT, NATIVE_WIDTH,
|
|
video->rgb32 ? DRM_FORMAT_XRGB8888 : DRM_FORMAT_XRGB8888);
|
|
vid->frameBuffer[i] = go2_frame_buffer_create(surface);
|
|
}
|
|
vid->cur_page = 0;
|
|
|
|
return vid;
|
|
}
|
|
|
|
int get_message_width(oga_video_t* vid, const char* msg)
|
|
{
|
|
int width = 0;
|
|
for (const char* c = msg; *c; c++)
|
|
{
|
|
const struct font_glyph* glyph = vid->font_driver->get_glyph(vid->font, *c);
|
|
if (unlikely(!glyph))
|
|
continue;
|
|
|
|
width += glyph->advance_x;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
static void render_msg(oga_video_t* vid,
|
|
go2_surface_t* surface, const char* msg, int width, int bpp)
|
|
{
|
|
const struct font_atlas* atlas = vid->font_driver->get_atlas(vid->font);
|
|
int msg_width = get_message_width(vid, msg);
|
|
int dest_x = MAX(0, width - get_message_width(vid, msg));
|
|
int dest_stride = go2_surface_stride_get(surface);
|
|
const char *c = msg;
|
|
|
|
while (*c)
|
|
{
|
|
const struct font_glyph* g = vid->font_driver->get_glyph(vid->font, *c);
|
|
if (!g)
|
|
continue;
|
|
if (dest_x + g->advance_x >= width)
|
|
break;
|
|
|
|
const uint8_t* source = atlas->buffer + g->atlas_offset_y * atlas->width + g->atlas_offset_x;
|
|
uint8_t* dest = (uint8_t*)go2_surface_map(surface) + dest_x * bpp;
|
|
|
|
for (int y = 0; y < g->height; y++)
|
|
{
|
|
for (int x = 0; x < g->advance_x; x++)
|
|
{
|
|
uint8_t px = (x < g->width) ? *(source++) : 0x00;
|
|
if (bpp == 4)
|
|
{
|
|
*(dest++) = px;
|
|
*(dest++) = px;
|
|
*(dest++) = px;
|
|
*(dest++) = px;
|
|
}
|
|
else
|
|
{
|
|
*(dest++) = px;
|
|
*(dest++) = px;
|
|
}
|
|
}
|
|
dest += dest_stride - g->advance_x * bpp;
|
|
source += atlas->width - g->width;
|
|
}
|
|
|
|
c++;
|
|
dest_x += g->advance_x;
|
|
}
|
|
}
|
|
|
|
static bool oga_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)
|
|
{
|
|
int out_w, out_h;
|
|
int yy, stride, dst_stride;
|
|
int out_x = 0;
|
|
int out_y = 0;
|
|
go2_display_t *display = NULL;
|
|
go2_frame_buffer_t *dstFrameBuffer = NULL;
|
|
go2_surface_t *dstSurface = NULL;
|
|
uint8_t *dst = NULL;
|
|
oga_video_t *vid = (oga_video_t*)data;
|
|
go2_surface_t *dst_surface = vid->frame;
|
|
uint8_t *src = (uint8_t*)frame;
|
|
int bpp = go2_drm_format_get_bpp(
|
|
go2_surface_format_get(dst_surface)) / 8;
|
|
bool menu_is_alive = video_info->menu_is_alive;
|
|
|
|
#ifdef HAVE_MENU
|
|
if (unlikely(menu_is_alive))
|
|
{
|
|
menu_driver_frame(menu_is_alive, video_info);
|
|
dst_surface = vid->menu_surface;
|
|
src = (uint8_t*)vid->menu_frame;
|
|
width = vid->menu_width;
|
|
height = vid->menu_height;
|
|
pitch = vid->menu_pitch;
|
|
bpp = vid->menu_pitch / vid->menu_width;
|
|
}
|
|
#endif
|
|
|
|
if (unlikely(!frame || width == 0 || height == 0))
|
|
return true;
|
|
|
|
/* copy buffer to surface */
|
|
dst = (uint8_t*)go2_surface_map(dst_surface);
|
|
yy = height;
|
|
stride = width * bpp;
|
|
dst_stride = go2_surface_stride_get(dst_surface);
|
|
|
|
while (yy > 0)
|
|
{
|
|
memcpy(dst, src, stride);
|
|
src += pitch;
|
|
dst += dst_stride;
|
|
--yy;
|
|
}
|
|
|
|
out_w = NATIVE_WIDTH;
|
|
out_h = NATIVE_HEIGHT;
|
|
|
|
if ((out_w != width || out_h != height))
|
|
{
|
|
out_w = MIN(out_h * video_driver_get_aspect_ratio(), NATIVE_WIDTH);
|
|
out_x = MAX((NATIVE_WIDTH - out_w) / 2, 0);
|
|
}
|
|
|
|
if (msg && vid->font)
|
|
render_msg(vid, dst_surface, msg, width, bpp);
|
|
|
|
dstFrameBuffer = vid->frameBuffer[vid->cur_page];
|
|
dstSurface = go2_frame_buffer_surface_get(dstFrameBuffer);
|
|
|
|
go2_surface_blit(dst_surface, 0, 0, width, height,
|
|
dstSurface, out_y, out_x, out_h, out_w,
|
|
!menu_is_alive ? oga_rotation : GO2_ROTATION_DEGREES_270, 2);
|
|
|
|
display = go2_presenter_display_get(vid->presenter);
|
|
go2_display_present(display, dstFrameBuffer);
|
|
vid->cur_page = !vid->cur_page;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void oga_set_texture_frame(void *data, const void *frame, bool rgb32,
|
|
unsigned width, unsigned height, float alpha)
|
|
{
|
|
oga_video_t *vid = (oga_video_t*)data;
|
|
|
|
vid->menu_width = width;
|
|
vid->menu_height = height;
|
|
vid->menu_pitch = width * (rgb32 ? 4 : 2);
|
|
|
|
if (unlikely(!vid->menu_frame))
|
|
vid->menu_frame = frame;
|
|
}
|
|
|
|
static void oga_gfx_set_nonblock_state(void *a, bool b, bool c, unsigned d)
|
|
{
|
|
}
|
|
|
|
static bool oga_gfx_alive(void *data)
|
|
{
|
|
return !frontend_driver_get_signal_handler_state();
|
|
}
|
|
|
|
static bool oga_gfx_focus(void *data)
|
|
{
|
|
(void)data;
|
|
return true;
|
|
}
|
|
|
|
static bool oga_gfx_suppress_screensaver(void *data, bool enable)
|
|
{
|
|
(void)data;
|
|
(void)enable;
|
|
return false;
|
|
}
|
|
|
|
static bool oga_gfx_has_windowed(void *data)
|
|
{
|
|
(void)data;
|
|
return false;
|
|
}
|
|
|
|
static void oga_gfx_viewport_info(void *data, struct video_viewport *vp)
|
|
{
|
|
oga_video_t *vid = (oga_video_t*)data;
|
|
vp->x = 0;
|
|
vp->y = 0;
|
|
vp->width = vp->full_width = NATIVE_WIDTH;
|
|
vp->height = vp->full_height = NATIVE_HEIGHT;
|
|
}
|
|
|
|
static const video_poke_interface_t oga_poke_interface = {
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
oga_set_texture_frame,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
void oga_set_rotation(void *data, unsigned rotation)
|
|
{
|
|
/* called before init? */
|
|
(void)data;
|
|
switch (rotation)
|
|
{
|
|
case 0:
|
|
oga_rotation = GO2_ROTATION_DEGREES_270;
|
|
break;
|
|
case 1:
|
|
oga_rotation = GO2_ROTATION_DEGREES_180;
|
|
break;
|
|
case 2:
|
|
oga_rotation = GO2_ROTATION_DEGREES_90;
|
|
break;
|
|
case 3:
|
|
oga_rotation = GO2_ROTATION_DEGREES_0;
|
|
break;
|
|
default:
|
|
RARCH_ERR("Unhandled rotation %hu\n", rotation);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void oga_get_poke_interface(void *data, const video_poke_interface_t **iface)
|
|
{
|
|
(void)data;
|
|
*iface = &oga_poke_interface;
|
|
}
|
|
|
|
video_driver_t video_oga = {
|
|
oga_gfx_init,
|
|
oga_gfx_frame,
|
|
oga_gfx_set_nonblock_state,
|
|
oga_gfx_alive,
|
|
oga_gfx_focus,
|
|
oga_gfx_suppress_screensaver,
|
|
oga_gfx_has_windowed,
|
|
NULL,
|
|
oga_gfx_free,
|
|
"oga",
|
|
NULL,
|
|
oga_set_rotation,
|
|
oga_gfx_viewport_info,
|
|
NULL,
|
|
NULL,
|
|
#ifdef HAVE_OVERLAY
|
|
NULL,
|
|
#endif
|
|
#ifdef HAVE_VIDEO_LAYOUT
|
|
NULL,
|
|
#endif
|
|
oga_get_poke_interface
|
|
};
|