Files
Xenia-Canary/src/xenia/app/emulator_window.h
2025-10-28 21:48:10 +01:00

327 lines
10 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APP_EMULATOR_WINDOW_H_
#define XENIA_APP_EMULATOR_WINDOW_H_
#include <memory>
#include <string>
#include "xenia/app/profile_dialogs.h"
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/ui/immediate_drawer.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/presenter.h"
#include "xenia/ui/window.h"
#include "xenia/ui/window_listener.h"
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
namespace xe {
namespace app {
struct RecentTitleEntry {
std::string title_name;
std::filesystem::path path_to_file;
std::time_t last_run_time;
};
class EmulatorWindow {
public:
using steady_clock = std::chrono::steady_clock; // stdlib steady clock
enum : size_t {
// The UI is on top of the game and is open in special cases, so
// lowest-priority.
kZOrderHidInput,
kZOrderImGui,
kZOrderProfiler,
// Emulator window controls are expected to be always accessible by the
// user, so highest-priority.
kZOrderEmulatorWindowInput,
};
virtual ~EmulatorWindow();
static std::unique_ptr<EmulatorWindow> Create(
Emulator* emulator, ui::WindowedAppContext& app_context, uint32_t width,
uint32_t height);
std::unique_ptr<xe::threading::Thread> Gamepad_HotKeys_Listener;
int32_t selected_title_index = -1;
static constexpr int64_t diff_in_ms(
const steady_clock::time_point t1,
const steady_clock::time_point t2) noexcept {
using ms = std::chrono::milliseconds;
return std::chrono::duration_cast<ms>(t1 - t2).count();
}
steady_clock::time_point last_mouse_up = steady_clock::now();
steady_clock::time_point last_mouse_down = steady_clock::now();
Emulator* emulator() const { return emulator_; }
ui::WindowedAppContext& app_context() const { return app_context_; }
ui::Window* window() const { return window_.get(); }
ui::ImGuiDrawer* imgui_drawer() const { return imgui_drawer_.get(); }
ui::Presenter* GetGraphicsSystemPresenter() const;
void SetupGraphicsSystemPresenterPainting();
void ShutdownGraphicsSystemPresenterPainting();
void OnEmulatorInitialized();
xe::X_STATUS RunTitle(const std::filesystem::path& path_to_file);
void UpdateTitle();
void SetFullscreen(bool fullscreen);
void ToggleFullscreen();
void SetInitializingShaderStorage(bool initializing);
void TakeScreenshot();
void ExportScreenshot(const xe::ui::RawImage& image);
void SaveImage(const std::filesystem::path& path,
const xe::ui::RawImage& image);
void ToggleProfilesConfigDialog();
void ToggleXMPConfigDialog();
void SetHotkeysState(bool enabled) { disable_hotkeys_ = !enabled; }
// Types of button functions for hotkeys.
enum class ButtonFunctions {
ToggleFullscreen,
RunTitle,
CpuTimeScalarSetHalf,
CpuTimeScalarSetDouble,
CpuTimeScalarReset,
ClearGPUCache,
ToggleControllerVibration,
ClearMemoryPageState,
ReadbackResolve,
ToggleLogging,
IncTitleSelect,
DecTitleSelect,
Unknown
};
class ControllerHotKey {
public:
// If true the hotkey can be activated while a title is running, otherwise
// false.
bool title_passthru;
// If true vibrate the controller after activating the hotkey, otherwise
// false.
bool rumble;
std::string pretty;
ButtonFunctions function;
ControllerHotKey(ButtonFunctions fn = ButtonFunctions::Unknown,
std::string pretty = "", bool rumble = false,
bool active = true) {
function = fn;
this->pretty = pretty;
title_passthru = active;
this->rumble = rumble;
}
};
private:
class EmulatorWindowListener final : public ui::WindowListener,
public ui::WindowInputListener {
public:
explicit EmulatorWindowListener(EmulatorWindow& emulator_window)
: emulator_window_(emulator_window) {}
void OnClosing(ui::UIEvent& e) override;
void OnFileDrop(ui::FileDropEvent& e) override;
void OnKeyDown(ui::KeyEvent& e) override;
void OnMouseDown(ui::MouseEvent& e) override;
void OnMouseUp(ui::MouseEvent& e) override;
private:
EmulatorWindow& emulator_window_;
};
class DisplayConfigGameConfigLoadCallback
: public Emulator::GameConfigLoadCallback {
public:
DisplayConfigGameConfigLoadCallback(Emulator& emulator,
EmulatorWindow& emulator_window)
: Emulator::GameConfigLoadCallback(emulator),
emulator_window_(emulator_window) {}
void PostGameConfigLoad() override;
private:
EmulatorWindow& emulator_window_;
};
class ContentInstallDialog final : public ui::ImGuiDialog {
public:
ContentInstallDialog(
ui::ImGuiDrawer* imgui_drawer, EmulatorWindow& emulator_window,
std::shared_ptr<std::vector<Emulator::ContentInstallEntry>> entries)
: ui::ImGuiDialog(imgui_drawer),
emulator_window_(emulator_window),
installation_entries_(entries) {
window_id_ = GetWindowId();
}
~ContentInstallDialog() {
for (auto& entry : *installation_entries_) {
entry.icon_.release();
}
}
protected:
void OnDraw(ImGuiIO& io) override;
private:
uint64_t window_id_;
EmulatorWindow& emulator_window_;
std::shared_ptr<std::vector<Emulator::ContentInstallEntry>>
installation_entries_;
};
class DisplayConfigDialog final : public ui::ImGuiDialog {
public:
DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow& emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {}
protected:
void OnDraw(ImGuiIO& io) override;
private:
EmulatorWindow& emulator_window_;
};
class XMPConfigDialog final : public ui::ImGuiDialog {
public:
XMPConfigDialog(ui::ImGuiDrawer* imgui_drawer,
EmulatorWindow& emulator_window)
: ui::ImGuiDialog(imgui_drawer), emulator_window_(emulator_window) {
if (emulator_window_.emulator_->audio_media_player()) {
volume_ = emulator_window_.emulator_->audio_media_player()
->GetVolume()
->load();
}
}
protected:
void OnDraw(ImGuiIO& io) override;
private:
EmulatorWindow& emulator_window_;
float volume_ = 0.0f;
};
explicit EmulatorWindow(Emulator* emulator,
ui::WindowedAppContext& app_context, uint32_t width,
uint32_t height);
bool Initialize();
// For comparisons, use GetSwapPostEffectForCvarValue instead as the default
// fallback may be used for multiple values.
static const char* GetCvarValueForSwapPostEffect(
gpu::CommandProcessor::SwapPostEffect effect);
static gpu::CommandProcessor::SwapPostEffect GetSwapPostEffectForCvarValue(
const std::string& cvar_value);
// For comparisons, use GetGuestOutputPaintEffectForCvarValue instead as the
// default fallback may be used for multiple values.
static const char* GetCvarValueForGuestOutputPaintEffect(
ui::Presenter::GuestOutputPaintConfig::Effect effect);
static ui::Presenter::GuestOutputPaintConfig::Effect
GetGuestOutputPaintEffectForCvarValue(const std::string& cvar_value);
static ui::Presenter::GuestOutputPaintConfig
GetGuestOutputPaintConfigForCvars();
void ApplyDisplayConfigForCvars();
void OnKeyDown(ui::KeyEvent& e);
void OnMouseDown(const ui::MouseEvent& e);
void ToggleFullscreenOnDoubleClick();
void FileDrop(const std::filesystem::path& filename);
void OnMouseUp(const ui::MouseEvent& e);
void FileOpen();
void FileClose();
void InstallContent();
void ExtractZarchive();
void CreateZarchive();
void ShowContentDirectory();
void CpuTimeScalarReset();
void CpuTimeScalarSetHalf();
void CpuTimeScalarSetDouble();
void CpuBreakIntoDebugger();
void CpuBreakIntoHostDebugger();
void GpuTraceFrame();
void GpuClearCaches();
void ToggleDisplayConfigDialog();
void ToggleControllerVibration();
void ShowCompatibility();
void ShowFAQ();
void ShowBuildCommit();
EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons);
void VibrateController(xe::hid::InputSystem* input_sys, uint32_t user_index,
bool vibrate = true);
void GamepadHotKeys();
void ToggleGPUSetting(gpu::GPUSetting setting);
void DisplayHotKeysConfig();
static std::string CanonicalizeFileExtension(
const std::filesystem::path& path);
void RunPreviouslyPlayedTitle();
void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu);
void LoadRecentlyLaunchedTitles();
void AddRecentlyLaunchedTitle(std::filesystem::path path_to_file,
std::string title_name);
void ClearDialogs();
Emulator* emulator_;
ui::WindowedAppContext& app_context_;
EmulatorWindowListener window_listener_;
std::unique_ptr<ui::Window> window_;
std::unique_ptr<ui::ImGuiDrawer> imgui_drawer_;
std::unique_ptr<DisplayConfigGameConfigLoadCallback>
display_config_game_config_load_callback_;
// Creation may fail, in this case immediate drawer UI must not be drawn.
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
bool emulator_initialized_ = false;
std::atomic<bool> disable_hotkeys_ = false;
std::string base_title_;
bool initializing_shader_storage_ = false;
std::unique_ptr<DisplayConfigDialog> display_config_dialog_;
// Storing pointers and toggling dialog state is useful for broadcasting
// messages back to guest.
std::unique_ptr<ProfileConfigDialog> profile_config_dialog_;
std::unique_ptr<XMPConfigDialog> xmp_config_dialog_;
std::vector<RecentTitleEntry> recently_launched_titles_;
};
} // namespace app
} // namespace xe
#endif // XENIA_APP_EMULATOR_WINDOW_H_