Files
libretro-3dengine/libretro.cpp
T

685 lines
18 KiB
C++

/*
* Libretro 3DEngine
* Copyright (C) 2013-2014 - Hans-Kristian Arntzen
* Copyright (C) 2013-2014 - Daniel De Matteis
* Copyright (C) 2013-2014 - Michael Lelli
*
* Libretro 3DEngine 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.
*
* Libretro 3DEngine 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 Libretro 3DEngine.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "libretro.h"
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <vector>
#include "rpng.h"
#include "program.h"
#include "gl.hpp"
#define FPS 60.0
retro_position_t previous_location;
retro_position_t current_location;
bool renderer_dead_state = true;
bool sensor_initialized = false;
bool sensor_enable = false;
bool location_camera_control_enable = false;
static bool location_enable = false;
bool camera_enable = false;
struct retro_hw_render_callback hw_render;
static struct retro_camera_callback camera_cb;
retro_log_printf_t log_cb;
struct retro_location_callback location_cb;
retro_video_refresh_t video_cb;
static retro_audio_sample_t audio_cb;
static retro_audio_sample_batch_t audio_batch_cb;
retro_environment_t environ_cb;
retro_input_poll_t input_poll_cb;
retro_input_state_t input_state_cb;
static const engine_program_t *engine_program_cb;
struct retro_sensor_interface sensor_cb;
static bool display_position;
#define BASE_WIDTH 320
#define BASE_HEIGHT 240
#ifdef HAVE_OPENGLES
#define MAX_WIDTH 1024
#define MAX_HEIGHT 1024
#else
#define MAX_WIDTH 2048
#define MAX_HEIGHT 2048
#endif
unsigned engine_width = BASE_WIDTH;
unsigned engine_height = BASE_HEIGHT;
GLuint tex;
GLuint g_texture_target = GL_TEXTURE_2D;
bool vr_active = false;
unsigned vr_eye_width = 0;
unsigned vr_eye_height = 0;
bool vr_user_enable = true;
void retro_init(void)
{
struct retro_log_callback log;
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
log_cb = log.log;
else
log_cb = NULL;
environ_cb(RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE, &sensor_cb);
}
void retro_deinit(void)
{
}
unsigned retro_api_version(void)
{
return RETRO_API_VERSION;
}
void retro_set_controller_port_device(unsigned port, unsigned device)
{
(void)port;
(void)device;
}
void retro_get_system_info(struct retro_system_info *info)
{
memset(info, 0, sizeof(*info));
info->library_name = "Libretro 3DEngine";
#ifndef GIT_VERSION
#define GIT_VERSION ""
#endif
info->library_version = "v1" GIT_VERSION;
info->need_fullpath = false;
info->valid_extensions = "png|jpg|mtl|obj";
}
void retro_get_system_av_info(struct retro_system_av_info *info)
{
info->timing.fps = FPS;
info->timing.sample_rate = 30000.0;
info->geometry.base_width = BASE_WIDTH;
info->geometry.base_height = BASE_HEIGHT;
info->geometry.max_width = MAX_WIDTH;
info->geometry.max_height = MAX_HEIGHT;
if (vr_active)
{
info->geometry.base_width = info->geometry.max_width = vr_eye_width * 2;
info->geometry.base_height = info->geometry.max_height = vr_eye_height;
info->geometry.aspect_ratio = (float)(vr_eye_width * 2) / vr_eye_height;
}
}
void retro_set_environment(retro_environment_t cb)
{
environ_cb = cb;
struct retro_variable variables[] = {
{ "3dengine-resolution",
"Internal resolution; 320x240|360x480|480x272|512x384|512x512|640x240|640x448|640x480|720x576|800x600|960x720|1024x768"
#ifndef HAVE_OPENGLES
"|1024x1024|1280x720|1280x960|1600x1200|1920x1080|1920x1440|1920x1600|2048x1152|2048x1536|2048x2048|320x240"
#endif
},
{
"3dengine-cube-size",
"Cube size; 0|1|2|4|8|16|32|64|128" },
{
"3dengine-cube-stride",
"Cube stride; 2.0|3.0|4.0|5.0|6.0|7.0|8.0" },
{
"3dengine-camera-enable",
"Camera enable; disabled|enabled" },
{
"3dengine-camera-type",
"Camera FB Type; texture|raw framebuffer" },
{
"3dengine-sensor-enable",
"Sensor enable; disabled|enabled"
},
{
"3dengine-location-enable",
"Location enable; disabled|enabled"
},
{
"3dengine-location-camera-control-enable",
"Location camera control; disabled|enabled"
},
{ "3dengine-modelviewer-discard-hack", "Discard hack enable; disabled|enabled" },
{ "3dengine-location-display-position", "Location position OSD; disabled|enabled" },
{ "3dengine-modelviewer-vr-enable", "VR enable in modelviewer; enabled|disabled" },
{ NULL, NULL },
};
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);
}
void retro_set_audio_sample(retro_audio_sample_t cb)
{
audio_cb = cb;
}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
{
audio_batch_cb = cb;
}
void retro_set_input_poll(retro_input_poll_t cb)
{
input_poll_cb = cb;
}
void retro_set_input_state(retro_input_state_t cb)
{
input_state_cb = cb;
}
void retro_set_video_refresh(retro_video_refresh_t cb)
{
video_cb = cb;
}
static void update_variables(void)
{
struct retro_variable var;
var.key = "3dengine-resolution";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
char *pch;
char str[100];
snprintf(str, sizeof(str), "%s", var.value);
pch = strtok(str, "x");
if (pch)
engine_width = strtoul(pch, NULL, 0);
pch = strtok(NULL, "x");
if (pch)
engine_height = strtoul(pch, NULL, 0);
if (log_cb)
log_cb(RETRO_LOG_INFO, "Got size: %u x %u.\n", engine_width, engine_height);
}
var.key = "3dengine-location-display-position";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
display_position = false;
else if (!strcmp(var.value, "enabled"))
display_position = true;
}
var.key = "3dengine-location-camera-control-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
location_camera_control_enable = false;
else if (!strcmp(var.value, "enabled"))
location_camera_control_enable = true;
}
if (engine_program_cb && engine_program_cb->update_variables)
engine_program_cb->update_variables(environ_cb);
}
void retro_run(void)
{
bool updated = false;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
update_variables();
if (location_enable && location_cb.get_position)
{
double lat, lon, hacc, vacc;
if (location_cb.get_position(&lat, &lon, &hacc, &vacc))
{
previous_location = current_location;
current_location.latitude = lat;
current_location.longitude = lon;
current_location.horizontal_accuracy = hacc;
current_location.vertical_accuracy = vacc;
if (display_position)
{
struct retro_message msg;
char msg_local[512];
snprintf(msg_local, sizeof(msg_local), "LAT %f LON %f HACC %f VACC %f", current_location.latitude, current_location.longitude,
current_location.horizontal_accuracy, current_location.vertical_accuracy);
msg.msg = msg_local;
msg.frames = 180;
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, (void*)&msg);
}
}
}
if (engine_program_cb && engine_program_cb->run)
engine_program_cb->run();
}
static void camera_gl_callback(unsigned texture_id, unsigned texture_target, const float *affine)
{
g_texture_target = texture_target;
// TODO: support GL_TEXTURE_RECTANGLE and others?
if (texture_target == GL_TEXTURE_2D)
tex = texture_id;
#ifdef ANDROID
else if (texture_target == GL_TEXTURE_EXTERNAL_OES)
tex = texture_id;
#endif
}
static inline bool gl_query_extension(const char *ext)
{
#if !defined(ANDROID) && !defined(EMSCRIPTEN)
// This code crashes right now on Android 4.4 (but not 4.0 to 4.3), so comment it out for now
const char *str = (const char*)glGetString(GL_EXTENSIONS);
bool ret = str && strstr(str, ext);
return ret;
#else
return false;
#endif
}
static bool support_unpack_row_length;
static uint8_t *convert_buffer;
#ifndef GL_BGRA_EXT
#define GL_BGRA_EXT 0x80E1
#endif
#ifndef GL_UNPACK_ROW_LENGTH
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#endif
#ifdef HAVE_OPENGLES
#define INTERNAL_FORMAT GL_BGRA_EXT
#define TEX_TYPE GL_BGRA_EXT
#define TEX_FORMAT GL_UNSIGNED_BYTE
#else
#define INTERNAL_FORMAT GL_RGBA
#define TEX_TYPE GL_BGRA
#define TEX_FORMAT GL_UNSIGNED_INT_8_8_8_8_REV
#endif
static void camera_raw_fb_callback(const uint32_t *buffer, unsigned width, unsigned height, size_t pitch)
{
unsigned base_size = 4;
unsigned h;
if (!tex)
{
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, width, height, 0, TEX_TYPE, TEX_FORMAT, NULL);
if (!support_unpack_row_length)
convert_buffer = new uint8_t[width * height * 4];
}
else
glBindTexture(GL_TEXTURE_2D, tex);
if (support_unpack_row_length)
{
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / base_size);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, width, height, TEX_TYPE,
TEX_FORMAT, buffer);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
else
{
// No GL_UNPACK_ROW_LENGTH ;(
unsigned pitch_width = pitch / base_size;
if (width == pitch_width) // Happy path :D
{
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, width, height, TEX_TYPE,
TEX_FORMAT, buffer);
}
else // Slower path.
{
const unsigned line_bytes = width * base_size;
uint8_t *dst = (uint8_t*)convert_buffer; // This buffer is preallocated for this purpose.
const uint8_t *src = (const uint8_t*)buffer;
for (h = 0; h < height; h++, src += pitch, dst += line_bytes)
memcpy(dst, src, line_bytes);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, width, height, TEX_TYPE,
TEX_FORMAT, convert_buffer);
}
}
glBindTexture(GL_TEXTURE_2D, 0);
}
static void camera_initialized(void)
{
if (camera_enable && camera_cb.start)
camera_cb.start();
}
static void context_reset(void)
{
if (engine_program_cb && engine_program_cb->context_reset)
engine_program_cb->context_reset();
}
char retro_path_info[1024];
static void location_initialized(void)
{
if (location_enable && location_cb.start)
location_cb.start();
}
static void location_deinitialized(void)
{
if (location_enable && location_cb.stop)
location_cb.stop();
}
// VAR
static bool vr_request_session(void)
{
struct retro_vr_content_info vr;
memset(&vr, 0, sizeof(vr));
vr.stereo_native = true;
vr.ipd_hint_m = 0.0f;
vr.layout = RETRO_VR_LAYOUT_SIDE_BY_SIDE;
vr.request_flat = !vr_user_enable;
vr.reference_space = RETRO_VR_REFERENCE_SPACE_LOCAL; /* eye height == player_pos.y */
if (!environ_cb(RETRO_ENVIRONMENT_SET_VR_CONTENT_INFO, &vr))
{
log_cb(RETRO_LOG_INFO, "VR session request failed.\n");
return false; /* old frontend, no headset, or VR disabled -> flat */
}
vr_eye_width = vr.recommended_eye_width ? vr.recommended_eye_width : 1024;
vr_eye_height = vr.recommended_eye_height ? vr.recommended_eye_height : 1024;
log_cb(RETRO_LOG_INFO, "VR session request success. vr_eye_width: %d vr_eye_height: %d.\n", vr_eye_width, vr_eye_height);
return true;
}
bool vr_poll_frame(struct retro_vr_frame_state *fs)
{
struct retro_vr_frame_state tmp = *fs;
if (!environ_cb(RETRO_ENVIRONMENT_GET_VR_FRAME_STATE, &tmp))
return false; /* keep last pose */
*fs = tmp;
if (tmp.flags & RETRO_VR_FRAME_TARGET_RESIZED)
{
if (vr_request_session())
{
struct retro_system_av_info av;
retro_get_system_av_info(&av);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av);
}
else
vr_active = false;
}
return true;
}
// END VR
bool retro_load_game(const struct retro_game_info *info)
{
retro_variable var;
if (!info)
return false;
strcpy(retro_path_info, info->path);
if (strstr(info->path, ".obj") || strstr(info->path, ".mtl"))
engine_program_cb = &engine_program_modelviewer;
else
engine_program_cb = &engine_program_instancingviewer;
update_variables();
var.key = "3dengine-sensor-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "enabled"))
{
if (sensor_initialized)
return true;
log_cb(RETRO_LOG_INFO, "Sensor interface found, enabling...\n");
if (sensor_cb.set_sensor_state)
{
sensor_cb.set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_ENABLE, FPS);
sensor_enable = true;
}
sensor_initialized = true;
}
if (!strcmp(var.value, "disabled"))
{
if (sensor_initialized)
return true;
log_cb(RETRO_LOG_INFO, "Sensor interface found, disabling...\n");
if (sensor_cb.set_sensor_state)
{
sensor_cb.set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, FPS);
sensor_enable = false;
}
}
}
var.key = "3dengine-location-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "enabled"))
{
location_enable = true;
location_cb.initialized = location_initialized;
location_cb.deinitialized = location_deinitialized;
environ_cb(RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE, &location_cb);
}
}
memset(&camera_cb, 0, sizeof(camera_cb));
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
{
if (log_cb)
log_cb(RETRO_LOG_ERROR, "XRGB8888 is not supported.\n");
return false;
}
var.key = "3dengine-camera-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "enabled"))
{
camera_enable = true;
var.key = "3dengine-camera-type";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "texture"))
{
camera_cb.caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE);
camera_cb.frame_opengl_texture = camera_gl_callback;
}
else
{
camera_cb.caps = (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER);
camera_cb.frame_raw_framebuffer = camera_raw_fb_callback;
}
}
camera_cb.initialized = camera_initialized;
if (camera_enable && !environ_cb(RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE, &camera_cb))
{
if (log_cb)
log_cb(RETRO_LOG_ERROR, "camera is not supported.\n");
return false;
}
}
}
#ifdef HAVE_OPENGLES
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
#endif
hw_render.context_reset = context_reset;
hw_render.depth = true;
if (!camera_enable)
hw_render.bottom_left_origin = true;
if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
return false;
#ifdef HAVE_OPENGLES
if (camera_enable && camera_cb.caps & (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER) && !gl_query_extension("BGRA8888"))
{
if (log_cb)
log_cb(RETRO_LOG_ERROR, "no BGRA8888 support for raw framebuffer, exiting...\n");
return false;
}
support_unpack_row_length = gl_query_extension("GL_EXT_unpack_subimage");
#else
support_unpack_row_length = true;
#endif
if (log_cb)
log_cb(RETRO_LOG_INFO, "Loaded game!\n");
if (engine_program_cb && engine_program_cb->load_game)
engine_program_cb->load_game(info);
vr_active = false;
if (vr_user_enable && engine_program_cb == &engine_program_modelviewer &&
modelviewer_vr_capable())
vr_active = vr_request_session();
return true;
}
void retro_unload_game(void)
{
renderer_dead_state = true;
if (convert_buffer)
delete[] convert_buffer;
convert_buffer = NULL;
vr_active = false;
}
unsigned retro_get_region(void)
{
return RETRO_REGION_NTSC;
}
bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num)
{
(void)type;
(void)info;
(void)num;
return false;
}
size_t retro_serialize_size(void)
{
return 0;
}
bool retro_serialize(void *data, size_t size)
{
(void)data;
(void)size;
return false;
}
bool retro_unserialize(const void *data, size_t size)
{
(void)data;
(void)size;
return false;
}
void *retro_get_memory_data(unsigned id)
{
(void)id;
return NULL;
}
size_t retro_get_memory_size(unsigned id)
{
(void)id;
return 0;
}
void retro_reset(void)
{
if (engine_program_cb && engine_program_cb->context_reset)
engine_program_cb->context_reset();
}
void retro_cheat_reset(void)
{}
void retro_cheat_set(unsigned index, bool enabled, const char *code)
{
(void)index;
(void)enabled;
(void)code;
}