You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

407 lines
16 KiB

/*
Copyright 2020-2021 Brad Parker
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <QGuiApplication>
#include <QRegularExpression>
#include <QScreen>
#include <iostream>
#include "libretro.h"
#include "core.h"
#include "../common/video.h"
#include "../frontend/mainwindow.h"
typedef struct {
const char *name;
unsigned cmd;
} EnvCommand;
static EnvCommand s_envCommands[] = {
{"RETRO_ENVIRONMENT_SET_ROTATION", 1},
{"RETRO_ENVIRONMENT_GET_OVERSCAN", 2},
{"RETRO_ENVIRONMENT_GET_CAN_DUPE", 3},
{"RETRO_ENVIRONMENT_SET_MESSAGE", 6},
{"RETRO_ENVIRONMENT_SHUTDOWN", 7},
{"RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL", 8},
{"RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY", 9},
{"RETRO_ENVIRONMENT_SET_PIXEL_FORMAT", 10},
{"RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS", 11},
{"RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK", 12},
{"RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE", 13},
{"RETRO_ENVIRONMENT_SET_HW_RENDER", 14},
{"RETRO_ENVIRONMENT_GET_VARIABLE", 15},
{"RETRO_ENVIRONMENT_SET_VARIABLES", 16},
{"RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE", 17},
{"RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME", 18},
{"RETRO_ENVIRONMENT_GET_LIBRETRO_PATH", 19},
{"RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK", 21},
{"RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK", 22},
{"RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE", 23},
{"RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES", 24},
{"RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE", (25 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE", (26 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_LOG_INTERFACE", 27},
{"RETRO_ENVIRONMENT_GET_PERF_INTERFACE", 28},
{"RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE", 29},
{"RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY", 30},
{"RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY", 30},
{"RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY", 31},
{"RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO", 32},
{"RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK", 33},
{"RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO", 34},
{"RETRO_ENVIRONMENT_SET_CONTROLLER_INFO", 35},
{"RETRO_ENVIRONMENT_SET_MEMORY_MAPS", (36 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_SET_GEOMETRY", 37},
{"RETRO_ENVIRONMENT_GET_USERNAME", 38},
{"RETRO_ENVIRONMENT_GET_LANGUAGE", 39},
{"RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER", (40 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE", (41 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS", (42 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE", (43 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS", 44},
{"RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT", (44 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_VFS_INTERFACE", (45 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_LED_INTERFACE", (46 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE", (47 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_MIDI_INTERFACE", (48 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_FASTFORWARDING", (49 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE", (50 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_INPUT_BITMASKS", (51 | RETRO_ENVIRONMENT_EXPERIMENTAL)},
{"RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION", 52},
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS", 53},
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL", 54},
{"RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY", 55},
{"RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER", 56},
{"RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION", 57},
{"RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE", 58},
};
static void core_log(enum retro_log_level level, const char *fmt, ...) {
char buffer[4093] = {0};
char buffer2[4096] = {0};
static const char *levelstr[] = {"debug", "info", "warn", "error"};
va_list va;
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
if(level == 0)
return;
sprintf(buffer2, "[%s] %s", levelstr[level], buffer);
fprintf(stdout, "%s", buffer2);
}
bool Core::environment(unsigned cmd, void *data) {
for(unsigned i = 0; i < sizeof(s_envCommands) / sizeof(s_envCommands[0]); ++i) {
if(s_envCommands[i].cmd == cmd) {
switch(cmd) {
case RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT:
return true;
case RETRO_ENVIRONMENT_SET_HW_RENDER:
{
auto *cb = reinterpret_cast<struct retro_hw_render_callback*>(data);
if(cb->context_type == RETRO_HW_CONTEXT_OPENGL || cb->context_type == RETRO_HW_CONTEXT_OPENGL_CORE) {
cb->get_current_framebuffer = []() { return Core::instance()->getCurrentFramebuffer(); };
void* (*getProcAddressPtr)(const char *sym) = [](const char *sym) -> void* { return Core::instance()->getProcAddress(sym); };
cb->get_proc_address = reinterpret_cast<retro_hw_get_proc_address_t>(getProcAddressPtr);
m_retroHWContextReset = cb->context_reset;
m_isHWRender = true;
m_bottomLeftOrigin = cb->bottom_left_origin ? true : false;
std::cout << "core uses bottom left origin? " << (m_bottomLeftOrigin ? "yes" : "no") << std::endl;
return true;
}else{
return false;
}
break;
}
// tells the frontend what buttons it supports on what kind of devices, for however many users
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
{
auto *descs = reinterpret_cast<const struct retro_input_descriptor*>(data);
/* TODO: FIXME: I used an arbitrary limit of 100 */
for(unsigned j = 0; j < 100; ++j) {
if(descs[j].port == 0 && descs[j].device == 0 && descs[j].index == 0 && descs[j].id == 0 && descs[j].description == nullptr)
break;
if(descs[j].description && *(descs[j].description))
std::cout << "got input descriptor at port " << descs[j].port << " device " << descs[j].device << " index " << descs[j].index << " id " << descs[j].id << ": " << descs[j].description << std::endl;
else
std::cout << "got input descriptor at port " << descs[j].port << " device " << descs[j].device << " index " << descs[j].index << " id " << descs[j].id << ": (no description)" << std::endl;
}
return true;
}
// whether frontend supports the newer advanced core option interfaces
case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION:
{
unsigned *ver = reinterpret_cast<unsigned*>(data);
// not yet
if(ver)
*ver = 0;
return true;
}
// asks the frontend if it changed any core variables
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
{
bool *update = reinterpret_cast<bool*>(data);
if(update)
*update = m_variablesChanged;
if(m_variablesChanged)
m_variablesChanged = false;
return true;
}
// tells the frontend we support running without any content loaded
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
{
bool *nogame = reinterpret_cast<bool*>(data);
m_noGame = nogame;
return true;
}
// tells the frontend what core options are available
case RETRO_ENVIRONMENT_SET_VARIABLES:
{
auto *vars = reinterpret_cast<const struct retro_variable*>(data);
std::cout << "core is sending its variables" << std::endl;
if(vars) {
int i = 0;
for(;;) {
if(vars[i].key && *(vars[i].key) && vars[i].value && *(vars[i].value)) {
//std::cout << "storing variable " << vars[i].key << ": " << vars[i].value << std::endl;
QStringList name_values = QString(vars[i].value).split(";");
if(name_values.size() > 0) {
CoreOption op{};
op.key = vars[i].key;
op.name = name_values.at(0);
QStringList values = name_values.at(1).split("|");
for(int j = 0; j < values.size(); ++j) {
QString val = values.at(j);
val = val.replace(QRegularExpression("^\\s+"), "");
val = val.replace(QRegularExpression("\\s+$"), "");
op.values.append(val);
}
// use first value as the default
op.val = op.values.at(0);
op.valArray = op.val.toUtf8();
op.valData = op.valArray.constData();
m_options[vars[i].key] = op;
}else{
//std::cerr << "no options found for variable " << vars[i].key << std::endl;
}
}else{
break;
}
++i;
}
}
emit coreOptionsChanged();
return true;
}
// asks the frontend for the current value of a variable
case RETRO_ENVIRONMENT_GET_VARIABLE:
{
auto *var = reinterpret_cast<struct retro_variable*>(data);
if(!var->key || !*(var->key)) {
static QString allEnvs;
static QByteArray allEnvsArray;
static const char *allEnvsData = nullptr;
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ") with no key, sending everything we have" << std::endl;
allEnvs.clear();
const QList<Core::CoreOption> options = m_options.values();
for(const CoreOption &op : options) {
allEnvs += op.key + "=" + op.val + ";";
}
allEnvsArray = allEnvs.toUtf8();
allEnvsData = allEnvsArray.constData();
var->key = allEnvsData;
var->value = NULL;
return true;
}
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ") with key \"" << var->key << "\"" << std::endl;
if(m_options.contains(var->key)) {
const CoreOption &op = m_options.value(var->key);
var->value = op.valData;
std::cout << "frontend to core: variable " << var->key << " = " << var->value << std::endl;
}else{
var->value = NULL;
return false;
}
return true;
}
// tells the frontend there was a major change in the A/V setup of the core, in RetroArch's case this constitutes an entire teardown/reinit of the whole application, but we don't need to
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO:
{
auto *info = reinterpret_cast<const struct retro_system_av_info*>(data);
std::cout << "got env " << cmd << " (" << s_envCommands[i].name << ")" << std::endl;
std::cout << "desired AV info: " << info->geometry.base_width << "x" << info->geometry.base_height << " (max " << info->geometry.max_width << "x" << info->geometry.max_height << ") aspect " << info->geometry.aspect_ratio << " fps " << info->timing.fps << std::endl;
// TODO: FIXME: not implemented: changing of audio parameters
auto videoState = VideoState::instance();
struct retro_system_av_info *avInfo = videoState->avInfo();
videoState->setAspect(info->geometry.aspect_ratio);
videoState->setBaseWidth(info->geometry.base_width);
videoState->setBaseHeight(info->geometry.base_height);
avInfo->geometry.aspect_ratio = info->geometry.aspect_ratio;
avInfo->geometry.base_width = info->geometry.base_width;
avInfo->geometry.base_height = info->geometry.base_height;
avInfo->geometry.max_width = info->geometry.max_width;
avInfo->geometry.max_height = info->geometry.max_height;
avInfo->timing.fps = info->timing.fps;
avInfo->timing.sample_rate = info->timing.sample_rate;
float screenRefresh = 0;
QScreen *screen = QGuiApplication::primaryScreen();
if(screen)
screenRefresh = screen->refreshRate();
// if core refresh rate is within half a percent of the screen's, just call it equal, it's hopefully close enough to not be noticeable
if((screenRefresh - (screenRefresh * 0.005)) <= avInfo->timing.fps && (screenRefresh + (screenRefresh * 0.005)) >= avInfo->timing.fps) {
avInfo->timing.fps = screenRefresh;
}
return true;
}
// if the core needs to handle saving on its own, this asks the frontend for a path to do so in
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
{
const char **save_dir = reinterpret_cast<const char**>(data);
*save_dir = m_savePath;
std::cout << "core requesting current save directory: " << *save_dir << std::endl;
return true;
}
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
{
const char **system_dir = reinterpret_cast<const char**>(data);
*system_dir = m_systemPath;
std::cout << "core requesting current assets directory: " << *system_dir << std::endl;
return true;
}
// if the core needs to load a BIOS or other support files, this asks the frontend for a path to find them in
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
{
const char **system_dir = reinterpret_cast<const char**>(data);
*system_dir = m_systemPath;
std::cout << "core requesting current system directory: " << *system_dir << std::endl;
return true;
}
// asks the frontend for a callback to send core log messages to
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
{
auto *cb = reinterpret_cast<struct retro_log_callback*>(data);
cb->log = core_log;
return true;
}
case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE:
break;
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
{
const auto *info = reinterpret_cast<const struct retro_controller_info*>(data);
std::cout << "core has set the controller info as:" << std::endl;
unsigned i = 0;
for(;;) {
const auto &desc = info->types[i];
if(i == info->num_types || (!desc.desc || !*(desc.desc)))
break;
std::cout << desc.desc << " (" << desc.id << ")" << std::endl;
m_controllerInfo[desc.desc] = desc.id;
++i;
}
return true;
}
case RETRO_ENVIRONMENT_SET_GEOMETRY:
{
auto *geom = reinterpret_cast<struct retro_game_geometry*>(data);
std::cout << "core requesting geometry change: base " << geom->base_width << "x" << geom->base_height << " max " << geom->max_width << "x" << geom->max_height << " aspect " << geom->aspect_ratio << std::endl;
auto videoState = VideoState::instance();
videoState->setAspect(geom->aspect_ratio);
videoState->setBaseWidth(geom->base_width);
videoState->setBaseHeight(geom->base_height);
return true;
}
// tells the frontend what pixel format the core wants to use, might be called multiple times to find a preferred format if the frontend rejects anything.
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
{
auto *pixfmt = reinterpret_cast<const enum retro_pixel_format*>(data);
m_pixFmt = *pixfmt;
if(m_pixFmt == RETRO_PIXEL_FORMAT_XRGB8888) {
std::cout << "core requesting pixel format " << *pixfmt << " (XRGB8888)" << std::endl;
return true;
}else if(m_pixFmt == RETRO_PIXEL_FORMAT_RGB565) {
std::cout << "core requesting pixel format " << *pixfmt << " (RGB565)" << std::endl;
return true;
}
// for now we only support XRGB8888 and RGB565
std::cout << "core requesting unsupported pixel format " << pixfmt << std::endl;
return false;
}
default:
//std::cout << "got unhandled env " << cmd << " (" << s_envCommands[i].name << ")" << std::endl;
break;
}
break;
}
}
return false;
}