mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
384 lines
10 KiB
C++
384 lines
10 KiB
C++
#include "stdafx.h"
|
|
#include "main_application.h"
|
|
#include "display_sleep_control.h"
|
|
|
|
#include "util/types.hpp"
|
|
#include "util/logs.hpp"
|
|
#include "util/sysinfo.hpp"
|
|
|
|
#include "Utilities/Thread.h"
|
|
#include "Utilities/File.h"
|
|
#include "Input/pad_thread.h"
|
|
#include "Emu/System.h"
|
|
#include "Emu/system_config.h"
|
|
#include "Emu/system_utils.hpp"
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/Io/Null/NullKeyboardHandler.h"
|
|
#include "Emu/Io/Null/NullMouseHandler.h"
|
|
#include "Emu/Io/KeyboardHandler.h"
|
|
#include "Emu/Io/MouseHandler.h"
|
|
#include "Input/basic_keyboard_handler.h"
|
|
#include "Input/basic_mouse_handler.h"
|
|
#include "Input/raw_mouse_handler.h"
|
|
|
|
#include "Emu/Audio/AudioBackend.h"
|
|
#include "Emu/Audio/Null/NullAudioBackend.h"
|
|
#include "Emu/Audio/Null/null_enumerator.h"
|
|
#include "Emu/Audio/Cubeb/CubebBackend.h"
|
|
#include "Emu/Audio/Cubeb/cubeb_enumerator.h"
|
|
#ifdef _WIN32
|
|
#include "Emu/Audio/XAudio2/XAudio2Backend.h"
|
|
#include "Emu/Audio/XAudio2/xaudio2_enumerator.h"
|
|
#endif
|
|
#ifdef HAVE_FAUDIO
|
|
#include "Emu/Audio/FAudio/FAudioBackend.h"
|
|
#include "Emu/Audio/FAudio/faudio_enumerator.h"
|
|
#endif
|
|
|
|
#include <QFileInfo> // This shouldn't be outside rpcs3qt...
|
|
#include <QImageReader> // This shouldn't be outside rpcs3qt...
|
|
#include <QStandardPaths> // This shouldn't be outside rpcs3qt...
|
|
#include <thread>
|
|
|
|
LOG_CHANNEL(sys_log, "SYS");
|
|
|
|
namespace audio
|
|
{
|
|
extern void configure_audio(bool force_reset = false);
|
|
extern void configure_rsxaudio();
|
|
}
|
|
|
|
namespace rsx::overlays
|
|
{
|
|
extern void reset_performance_overlay();
|
|
extern void reset_debug_overlay();
|
|
}
|
|
|
|
/** Emu.Init() wrapper for user management */
|
|
void main_application::InitializeEmulator(const std::string& user, bool show_gui)
|
|
{
|
|
Emu.SetHasGui(show_gui);
|
|
Emu.SetUsr(user);
|
|
Emu.Init();
|
|
|
|
// Log Firmware Version after Emu was initialized
|
|
const std::string firmware_version = utils::get_firmware_version();
|
|
const std::string firmware_string = firmware_version.empty() ? "Missing Firmware" : ("Firmware version: " + firmware_version);
|
|
sys_log.always()("%s", firmware_string);
|
|
}
|
|
|
|
void main_application::OnEmuSettingsChange()
|
|
{
|
|
if (Emu.IsRunning())
|
|
{
|
|
if (g_cfg.misc.prevent_display_sleep)
|
|
{
|
|
disable_display_sleep();
|
|
}
|
|
else
|
|
{
|
|
enable_display_sleep();
|
|
}
|
|
}
|
|
|
|
if (!Emu.IsStopped())
|
|
{
|
|
// Change logging (only allowed during gameplay)
|
|
rpcs3::utils::configure_logs();
|
|
|
|
// Force audio provider
|
|
g_cfg.audio.provider.set(Emu.IsVsh() ? audio_provider::rsxaudio : audio_provider::cell_audio);
|
|
}
|
|
|
|
audio::configure_audio();
|
|
audio::configure_rsxaudio();
|
|
rsx::overlays::reset_performance_overlay();
|
|
rsx::overlays::reset_debug_overlay();
|
|
}
|
|
|
|
/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */
|
|
EmuCallbacks main_application::CreateCallbacks()
|
|
{
|
|
EmuCallbacks callbacks{};
|
|
|
|
callbacks.update_emu_settings = [this]()
|
|
{
|
|
Emu.CallFromMainThread([&]()
|
|
{
|
|
OnEmuSettingsChange();
|
|
});
|
|
};
|
|
|
|
callbacks.save_emu_settings = [this]()
|
|
{
|
|
Emu.BlockingCallFromMainThread([&]()
|
|
{
|
|
Emulator::SaveSettings(g_cfg.to_string(), Emu.GetTitleID());
|
|
});
|
|
};
|
|
|
|
callbacks.init_kb_handler = [this]()
|
|
{
|
|
switch (g_cfg.io.keyboard.get())
|
|
{
|
|
case keyboard_handler::null:
|
|
{
|
|
g_fxo->init<KeyboardHandlerBase, NullKeyboardHandler>(Emu.DeserialManager());
|
|
break;
|
|
}
|
|
case keyboard_handler::basic:
|
|
{
|
|
basic_keyboard_handler* ret = g_fxo->init<KeyboardHandlerBase, basic_keyboard_handler>(Emu.DeserialManager());
|
|
ensure(ret);
|
|
ret->moveToThread(get_thread());
|
|
ret->SetTargetWindow(m_game_window);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
callbacks.init_mouse_handler = [this]()
|
|
{
|
|
mouse_handler handler = g_cfg.io.mouse;
|
|
|
|
if (handler == mouse_handler::null)
|
|
{
|
|
switch (g_cfg.io.move)
|
|
{
|
|
case move_handler::mouse:
|
|
handler = mouse_handler::basic;
|
|
break;
|
|
case move_handler::raw_mouse:
|
|
handler = mouse_handler::raw;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (handler)
|
|
{
|
|
case mouse_handler::null:
|
|
{
|
|
g_fxo->init<MouseHandlerBase, NullMouseHandler>(Emu.DeserialManager());
|
|
break;
|
|
}
|
|
case mouse_handler::basic:
|
|
{
|
|
basic_mouse_handler* ret = g_fxo->init<MouseHandlerBase, basic_mouse_handler>(Emu.DeserialManager());
|
|
ensure(ret);
|
|
ret->moveToThread(get_thread());
|
|
ret->SetTargetWindow(m_game_window);
|
|
break;
|
|
}
|
|
case mouse_handler::raw:
|
|
{
|
|
g_fxo->init<MouseHandlerBase, raw_mouse_handler>(Emu.DeserialManager());
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
callbacks.init_pad_handler = [this](std::string_view title_id)
|
|
{
|
|
ensure(g_fxo->init<named_thread<pad_thread>>(get_thread(), m_game_window, title_id));
|
|
extern void process_qt_events();
|
|
while (!pad::g_started) process_qt_events();
|
|
};
|
|
|
|
callbacks.get_audio = []() -> std::shared_ptr<AudioBackend>
|
|
{
|
|
std::shared_ptr<AudioBackend> result;
|
|
switch (g_cfg.audio.renderer.get())
|
|
{
|
|
case audio_renderer::null: result = std::make_shared<NullAudioBackend>(); break;
|
|
#ifdef _WIN32
|
|
case audio_renderer::xaudio: result = std::make_shared<XAudio2Backend>(); break;
|
|
#endif
|
|
case audio_renderer::cubeb: result = std::make_shared<CubebBackend>(); break;
|
|
#ifdef HAVE_FAUDIO
|
|
case audio_renderer::faudio: result = std::make_shared<FAudioBackend>(); break;
|
|
#endif
|
|
}
|
|
|
|
if (!result->Initialized())
|
|
{
|
|
// Fall back to a null backend if something went wrong
|
|
sys_log.error("Audio renderer %s could not be initialized, using a Null renderer instead. Make sure that no other application is running that might block audio access (e.g. Netflix).", result->GetName());
|
|
result = std::make_shared<NullAudioBackend>();
|
|
}
|
|
return result;
|
|
};
|
|
|
|
callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr<audio_device_enumerator>
|
|
{
|
|
switch (static_cast<audio_renderer>(renderer))
|
|
{
|
|
case audio_renderer::null: return std::make_shared<null_enumerator>();
|
|
#ifdef _WIN32
|
|
case audio_renderer::xaudio: return std::make_shared<xaudio2_enumerator>();
|
|
#endif
|
|
case audio_renderer::cubeb: return std::make_shared<cubeb_enumerator>();
|
|
#ifdef HAVE_FAUDIO
|
|
case audio_renderer::faudio: return std::make_shared<faudio_enumerator>();
|
|
#endif
|
|
default: fmt::throw_exception("Invalid renderer index %u", renderer);
|
|
}
|
|
};
|
|
|
|
callbacks.get_image_info = [](const std::string& filename, std::string& sub_type, s32& width, s32& height, s32& orientation) -> bool
|
|
{
|
|
sub_type.clear();
|
|
width = 0;
|
|
height = 0;
|
|
orientation = 0; // CELL_SEARCH_ORIENTATION_UNKNOWN
|
|
|
|
bool success = false;
|
|
Emu.BlockingCallFromMainThread([&]()
|
|
{
|
|
const QImageReader reader(QString::fromStdString(filename));
|
|
if (reader.canRead())
|
|
{
|
|
const QSize size = reader.size();
|
|
width = size.width();
|
|
height = size.height();
|
|
sub_type = reader.subType().toStdString();
|
|
|
|
switch (reader.transformation())
|
|
{
|
|
case QImageIOHandler::Transformation::TransformationNone:
|
|
orientation = 1; // CELL_SEARCH_ORIENTATION_TOP_LEFT = 0°
|
|
break;
|
|
case QImageIOHandler::Transformation::TransformationRotate90:
|
|
orientation = 2; // CELL_SEARCH_ORIENTATION_TOP_RIGHT = 90°
|
|
break;
|
|
case QImageIOHandler::Transformation::TransformationRotate180:
|
|
orientation = 3; // CELL_SEARCH_ORIENTATION_BOTTOM_RIGHT = 180°
|
|
break;
|
|
case QImageIOHandler::Transformation::TransformationRotate270:
|
|
orientation = 4; // CELL_SEARCH_ORIENTATION_BOTTOM_LEFT = 270°
|
|
break;
|
|
default:
|
|
// Ignore other transformations for now
|
|
break;
|
|
}
|
|
|
|
success = true;
|
|
sys_log.notice("get_image_info found image: filename='%s', sub_type='%s', width=%d, height=%d, orientation=%d", filename, sub_type, width, height, orientation);
|
|
}
|
|
else
|
|
{
|
|
sys_log.error("get_image_info failed to read '%s'. Error='%s'", filename, reader.errorString());
|
|
}
|
|
});
|
|
return success;
|
|
};
|
|
|
|
callbacks.get_scaled_image = [](const std::string& path, s32 target_width, s32 target_height, s32& width, s32& height, u8* dst, bool force_fit) -> bool
|
|
{
|
|
width = 0;
|
|
height = 0;
|
|
|
|
if (target_width <= 0 || target_height <= 0 || !dst || !fs::is_file(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool success = false;
|
|
Emu.BlockingCallFromMainThread([&]()
|
|
{
|
|
// We use QImageReader instead of QImage. This way we can load and scale image in one step.
|
|
QImageReader reader(QString::fromStdString(path));
|
|
|
|
if (reader.canRead())
|
|
{
|
|
QSize size = reader.size();
|
|
width = size.width();
|
|
height = size.height();
|
|
|
|
if (width <= 0 || height <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (force_fit || width > target_width || height > target_height)
|
|
{
|
|
const f32 target_ratio = target_width / static_cast<f32>(target_height);
|
|
const f32 image_ratio = width / static_cast<f32>(height);
|
|
const f32 convert_ratio = image_ratio / target_ratio;
|
|
|
|
if (convert_ratio > 1.0f)
|
|
{
|
|
size = QSize(target_width, target_height / convert_ratio);
|
|
}
|
|
else if (convert_ratio < 1.0f)
|
|
{
|
|
size = QSize(target_width * convert_ratio, target_height);
|
|
}
|
|
else
|
|
{
|
|
size = QSize(target_width, target_height);
|
|
}
|
|
|
|
reader.setScaledSize(size);
|
|
width = size.width();
|
|
height = size.height();
|
|
}
|
|
|
|
QImage image = reader.read();
|
|
|
|
if (image.format() != QImage::Format::Format_RGBA8888)
|
|
{
|
|
image = image.convertToFormat(QImage::Format::Format_RGBA8888);
|
|
}
|
|
|
|
std::memcpy(dst, image.constBits(), std::min(target_width * target_height * 4LL, image.height() * image.bytesPerLine()));
|
|
success = true;
|
|
sys_log.notice("get_scaled_image scaled image: path='%s', width=%d, height=%d", path, width, height);
|
|
}
|
|
else
|
|
{
|
|
sys_log.error("get_scaled_image failed to read '%s'. Error='%s'", path, reader.errorString());
|
|
}
|
|
});
|
|
return success;
|
|
};
|
|
|
|
callbacks.resolve_path = [](std::string_view sv)
|
|
{
|
|
// May result in an empty string if path does not exist
|
|
return QFileInfo(QString::fromUtf8(sv.data(), static_cast<int>(sv.size()))).canonicalFilePath().toStdString();
|
|
};
|
|
|
|
callbacks.get_font_dirs = []()
|
|
{
|
|
const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation);
|
|
std::vector<std::string> font_dirs;
|
|
for (const QString& location : locations)
|
|
{
|
|
std::string font_dir = location.toStdString();
|
|
if (!font_dir.ends_with('/'))
|
|
{
|
|
font_dir += '/';
|
|
}
|
|
font_dirs.push_back(font_dir);
|
|
}
|
|
return font_dirs;
|
|
};
|
|
|
|
callbacks.on_install_pkgs = [](const std::vector<std::string>& pkgs)
|
|
{
|
|
for (const std::string& pkg : pkgs)
|
|
{
|
|
if (!rpcs3::utils::install_pkg(pkg))
|
|
{
|
|
sys_log.error("Failed to install %s", pkg);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
return callbacks;
|
|
}
|