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.
248 lines
7.5 KiB
248 lines
7.5 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 <QOpenGLFramebufferObject>
|
|
#include <QScreen>
|
|
#include <iostream>
|
|
#include "core.h"
|
|
#include "../common/video.h"
|
|
|
|
bool Core::run(QString contentPath) {
|
|
memset(&m_gameInfo, 0, sizeof(m_gameInfo));
|
|
|
|
if(contentPath.isEmpty()) {
|
|
m_path.clear();
|
|
m_pathArray.clear();
|
|
m_pathData = nullptr;
|
|
}else{
|
|
m_path = QFileInfo(contentPath).absoluteFilePath();
|
|
m_pathArray = m_path.toUtf8();
|
|
m_pathData = m_pathArray.constData();
|
|
|
|
std::cout << "loading content: " << m_pathData << std::endl;
|
|
|
|
m_gameInfo.path = m_pathData;
|
|
}
|
|
|
|
memset(&m_info, 0, sizeof(m_info));
|
|
|
|
std::cout << "retro_get_system_info";
|
|
|
|
m_retroGetSystemInfo(&m_info);
|
|
|
|
std::cout << "." << std::endl;
|
|
|
|
if(m_info.need_fullpath) {
|
|
std::cout << "core needs full path to content and will load it on its own." << std::endl;
|
|
}else{
|
|
std::cout << "core does not load its own content, the frontend will do it." << std::endl;
|
|
}
|
|
|
|
if(m_info.block_extract) {
|
|
std::cout << "core does not allow frontend to extract archives." << std::endl;
|
|
}else{
|
|
std::cout << "core allows frontend to extract archives." << std::endl;
|
|
// but we do not support it
|
|
}
|
|
|
|
/* TODO: At this point, one consideration to make might be the amount of memory available on the system.
|
|
* If the core does not load its own content, you don't want to try to load something into memory
|
|
* that is so large it will not fit. Better to alert the user in that case than silently crash.
|
|
*/
|
|
if(!m_path.isEmpty()) {
|
|
uint64_t contentSize = QFileInfo(m_path).size();
|
|
|
|
std::cout << "content size: " << contentSize / 1024.0 / 1024 << "MB" << std::endl;
|
|
|
|
if(m_info.need_fullpath) {
|
|
std::cout << "since core loads its own content, we will assume it won't load the whole thing into memory (or will check if there is enough), and continue on." << std::endl;
|
|
}else{
|
|
std::cout << "core doesn't load its own content, let's hope we have enough memory to load the whole thing." << std::endl;
|
|
}
|
|
}
|
|
|
|
if(m_info.library_name && *(m_info.library_name))
|
|
std::cout << "core name: " << m_info.library_name << std::endl;
|
|
|
|
if(m_info.library_version && *(m_info.library_version))
|
|
std::cout << "core version: " << m_info.library_version << std::endl;
|
|
|
|
if(m_info.valid_extensions && *(m_info.valid_extensions)) {
|
|
std::cout << "core supports the following extensions: " << m_info.valid_extensions << std::endl;
|
|
|
|
if(!m_path.isEmpty()) {
|
|
QStringList extens = QString(m_info.valid_extensions).split("|");
|
|
QString gameExten = QFileInfo(m_path).suffix();
|
|
|
|
bool found = false;
|
|
|
|
for(int i = 0; i < extens.size(); ++i) {
|
|
const QString &exten = extens.at(i);
|
|
|
|
if(exten.toLower() == gameExten.toLower()) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found) {
|
|
std::cerr << "unsupported file extension: " << qUtf8Printable(gameExten) << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
}else if(!m_noGame) {
|
|
std::cerr << "core does not list any valid extensions!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if(!(m_noGame && m_path.isEmpty()) && !m_info.need_fullpath) {
|
|
// core cannot load its own content, we must hold it in memory
|
|
if(!loadContentIntoMemory()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::cout << "retro_load_game";
|
|
|
|
if(!m_retroLoadGame(&m_gameInfo)) {
|
|
std::cerr << "could not load content" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::cout << "." << std::endl;
|
|
|
|
auto videoState = VideoState::instance();
|
|
|
|
struct retro_system_av_info *avInfo = videoState->avInfo();
|
|
|
|
memset(avInfo, 0, sizeof(*avInfo));
|
|
|
|
std::cout << "retro_get_system_av_info";
|
|
|
|
m_retroGetSystemAVInfo(avInfo);
|
|
|
|
std::cout << "." << std::endl;
|
|
|
|
videoState->setAspect(avInfo->geometry.aspect_ratio);
|
|
videoState->setBaseWidth(avInfo->geometry.base_width);
|
|
videoState->setBaseHeight(avInfo->geometry.base_height);
|
|
|
|
if(!videoState->aspect())
|
|
videoState->setAspect(1);
|
|
|
|
float screenRefresh = 0;
|
|
|
|
QScreen *screen = QGuiApplication::primaryScreen();
|
|
|
|
if(screen) {
|
|
screenRefresh = screen->refreshRate();
|
|
std::cout << "monitor refresh rate: " << screenRefresh << "HZ, DPI " << screen->logicalDotsPerInch() << std::endl;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
std::cout << "current AV info: " << avInfo->geometry.base_width << "x" << avInfo->geometry.base_height << " (max " << avInfo->geometry.max_width << "x" << avInfo->geometry.max_height << ") aspect " << avInfo->geometry.aspect_ratio << " fps " << avInfo->timing.fps << " audio rate " << avInfo->timing.sample_rate << std::endl;
|
|
|
|
if(m_isHWRender) {
|
|
QOpenGLFramebufferObject *fbo = videoState->fbo();
|
|
|
|
if(fbo)
|
|
delete fbo;
|
|
|
|
std::cout << "creating FBO with size " << avInfo->geometry.max_width << "x" << avInfo->geometry.max_height << std::endl;
|
|
fbo = new QOpenGLFramebufferObject(avInfo->geometry.max_width, avInfo->geometry.max_height, QOpenGLFramebufferObject::CombinedDepthStencil);
|
|
videoState->setFBO(fbo);
|
|
|
|
std::cout << "created FBO id " << fbo->handle() << std::endl;
|
|
|
|
std::cout << "context_reset" << std::endl;
|
|
|
|
if(m_retroHWContextReset) {
|
|
m_retroHWContextReset();
|
|
}else{
|
|
std::cerr << "no context reset function defined" << std::endl;
|
|
}
|
|
|
|
std::cout << "." << std::endl;
|
|
}
|
|
|
|
setupAudio();
|
|
|
|
if(m_imgData) {
|
|
delete []m_imgData;
|
|
m_imgData = nullptr;
|
|
}
|
|
|
|
loadSRAM();
|
|
|
|
std::cout << "retro_run loop starting at " << avInfo->timing.fps << " fps." << std::endl;
|
|
|
|
m_elapsedTimer.start();
|
|
|
|
m_doRender = true;
|
|
|
|
connect(&m_timer, &QTimer::timeout, this, &Core::repaint);
|
|
|
|
m_timer.start(0);
|
|
|
|
m_isRunning = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Core::loadContentIntoMemory() {
|
|
if(!m_path.isEmpty()) {
|
|
// per libretro.h we have to set data/size even if the core can load from a path
|
|
std::cout << "core does not need full path, reading in content to load ourselves: " << m_gameInfo.path << std::endl;
|
|
QFile f(m_gameInfo.path);
|
|
|
|
if(f.open(QIODevice::ReadOnly)) {
|
|
m_gameInfo.size = f.size();
|
|
m_gameDataSize = m_gameInfo.size;
|
|
|
|
if(m_gameData)
|
|
delete m_gameData;
|
|
|
|
m_gameData = new(std::nothrow) char[m_gameDataSize];
|
|
|
|
int64_t readBytes = f.read(m_gameData, m_gameDataSize);
|
|
|
|
f.close();
|
|
|
|
if(readBytes < 0 || readBytes != m_gameDataSize) {
|
|
std::cerr << "could not read content from disk: " << qUtf8Printable(f.fileName()) << std::endl;
|
|
return false;
|
|
}
|
|
|
|
m_gameInfo.data = m_gameData;
|
|
}else{
|
|
std::cerr << "could not open content for reading: " << qUtf8Printable(f.fileName()) << std::endl;
|
|
return false;
|
|
}
|
|
}else{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Core::supportsNoGame() {
|
|
return m_noGame;
|
|
}
|
|
|