host: Move several items from host to emulator:

* init
* handle_events
* screen_render.cpp and screen_render.h
* app.cpp and app.h
* config functions
* discrete GPU exports
* emulator-called SFO functions
This commit is contained in:
Peter Mackay 2019-03-14 23:58:23 +00:00
parent a5502792b0
commit 6dcc7633ea
19 changed files with 462 additions and 366 deletions

View File

@ -76,14 +76,24 @@ add_subdirectory(gui)
add_executable( add_executable(
emulator emulator
MACOSX_BUNDLE MACOSX_BUNDLE
app.cpp
app.h
config.cpp
config.h
init.cpp
main.cpp main.cpp
performance.cpp
screen_render.cpp
screen_render.h
sfo.cpp
sfo.h
) )
target_link_libraries(emulator PRIVATE ${Boost_LIBRARIES} cpu dialog elfio glbinding-aux glutil gui host shader imgui modules nids util vita-toolchain yaml-cpp)
if(LINUX) if(LINUX)
target_link_libraries(emulator PRIVATE ${Boost_LIBRARIES} cpu dialog elfio glutil gui host shader imgui modules nids vita-toolchain util stdc++fs) target_link_libraries(emulator PRIVATE stdc++fs)
else()
target_link_libraries(emulator PRIVATE ${Boost_LIBRARIES} cpu dialog elfio glutil gui host shader imgui modules nids vita-toolchain util)
endif() endif()
set_target_properties(emulator PROPERTIES OUTPUT_NAME Vita3K set_target_properties(emulator PROPERTIES OUTPUT_NAME Vita3K
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"

View File

@ -15,16 +15,19 @@
// with this program; if not, write to the Free Software Foundation, Inc., // with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "app.h"
#include "sfo.h"
#include <gui/functions.h> #include <gui/functions.h>
#include <gui/imgui_impl_sdl_gl3.h> #include <gui/imgui_impl_sdl_gl3.h>
#include <host/app.h>
#include <host/functions.h> #include <host/functions.h>
#include <host/sfo.h>
#include <host/state.h> #include <host/state.h>
#include <host/version.h> #include <host/version.h>
#include <io/functions.h> #include <io/functions.h>
#include <io/io.h> #include <io/io.h>
#include <io/state.h> #include <io/state.h>
#include <kernel/functions.h>
#include <kernel/load_self.h> #include <kernel/load_self.h>
#include <kernel/state.h> #include <kernel/state.h>
#include <kernel/thread/thread_functions.h> #include <kernel/thread/thread_functions.h>
@ -48,6 +51,47 @@ static const char *EBOOT_PATH_ABS = "app0:eboot.bin";
using namespace gl; using namespace gl;
static void handle_window_event(HostState &state, const SDL_WindowEvent event) {
switch (static_cast<SDL_WindowEventID>(event.event)) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
update_viewport(state);
break;
default:
break;
}
}
bool handle_events(HostState &host) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
ImGui_ImplSdlGL3_ProcessEvent(&event);
switch (event.type) {
case SDL_QUIT:
stop_all_threads(host.kernel);
host.gxm.display_queue.abort();
host.display.abort.exchange(true);
host.display.condvar.notify_all();
return false;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_g) {
auto &display = host.display;
// toggle gui state
bool old_imgui_render = display.imgui_render.load();
while (!display.imgui_render.compare_exchange_weak(old_imgui_render, !old_imgui_render)) {
}
}
case SDL_WINDOWEVENT:
handle_window_event(host, event.window);
break;
}
}
return true;
}
void error_dialog(const std::string &message, SDL_Window *window) { void error_dialog(const std::string &message, SDL_Window *window) {
if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message.c_str(), window) < 0) { if (SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message.c_str(), window) < 0) {
LOG_ERROR("SDL Error: {}", message); LOG_ERROR("SDL Error: {}", message);

View File

@ -19,6 +19,7 @@
#include <string> #include <string>
struct Config;
struct HostState; struct HostState;
struct SDL_Window; struct SDL_Window;
template <class T> template <class T>
@ -47,6 +48,9 @@ enum class AppRunType {
Vpk, Vpk,
}; };
bool init(HostState &state, Config cfg);
void update_viewport(HostState &state);
bool handle_events(HostState &host);
void error_dialog(const std::string &message, SDL_Window *window = nullptr); void error_dialog(const std::string &message, SDL_Window *window = nullptr);
ExitCode load_app(Ptr<const void> &entry_point, HostState &host, const std::wstring &path, AppRunType run_type); ExitCode load_app(Ptr<const void> &entry_point, HostState &host, const std::wstring &path, AppRunType run_type);
ExitCode run_app(HostState &host, Ptr<const void> &entry_point); ExitCode run_app(HostState &host, Ptr<const void> &entry_point);

View File

@ -15,7 +15,7 @@
// with this program; if not, write to the Free Software Foundation, Inc., // with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <host/config.h> #include "config.h"
#include <host/version.h> #include <host/version.h>
#include <util/log.h> #include <util/log.h>
@ -111,7 +111,7 @@ bool serialize(Config &cfg) {
return true; return true;
} }
bool deserialize(Config &cfg) { static bool deserialize(Config &cfg) {
YAML::Node config_node{}; YAML::Node config_node{};
try { try {

45
src/emulator/config.h Normal file
View File

@ -0,0 +1,45 @@
// Vita3K emulator project
// Copyright (C) 2018 Vita3K team
//
// This program 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <host/config.h>
namespace config {
enum class InitResult {
OK,
QUIT,
INCORRECT_ARGS,
};
/*
* \brief Save emulator config to a YML file.
*
* \return True on saving success.
*/
bool serialize(Config &cfg);
/**
* \brief Initializes config system, parsing command-line args and handling some basic ones:
* --help, --version, --log-level
* \param cfg Config options are returned via this parameter.
* \return True on success, false on error.
*/
InitResult init(Config &cfg, int argc, char **argv);
} // namespace config

View File

@ -1,18 +1,13 @@
add_library( add_library(
host host
STATIC STATIC
include/host/app.h
include/host/config.h include/host/config.h
include/host/screen_render.h
include/host/functions.h include/host/functions.h
include/host/imports.h include/host/imports.h
include/host/sfo.h include/host/sfo.h
include/host/state.h include/host/state.h
include/host/version.h include/host/version.h
include/host/app_util.h include/host/app_util.h
src/app.cpp
src/screen_render.cpp
src/config.cpp
src/host.cpp src/host.cpp
src/sfo.cpp src/sfo.cpp
version.cpp version.cpp
@ -22,4 +17,4 @@ configure_file(src/version.cpp.in version.cpp)
target_include_directories(host PUBLIC include) target_include_directories(host PUBLIC include)
target_link_libraries(host PUBLIC audio cpu ctrl gxm io kernel mem net nids np renderer rtc util gui) target_link_libraries(host PUBLIC audio cpu ctrl gxm io kernel mem net nids np renderer rtc util gui)
target_link_libraries(host PRIVATE ${Boost_LIBRARIES} glbinding-aux glutil microprofile sdl2 yaml-cpp) target_link_libraries(host PRIVATE ${Boost_LIBRARIES} glutil microprofile sdl2)

View File

@ -40,36 +40,3 @@ struct Config {
optional<std::string> pref_path; optional<std::string> pref_path;
bool archive_log = false; bool archive_log = false;
}; };
namespace config {
enum class InitResult {
OK,
QUIT,
INCORRECT_ARGS,
};
/*
* \brief Initializes config system from a YML file.
*
* \param cfg Config options are returned via this parameter.
* \return True on loading success.
*/
bool deserialize(Config &cfg);
/*
* \brief Save emulator config to a YML file.
*
* \return True on saving success.
*/
bool serialize(Config &cfg);
/**
* \brief Initializes config system, parsing command-line args and handling some basic ones:
* --help, --version, --log-level
* \param cfg Config options are returned via this parameter.
* \return True on success, false on error.
*/
InitResult init(Config &cfg, int argc, char **argv);
} // namespace config

View File

@ -19,10 +19,11 @@
#include <psp2/types.h> #include <psp2/types.h>
struct Config; #include <string>
struct CPUState; struct CPUState;
struct HostState; struct HostState;
struct SfoFile;
bool init(HostState &state, Config cfg);
bool handle_events(HostState &host);
void call_import(HostState &host, CPUState &cpu, uint32_t nid, SceUID thread_id); void call_import(HostState &host, CPUState &cpu, uint32_t nid, SceUID thread_id);
bool get_data(std::string &out_data, SfoFile &file, int id);

View File

@ -19,7 +19,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <utility> // pair
#include <vector> #include <vector>
struct SfoHeader { struct SfoHeader {
@ -48,7 +48,3 @@ struct SfoFile {
std::vector<SfoEntry> entries; std::vector<SfoEntry> entries;
}; };
bool load_sfo(SfoFile &file, const std::vector<uint8_t> &data);
bool find_data(std::string &out_data, SfoFile &file, const std::string &key);
bool get_data(std::string &out_data, SfoFile &file, int id);

View File

@ -17,69 +17,16 @@
#include <host/functions.h> #include <host/functions.h>
#include <cpu/functions.h>
#include <host/imports.h> #include <host/imports.h>
#include <host/state.h> #include <host/state.h>
#include <host/version.h>
#include <audio/functions.h>
#include <cpu/functions.h>
#include <glutil/gl.h>
#include <io/functions.h>
#include <io/io.h>
#include <kernel/functions.h>
#include <kernel/thread/thread_state.h>
#include <nids/functions.h> #include <nids/functions.h>
#include <rtc/rtc.h>
#include <util/lock_and_find.h> #include <util/lock_and_find.h>
#include <util/log.h> #include <util/log.h>
#include <SDL_events.h>
#include <SDL_filesystem.h>
#include <SDL_video.h>
#include <glbinding-aux/types_to_string.h>
#include <glbinding/Binding.h>
#include <microprofile.h>
#include <cassert>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <utility>
#include <gui/imgui_impl_sdl_gl3.h>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <dirent.h>
#include <util/string_utils.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
#ifdef _WIN32
// Use discrete GPU by default
extern "C" {
// NVIDIA Optimus (Driver: 302+)
// See: http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
// AMD (Driver: 13.35+)
// See: https://gpuopen.com/amdpowerxpressrequesthighperformance/
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
using namespace glbinding;
static bool LOG_IMPORT_CALLS = false; static bool LOG_IMPORT_CALLS = false;
static constexpr bool LOG_UNK_NIDS_ALWAYS = false; static constexpr bool LOG_UNK_NIDS_ALWAYS = false;
static constexpr auto DEFAULT_RES_WIDTH = 960;
static constexpr auto DEFAULT_RES_HEIGHT = 544;
#define NID(name, nid) extern const ImportFn import_##name; #define NID(name, nid) extern const ImportFn import_##name;
#include <nids/nids.h> #include <nids/nids.h>
#undef NID #undef NID
@ -96,196 +43,6 @@ static ImportFn resolve_import(uint32_t nid) {
return ImportFn(); return ImportFn();
} }
#if MICROPROFILE_ENABLED
static void before_callback(const glbinding::FunctionCall &fn) {
const MicroProfileToken token = MicroProfileGetToken("OpenGL", fn.function->name(), MP_CYAN, MicroProfileTokenTypeCpu);
MICROPROFILE_ENTER_TOKEN(token);
}
#endif // MICROPROFILE_ENABLED
static void after_callback(const glbinding::FunctionCall &fn) {
MICROPROFILE_LEAVE();
for (GLenum error = glGetError(); error != GL_NO_ERROR; error = glGetError()) {
std::stringstream gl_error;
gl_error << error;
LOG_ERROR("OpenGL: {} set error {}.", fn.function->name(), gl_error.str());
assert(false);
}
}
static void update_viewport(HostState &state) {
int w = 0;
int h = 0;
SDL_GL_GetDrawableSize(state.window.get(), &w, &h);
state.drawable_size.x = w;
state.drawable_size.y = h;
if (h > 0) {
const float window_aspect = static_cast<float>(w) / h;
const float vita_aspect = static_cast<float>(DEFAULT_RES_WIDTH) / DEFAULT_RES_HEIGHT;
if (window_aspect > vita_aspect) {
// Window is wide. Pin top and bottom.
state.viewport_size.x = h * vita_aspect;
state.viewport_size.y = h;
state.viewport_pos.x = (w - state.viewport_size.x) / 2;
state.viewport_pos.y = 0;
} else {
// Window is tall. Pin left and right.
state.viewport_size.x = w;
state.viewport_size.y = w / vita_aspect;
state.viewport_pos.x = 0;
state.viewport_pos.y = (h - state.viewport_size.y) / 2;
}
} else {
state.viewport_pos.x = 0;
state.viewport_pos.y = 0;
state.viewport_size.x = 0;
state.viewport_size.y = 0;
}
}
static void handle_window_event(HostState &state, const SDL_WindowEvent event) {
switch (static_cast<SDL_WindowEventID>(event.event)) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
update_viewport(state);
break;
default:
break;
}
}
bool init(HostState &state, Config cfg) {
const std::unique_ptr<char, void (&)(void *)> base_path(SDL_GetBasePath(), SDL_free);
const std::unique_ptr<char, void (&)(void *)> pref_path(SDL_GetPrefPath(org_name, app_name), SDL_free);
const ResumeAudioThread resume_thread = [&state](SceUID thread_id) {
const ThreadStatePtr thread = lock_and_find(thread_id, state.kernel.threads, state.kernel.mutex);
const std::lock_guard<std::mutex> lock(thread->mutex);
if (thread->to_do == ThreadToDo::wait) {
thread->to_do = ThreadToDo::run;
}
thread->something_to_do.notify_all();
};
state.cfg = std::move(cfg);
state.base_path = base_path.get();
// If configuration already provides preference path
if (!state.cfg.pref_path) {
state.pref_path = pref_path.get();
state.cfg.pref_path = state.pref_path;
} else {
state.pref_path = state.cfg.pref_path.value();
}
state.window = WindowPtr(SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, DEFAULT_RES_WIDTH, DEFAULT_RES_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE), SDL_DestroyWindow);
if (!state.window || !init(state.mem) || !init(state.audio, resume_thread) || !init(state.io, state.pref_path.c_str(), state.base_path.c_str())) {
return false;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
state.glcontext = GLContextPtr(SDL_GL_CreateContext(state.window.get()), SDL_GL_DeleteContext);
if (!state.glcontext) {
LOG_ERROR("Could not create OpenGL context.");
return false;
}
update_viewport(state);
// Try adaptive vsync first, falling back to regular vsync.
if (SDL_GL_SetSwapInterval(-1) < 0) {
SDL_GL_SetSwapInterval(1);
}
LOG_INFO("Swap interval = {}", SDL_GL_GetSwapInterval());
const glbinding::GetProcAddress get_proc_address = [](const char *name) {
return reinterpret_cast<ProcAddress>(SDL_GL_GetProcAddress(name));
};
Binding::initialize(get_proc_address, false);
Binding::setCallbackMaskExcept(CallbackMask::Before | CallbackMask::After, { "glGetError" });
#if MICROPROFILE_ENABLED != 0
Binding::setBeforeCallback(before_callback);
#endif // MICROPROFILE_ENABLED
Binding::setAfterCallback(after_callback);
state.kernel.base_tick = { rtc_base_ticks() };
std::string dir_path = state.pref_path + "ux0/app";
#ifdef WIN32
_WDIR *d = _wopendir((string_utils::utf_to_wide(dir_path)).c_str());
_wdirent *dp;
#else
DIR *d = opendir(dir_path.c_str());
dirent *dp;
#endif
do {
std::string d_name_utf8;
#ifdef WIN32
if ((dp = _wreaddir(d)) != NULL) {
d_name_utf8 = string_utils::wide_to_utf(dp->d_name);
#else
if ((dp = readdir(d)) != NULL) {
d_name_utf8 = dp->d_name;
#endif
if ((strcmp(d_name_utf8.c_str(), ".")) && (strcmp(d_name_utf8.c_str(), ".."))) {
vfs::FileBuffer params;
state.io.title_id = d_name_utf8;
if (vfs::read_app_file(params, state.pref_path, state.io.title_id, "sce_sys/param.sfo")) {
SfoFile sfo_handle;
load_sfo(sfo_handle, params);
find_data(state.game_version, sfo_handle, "APP_VER");
find_data(state.game_title, sfo_handle, "TITLE");
std::replace(state.game_title.begin(), state.game_title.end(), '\n', ' ');
state.gui.game_selector.games.push_back({ state.game_version, state.game_title, state.io.title_id });
}
}
}
} while (dp);
#ifdef WIN32
_wclosedir(d);
#else
closedir(d);
#endif
return true;
}
bool handle_events(HostState &host) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
ImGui_ImplSdlGL3_ProcessEvent(&event);
switch (event.type) {
case SDL_QUIT:
stop_all_threads(host.kernel);
host.gxm.display_queue.abort();
host.display.abort.exchange(true);
host.display.condvar.notify_all();
return false;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_g) {
auto &display = host.display;
// toggle gui state
bool old_imgui_render = display.imgui_render.load();
while (!display.imgui_render.compare_exchange_weak(old_imgui_render, !old_imgui_render)) {
}
}
case SDL_WINDOWEVENT:
handle_window_event(host, event.window);
break;
}
}
return true;
}
/** /**
* \brief Resolves a function imported from a loaded module. * \brief Resolves a function imported from a loaded module.
* \param kernel Kernel state struct * \param kernel Kernel state struct

View File

@ -15,67 +15,10 @@
// with this program; if not, write to the Free Software Foundation, Inc., // with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <host/functions.h>
#include <host/sfo.h> #include <host/sfo.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
bool load_sfo(SfoFile &sfile, const std::vector<uint8_t> &content) {
if (content.empty()) {
return false;
}
memcpy(static_cast<void *>(&sfile.header), content.data(), sizeof(SfoHeader));
sfile.entries.resize(sfile.header.tables_entries + 1);
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
memcpy(static_cast<void *>(&sfile.entries[i].entry), content.data() + sizeof(SfoHeader) + i * sizeof(SfoIndexTableEntry), sizeof(SfoIndexTableEntry));
}
sfile.entries[sfile.header.tables_entries].entry.key_offset = sfile.header.data_table_start - sfile.header.key_table_start;
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
uint32_t keySize = sfile.entries[i + 1].entry.key_offset - sfile.entries[i].entry.key_offset;
sfile.entries[i].data.first.resize(keySize);
memcpy(&sfile.entries[i].data.first[0], &content[sfile.header.key_table_start + sfile.entries[i].entry.key_offset], keySize);
//Quick hack to remove garbage null terminator caused by reading directly
//to buffer
sfile.entries[i].data.first = sfile.entries[i].data.first.c_str();
}
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
uint32_t dataSize = sfile.entries[i].entry.data_len;
sfile.entries[i].data.second.resize(dataSize);
// The last of data is a terminator
memcpy(&sfile.entries[i].data.second[0], &content[sfile.header.data_table_start + sfile.entries[i].entry.data_offset], dataSize - 1);
//Quick hack to remove garbage null terminator caused by reading directly
//to buffer
sfile.entries[i].data.second = sfile.entries[i].data.second.c_str();
}
return true;
}
bool find_data(std::string &out_data, SfoFile &file, const std::string &key) {
auto res = std::find_if(file.entries.begin(), file.entries.end(),
[key](auto et) { return et.data.first == key; });
if (res == file.entries.end()) {
return false;
}
out_data = res->data.second;
return true;
}
bool get_data(std::string &out_data, SfoFile &file, int id) { bool get_data(std::string &out_data, SfoFile &file, int id) {
if (file.entries.size() < id) { if (file.entries.size() < id) {
return false; return false;

201
src/emulator/init.cpp Normal file
View File

@ -0,0 +1,201 @@
// Vita3K emulator project
// Copyright (C) 2018 Vita3K team
//
// This program 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "app.h"
#include "sfo.h"
#include <audio/functions.h>
#include <glutil/gl.h>
#include <host/state.h>
#include <host/version.h>
#include <io/functions.h>
#include <io/io.h> // vfs
#include <rtc/rtc.h>
#include <util/lock_and_find.h>
#include <util/log.h>
#include <SDL_filesystem.h>
#include <SDL_video.h>
#include <glbinding-aux/types_to_string.h>
#include <glbinding/Binding.h>
#include <microprofile.h>
#include <sstream>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <dirent.h>
#include <util/string_utils.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
using namespace glbinding;
static constexpr auto DEFAULT_RES_WIDTH = 960;
static constexpr auto DEFAULT_RES_HEIGHT = 544;
#if MICROPROFILE_ENABLED
static void before_callback(const glbinding::FunctionCall &fn) {
const MicroProfileToken token = MicroProfileGetToken("OpenGL", fn.function->name(), MP_CYAN, MicroProfileTokenTypeCpu);
MICROPROFILE_ENTER_TOKEN(token);
}
#endif // MICROPROFILE_ENABLED
static void after_callback(const glbinding::FunctionCall &fn) {
MICROPROFILE_LEAVE();
for (GLenum error = glGetError(); error != GL_NO_ERROR; error = glGetError()) {
std::stringstream gl_error;
gl_error << error;
LOG_ERROR("OpenGL: {} set error {}.", fn.function->name(), gl_error.str());
assert(false);
}
}
void update_viewport(HostState &state) {
int w = 0;
int h = 0;
SDL_GL_GetDrawableSize(state.window.get(), &w, &h);
state.drawable_size.x = w;
state.drawable_size.y = h;
if (h > 0) {
const float window_aspect = static_cast<float>(w) / h;
const float vita_aspect = static_cast<float>(DEFAULT_RES_WIDTH) / DEFAULT_RES_HEIGHT;
if (window_aspect > vita_aspect) {
// Window is wide. Pin top and bottom.
state.viewport_size.x = h * vita_aspect;
state.viewport_size.y = h;
state.viewport_pos.x = (w - state.viewport_size.x) / 2;
state.viewport_pos.y = 0;
} else {
// Window is tall. Pin left and right.
state.viewport_size.x = w;
state.viewport_size.y = w / vita_aspect;
state.viewport_pos.x = 0;
state.viewport_pos.y = (h - state.viewport_size.y) / 2;
}
} else {
state.viewport_pos.x = 0;
state.viewport_pos.y = 0;
state.viewport_size.x = 0;
state.viewport_size.y = 0;
}
}
bool init(HostState &state, Config cfg) {
const std::unique_ptr<char, void (&)(void *)> base_path(SDL_GetBasePath(), SDL_free);
const std::unique_ptr<char, void (&)(void *)> pref_path(SDL_GetPrefPath(org_name, app_name), SDL_free);
const ResumeAudioThread resume_thread = [&state](SceUID thread_id) {
const ThreadStatePtr thread = lock_and_find(thread_id, state.kernel.threads, state.kernel.mutex);
const std::lock_guard<std::mutex> lock(thread->mutex);
if (thread->to_do == ThreadToDo::wait) {
thread->to_do = ThreadToDo::run;
}
thread->something_to_do.notify_all();
};
state.cfg = std::move(cfg);
state.base_path = base_path.get();
// If configuration already provides preference path
if (!state.cfg.pref_path) {
state.pref_path = pref_path.get();
state.cfg.pref_path = state.pref_path;
} else {
state.pref_path = state.cfg.pref_path.value();
}
state.window = WindowPtr(SDL_CreateWindow(window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, DEFAULT_RES_WIDTH, DEFAULT_RES_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE), SDL_DestroyWindow);
if (!state.window || !init(state.mem) || !init(state.audio, resume_thread) || !init(state.io, state.pref_path.c_str(), state.base_path.c_str())) {
return false;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
state.glcontext = GLContextPtr(SDL_GL_CreateContext(state.window.get()), SDL_GL_DeleteContext);
if (!state.glcontext) {
LOG_ERROR("Could not create OpenGL context.");
return false;
}
update_viewport(state);
// Try adaptive vsync first, falling back to regular vsync.
if (SDL_GL_SetSwapInterval(-1) < 0) {
SDL_GL_SetSwapInterval(1);
}
LOG_INFO("Swap interval = {}", SDL_GL_GetSwapInterval());
const glbinding::GetProcAddress get_proc_address = [](const char *name) {
return reinterpret_cast<ProcAddress>(SDL_GL_GetProcAddress(name));
};
Binding::initialize(get_proc_address, false);
Binding::setCallbackMaskExcept(CallbackMask::Before | CallbackMask::After, { "glGetError" });
#if MICROPROFILE_ENABLED != 0
Binding::setBeforeCallback(before_callback);
#endif // MICROPROFILE_ENABLED
Binding::setAfterCallback(after_callback);
state.kernel.base_tick = { rtc_base_ticks() };
std::string dir_path = state.pref_path + "ux0/app";
#ifdef WIN32
_WDIR *d = _wopendir((string_utils::utf_to_wide(dir_path)).c_str());
_wdirent *dp;
#else
DIR *d = opendir(dir_path.c_str());
dirent *dp;
#endif
do {
std::string d_name_utf8;
#ifdef WIN32
if ((dp = _wreaddir(d)) != NULL) {
d_name_utf8 = string_utils::wide_to_utf(dp->d_name);
#else
if ((dp = readdir(d)) != NULL) {
d_name_utf8 = dp->d_name;
#endif
if ((strcmp(d_name_utf8.c_str(), ".")) && (strcmp(d_name_utf8.c_str(), ".."))) {
vfs::FileBuffer params;
state.io.title_id = d_name_utf8;
if (vfs::read_app_file(params, state.pref_path, state.io.title_id, "sce_sys/param.sfo")) {
SfoFile sfo_handle;
load_sfo(sfo_handle, params);
find_data(state.game_version, sfo_handle, "APP_VER");
find_data(state.game_title, sfo_handle, "TITLE");
std::replace(state.game_title.begin(), state.game_title.end(), '\n', ' ');
state.gui.game_selector.games.push_back({ state.game_version, state.game_title, state.io.title_id });
}
}
}
} while (dp);
#ifdef WIN32
_wclosedir(d);
#else
closedir(d);
#endif
return true;
}

View File

@ -15,14 +15,13 @@
// with this program; if not, write to the Free Software Foundation, Inc., // with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "app.h"
#include "config.h"
#include "screen_render.h"
#include <gui/functions.h> #include <gui/functions.h>
#include <host/app.h>
#include <host/config.h>
#include <host/functions.h>
#include <host/screen_render.h>
#include <host/state.h> #include <host/state.h>
#include <host/version.h> #include <host/version.h>
#include <kernel/thread/thread_functions.h>
#include <shader/spirv_recompiler.h> #include <shader/spirv_recompiler.h>
#include <util/log.h> #include <util/log.h>
#include <util/string_utils.h> #include <util/string_utils.h>
@ -30,8 +29,6 @@
#include <SDL.h> #include <SDL.h>
#include <cstdlib> #include <cstdlib>
#include <iterator>
#include <utility>
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
logging::init(); logging::init();

View File

@ -17,7 +17,8 @@
#include "SceAppMgrUser.h" #include "SceAppMgrUser.h"
#include <host/sfo.h> #include <host/functions.h>
#include <psp2/appmgr.h> #include <psp2/appmgr.h>
EXPORT(int, _sceAppMgrGetAppState, void *appState, uint32_t len, uint32_t version) { EXPORT(int, _sceAppMgrGetAppState, void *appState, uint32_t len, uint32_t version) {

View File

@ -0,0 +1,30 @@
// Vita3K emulator project
// Copyright (C) 2018 Vita3K team
//
// This program 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <cstdint>
#ifdef _WIN32
extern "C" {
// http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
__declspec(dllexport) uint32_t NvOptimusEnablement = 1;
// https://gpuopen.com/amdpowerxpressrequesthighperformance/
__declspec(dllexport) uint32_t AmdPowerXpressRequestHighPerformance = 1;
}
#endif // _WIN32

View File

@ -15,7 +15,8 @@
// with this program; if not, write to the Free Software Foundation, Inc., // with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <host/screen_render.h> #include "screen_render.h"
#include <host/state.h> #include <host/state.h>
#include <util/log.h> #include <util/log.h>

78
src/emulator/sfo.cpp Normal file
View File

@ -0,0 +1,78 @@
// Vita3K emulator project
// Copyright (C) 2018 Vita3K team
//
// This program 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "sfo.h"
#include <host/sfo.h>
#include <algorithm>
#include <cstring>
bool load_sfo(SfoFile &sfile, const std::vector<uint8_t> &content) {
if (content.empty()) {
return false;
}
memcpy(static_cast<void *>(&sfile.header), content.data(), sizeof(SfoHeader));
sfile.entries.resize(sfile.header.tables_entries + 1);
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
memcpy(static_cast<void *>(&sfile.entries[i].entry), content.data() + sizeof(SfoHeader) + i * sizeof(SfoIndexTableEntry), sizeof(SfoIndexTableEntry));
}
sfile.entries[sfile.header.tables_entries].entry.key_offset = sfile.header.data_table_start - sfile.header.key_table_start;
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
uint32_t keySize = sfile.entries[i + 1].entry.key_offset - sfile.entries[i].entry.key_offset;
sfile.entries[i].data.first.resize(keySize);
memcpy(&sfile.entries[i].data.first[0], &content[sfile.header.key_table_start + sfile.entries[i].entry.key_offset], keySize);
//Quick hack to remove garbage null terminator caused by reading directly
//to buffer
sfile.entries[i].data.first = sfile.entries[i].data.first.c_str();
}
for (uint32_t i = 0; i < sfile.header.tables_entries; i++) {
uint32_t dataSize = sfile.entries[i].entry.data_len;
sfile.entries[i].data.second.resize(dataSize);
// The last of data is a terminator
memcpy(&sfile.entries[i].data.second[0], &content[sfile.header.data_table_start + sfile.entries[i].entry.data_offset], dataSize - 1);
//Quick hack to remove garbage null terminator caused by reading directly
//to buffer
sfile.entries[i].data.second = sfile.entries[i].data.second.c_str();
}
return true;
}
bool find_data(std::string &out_data, SfoFile &file, const std::string &key) {
auto res = std::find_if(file.entries.begin(), file.entries.end(),
[key](auto et) { return et.data.first == key; });
if (res == file.entries.end()) {
return false;
}
out_data = res->data.second;
return true;
}

26
src/emulator/sfo.h Normal file
View File

@ -0,0 +1,26 @@
// Vita3K emulator project
// Copyright (C) 2018 Vita3K team
//
// This program 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <string>
#include <vector>
struct SfoFile;
bool load_sfo(SfoFile &file, const std::vector<uint8_t> &data);
bool find_data(std::string &out_data, SfoFile &file, const std::string &key);