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

/*
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;
}