[UI] Image post-processing and full presentation/window rework
[GPU] Add FXAA post-processing [UI] Add FidelityFX FSR and CAS post-processing [UI] Add blue noise dithering from 10bpc to 8bpc [GPU] Apply the DC PWL gamma ramp closer to the spec, supporting fully white color [UI] Allow the GPU CP thread to present on the host directly, bypassing the UI thread OS paint event [UI] Allow variable refresh rate (or tearing) [UI] Present the newest frame (restart) on DXGI [UI] Replace GraphicsContext with a far more advanced Presenter with more coherent surface connection and UI overlay state management [UI] Connect presentation to windows via the Surface class, not native window handles [Vulkan] Switch to simpler Vulkan setup with no instance/device separation due to interdependencies and to pass fewer objects around [Vulkan] Lower the minimum required Vulkan version to 1.0 [UI/GPU] Various cleanup, mainly ComPtr usage [UI] Support per-monitor DPI awareness v2 on Windows [UI] DPI-scale Dear ImGui [UI] Replace the remaining non-detachable window delegates with unified window event and input listeners [UI] Allow listeners to safely destroy or close the window, and to register/unregister listeners without use-after-free and the ABA problem [UI] Explicit Z ordering of input listeners and UI overlays, top-down for input, bottom-up for drawing [UI] Add explicit window lifecycle phases [UI] Replace Window virtual functions with explicit desired state, its application, actual state, its feedback [UI] GTK: Apply the initial size to the drawing area [UI] Limit internal UI frame rate to that of the monitor [UI] Hide the cursor using a timer instead of polling due to no repeated UI thread paints with GPU CP thread presentation, and only within the window
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "xenia/emulator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "config.h"
|
||||
@@ -23,7 +24,6 @@
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/cpu/backend/code_cache.h"
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/vfs/devices/disc_image_device.h"
|
||||
@@ -63,6 +64,15 @@ namespace xe {
|
||||
|
||||
using namespace xe::literals;
|
||||
|
||||
Emulator::GameConfigLoadCallback::GameConfigLoadCallback(Emulator& emulator)
|
||||
: emulator_(emulator) {
|
||||
emulator_.AddGameConfigLoadCallback(this);
|
||||
}
|
||||
|
||||
Emulator::GameConfigLoadCallback::~GameConfigLoadCallback() {
|
||||
emulator_.RemoveGameConfigLoadCallback(this);
|
||||
}
|
||||
|
||||
Emulator::Emulator(const std::filesystem::path& command_line,
|
||||
const std::filesystem::path& storage_root,
|
||||
const std::filesystem::path& content_root,
|
||||
@@ -116,7 +126,7 @@ Emulator::~Emulator() {
|
||||
}
|
||||
|
||||
X_STATUS Emulator::Setup(
|
||||
ui::Window* display_window,
|
||||
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
|
||||
std::function<std::unique_ptr<apu::AudioSystem>(cpu::Processor*)>
|
||||
audio_system_factory,
|
||||
std::function<std::unique_ptr<gpu::GraphicsSystem>()>
|
||||
@@ -126,6 +136,7 @@ X_STATUS Emulator::Setup(
|
||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||
|
||||
display_window_ = display_window;
|
||||
imgui_drawer_ = imgui_drawer;
|
||||
|
||||
// Initialize clock.
|
||||
// 360 uses a 50MHz clock.
|
||||
@@ -212,8 +223,10 @@ X_STATUS Emulator::Setup(
|
||||
kernel_state_ = std::make_unique<xe::kernel::KernelState>(this);
|
||||
|
||||
// Setup the core components.
|
||||
result = graphics_system_->Setup(processor_.get(), kernel_state_.get(),
|
||||
display_window_);
|
||||
result = graphics_system_->Setup(
|
||||
processor_.get(), kernel_state_.get(),
|
||||
display_window_ ? &display_window_->app_context() : nullptr,
|
||||
display_window_ != nullptr);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
@@ -236,14 +249,6 @@ X_STATUS Emulator::Setup(
|
||||
// Initialize emulator fallback exception handling last.
|
||||
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
||||
|
||||
if (display_window_) {
|
||||
// Finish initializing the display.
|
||||
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
||||
Profiler::set_window(display_window_);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -417,7 +422,7 @@ void Emulator::Resume() {
|
||||
bool Emulator::SaveToFile(const std::filesystem::path& path) {
|
||||
Pause();
|
||||
|
||||
filesystem::CreateFile(path);
|
||||
filesystem::CreateEmptyFile(path);
|
||||
auto map = MappedMemory::Open(path, MappedMemory::Mode::kReadWrite, 0, 2_GiB);
|
||||
if (!map) {
|
||||
return false;
|
||||
@@ -587,14 +592,16 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
||||
}
|
||||
|
||||
// Display a dialog telling the user the guest has crashed.
|
||||
display_window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window(), "Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
""
|
||||
"Xenia has now paused itself.\n"
|
||||
"A crash dump has been written into the log.");
|
||||
});
|
||||
if (display_window_ && imgui_drawer_) {
|
||||
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
imgui_drawer_, "Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
""
|
||||
"Xenia has now paused itself.\n"
|
||||
"A crash dump has been written into the log.");
|
||||
});
|
||||
}
|
||||
|
||||
// Now suspend ourself (we should be a guest thread).
|
||||
current_thread->Suspend(nullptr);
|
||||
@@ -621,6 +628,41 @@ void Emulator::WaitUntilExit() {
|
||||
on_exit();
|
||||
}
|
||||
|
||||
void Emulator::AddGameConfigLoadCallback(GameConfigLoadCallback* callback) {
|
||||
assert_not_null(callback);
|
||||
// Game config load callbacks handling is entirely in the UI thread.
|
||||
assert_true(!display_window_ ||
|
||||
display_window_->app_context().IsInUIThread());
|
||||
// Check if already added.
|
||||
if (std::find(game_config_load_callbacks_.cbegin(),
|
||||
game_config_load_callbacks_.cend(),
|
||||
callback) != game_config_load_callbacks_.cend()) {
|
||||
return;
|
||||
}
|
||||
game_config_load_callbacks_.push_back(callback);
|
||||
}
|
||||
|
||||
void Emulator::RemoveGameConfigLoadCallback(GameConfigLoadCallback* callback) {
|
||||
assert_not_null(callback);
|
||||
// Game config load callbacks handling is entirely in the UI thread.
|
||||
assert_true(!display_window_ ||
|
||||
display_window_->app_context().IsInUIThread());
|
||||
auto it = std::find(game_config_load_callbacks_.cbegin(),
|
||||
game_config_load_callbacks_.cend(), callback);
|
||||
if (it == game_config_load_callbacks_.cend()) {
|
||||
return;
|
||||
}
|
||||
if (game_config_load_callback_loop_next_index_ != SIZE_MAX) {
|
||||
// Actualize the next callback index after the erasure from the vector.
|
||||
size_t existing_index =
|
||||
size_t(std::distance(game_config_load_callbacks_.cbegin(), it));
|
||||
if (game_config_load_callback_loop_next_index_ > existing_index) {
|
||||
--game_config_load_callback_loop_next_index_;
|
||||
}
|
||||
}
|
||||
game_config_load_callbacks_.erase(it);
|
||||
}
|
||||
|
||||
std::string Emulator::FindLaunchModule() {
|
||||
std::string path("game:\\");
|
||||
|
||||
@@ -677,6 +719,10 @@ static std::string format_version(xex2_version version) {
|
||||
|
||||
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
const std::string_view module_path) {
|
||||
// Making changes to the UI (setting the icon) and executing game config load
|
||||
// callbacks which expect to be called from the UI thread.
|
||||
assert_true(display_window_->app_context().IsInUIThread());
|
||||
|
||||
// Setup NullDevices for raw HDD partition accesses
|
||||
// Cache/STFC code baked into games tries reading/writing to these
|
||||
// By using a NullDevice that just returns success to all IO requests it
|
||||
@@ -729,7 +775,19 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
// Try and load the resource database (xex only).
|
||||
if (module->title_id()) {
|
||||
auto title_id = fmt::format("{:08X}", module->title_id());
|
||||
|
||||
// Load the per-game configuration file and make sure updates are handled by
|
||||
// the callbacks.
|
||||
config::LoadGameConfig(title_id);
|
||||
assert_true(game_config_load_callback_loop_next_index_ == SIZE_MAX);
|
||||
game_config_load_callback_loop_next_index_ = 0;
|
||||
while (game_config_load_callback_loop_next_index_ <
|
||||
game_config_load_callbacks_.size()) {
|
||||
game_config_load_callbacks_[game_config_load_callback_loop_next_index_++]
|
||||
->PostGameConfigLoad();
|
||||
}
|
||||
game_config_load_callback_loop_next_index_ = SIZE_MAX;
|
||||
|
||||
uint32_t resource_data = 0;
|
||||
uint32_t resource_size = 0;
|
||||
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,
|
||||
|
||||
Reference in New Issue
Block a user