mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-22 05:07:01 +02:00
[desktop] More qt_common reorganization (#3916)
Ported from QML branch. Main "big" change is that EmuThread is now a shared state in QtCommon, not individually managed/passed around by GRenderWindow and MainWindow. Signed-off-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3916 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
parent
300a646a34
commit
feb8c5f88e
44 changed files with 963 additions and 932 deletions
|
|
@ -26,6 +26,7 @@ add_library(qt_common STATIC
|
|||
util/compress.h util/compress.cpp
|
||||
util/fs.h util/fs.cpp
|
||||
util/mod.h util/mod.cpp
|
||||
util/vk.h util/vk.cpp
|
||||
|
||||
abstract/frontend.h abstract/frontend.cpp
|
||||
abstract/progress.h abstract/progress.cpp
|
||||
|
|
@ -33,7 +34,9 @@ add_library(qt_common STATIC
|
|||
qt_string_lookup.h
|
||||
qt_compat.h
|
||||
|
||||
discord/discord.h)
|
||||
discord/discord.h
|
||||
render/context.h
|
||||
render/emu_thread.h render/emu_thread.cpp)
|
||||
|
||||
if (UNIX)
|
||||
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
|
||||
|
|
@ -56,9 +59,7 @@ if (USE_DISCORD_PRESENCE)
|
|||
endif()
|
||||
|
||||
# TODO(crueter)
|
||||
if (ENABLE_QT)
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
endif()
|
||||
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
|
||||
|
||||
target_compile_definitions(qt_common PUBLIC
|
||||
# Use QStringBuilder for string concatenation to reduce
|
||||
|
|
@ -80,7 +81,7 @@ target_compile_definitions(qt_common PUBLIC
|
|||
)
|
||||
|
||||
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen-headers)
|
||||
target_link_libraries(qt_common PUBLIC frozen::frozen-headers Vulkan::Headers)
|
||||
target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common)
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
|
|
|
|||
|
|
@ -165,8 +165,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||
INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"),
|
||||
tr("Allows saving shaders to storage for faster loading on following game "
|
||||
"boots.\nDisabling it is only intended for debugging."));
|
||||
INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
|
||||
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
|
||||
INSERT(
|
||||
Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
|
||||
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
|
||||
INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
|
||||
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
|
||||
"decoding, or perform no decoding at all (black screen on videos).\n"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,52 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "network/network.h"
|
||||
#include "qt_common.h"
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStringLiteral>
|
||||
#include "common/logging.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "qt_common/util/meta.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <thread>
|
||||
#include <JlCompress.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__APPLE__)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <objc/message.h>
|
||||
#include <objc/runtime.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <QApplication>
|
||||
#include <QSettings>
|
||||
#include <windows.h>
|
||||
#include "common/windows/timer_resolution.h"
|
||||
#include "core/core_timing.h"
|
||||
#endif
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
QWidget* rootObject = nullptr;
|
||||
|
|
@ -27,6 +54,11 @@ QWidget* rootObject = nullptr;
|
|||
std::unique_ptr<Core::System> system = nullptr;
|
||||
std::shared_ptr<FileSys::RealVfsFilesystem> vfs = nullptr;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> provider = nullptr;
|
||||
std::unique_ptr<EmuThread> emu_thread = nullptr;
|
||||
|
||||
const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"),
|
||||
QStringLiteral("nca"), QStringLiteral("xci"),
|
||||
QStringLiteral("nsp"), QStringLiteral("kip")};
|
||||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType() {
|
||||
// Determine WSI type based on Qt platform.
|
||||
|
|
@ -58,37 +90,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
// Our Win32 Qt external doesn't have the private API.
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
id layer = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
|
||||
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
|
||||
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
|
||||
// In Qt 6, the layer of the NSView might be a QContainerLayer.
|
||||
// MoltenVK needs a CAMetalLayer. We search for it in sublayers.
|
||||
Class metal_layer_class = objc_getClass("CAMetalLayer");
|
||||
id metal_layer = nullptr;
|
||||
|
||||
if (layer) {
|
||||
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
|
||||
layer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
|
||||
metal_layer = layer;
|
||||
} else {
|
||||
id sublayers = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
|
||||
layer, sel_registerName("sublayers"));
|
||||
if (sublayers) {
|
||||
unsigned long count = reinterpret_cast<unsigned long (*)(id, SEL)>(objc_msgSend)(
|
||||
sublayers, sel_registerName("count"));
|
||||
for (unsigned long i = 0; i < count; ++i) {
|
||||
id sublayer = reinterpret_cast<id (*)(id, SEL, unsigned long)>(objc_msgSend)(
|
||||
sublayers, sel_registerName("objectAtIndex:"), i);
|
||||
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
|
||||
sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
|
||||
metal_layer = sublayer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wsi.render_surface = reinterpret_cast<void*>(metal_layer ? metal_layer : layer);
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
|
|
@ -103,18 +106,148 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
}
|
||||
|
||||
const QString tr(const char* str) {
|
||||
return QGuiApplication::tr(str);
|
||||
return rootObject->tr(str);
|
||||
}
|
||||
|
||||
const QString tr(const std::string& str) {
|
||||
return QGuiApplication::tr(str.c_str());
|
||||
return rootObject->tr(str.c_str());
|
||||
}
|
||||
|
||||
static void LogRuntimes() {
|
||||
#ifdef _MSC_VER
|
||||
// It is possible that the name of the dll will change.
|
||||
// vcruntime140.dll is for 2015 and onwards
|
||||
static constexpr char runtime_dll_name[] = "vcruntime140.dll";
|
||||
UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
|
||||
bool runtime_version_inspection_worked = false;
|
||||
if (sz > 0) {
|
||||
std::vector<u8> buf(sz);
|
||||
if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
|
||||
VS_FIXEDFILEINFO* pvi;
|
||||
sz = sizeof(VS_FIXEDFILEINFO);
|
||||
if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
|
||||
if (pvi->dwSignature == VS_FFI_SIGNATURE) {
|
||||
runtime_version_inspection_worked = true;
|
||||
LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
|
||||
pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
|
||||
pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!runtime_version_inspection_worked) {
|
||||
LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion());
|
||||
}
|
||||
|
||||
static QString PrettyProductName() {
|
||||
#ifdef _WIN32
|
||||
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
|
||||
// With that notation change they changed the registry key used to denote the current version
|
||||
QSettings windows_registry(
|
||||
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
|
||||
QSettings::NativeFormat);
|
||||
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
|
||||
if (release_id == QStringLiteral("2009")) {
|
||||
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
|
||||
const QString display_version =
|
||||
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
|
||||
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
|
||||
u32 version = 10;
|
||||
if (current_build >= 22000) {
|
||||
version = 11;
|
||||
}
|
||||
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
|
||||
.arg(QString::number(version), display_version, QString::number(current_build),
|
||||
QString::number(ubr));
|
||||
}
|
||||
#endif
|
||||
return QSysInfo::prettyProductName();
|
||||
}
|
||||
|
||||
static void RemoveCachedContents() {
|
||||
const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
|
||||
const auto offline_fonts = cache_dir / "fonts";
|
||||
const auto offline_manual = cache_dir / "offline_web_applet_manual";
|
||||
const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
|
||||
const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
|
||||
|
||||
Common::FS::RemoveDirRecursively(offline_fonts);
|
||||
Common::FS::RemoveDirRecursively(offline_manual);
|
||||
Common::FS::RemoveDirRecursively(offline_legal_information);
|
||||
Common::FS::RemoveDirRecursively(offline_system_data);
|
||||
}
|
||||
|
||||
void Init(QWidget* root) {
|
||||
system = std::make_unique<Core::System>();
|
||||
rootObject = root;
|
||||
|
||||
system = std::make_unique<Core::System>();
|
||||
vfs = std::make_unique<FileSys::RealVfsFilesystem>();
|
||||
provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
|
||||
// initialization stuff
|
||||
Common::FS::CreateEdenPaths();
|
||||
|
||||
system->Initialize();
|
||||
|
||||
Common::Log::Initialize();
|
||||
Common::Log::Start();
|
||||
|
||||
Network::Init();
|
||||
|
||||
QtCommon::Meta::RegisterMetaTypes();
|
||||
|
||||
// build version
|
||||
const auto branch_name = std::string(Common::g_scm_branch);
|
||||
const auto description = std::string(Common::g_scm_desc);
|
||||
const auto build_id = std::string(Common::g_build_id);
|
||||
|
||||
const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description);
|
||||
const auto override_build =
|
||||
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
|
||||
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
|
||||
const auto processor_count = std::thread::hardware_concurrency();
|
||||
|
||||
// info logging
|
||||
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
|
||||
LogRuntimes();
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
const auto& caps = Common::GetCPUCaps();
|
||||
std::string cpu_string = caps.cpu_string;
|
||||
if (caps.avx || caps.avx2 || caps.avx512f) {
|
||||
cpu_string += " | AVX";
|
||||
if (caps.avx512f) {
|
||||
cpu_string += "512";
|
||||
} else if (caps.avx2) {
|
||||
cpu_string += '2';
|
||||
}
|
||||
if (caps.fma || caps.fma4) {
|
||||
cpu_string += " | FMA";
|
||||
}
|
||||
}
|
||||
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
|
||||
if (std::optional<int> processor_core = Common::GetProcessorCount()) {
|
||||
LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
|
||||
}
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
|
||||
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
|
||||
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
|
||||
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
|
||||
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
|
||||
#ifdef _WIN32
|
||||
LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms",
|
||||
std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>(
|
||||
Common::Windows::SetCurrentTimerResolutionToMaximum())
|
||||
.count());
|
||||
QtCommon::system->CoreTiming().SetTimerResolutionNs(
|
||||
Common::Windows::GetCurrentTimerResolution());
|
||||
#endif
|
||||
|
||||
// Remove cached contents generated during the previous session
|
||||
RemoveCachedContents();
|
||||
}
|
||||
|
||||
std::filesystem::path GetEdenCommand() {
|
||||
|
|
@ -137,4 +270,15 @@ std::filesystem::path GetEdenCommand() {
|
|||
return command;
|
||||
}
|
||||
|
||||
void SetupContentProviders() {
|
||||
system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
provider.get());
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
}
|
||||
|
||||
void SetupHID() {
|
||||
system->HIDCore().ReloadInputDevices();
|
||||
}
|
||||
|
||||
} // namespace QtCommon
|
||||
|
|
|
|||
|
|
@ -6,19 +6,31 @@
|
|||
|
||||
#include <memory>
|
||||
#include <QWindow>
|
||||
#include <core/frontend/emu_window.h>
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
|
||||
#include <core/file_sys/vfs/vfs_real.h>
|
||||
#include "qt_common/render/emu_thread.h"
|
||||
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
|
||||
enum class StartGameType {
|
||||
Normal, // Can use custom configuration
|
||||
Global, // Only uses global configuration
|
||||
};
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
// TODO: Remove QWidget dependency
|
||||
extern QWidget* rootObject;
|
||||
|
||||
extern std::unique_ptr<Core::System> system;
|
||||
extern std::shared_ptr<FileSys::RealVfsFilesystem> vfs;
|
||||
extern std::unique_ptr<FileSys::ManualContentProvider> provider;
|
||||
extern std::unique_ptr<EmuThread> emu_thread;
|
||||
|
||||
extern const QStringList supported_file_extensions;
|
||||
|
||||
typedef std::function<bool(std::size_t, std::size_t)> QtProgressCallback;
|
||||
|
||||
|
|
@ -28,6 +40,9 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
|
||||
void Init(QWidget* root);
|
||||
|
||||
void SetupContentProviders();
|
||||
void SetupHID();
|
||||
|
||||
const QString tr(const char* str);
|
||||
const QString tr(const std::string& str);
|
||||
|
||||
|
|
|
|||
107
src/qt_common/render/context.h
Normal file
107
src/qt_common/render/context.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSurface>
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#endif
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
|
||||
public:
|
||||
/// Create the original context that should be shared from
|
||||
explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} {
|
||||
QSurfaceFormat format;
|
||||
format.setVersion(4, 6);
|
||||
format.setProfile(QSurfaceFormat::CompatibilityProfile);
|
||||
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
|
||||
if (Settings::values.renderer_debug) {
|
||||
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||
}
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
format.setSwapInterval(0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create main openGL context");
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the shared contexts for rendering and presentation
|
||||
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = share_context->format();
|
||||
const int swap_interval =
|
||||
Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1;
|
||||
|
||||
format.setSwapInterval(main_surface ? swap_interval : 0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setShareContext(share_context);
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create shared openGL context");
|
||||
}
|
||||
|
||||
if (!main_surface) {
|
||||
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
|
||||
offscreen_surface->setFormat(format);
|
||||
offscreen_surface->create();
|
||||
surface = offscreen_surface.get();
|
||||
} else {
|
||||
surface = main_surface;
|
||||
}
|
||||
}
|
||||
|
||||
~OpenGLSharedContext() {
|
||||
DoneCurrent();
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
context->swapBuffers(surface);
|
||||
}
|
||||
|
||||
void MakeCurrent() override {
|
||||
// We can't track the current state of the underlying context in this wrapper class because
|
||||
// Qt may make the underlying context not current for one reason or another. In particular,
|
||||
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
|
||||
// Instead of always just making the context current (which does not have any caching to
|
||||
// check if the underlying context is already current) we can check for the current context
|
||||
// in the thread local data by calling `currentContext()` and checking if its ours.
|
||||
if (QOpenGLContext::currentContext() != context.get()) {
|
||||
context->makeCurrent(surface);
|
||||
}
|
||||
}
|
||||
|
||||
void DoneCurrent() override {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
QOpenGLContext* GetShareContext() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
const QOpenGLContext* GetShareContext() const {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Avoid using Qt parent system here since we might move the QObjects to new threads
|
||||
// As a note, this means we should avoid using slots/signals with the objects too
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||
QSurface* surface;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DummyContext : public Core::Frontend::GraphicsContext {};
|
||||
83
src/qt_common/render/emu_thread.cpp
Normal file
83
src/qt_common/render/emu_thread.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <qdebug.h>
|
||||
#include "core/core.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "emu_thread.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
EmuThread::EmuThread() {}
|
||||
|
||||
EmuThread::~EmuThread() = default;
|
||||
|
||||
void EmuThread::run() {
|
||||
Common::SetCurrentThreadName("EmuControlThread");
|
||||
|
||||
auto& gpu = QtCommon::system->GPU();
|
||||
auto stop_token = m_stop_source.get_token();
|
||||
|
||||
QtCommon::system->RegisterHostThread();
|
||||
|
||||
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
|
||||
// execution.
|
||||
gpu.ObtainContext();
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
QtCommon::system->Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
QtCommon::system->GetApplicationProcessProgramID(), stop_token,
|
||||
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||
emit LoadProgress(stage, value, total);
|
||||
});
|
||||
}
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
|
||||
gpu.ReleaseContext();
|
||||
gpu.Start();
|
||||
|
||||
QtCommon::system->GetCpuManager().OnGpuReady();
|
||||
|
||||
if (QtCommon::system->DebuggerEnabled()) {
|
||||
QtCommon::system->InitializeDebugger();
|
||||
}
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
std::unique_lock lk{m_should_run_mutex};
|
||||
if (m_should_run) {
|
||||
QtCommon::system->Run();
|
||||
m_stopped.Reset();
|
||||
|
||||
m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; });
|
||||
} else {
|
||||
QtCommon::system->Pause();
|
||||
m_stopped.Set();
|
||||
|
||||
EmulationPaused(lk);
|
||||
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
|
||||
EmulationResumed(lk);
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown the main emulated process
|
||||
QtCommon::system->DetachDebugger();
|
||||
QtCommon::system->ShutdownMainProcess();
|
||||
}
|
||||
|
||||
// Unlock while emitting signals so that the main thread can
|
||||
// continue pumping events.
|
||||
|
||||
void EmuThread::EmulationPaused(std::unique_lock<std::mutex>& lk) {
|
||||
lk.unlock();
|
||||
emit DebugModeEntered();
|
||||
lk.lock();
|
||||
}
|
||||
|
||||
void EmuThread::EmulationResumed(std::unique_lock<std::mutex>& lk) {
|
||||
lk.unlock();
|
||||
emit DebugModeLeft();
|
||||
lk.lock();
|
||||
}
|
||||
97
src/qt_common/render/emu_thread.h
Normal file
97
src/qt_common/render/emu_thread.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
#include "common/logging.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
} // namespace VideoCore
|
||||
|
||||
class EmuThread final : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmuThread();
|
||||
~EmuThread() override;
|
||||
|
||||
/**
|
||||
* Start emulation (on new thread)
|
||||
* @warning Only call when not running!
|
||||
*/
|
||||
void run() override;
|
||||
|
||||
/**
|
||||
* Sets whether the emulation thread should run or not
|
||||
* @param should_run Boolean value, set the emulation thread to running if true
|
||||
*/
|
||||
void SetRunning(bool should_run) {
|
||||
// TODO: Prevent other threads from modifying the state until we finish.
|
||||
{
|
||||
// Notify the running thread to change state.
|
||||
std::unique_lock run_lk{m_should_run_mutex};
|
||||
m_should_run = should_run;
|
||||
m_should_run_cv.notify_one();
|
||||
}
|
||||
|
||||
// Wait until paused, if pausing.
|
||||
if (!should_run) {
|
||||
m_stopped.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the emulation thread is running or not
|
||||
* @return True if the emulation thread is running, otherwise false
|
||||
*/
|
||||
bool IsRunning() const {
|
||||
return m_should_run;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests for the emulation thread to immediately stop running
|
||||
*/
|
||||
void ForceStop() {
|
||||
LOG_WARNING(Frontend, "Force stopping EmuThread");
|
||||
m_stop_source.request_stop();
|
||||
}
|
||||
|
||||
private:
|
||||
void EmulationPaused(std::unique_lock<std::mutex>& lk);
|
||||
void EmulationResumed(std::unique_lock<std::mutex>& lk);
|
||||
|
||||
private:
|
||||
std::stop_source m_stop_source;
|
||||
std::mutex m_should_run_mutex;
|
||||
std::condition_variable_any m_should_run_cv;
|
||||
Common::Event m_stopped;
|
||||
bool m_should_run{true};
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Emitted when the CPU has halted execution
|
||||
*
|
||||
* @warning When connecting to this signal from other threads, make sure to specify either
|
||||
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
|
||||
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
||||
*/
|
||||
void DebugModeEntered();
|
||||
|
||||
/**
|
||||
* Emitted right before the CPU continues execution
|
||||
*
|
||||
* @warning When connecting to this signal from other threads, make sure to specify either
|
||||
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
|
||||
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
|
||||
*/
|
||||
void DebugModeLeft();
|
||||
|
||||
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "qt_common/util/content.h"
|
||||
#include "qt_common/util/game.h"
|
||||
|
||||
|
|
@ -494,5 +495,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str
|
|||
});
|
||||
}
|
||||
|
||||
// TODO(crueter): Port InstallFirmware et al. from QML Branch
|
||||
bool CheckKeys() {
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QtCommon::Frontend::Information(
|
||||
tr("Keys not installed"),
|
||||
tr("Install decryption keys and restart Eden before attempting to install firmware."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallFirmware() {
|
||||
if (!CheckKeys())
|
||||
return;
|
||||
|
||||
const QString firmware_source_location =
|
||||
QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {});
|
||||
|
||||
if (!firmware_source_location.isEmpty())
|
||||
QtCommon::Content::InstallFirmware(firmware_source_location, false);
|
||||
}
|
||||
|
||||
void InstallFirmwareZip() {
|
||||
if (!CheckKeys())
|
||||
return;
|
||||
|
||||
const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName(
|
||||
tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (firmware_zip_location.isEmpty())
|
||||
return;
|
||||
|
||||
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
|
||||
|
||||
// In this case, it has to be done recursively, since sometimes people
|
||||
// will pack it into a subdirectory after dumping
|
||||
if (!qCacheDir.isEmpty()) {
|
||||
QtCommon::Content::InstallFirmware(qCacheDir, true);
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware",
|
||||
ec);
|
||||
|
||||
if (ec) {
|
||||
QtCommon::Frontend::Warning(
|
||||
tr("Firmware cleanup failed"),
|
||||
tr("Failed to clean up extracted firmware cache.\n"
|
||||
"Check write permissions in the system temp directory and try "
|
||||
"again.\nOS reported error: %1")
|
||||
.arg(QString::fromStdString(ec.message())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void configureFilesystemProvider(const std::string& filepath) {
|
||||
// Ensure all NCAs are registered before launching the game
|
||||
const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(*QtCommon::system, file);
|
||||
if (!loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
QtCommon::provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Content
|
||||
|
|
|
|||
|
|
@ -37,9 +37,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult
|
|||
}
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive);
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location);
|
||||
|
||||
bool CheckKeys();
|
||||
void InstallFirmware();
|
||||
void InstallFirmwareZip();
|
||||
|
||||
// Keys //
|
||||
void InstallKeys();
|
||||
|
||||
|
|
@ -54,6 +57,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string&
|
|||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
|
||||
std::function<void()> callback = {});
|
||||
|
||||
// Loader //
|
||||
void configureFilesystemProvider(const std::string& filepath);
|
||||
|
||||
// Profiles //
|
||||
void FixProfiles();
|
||||
} // namespace QtCommon::Content
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include "qt_common/abstract/frontend.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QStandardPaths>
|
||||
|
|
@ -22,6 +21,7 @@
|
|||
#include <windows.h>
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/fs/file.h"
|
||||
#else
|
||||
#include <fstream>
|
||||
#include "fmt/ostream.h"
|
||||
|
|
@ -367,6 +367,7 @@ void ResetMetadata(bool show_message) {
|
|||
|
||||
// Uhhh //
|
||||
|
||||
// TODO(crueter): Make QtCommon::Shortcut
|
||||
// Messages in pre-defined message boxes for less code spaghetti
|
||||
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) {
|
||||
int result = 0;
|
||||
|
|
@ -533,4 +534,110 @@ void CreateHomeMenuShortcut(ShortcutTarget target) {
|
|||
CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
|
||||
}
|
||||
|
||||
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
|
||||
#if defined(WIN32)
|
||||
#pragma pack(push, 2)
|
||||
struct IconDir {
|
||||
WORD id_reserved;
|
||||
WORD id_type;
|
||||
WORD id_count;
|
||||
};
|
||||
|
||||
struct IconDirEntry {
|
||||
BYTE width;
|
||||
BYTE height;
|
||||
BYTE color_count;
|
||||
BYTE reserved;
|
||||
WORD planes;
|
||||
WORD bit_count;
|
||||
DWORD bytes_in_res;
|
||||
DWORD image_offset;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
|
||||
constexpr int bytes_per_pixel = 4;
|
||||
|
||||
const IconDir icon_dir{
|
||||
.id_reserved = 0,
|
||||
.id_type = 1,
|
||||
.id_count = static_cast<WORD>(scale_sizes.size()),
|
||||
};
|
||||
|
||||
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile);
|
||||
if (!icon_file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon_file.Write(icon_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
|
||||
const IconDirEntry icon_entry{
|
||||
.width = static_cast<BYTE>(scale_sizes[i]),
|
||||
.height = static_cast<BYTE>(scale_sizes[i]),
|
||||
.color_count = 0,
|
||||
.reserved = 0,
|
||||
.planes = 1,
|
||||
.bit_count = bytes_per_pixel * 8,
|
||||
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
||||
.image_offset = static_cast<DWORD>(image_offset),
|
||||
};
|
||||
image_offset += icon_entry.bytes_in_res;
|
||||
if (!icon_file.Write(icon_entry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||
const QImage scaled_image = source_image.scaled(
|
||||
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
const BITMAPINFOHEADER info_header{
|
||||
.biSize = sizeof(BITMAPINFOHEADER),
|
||||
.biWidth = scaled_image.width(),
|
||||
.biHeight = scaled_image.height() * 2,
|
||||
.biPlanes = 1,
|
||||
.biBitCount = bytes_per_pixel * 8,
|
||||
.biCompression = BI_RGB,
|
||||
.biSizeImage{},
|
||||
.biXPelsPerMeter{},
|
||||
.biYPelsPerMeter{},
|
||||
.biClrUsed{},
|
||||
.biClrImportant{},
|
||||
};
|
||||
|
||||
if (!icon_file.Write(info_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < scaled_image.height(); y++) {
|
||||
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
|
||||
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
|
||||
std::memcpy(line_data.data(), line, line_data.size());
|
||||
if (!icon_file.Write(line_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
icon_file.Close();
|
||||
|
||||
return true;
|
||||
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
|
||||
// Convert and write the icon as a PNG
|
||||
if (!image.save(QString::fromStdString(icon_path.string()))) {
|
||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||
} else {
|
||||
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ void CreateShortcut(const std::string& game_path, const u64 program_id,
|
|||
std::string GetShortcutPath(ShortcutTarget target);
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target);
|
||||
|
||||
/**
|
||||
* Saves a windows icon to a file
|
||||
* @param path The icons path
|
||||
* @param image The image to save
|
||||
* @return bool If the operation succeeded
|
||||
*/
|
||||
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
|
||||
#endif // QT_GAME_UTIL_H
|
||||
|
|
|
|||
81
src/qt_common/util/vk.cpp
Normal file
81
src/qt_common/util/vk.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/logging.h"
|
||||
#include "qt_common/util/vk.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_library.h"
|
||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace VkDeviceInfo {
|
||||
Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_,
|
||||
bool has_broken_compute_)
|
||||
: name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {}
|
||||
|
||||
Record::~Record() = default;
|
||||
|
||||
void PopulateRecords(std::vector<Record>& records, QWindow* window) try {
|
||||
using namespace Vulkan;
|
||||
|
||||
// Create a test window with a Vulkan surface type for checking present modes.
|
||||
QWindow test_window(window);
|
||||
test_window.setSurfaceType(QWindow::VulkanSurface);
|
||||
test_window.create();
|
||||
auto wsi = QtCommon::GetWindowSystemInfo(&test_window);
|
||||
|
||||
vk::InstanceDispatch dld;
|
||||
const auto library = OpenLibrary();
|
||||
const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
|
||||
const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
|
||||
vk::SurfaceKHR surface = CreateSurface(instance, wsi);
|
||||
|
||||
records.clear();
|
||||
records.reserve(physical_devices.size());
|
||||
for (const VkPhysicalDevice device : physical_devices) {
|
||||
const auto physical_device = vk::PhysicalDevice(device, dld);
|
||||
std::string name = physical_device.GetProperties().deviceName;
|
||||
|
||||
const std::vector<VkPresentModeKHR> present_modes =
|
||||
physical_device.GetSurfacePresentModesKHR(*surface);
|
||||
|
||||
VkPhysicalDeviceDriverProperties driver_properties{};
|
||||
driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
|
||||
driver_properties.pNext = nullptr;
|
||||
VkPhysicalDeviceProperties2 properties{};
|
||||
properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
||||
properties.pNext = &driver_properties;
|
||||
dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
|
||||
|
||||
const auto driverID = driver_properties.driverID;
|
||||
|
||||
bool has_broken_compute{
|
||||
Vulkan::Device::CheckBrokenCompute(driverID, properties.properties.driverVersion)};
|
||||
|
||||
std::string driver_string = Vulkan::vk::GetDriverName(driver_properties);
|
||||
|
||||
if (driver_string.empty())
|
||||
driver_string = "Unknown";
|
||||
|
||||
name = fmt::format("{} ({})", name, driver_string);
|
||||
|
||||
records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute));
|
||||
}
|
||||
} catch (const Vulkan::vk::Exception& exception) {
|
||||
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
|
||||
}
|
||||
|
||||
} // namespace VkDeviceInfo
|
||||
71
src/qt_common/util/vk.h
Normal file
71
src/qt_common/util/vk.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||
VK_PRESENT_MODE_FIFO_KHR};
|
||||
|
||||
// Converts a setting to a present mode (or vice versa)
|
||||
static inline constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
|
||||
switch (mode) {
|
||||
case Settings::VSyncMode::Immediate:
|
||||
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
case Settings::VSyncMode::Mailbox:
|
||||
return VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
case Settings::VSyncMode::Fifo:
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
case Settings::VSyncMode::FifoRelaxed:
|
||||
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
|
||||
default:
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
}
|
||||
|
||||
static inline constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
|
||||
switch (mode) {
|
||||
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
||||
return Settings::VSyncMode::Immediate;
|
||||
case VK_PRESENT_MODE_MAILBOX_KHR:
|
||||
return Settings::VSyncMode::Mailbox;
|
||||
case VK_PRESENT_MODE_FIFO_KHR:
|
||||
return Settings::VSyncMode::Fifo;
|
||||
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
||||
return Settings::VSyncMode::FifoRelaxed;
|
||||
default:
|
||||
return Settings::VSyncMode::Fifo;
|
||||
}
|
||||
}
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace Settings {
|
||||
enum class VSyncMode : u32;
|
||||
}
|
||||
|
||||
namespace VkDeviceInfo {
|
||||
// Short class to record Vulkan driver information for configuration purposes
|
||||
class Record {
|
||||
public:
|
||||
explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes,
|
||||
bool has_broken_compute);
|
||||
~Record();
|
||||
|
||||
const std::string name;
|
||||
const std::vector<VkPresentModeKHR> vsync_support;
|
||||
const bool has_broken_compute;
|
||||
};
|
||||
|
||||
// TODO(crueter): Port some configure_graphics.cpp stuff
|
||||
void PopulateRecords(std::vector<Record>& records, QWindow* window);
|
||||
} // namespace VkDeviceInfo
|
||||
Loading…
Add table
Add a link
Reference in a new issue