[UI] Loop thread to main thread WindowedAppContext
This commit is contained in:
parent
f540c188bf
commit
6ce5330f5f
12
premake5.lua
12
premake5.lua
|
@ -189,6 +189,12 @@ filter("platforms:Windows")
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- Embed the manifest for things like dependencies and DPI awareness.
|
||||||
|
filter({"platforms:Windows", "kind:ConsoleApp or WindowedApp"})
|
||||||
|
files({
|
||||||
|
"src/xenia/base/app_win32.manifest"
|
||||||
|
})
|
||||||
|
|
||||||
-- Create scratch/ path
|
-- Create scratch/ path
|
||||||
if not os.isdir("scratch") then
|
if not os.isdir("scratch") then
|
||||||
os.mkdir("scratch")
|
os.mkdir("scratch")
|
||||||
|
@ -243,9 +249,13 @@ workspace("xenia")
|
||||||
include("third_party/SDL2.lua")
|
include("third_party/SDL2.lua")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Disable treating warnings as fatal errors for all third party projects:
|
-- Disable treating warnings as fatal errors for all third party projects, as
|
||||||
|
-- well as other things relevant only to Xenia itself.
|
||||||
for _, prj in ipairs(premake.api.scope.current.solution.projects) do
|
for _, prj in ipairs(premake.api.scope.current.solution.projects) do
|
||||||
project(prj.name)
|
project(prj.name)
|
||||||
|
removefiles({
|
||||||
|
"src/xenia/base/app_win32.manifest"
|
||||||
|
})
|
||||||
removeflags({
|
removeflags({
|
||||||
"FatalWarnings",
|
"FatalWarnings",
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
|
@ -42,10 +43,11 @@ using xe::ui::UIEvent;
|
||||||
|
|
||||||
const std::string kBaseTitle = "xenia";
|
const std::string kBaseTitle = "xenia";
|
||||||
|
|
||||||
EmulatorWindow::EmulatorWindow(Emulator* emulator)
|
EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
||||||
|
ui::WindowedAppContext& app_context)
|
||||||
: emulator_(emulator),
|
: emulator_(emulator),
|
||||||
loop_(ui::Loop::Create()),
|
app_context_(app_context),
|
||||||
window_(ui::Window::Create(loop_.get(), kBaseTitle)) {
|
window_(ui::Window::Create(app_context, kBaseTitle)) {
|
||||||
base_title_ = kBaseTitle +
|
base_title_ = kBaseTitle +
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
#if _NO_DEBUG_HEAP == 1
|
#if _NO_DEBUG_HEAP == 1
|
||||||
|
@ -63,23 +65,14 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator)
|
||||||
")";
|
")";
|
||||||
}
|
}
|
||||||
|
|
||||||
EmulatorWindow::~EmulatorWindow() {
|
std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(
|
||||||
loop_->PostSynchronous([this]() { window_.reset(); });
|
Emulator* emulator, ui::WindowedAppContext& app_context) {
|
||||||
}
|
assert_true(app_context.IsInUIThread());
|
||||||
|
std::unique_ptr<EmulatorWindow> emulator_window(
|
||||||
std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(Emulator* emulator) {
|
new EmulatorWindow(emulator, app_context));
|
||||||
std::unique_ptr<EmulatorWindow> emulator_window(new EmulatorWindow(emulator));
|
if (!emulator_window->Initialize()) {
|
||||||
|
return nullptr;
|
||||||
emulator_window->loop()->PostSynchronous([&emulator_window]() {
|
}
|
||||||
xe::threading::set_name("Windowing Loop");
|
|
||||||
xe::Profiler::ThreadEnter("Windowing Loop");
|
|
||||||
|
|
||||||
if (!emulator_window->Initialize()) {
|
|
||||||
xe::FatalError("Failed to initialize main window");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return emulator_window;
|
return emulator_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +84,8 @@ bool EmulatorWindow::Initialize() {
|
||||||
|
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
|
|
||||||
window_->on_closed.AddListener([this](UIEvent* e) { loop_->Quit(); });
|
window_->on_closed.AddListener(
|
||||||
loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); });
|
[this](UIEvent* e) { app_context_.QuitFromUIThread(); });
|
||||||
|
|
||||||
window_->on_file_drop.AddListener(
|
window_->on_file_drop.AddListener(
|
||||||
[this](FileDropEvent* e) { FileDrop(e->filename()); });
|
[this](FileDropEvent* e) { FileDrop(e->filename()); });
|
||||||
|
@ -313,7 +306,7 @@ void EmulatorWindow::FileOpen() {
|
||||||
file_picker->set_multi_selection(false);
|
file_picker->set_multi_selection(false);
|
||||||
file_picker->set_title("Select Content Package");
|
file_picker->set_title("Select Content Package");
|
||||||
file_picker->set_extensions({
|
file_picker->set_extensions({
|
||||||
{"Supported Files", "*.iso;*.xex;*.xcp;*.*"},
|
{"Supported Files", "*.iso;*.xex;*.*"},
|
||||||
{"Disc Image (*.iso)", "*.iso"},
|
{"Disc Image (*.iso)", "*.iso"},
|
||||||
{"Xbox Executable (*.xex)", "*.xex"},
|
{"Xbox Executable (*.xex)", "*.xex"},
|
||||||
//{"Content Package (*.xcp)", "*.xcp" },
|
//{"Content Package (*.xcp)", "*.xcp" },
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
#include "xenia/ui/menu_item.h"
|
#include "xenia/ui/menu_item.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -27,12 +27,11 @@ namespace app {
|
||||||
|
|
||||||
class EmulatorWindow {
|
class EmulatorWindow {
|
||||||
public:
|
public:
|
||||||
virtual ~EmulatorWindow();
|
static std::unique_ptr<EmulatorWindow> Create(
|
||||||
|
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||||
static std::unique_ptr<EmulatorWindow> Create(Emulator* emulator);
|
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
Emulator* emulator() const { return emulator_; }
|
||||||
ui::Loop* loop() const { return loop_.get(); }
|
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||||
ui::Window* window() const { return window_.get(); }
|
ui::Window* window() const { return window_.get(); }
|
||||||
|
|
||||||
void UpdateTitle();
|
void UpdateTitle();
|
||||||
|
@ -40,7 +39,8 @@ class EmulatorWindow {
|
||||||
void SetInitializingShaderStorage(bool initializing);
|
void SetInitializingShaderStorage(bool initializing);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit EmulatorWindow(Emulator* emulator);
|
explicit EmulatorWindow(Emulator* emulator,
|
||||||
|
ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class EmulatorWindow {
|
||||||
void ShowCommitID();
|
void ShowCommitID();
|
||||||
|
|
||||||
Emulator* emulator_;
|
Emulator* emulator_;
|
||||||
std::unique_ptr<ui::Loop> loop_;
|
ui::WindowedAppContext& app_context_;
|
||||||
std::unique_ptr<ui::Window> window_;
|
std::unique_ptr<ui::Window> window_;
|
||||||
std::string base_title_;
|
std::string base_title_;
|
||||||
uint64_t cursor_hide_time_ = 0;
|
uint64_t cursor_hide_time_ = 0;
|
||||||
|
|
|
@ -52,8 +52,8 @@ project("xenia-app")
|
||||||
local_platform_files()
|
local_platform_files()
|
||||||
files({
|
files({
|
||||||
"xenia_main.cc",
|
"xenia_main.cc",
|
||||||
"../base/main_"..platform_suffix..".cc",
|
|
||||||
"../base/main_init_"..platform_suffix..".cc",
|
"../base/main_init_"..platform_suffix..".cc",
|
||||||
|
"../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
|
|
||||||
resincludedirs({
|
resincludedirs({
|
||||||
|
|
|
@ -7,18 +7,29 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/app/discord/discord_presence.h"
|
#include "xenia/app/discord/discord_presence.h"
|
||||||
#include "xenia/app/emulator_window.h"
|
#include "xenia/app/emulator_window.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/config.h"
|
#include "xenia/config.h"
|
||||||
#include "xenia/debug/ui/debug_window.h"
|
#include "xenia/debug/ui/debug_window.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/vfs/devices/host_path_device.h"
|
#include "xenia/vfs/devices/host_path_device.h"
|
||||||
|
|
||||||
// Available audio systems:
|
// Available audio systems:
|
||||||
|
@ -44,7 +55,6 @@
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif // XE_PLATFORM_WIN32
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
|
||||||
|
|
||||||
DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU");
|
DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU");
|
||||||
DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
|
DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
|
||||||
|
@ -91,83 +101,134 @@ DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
class EmulatorApp final : public xe::ui::WindowedApp {
|
||||||
class Factory {
|
public:
|
||||||
|
static std::unique_ptr<xe::ui::WindowedApp> Create(
|
||||||
|
xe::ui::WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<xe::ui::WindowedApp>(new EmulatorApp(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
|
~EmulatorApp();
|
||||||
|
|
||||||
|
bool OnInitialize() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnDestroy() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Creator {
|
template <typename T, typename... Args>
|
||||||
std::string name;
|
class Factory {
|
||||||
std::function<bool()> is_available;
|
private:
|
||||||
std::function<std::unique_ptr<T>(Args...)> instantiate;
|
struct Creator {
|
||||||
|
std::string name;
|
||||||
|
std::function<bool()> is_available;
|
||||||
|
std::function<std::unique_ptr<T>(Args...)> instantiate;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Creator> creators_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Add(const std::string_view name, std::function<bool()> is_available,
|
||||||
|
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||||
|
creators_.push_back({std::string(name), is_available, instantiate});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(const std::string_view name,
|
||||||
|
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||||
|
auto always_available = []() { return true; };
|
||||||
|
Add(name, always_available, instantiate);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename DT>
|
||||||
|
void Add(const std::string_view name) {
|
||||||
|
Add(name, DT::IsAvailable, [](Args... args) {
|
||||||
|
return std::make_unique<DT>(std::forward<Args>(args)...);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<T> Create(const std::string_view name, Args... args) {
|
||||||
|
if (!name.empty() && name != "any") {
|
||||||
|
auto it = std::find_if(
|
||||||
|
creators_.cbegin(), creators_.cend(),
|
||||||
|
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||||
|
if (it != creators_.cend() && (*it).is_available()) {
|
||||||
|
return (*it).instantiate(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
for (const auto& creator : creators_) {
|
||||||
|
if (!creator.is_available()) continue;
|
||||||
|
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||||
|
if (!instance) continue;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name,
|
||||||
|
Args... args) {
|
||||||
|
std::vector<std::unique_ptr<T>> instances;
|
||||||
|
if (!name.empty() && name != "any") {
|
||||||
|
auto it = std::find_if(
|
||||||
|
creators_.cbegin(), creators_.cend(),
|
||||||
|
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||||
|
if (it != creators_.cend() && (*it).is_available()) {
|
||||||
|
auto instance = (*it).instantiate(std::forward<Args>(args)...);
|
||||||
|
if (instance) {
|
||||||
|
instances.emplace_back(std::move(instance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const auto& creator : creators_) {
|
||||||
|
if (!creator.is_available()) continue;
|
||||||
|
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||||
|
if (instance) {
|
||||||
|
instances.emplace_back(std::move(instance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Creator> creators_;
|
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
public:
|
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
|
||||||
void Add(const std::string_view name, std::function<bool()> is_available,
|
cpu::Processor* processor);
|
||||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
static std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem();
|
||||||
creators_.push_back({std::string(name), is_available, instantiate});
|
static std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||||
}
|
ui::Window* window);
|
||||||
|
|
||||||
void Add(const std::string_view name,
|
void EmulatorThread();
|
||||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
void ShutdownEmulatorThreadFromUIThread();
|
||||||
auto always_available = []() { return true; };
|
|
||||||
Add(name, always_available, instantiate);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename DT>
|
std::unique_ptr<Emulator> emulator_;
|
||||||
void Add(const std::string_view name) {
|
std::unique_ptr<EmulatorWindow> emulator_window_;
|
||||||
Add(name, DT::IsAvailable, [](Args... args) {
|
|
||||||
return std::make_unique<DT>(std::forward<Args>(args)...);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<T> Create(const std::string_view name, Args... args) {
|
// Created on demand, used by the emulator.
|
||||||
if (!name.empty() && name != "any") {
|
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window_;
|
||||||
auto it = std::find_if(
|
|
||||||
creators_.cbegin(), creators_.cend(),
|
|
||||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
|
||||||
if (it != creators_.cend() && (*it).is_available()) {
|
|
||||||
return (*it).instantiate(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
} else {
|
|
||||||
for (const auto& creator : creators_) {
|
|
||||||
if (!creator.is_available()) continue;
|
|
||||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
|
||||||
if (!instance) continue;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name,
|
// Refreshing the emulator - placed after its dependencies.
|
||||||
Args... args) {
|
std::atomic<bool> emulator_thread_quit_requested_;
|
||||||
std::vector<std::unique_ptr<T>> instances;
|
std::unique_ptr<xe::threading::Event> emulator_thread_event_;
|
||||||
if (!name.empty() && name != "any") {
|
std::thread emulator_thread_;
|
||||||
auto it = std::find_if(
|
|
||||||
creators_.cbegin(), creators_.cend(),
|
|
||||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
|
||||||
if (it != creators_.cend() && (*it).is_available()) {
|
|
||||||
auto instance = (*it).instantiate(std::forward<Args>(args)...);
|
|
||||||
if (instance) {
|
|
||||||
instances.emplace_back(std::move(instance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const auto& creator : creators_) {
|
|
||||||
if (!creator.is_available()) continue;
|
|
||||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
|
||||||
if (instance) {
|
|
||||||
instances.emplace_back(std::move(instance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instances;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<apu::AudioSystem> CreateAudioSystem(cpu::Processor* processor) {
|
EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context)
|
||||||
|
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]") {
|
||||||
|
AddPositionalOption("target");
|
||||||
|
}
|
||||||
|
|
||||||
|
EmulatorApp::~EmulatorApp() {
|
||||||
|
// Should be shut down from OnDestroy if OnInitialize has ever been done, but
|
||||||
|
// for the most safety as a running thread may be destroyed only after
|
||||||
|
// joining.
|
||||||
|
ShutdownEmulatorThreadFromUIThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<apu::AudioSystem> EmulatorApp::CreateAudioSystem(
|
||||||
|
cpu::Processor* processor) {
|
||||||
Factory<apu::AudioSystem, cpu::Processor*> factory;
|
Factory<apu::AudioSystem, cpu::Processor*> factory;
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
|
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
|
||||||
|
@ -177,7 +238,7 @@ std::unique_ptr<apu::AudioSystem> CreateAudioSystem(cpu::Processor* processor) {
|
||||||
return factory.Create(cvars::apu, processor);
|
return factory.Create(cvars::apu, processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() {
|
std::unique_ptr<gpu::GraphicsSystem> EmulatorApp::CreateGraphicsSystem() {
|
||||||
Factory<gpu::GraphicsSystem> factory;
|
Factory<gpu::GraphicsSystem> factory;
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
factory.Add<gpu::d3d12::D3D12GraphicsSystem>("d3d12");
|
factory.Add<gpu::d3d12::D3D12GraphicsSystem>("d3d12");
|
||||||
|
@ -187,7 +248,7 @@ std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() {
|
||||||
return factory.Create(cvars::gpu);
|
return factory.Create(cvars::gpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
|
||||||
ui::Window* window) {
|
ui::Window* window) {
|
||||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||||
if (cvars::hid.compare("nop") == 0) {
|
if (cvars::hid.compare("nop") == 0) {
|
||||||
|
@ -215,9 +276,9 @@ std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||||
return drivers;
|
return drivers;
|
||||||
}
|
}
|
||||||
|
|
||||||
int xenia_main(const std::vector<std::string>& args) {
|
bool EmulatorApp::OnInitialize() {
|
||||||
Profiler::Initialize();
|
Profiler::Initialize();
|
||||||
Profiler::ThreadEnter("main");
|
Profiler::ThreadEnter("Main");
|
||||||
|
|
||||||
// Figure out where internal files and content should go.
|
// Figure out where internal files and content should go.
|
||||||
std::filesystem::path storage_root = cvars::storage_root;
|
std::filesystem::path storage_root = cvars::storage_root;
|
||||||
|
@ -275,20 +336,55 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the emulator but don't initialize so we can setup the window.
|
// Create the emulator but don't initialize so we can setup the window.
|
||||||
auto emulator =
|
emulator_ =
|
||||||
std::make_unique<Emulator>("", storage_root, content_root, cache_root);
|
std::make_unique<Emulator>("", storage_root, content_root, cache_root);
|
||||||
|
|
||||||
// Main emulator display window.
|
// Main emulator display window.
|
||||||
auto emulator_window = EmulatorWindow::Create(emulator.get());
|
emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context());
|
||||||
|
if (!emulator_window_) {
|
||||||
|
XELOGE("Failed to create the main emulator window");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the emulator and run its loop in a separate thread.
|
||||||
|
emulator_thread_quit_requested_.store(false, std::memory_order_relaxed);
|
||||||
|
emulator_thread_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
||||||
|
emulator_thread_ = std::thread(&EmulatorApp::EmulatorThread, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorApp::OnDestroy() {
|
||||||
|
ShutdownEmulatorThreadFromUIThread();
|
||||||
|
|
||||||
|
if (cvars::discord) {
|
||||||
|
discord::DiscordPresence::Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
Profiler::Dump();
|
||||||
|
// The profiler needs to shut down before the graphics context.
|
||||||
|
Profiler::Shutdown();
|
||||||
|
|
||||||
|
// TODO(DrChat): Remove this code and do a proper exit.
|
||||||
|
XELOGI("Cheap-skate exit!");
|
||||||
|
std::quick_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorApp::EmulatorThread() {
|
||||||
|
assert_not_null(emulator_thread_event_);
|
||||||
|
|
||||||
|
xe::threading::set_name("Emulator");
|
||||||
|
Profiler::ThreadEnter("Emulator");
|
||||||
|
|
||||||
// Setup and initialize all subsystems. If we can't do something
|
// Setup and initialize all subsystems. If we can't do something
|
||||||
// (unsupported system, memory issues, etc) this will fail early.
|
// (unsupported system, memory issues, etc) this will fail early.
|
||||||
X_STATUS result =
|
X_STATUS result =
|
||||||
emulator->Setup(emulator_window->window(), CreateAudioSystem,
|
emulator_->Setup(emulator_window_->window(), CreateAudioSystem,
|
||||||
CreateGraphicsSystem, CreateInputDrivers);
|
CreateGraphicsSystem, CreateInputDrivers);
|
||||||
if (XFAILED(result)) {
|
if (XFAILED(result)) {
|
||||||
XELOGE("Failed to setup emulator: {:08X}", result);
|
XELOGE("Failed to setup emulator: {:08X}", result);
|
||||||
return 1;
|
app_context().RequestDeferredQuit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cvars::mount_scratch) {
|
if (cvars::mount_scratch) {
|
||||||
|
@ -297,10 +393,11 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
if (!scratch_device->Initialize()) {
|
if (!scratch_device->Initialize()) {
|
||||||
XELOGE("Unable to scan scratch path");
|
XELOGE("Unable to scan scratch path");
|
||||||
} else {
|
} else {
|
||||||
if (!emulator->file_system()->RegisterDevice(std::move(scratch_device))) {
|
if (!emulator_->file_system()->RegisterDevice(
|
||||||
|
std::move(scratch_device))) {
|
||||||
XELOGE("Unable to register scratch path");
|
XELOGE("Unable to register scratch path");
|
||||||
} else {
|
} else {
|
||||||
emulator->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH");
|
emulator_->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,10 +408,10 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
if (!cache0_device->Initialize()) {
|
if (!cache0_device->Initialize()) {
|
||||||
XELOGE("Unable to scan cache0 path");
|
XELOGE("Unable to scan cache0 path");
|
||||||
} else {
|
} else {
|
||||||
if (!emulator->file_system()->RegisterDevice(std::move(cache0_device))) {
|
if (!emulator_->file_system()->RegisterDevice(std::move(cache0_device))) {
|
||||||
XELOGE("Unable to register cache0 path");
|
XELOGE("Unable to register cache0 path");
|
||||||
} else {
|
} else {
|
||||||
emulator->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0");
|
emulator_->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,10 +420,10 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
if (!cache1_device->Initialize()) {
|
if (!cache1_device->Initialize()) {
|
||||||
XELOGE("Unable to scan cache1 path");
|
XELOGE("Unable to scan cache1 path");
|
||||||
} else {
|
} else {
|
||||||
if (!emulator->file_system()->RegisterDevice(std::move(cache1_device))) {
|
if (!emulator_->file_system()->RegisterDevice(std::move(cache1_device))) {
|
||||||
XELOGE("Unable to register cache1 path");
|
XELOGE("Unable to register cache1 path");
|
||||||
} else {
|
} else {
|
||||||
emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
|
emulator_->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,79 +436,62 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
if (!cache_device->Initialize()) {
|
if (!cache_device->Initialize()) {
|
||||||
XELOGE("Unable to scan cache path");
|
XELOGE("Unable to scan cache path");
|
||||||
} else {
|
} else {
|
||||||
if (!emulator->file_system()->RegisterDevice(std::move(cache_device))) {
|
if (!emulator_->file_system()->RegisterDevice(std::move(cache_device))) {
|
||||||
XELOGE("Unable to register cache path");
|
XELOGE("Unable to register cache path");
|
||||||
} else {
|
} else {
|
||||||
emulator->file_system()->RegisterSymbolicLink("cache:", "\\CACHE");
|
emulator_->file_system()->RegisterSymbolicLink("cache:", "\\CACHE");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a debug handler.
|
// Set a debug handler.
|
||||||
// This will respond to debugging requests so we can open the debug UI.
|
// This will respond to debugging requests so we can open the debug UI.
|
||||||
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window;
|
|
||||||
if (cvars::debug) {
|
if (cvars::debug) {
|
||||||
emulator->processor()->set_debug_listener_request_handler(
|
emulator_->processor()->set_debug_listener_request_handler(
|
||||||
[&](xe::cpu::Processor* processor) {
|
[this](xe::cpu::Processor* processor) {
|
||||||
if (debug_window) {
|
if (debug_window_) {
|
||||||
return debug_window.get();
|
return debug_window_.get();
|
||||||
}
|
}
|
||||||
emulator_window->loop()->PostSynchronous([&]() {
|
app_context().CallInUIThreadSynchronous([this]() {
|
||||||
debug_window = xe::debug::ui::DebugWindow::Create(
|
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
||||||
emulator.get(), emulator_window->loop());
|
app_context());
|
||||||
debug_window->window()->on_closed.AddListener(
|
debug_window_->window()->on_closed.AddListener(
|
||||||
[&](xe::ui::UIEvent* e) {
|
[this](xe::ui::UIEvent* e) {
|
||||||
emulator->processor()->set_debug_listener(nullptr);
|
emulator_->processor()->set_debug_listener(nullptr);
|
||||||
emulator_window->loop()->Post(
|
app_context().CallInUIThread(
|
||||||
[&]() { debug_window.reset(); });
|
[this]() { debug_window_.reset(); });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return debug_window.get();
|
// If failed to enqueue the UI thread call, this will just be null.
|
||||||
|
return debug_window_.get();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evt = xe::threading::Event::CreateAutoResetEvent(false);
|
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
||||||
emulator->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
|
||||||
if (cvars::discord) {
|
if (cvars::discord) {
|
||||||
discord::DiscordPresence::PlayingTitle(
|
discord::DiscordPresence::PlayingTitle(
|
||||||
game_title.empty() ? "Unknown Title" : std::string(game_title));
|
game_title.empty() ? "Unknown Title" : std::string(game_title));
|
||||||
}
|
}
|
||||||
emulator_window->UpdateTitle();
|
app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); });
|
||||||
evt->Set();
|
emulator_thread_event_->Set();
|
||||||
});
|
});
|
||||||
|
|
||||||
emulator->on_shader_storage_initialization.AddListener(
|
emulator_->on_shader_storage_initialization.AddListener(
|
||||||
[&](bool initializing) {
|
[this](bool initializing) {
|
||||||
emulator_window->SetInitializingShaderStorage(initializing);
|
app_context().CallInUIThread([this, initializing]() {
|
||||||
|
emulator_window_->SetInitializingShaderStorage(initializing);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
emulator->on_terminate.AddListener([&]() {
|
emulator_->on_terminate.AddListener([]() {
|
||||||
if (cvars::discord) {
|
if (cvars::discord) {
|
||||||
discord::DiscordPresence::NotPlaying();
|
discord::DiscordPresence::NotPlaying();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
emulator_window->window()->on_closing.AddListener([&](ui::UIEvent* e) {
|
|
||||||
// This needs to shut down before the graphics context.
|
|
||||||
Profiler::Shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
bool exiting = false;
|
|
||||||
emulator_window->loop()->on_quit.AddListener([&](ui::UIEvent* e) {
|
|
||||||
exiting = true;
|
|
||||||
evt->Set();
|
|
||||||
|
|
||||||
if (cvars::discord) {
|
|
||||||
discord::DiscordPresence::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(DrChat): Remove this code and do a proper exit.
|
|
||||||
XELOGI("Cheap-skate exit!");
|
|
||||||
exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enable the main menu now that the emulator is properly loaded
|
// Enable the main menu now that the emulator is properly loaded
|
||||||
emulator_window->window()->EnableMainMenu();
|
app_context().CallInUIThread(
|
||||||
|
[this]() { emulator_window_->window()->EnableMainMenu(); });
|
||||||
|
|
||||||
// Grab path from the flag or unnamed argument.
|
// Grab path from the flag or unnamed argument.
|
||||||
std::filesystem::path path;
|
std::filesystem::path path;
|
||||||
|
@ -420,50 +500,56 @@ int xenia_main(const std::vector<std::string>& args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggles fullscreen
|
// Toggles fullscreen
|
||||||
if (cvars::fullscreen) emulator_window->ToggleFullscreen();
|
if (cvars::fullscreen) {
|
||||||
|
app_context().CallInUIThread(
|
||||||
|
[this]() { emulator_window_->ToggleFullscreen(); });
|
||||||
|
}
|
||||||
|
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
// Normalize the path and make absolute.
|
// Normalize the path and make absolute.
|
||||||
auto abs_path = std::filesystem::absolute(path);
|
auto abs_path = std::filesystem::absolute(path);
|
||||||
result = emulator->LaunchPath(abs_path);
|
result = emulator_->LaunchPath(abs_path);
|
||||||
if (XFAILED(result)) {
|
if (XFAILED(result)) {
|
||||||
xe::FatalError(fmt::format("Failed to launch target: {:08X}", result));
|
xe::FatalError(fmt::format("Failed to launch target: {:08X}", result));
|
||||||
emulator.reset();
|
app_context().RequestDeferredQuit();
|
||||||
emulator_window.reset();
|
return;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we're going to use the main thread to drive events related to
|
// Now, we're going to use this thread to drive events related to emulation.
|
||||||
// emulation.
|
while (!emulator_thread_quit_requested_.load(std::memory_order_relaxed)) {
|
||||||
while (!exiting) {
|
xe::threading::Wait(emulator_thread_event_.get(), false);
|
||||||
xe::threading::Wait(evt.get(), false);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
emulator->WaitUntilExit();
|
emulator_->WaitUntilExit();
|
||||||
if (emulator->TitleRequested()) {
|
if (emulator_->TitleRequested()) {
|
||||||
emulator->LaunchNextTitle();
|
emulator_->LaunchNextTitle();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug_window.reset();
|
void EmulatorApp::ShutdownEmulatorThreadFromUIThread() {
|
||||||
emulator.reset();
|
// TODO(Triang3l): Proper shutdown of the emulator (relying on std::quick_exit
|
||||||
|
// for now) - currently WaitUntilExit loops forever otherwise (plus possibly
|
||||||
if (cvars::discord) {
|
// lots of other things not shutting down correctly now). Some parts of the
|
||||||
discord::DiscordPresence::Shutdown();
|
// code call the regular std::exit, which seems to be calling destructors (at
|
||||||
|
// least on Linux), so the entire join is currently commented out.
|
||||||
|
#if 0
|
||||||
|
// Same thread as the one created it, to make sure there's zero possibility of
|
||||||
|
// a race with the creation of the emulator thread.
|
||||||
|
assert_true(app_context().IsInUIThread());
|
||||||
|
emulator_thread_quit_requested_.store(true, std::memory_order_relaxed);
|
||||||
|
if (!emulator_thread_.joinable()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
emulator_thread_event_->Set();
|
||||||
Profiler::Dump();
|
emulator_thread_.join();
|
||||||
Profiler::Shutdown();
|
#endif
|
||||||
emulator_window.reset();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia", xe::app::xenia_main, "[Path to .iso/.xex]",
|
XE_DEFINE_WINDOWED_APP(xenia, xe::app::EmulatorApp::Create);
|
||||||
"target");
|
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
|
|
||||||
#include "xenia/apu/xaudio2/xaudio2_audio_driver.h"
|
#include "xenia/apu/xaudio2/xaudio2_audio_driver.h"
|
||||||
|
|
||||||
// Must be included before xaudio2.h so we get the right windows.h include.
|
|
||||||
#include "xenia/base/platform_win.h"
|
|
||||||
|
|
||||||
#include "xenia/apu/apu_flags.h"
|
#include "xenia/apu/apu_flags.h"
|
||||||
#include "xenia/apu/conversion.h"
|
#include "xenia/apu/conversion.h"
|
||||||
|
#include "xenia/apu/xaudio2/xaudio2_api.h"
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace apu {
|
namespace apu {
|
||||||
|
@ -73,11 +74,6 @@ bool XAudio2AudioDriver::Initialize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too
|
|
||||||
// much time from other things. Ideally should process audio on what roughly
|
|
||||||
// represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or
|
|
||||||
// even beyond the 6 guest cores.
|
|
||||||
api::XAUDIO2_PROCESSOR processor = 0x00000001;
|
|
||||||
if (api_minor_version_ >= 8) {
|
if (api_minor_version_ >= 8) {
|
||||||
union {
|
union {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -94,7 +90,7 @@ bool XAudio2AudioDriver::Initialize() {
|
||||||
assert_always();
|
assert_always();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
hr = xaudio2_create(&objects_.api_2_8.audio, 0, processor);
|
hr = xaudio2_create(&objects_.api_2_8.audio, 0, kProcessor);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
XELOGE("XAudio2Create failed with {:08X}", hr);
|
XELOGE("XAudio2Create failed with {:08X}", hr);
|
||||||
assert_always();
|
assert_always();
|
||||||
|
@ -102,20 +98,38 @@ bool XAudio2AudioDriver::Initialize() {
|
||||||
}
|
}
|
||||||
return InitializeObjects(objects_.api_2_8);
|
return InitializeObjects(objects_.api_2_8);
|
||||||
} else {
|
} else {
|
||||||
hr = CoCreateInstance(__uuidof(api::XAudio2_7), NULL, CLSCTX_INPROC_SERVER,
|
// We need to be able to accept frames from any non-STA thread - primarily
|
||||||
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
// from any guest thread, so MTA needs to be used. The AudioDriver, however,
|
||||||
if (FAILED(hr)) {
|
// may be initialized from the UI thread, which has the STA concurrency
|
||||||
XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr);
|
// model, or from another thread regardless of its concurrency model. So,
|
||||||
assert_always();
|
// all management of the objects needs to be performed in MTA. Launch the
|
||||||
|
// lifecycle management thread, which will handle initialization and
|
||||||
|
// shutdown, and also provide a scope for implicit MTA in threads that have
|
||||||
|
// never initialized COM explicitly, which lasts until all threads that have
|
||||||
|
// initialized MTA explicitly have uninitialized it - the thread that holds
|
||||||
|
// the MTA scope needs to be running while other threads are able to submit
|
||||||
|
// frames as they might have not initialized MTA explicitly.
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||||
|
assert_false(mta_thread_.joinable());
|
||||||
|
mta_thread_initialization_attempt_completed_ = false;
|
||||||
|
mta_thread_shutdown_requested_ = false;
|
||||||
|
mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this);
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||||
|
mta_thread_initialization_completion_mutex_);
|
||||||
|
while (true) {
|
||||||
|
if (mta_thread_initialization_attempt_completed_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mta_thread_initialization_completion_cond_.wait(
|
||||||
|
mta_thread_initialization_completion_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mta_thread_initialization_completion_result_) {
|
||||||
|
mta_thread_.join();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
hr = objects_.api_2_7.audio->Initialize(0, processor);
|
return true;
|
||||||
if (FAILED(hr)) {
|
|
||||||
XELOGE("IXAudio2::Initialize failed with {:08X}", hr);
|
|
||||||
assert_always();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return InitializeObjects(objects_.api_2_7);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +263,16 @@ void XAudio2AudioDriver::Shutdown() {
|
||||||
if (api_minor_version_ >= 8) {
|
if (api_minor_version_ >= 8) {
|
||||||
ShutdownObjects(objects_.api_2_8);
|
ShutdownObjects(objects_.api_2_8);
|
||||||
} else {
|
} else {
|
||||||
ShutdownObjects(objects_.api_2_7);
|
// XAudio 2.7 lifecycle is managed by the MTA thread.
|
||||||
|
if (mta_thread_.joinable()) {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||||
|
mta_thread_shutdown_request_mutex_);
|
||||||
|
mta_thread_shutdown_requested_ = true;
|
||||||
|
}
|
||||||
|
mta_thread_shutdown_request_cond_.notify_all();
|
||||||
|
mta_thread_.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xaudio2_module_) {
|
if (xaudio2_module_) {
|
||||||
|
@ -283,6 +306,82 @@ void XAudio2AudioDriver::ShutdownObjects(Objects& objects) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void XAudio2AudioDriver::MTAThread() {
|
||||||
|
xe::threading::set_name("XAudio 2.7 MTA");
|
||||||
|
|
||||||
|
assert_false(mta_thread_initialization_attempt_completed_);
|
||||||
|
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
|
// Initializing MTA COM in this thread, as well making other (guest) threads
|
||||||
|
// that don't explicitly call CoInitializeEx implicitly MTA for the period of
|
||||||
|
// time when they can interact with XAudio 2.7 through the XAudio2AudioDriver,
|
||||||
|
// until the CoUninitialize (to be more precise, the CoUninitialize for the
|
||||||
|
// last remaining MTA thread, but we need implicit MTA for the audio here).
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||||
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
XELOGE("XAudio 2.7 MTA thread CoInitializeEx failed with {:08X}", hr);
|
||||||
|
} else {
|
||||||
|
hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr,
|
||||||
|
CLSCTX_INPROC_SERVER,
|
||||||
|
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr);
|
||||||
|
} else {
|
||||||
|
hr = objects_.api_2_7.audio->Initialize(0, kProcessor);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
XELOGE("IXAudio2::Initialize failed with {:08X}", hr);
|
||||||
|
} else {
|
||||||
|
if (InitializeObjects(objects_.api_2_7)) {
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
// Initialized successfully, await a shutdown request while keeping an
|
||||||
|
// implicit COM MTA scope.
|
||||||
|
|
||||||
|
mta_thread_initialization_completion_result_ = true;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex>
|
||||||
|
mta_thread_initialization_completion_lock(
|
||||||
|
mta_thread_initialization_completion_mutex_);
|
||||||
|
mta_thread_initialization_attempt_completed_ = true;
|
||||||
|
}
|
||||||
|
mta_thread_initialization_completion_cond_.notify_all();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||||
|
mta_thread_shutdown_request_mutex_);
|
||||||
|
while (true) {
|
||||||
|
if (mta_thread_shutdown_requested_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mta_thread_shutdown_request_cond_.wait(
|
||||||
|
mta_thread_shutdown_request_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if InitializeObjects has failed, need to clean up with
|
||||||
|
// ShutdownObjects.
|
||||||
|
ShutdownObjects(objects_.api_2_7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CoUninitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
mta_thread_initialization_completion_result_ = false;
|
||||||
|
{
|
||||||
|
// Failed to initialize - notify the threads waiting for the
|
||||||
|
// initialization.
|
||||||
|
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||||
|
mta_thread_initialization_completion_mutex_);
|
||||||
|
mta_thread_initialization_attempt_completed_ = true;
|
||||||
|
}
|
||||||
|
mta_thread_initialization_completion_cond_.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace xaudio2
|
} // namespace xaudio2
|
||||||
} // namespace apu
|
} // namespace apu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -10,8 +10,13 @@
|
||||||
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||||
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "xenia/apu/audio_driver.h"
|
#include "xenia/apu/audio_driver.h"
|
||||||
#include "xenia/apu/xaudio2/xaudio2_api.h"
|
#include "xenia/apu/xaudio2/xaudio2_api.h"
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
struct IXAudio2;
|
struct IXAudio2;
|
||||||
|
@ -28,17 +33,45 @@ class XAudio2AudioDriver : public AudioDriver {
|
||||||
~XAudio2AudioDriver() override;
|
~XAudio2AudioDriver() override;
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
// Must not be called from COM STA threads as MTA XAudio 2.7 may be used. It's
|
||||||
|
// fine to call this from threads that have never initialized COM as
|
||||||
|
// initializing MTA for any thread implicitly initializes it for all threads
|
||||||
|
// not explicitly requesting STA (until COM is uninitialized all threads that
|
||||||
|
// have initialized MTA).
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||||
void SubmitFrame(uint32_t frame_ptr) override;
|
void SubmitFrame(uint32_t frame_ptr) override;
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too
|
||||||
|
// much time from other things. Ideally should process audio on what roughly
|
||||||
|
// represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or
|
||||||
|
// even beyond the 6 guest cores.
|
||||||
|
api::XAUDIO2_PROCESSOR kProcessor = 0x00000001;
|
||||||
|
|
||||||
|
// For XAudio 2.7, InitializeObjects and ShutdownObjects must be called only
|
||||||
|
// in the lifecycle management thread with COM MTA initialized.
|
||||||
template <typename Objects>
|
template <typename Objects>
|
||||||
bool InitializeObjects(Objects& objects);
|
bool InitializeObjects(Objects& objects);
|
||||||
template <typename Objects>
|
template <typename Objects>
|
||||||
void ShutdownObjects(Objects& objects);
|
void ShutdownObjects(Objects& objects);
|
||||||
|
|
||||||
|
void MTAThread();
|
||||||
|
|
||||||
void* xaudio2_module_ = nullptr;
|
void* xaudio2_module_ = nullptr;
|
||||||
uint32_t api_minor_version_ = 7;
|
uint32_t api_minor_version_ = 7;
|
||||||
|
|
||||||
|
bool mta_thread_initialization_completion_result_;
|
||||||
|
std::mutex mta_thread_initialization_completion_mutex_;
|
||||||
|
std::condition_variable mta_thread_initialization_completion_cond_;
|
||||||
|
bool mta_thread_initialization_attempt_completed_;
|
||||||
|
|
||||||
|
std::mutex mta_thread_shutdown_request_mutex_;
|
||||||
|
std::condition_variable mta_thread_shutdown_request_cond_;
|
||||||
|
bool mta_thread_shutdown_requested_;
|
||||||
|
|
||||||
|
std::thread mta_thread_;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
api::IXAudio2_7* audio;
|
api::IXAudio2_7* audio;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_BASE_CONSOLE_H_
|
||||||
|
#define XENIA_BASE_CONSOLE_H_
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
|
||||||
|
// Returns true if there is a user-visible console attached to receive stdout.
|
||||||
|
bool has_console_attached();
|
||||||
|
|
||||||
|
void AttachConsole();
|
||||||
|
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_BASE_CONSOLE_H_
|
|
@ -2,54 +2,50 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef XENIA_BASE_MAIN_H_
|
#ifndef XENIA_BASE_CONSOLE_APP_MAIN_H_
|
||||||
#define XENIA_BASE_MAIN_H_
|
#define XENIA_BASE_CONSOLE_APP_MAIN_H_
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/cvar.h"
|
|
||||||
#include "xenia/base/platform.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
// Returns true if there is a user-visible console attached to receive stdout.
|
|
||||||
bool has_console_attached();
|
|
||||||
|
|
||||||
void AttachConsole();
|
|
||||||
|
|
||||||
// Extern defined by user code. This must be present for the application to
|
// Extern defined by user code. This must be present for the application to
|
||||||
// launch.
|
// launch.
|
||||||
struct EntryInfo {
|
struct ConsoleAppEntryInfo {
|
||||||
std::string name;
|
std::string name;
|
||||||
int (*entry_point)(const std::vector<std::string>& args);
|
int (*entry_point)(const std::vector<std::string>& args);
|
||||||
bool transparent_options; // no argument parsing
|
bool transparent_options; // no argument parsing
|
||||||
std::optional<std::string> positional_usage;
|
std::string positional_usage;
|
||||||
std::optional<std::vector<std::string>> positional_options;
|
std::vector<std::string> positional_options;
|
||||||
};
|
};
|
||||||
EntryInfo GetEntryInfo();
|
ConsoleAppEntryInfo GetConsoleAppEntryInfo();
|
||||||
|
|
||||||
#define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \
|
// TODO(Triang3l): Multiple console app entry points on Android when a console
|
||||||
xe::EntryInfo xe::GetEntryInfo() { \
|
// activity is added. This is currently for individual executables running on
|
||||||
|
// Termux.
|
||||||
|
|
||||||
|
#define XE_DEFINE_CONSOLE_APP(name, entry_point, positional_usage, ...) \
|
||||||
|
xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \
|
||||||
std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \
|
std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \
|
||||||
return xe::EntryInfo{ \
|
return xe::ConsoleAppEntryInfo{ \
|
||||||
name, entry_point, false, positional_usage, \
|
name, entry_point, false, positional_usage, \
|
||||||
std::vector<std::string>(std::move(positional_options))}; \
|
std::vector<std::string>(std::move(positional_options))}; \
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Joel Linn): Add some way to filter consumed arguments in
|
// TODO(Joel Linn): Add some way to filter consumed arguments in
|
||||||
// cvar::ParseLaunchArguments()
|
// cvar::ParseLaunchArguments()
|
||||||
#define DEFINE_ENTRY_POINT_TRANSPARENT(name, entry_point) \
|
#define XE_DEFINE_CONSOLE_APP_TRANSPARENT(name, entry_point) \
|
||||||
xe::EntryInfo xe::GetEntryInfo() { \
|
xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \
|
||||||
return xe::EntryInfo{name, entry_point, true, std::nullopt, std::nullopt}; \
|
return xe::ConsoleAppEntryInfo{name, entry_point, true, std::string(), \
|
||||||
|
std::vector<std::string>()}; \
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_MAIN_H_
|
#endif // XENIA_BASE_CONSOLE_APP_MAIN_H_
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_posix.cc"
|
|
@ -7,42 +7,32 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <string>
|
||||||
#include <unistd.h>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
|
|
||||||
#include "xenia/base/filesystem.h"
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/string.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
|
|
||||||
bool has_console_attached() { return isatty(fileno(stdin)) == 1; }
|
|
||||||
|
|
||||||
void AttachConsole() {}
|
|
||||||
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
extern "C" int main(int argc, char** argv) {
|
extern "C" int main(int argc, char** argv) {
|
||||||
auto entry_info = xe::GetEntryInfo();
|
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||||
|
|
||||||
if (!entry_info.transparent_options) {
|
if (!entry_info.transparent_options) {
|
||||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||||
entry_info.positional_options.value());
|
entry_info.positional_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize logging. Needs parsed cvars.
|
||||||
|
xe::InitializeLogging(entry_info.name);
|
||||||
|
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
for (int n = 0; n < argc; n++) {
|
for (int n = 0; n < argc; n++) {
|
||||||
args.push_back(argv[n]);
|
args.emplace_back(argv[n]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize logging. Needs parsed FLAGS.
|
|
||||||
xe::InitializeLogging(entry_info.name);
|
|
||||||
|
|
||||||
// Call app-provided entry point.
|
|
||||||
int result = entry_info.entry_point(args);
|
int result = entry_info.entry_point(args);
|
||||||
|
|
||||||
|
xe::ShutdownLogging();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
|
#include "xenia/base/main_win.h"
|
||||||
|
|
||||||
|
int main(int argc_ignored, char** argv_ignored) {
|
||||||
|
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||||
|
|
||||||
|
std::vector<std::string> args;
|
||||||
|
if (!xe::ParseWin32LaunchArguments(entry_info.transparent_options,
|
||||||
|
entry_info.positional_usage,
|
||||||
|
entry_info.positional_options, &args)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = xe::InitializeWin32App(entry_info.name);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = entry_info.entry_point(args);
|
||||||
|
|
||||||
|
xe::ShutdownWin32App();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "xenia/base/console.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
|
||||||
|
bool has_console_attached() { return isatty(fileno(stdin)) == 1; }
|
||||||
|
|
||||||
|
void AttachConsole() {}
|
||||||
|
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "xenia/base/console.h"
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
|
||||||
|
// TODO(Triang3l): Set the default depending on the actual subsystem. Currently
|
||||||
|
// it inhibits message boxes.
|
||||||
|
static bool has_console_attached_ = true;
|
||||||
|
|
||||||
|
bool has_console_attached() { return has_console_attached_; }
|
||||||
|
|
||||||
|
static bool has_shell_environment_variable() {
|
||||||
|
size_t size = 0;
|
||||||
|
// Check if SHELL exists
|
||||||
|
// If it doesn't, then we are in a Windows Terminal
|
||||||
|
auto error = getenv_s(&size, nullptr, 0, "SHELL");
|
||||||
|
if (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !!size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachConsole() {
|
||||||
|
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||||
|
if (!has_console || !has_shell_environment_variable()) {
|
||||||
|
// We weren't launched from a console, so just return.
|
||||||
|
has_console_attached_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocConsole();
|
||||||
|
|
||||||
|
has_console_attached_ = true;
|
||||||
|
|
||||||
|
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||||
|
auto fp = _fdopen(con_handle, "w");
|
||||||
|
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||||
|
|
||||||
|
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||||
|
fp = _fdopen(con_handle, "w");
|
||||||
|
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xe
|
|
@ -14,8 +14,8 @@
|
||||||
#define UTF_CPP_CPLUSPLUS 201703L
|
#define UTF_CPP_CPLUSPLUS 201703L
|
||||||
#include "third_party/utfcpp/source/utf8.h"
|
#include "third_party/utfcpp/source/utf8.h"
|
||||||
|
|
||||||
|
#include "xenia/base/console.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/system.h"
|
#include "xenia/base/system.h"
|
||||||
|
|
||||||
namespace utfcpp = utf8;
|
namespace utfcpp = utf8;
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
#include "third_party/disruptorplus/include/disruptorplus/sequence_barrier.hpp"
|
#include "third_party/disruptorplus/include/disruptorplus/sequence_barrier.hpp"
|
||||||
#include "third_party/disruptorplus/include/disruptorplus/spin_wait_strategy.hpp"
|
#include "third_party/disruptorplus/include/disruptorplus/spin_wait_strategy.hpp"
|
||||||
#include "xenia/base/atomic.h"
|
#include "xenia/base/atomic.h"
|
||||||
|
#include "xenia/base/console.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/debugging.h"
|
#include "xenia/base/debugging.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/ring_buffer.h"
|
#include "xenia/base/ring_buffer.h"
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Nothing. Stub.
|
|
@ -2,13 +2,9 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
// Nothing. Stub.
|
// Nothing. Stub.
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/platform_win.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
// Includes Windows headers, so it goes after platform_win.h.
|
||||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
||||||
|
|
||||||
class StartupAvxCheck {
|
class StartupAvxCheck {
|
||||||
|
|
|
@ -7,14 +7,12 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <malloc.h>
|
||||||
#include <io.h>
|
#include <cstring>
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/main_win.h"
|
||||||
#include "xenia/base/platform_win.h"
|
#include "xenia/base/platform_win.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
|
@ -24,54 +22,11 @@
|
||||||
// For RequestHighPerformance.
|
// For RequestHighPerformance.
|
||||||
#include <winternl.h>
|
#include <winternl.h>
|
||||||
|
|
||||||
// Includes Windows headers, so it goes here.
|
|
||||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
|
||||||
|
|
||||||
DEFINE_bool(win32_high_freq, true,
|
DEFINE_bool(win32_high_freq, true,
|
||||||
"Requests high performance from the NT kernel", "Kernel");
|
"Requests high performance from the NT kernel", "Kernel");
|
||||||
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
|
||||||
"General");
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
bool has_console_attached_ = true;
|
|
||||||
|
|
||||||
bool has_console_attached() { return has_console_attached_; }
|
|
||||||
|
|
||||||
bool has_shell_environment_variable() {
|
|
||||||
size_t size = 0;
|
|
||||||
// Check if SHELL exists
|
|
||||||
// If it doesn't, then we are in a Windows Terminal
|
|
||||||
auto error = getenv_s(&size, nullptr, 0, "SHELL");
|
|
||||||
if (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !!size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AttachConsole() {
|
|
||||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
|
||||||
if (!has_console || !has_shell_environment_variable()) {
|
|
||||||
// We weren't launched from a console, so just return.
|
|
||||||
has_console_attached_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AllocConsole();
|
|
||||||
|
|
||||||
has_console_attached_ = true;
|
|
||||||
|
|
||||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
|
||||||
auto fp = _fdopen(con_handle, "w");
|
|
||||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
|
||||||
|
|
||||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
|
||||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
|
||||||
fp = _fdopen(con_handle, "w");
|
|
||||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RequestHighPerformance() {
|
static void RequestHighPerformance() {
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
NTSTATUS(*NtQueryTimerResolution)
|
NTSTATUS(*NtQueryTimerResolution)
|
||||||
|
@ -97,8 +52,10 @@ static void RequestHighPerformance() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
bool ParseWin32LaunchArguments(
|
||||||
std::vector<std::string>& args) {
|
bool transparent_options, const std::string_view positional_usage,
|
||||||
|
const std::vector<std::string>& positional_options,
|
||||||
|
std::vector<std::string>* args_out) {
|
||||||
auto command_line = GetCommandLineW();
|
auto command_line = GetCommandLineW();
|
||||||
|
|
||||||
int wargc;
|
int wargc;
|
||||||
|
@ -118,48 +75,24 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
||||||
|
|
||||||
LocalFree(wargv);
|
LocalFree(wargv);
|
||||||
|
|
||||||
if (!entry_info.transparent_options) {
|
if (!transparent_options) {
|
||||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
cvar::ParseLaunchArguments(argc, argv, positional_usage,
|
||||||
entry_info.positional_options.value());
|
positional_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.clear();
|
if (args_out) {
|
||||||
for (int n = 0; n < argc; n++) {
|
args_out->clear();
|
||||||
args.push_back(std::string(argv[n]));
|
for (int n = 0; n < argc; n++) {
|
||||||
|
args_out->push_back(std::string(argv[n]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Main() {
|
int InitializeWin32App(const std::string_view app_name) {
|
||||||
auto entry_info = xe::GetEntryInfo();
|
|
||||||
|
|
||||||
std::vector<std::string> args;
|
|
||||||
if (!parse_launch_arguments(entry_info, args)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach a console so we can write output to stdout. If the user hasn't
|
|
||||||
// redirected output themselves it'll pop up a window.
|
|
||||||
if (cvars::enable_console) {
|
|
||||||
xe::AttachConsole();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup COM on the main thread.
|
|
||||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
|
||||||
#pragma warning(suppress : 6031)
|
|
||||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
||||||
|
|
||||||
// Initialize logging. Needs parsed FLAGS.
|
// Initialize logging. Needs parsed FLAGS.
|
||||||
xe::InitializeLogging(entry_info.name);
|
xe::InitializeLogging(app_name);
|
||||||
|
|
||||||
Xbyak::util::Cpu cpu;
|
|
||||||
if (!cpu.has(Xbyak::util::Cpu::tAVX)) {
|
|
||||||
xe::FatalError(
|
|
||||||
"Your CPU does not support AVX, which is required by Xenia. See the "
|
|
||||||
"FAQ for system requirements at https://xenia.jp");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print version info.
|
// Print version info.
|
||||||
XELOGI(
|
XELOGI(
|
||||||
|
@ -175,38 +108,9 @@ int Main() {
|
||||||
RequestHighPerformance();
|
RequestHighPerformance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call app-provided entry point.
|
return 0;
|
||||||
int result = entry_info.entry_point(args);
|
|
||||||
|
|
||||||
xe::ShutdownLogging();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShutdownWin32App() { xe::ShutdownLogging(); }
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
// Used in console mode apps; automatically picked based on subsystem.
|
|
||||||
int main(int argc_ignored, char** argv_ignored) { return xe::Main(); }
|
|
||||||
|
|
||||||
// Used in windowed apps; automatically picked based on subsystem.
|
|
||||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
|
||||||
// Run normal entry point.
|
|
||||||
return xe::Main();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined _M_IX86
|
|
||||||
#pragma comment( \
|
|
||||||
linker, \
|
|
||||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
|
||||||
#elif defined _M_IA64
|
|
||||||
#pragma comment( \
|
|
||||||
linker, \
|
|
||||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
|
||||||
#elif defined _M_X64
|
|
||||||
#pragma comment( \
|
|
||||||
linker, \
|
|
||||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
|
||||||
#else
|
|
||||||
#pragma comment( \
|
|
||||||
linker, \
|
|
||||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_BASE_MAIN_WIN_H_
|
||||||
|
#define XENIA_BASE_MAIN_WIN_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
|
||||||
|
// Functions for calling in both windowed and console entry points.
|
||||||
|
bool ParseWin32LaunchArguments(
|
||||||
|
bool transparent_options, const std::string_view positional_usage,
|
||||||
|
const std::vector<std::string>& positional_options,
|
||||||
|
std::vector<std::string>* args_out);
|
||||||
|
// InitializeWin32App uses cvars, call ParseWin32LaunchArguments before.
|
||||||
|
int InitializeWin32App(const std::string_view app_name);
|
||||||
|
void ShutdownWin32App();
|
||||||
|
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_BASE_MAIN_WIN_H_
|
|
@ -11,7 +11,8 @@ project("xenia-base")
|
||||||
defines({
|
defines({
|
||||||
})
|
})
|
||||||
local_platform_files()
|
local_platform_files()
|
||||||
removefiles({"main_*.cc"})
|
removefiles({"console_app_main_*.cc"})
|
||||||
|
removefiles({"main_init_*.cc"})
|
||||||
files({
|
files({
|
||||||
"debug_visualizers.natvis",
|
"debug_visualizers.natvis",
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string_buffer.h"
|
#include "xenia/base/string_buffer.h"
|
||||||
|
@ -483,5 +483,5 @@ int main(const std::vector<std::string>& args) {
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
||||||
"test_name");
|
"test_name");
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/main.h"
|
||||||
|
@ -23,6 +25,7 @@ DEFINE_string(test_path, "src/xenia/cpu/ppc/testing/",
|
||||||
"Directory scanned for test files.");
|
"Directory scanned for test files.");
|
||||||
DEFINE_string(test_bin_path, "src/xenia/cpu/ppc/testing/bin/",
|
DEFINE_string(test_bin_path, "src/xenia/cpu/ppc/testing/bin/",
|
||||||
"Directory with binary outputs of the test files.");
|
"Directory with binary outputs of the test files.");
|
||||||
|
DEFINE_transient_string(test_name, "", "Test suite name.", "General");
|
||||||
|
|
||||||
extern "C" void xe_call_native(void* context, void* fn);
|
extern "C" void xe_call_native(void* context, void* fn);
|
||||||
|
|
||||||
|
@ -505,19 +508,13 @@ bool RunTests(const std::wstring& test_name) {
|
||||||
return failed_count ? false : true;
|
return failed_count ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(const std::vector<std::wstring>& args) {
|
int main(const std::vector<std::string>& args) {
|
||||||
// Grab test name, if present.
|
return RunTests(cvars::test_name) ? 0 : 1;
|
||||||
std::wstring test_name;
|
|
||||||
if (args.size() >= 2) {
|
|
||||||
test_name = args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return RunTests(test_name) ? 0 : 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT(L"xenia-cpu-ppc-test", L"xenia-cpu-ppc-test [test name]",
|
XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
||||||
xe::cpu::test::main);
|
"test_name");
|
||||||
|
|
|
@ -17,7 +17,7 @@ project("xenia-cpu-ppc-tests")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"ppc_testing_main.cc",
|
"ppc_testing_main.cc",
|
||||||
"../../../base/main_"..platform_suffix..".cc",
|
"../../../base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"*.s",
|
"*.s",
|
||||||
|
@ -46,7 +46,7 @@ project("xenia-cpu-ppc-nativetests")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"ppc_testing_native_main.cc",
|
"ppc_testing_native_main.cc",
|
||||||
"../../../base/main_"..platform_suffix..".cc",
|
"../../../base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"instr_*.s",
|
"instr_*.s",
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||||
#include "xenia/cpu/cpu.h"
|
#include "xenia/cpu/cpu.h"
|
||||||
#include "xenia/cpu/ppc/ppc_context.h"
|
#include "xenia/cpu/ppc/ppc_context.h"
|
||||||
|
@ -23,10 +23,10 @@ using xe::cpu::ppc::PPCContext;
|
||||||
|
|
||||||
// TODO(benvanik): simple memory? move more into core?
|
// TODO(benvanik): simple memory? move more into core?
|
||||||
|
|
||||||
int main(std::vector<std::wstring>& args) {
|
int main(std::vector<std::string>& args) {
|
||||||
#if XE_OPTION_PROFILING
|
#if XE_OPTION_PROFILING
|
||||||
xe::Profiler::Initialize();
|
xe::Profiler::Initialize();
|
||||||
xe::Profiler::ThreadEnter("main");
|
xe::Profiler::ThreadEnter("Main");
|
||||||
#endif // XE_OPTION_PROFILING
|
#endif // XE_OPTION_PROFILING
|
||||||
|
|
||||||
size_t memory_size = 16 * 1024 * 1024;
|
size_t memory_size = 16 * 1024 * 1024;
|
||||||
|
@ -79,4 +79,4 @@ int main(std::vector<std::wstring>& args) {
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT(L"xenia-cpu-sandbox", L"?", xe::cpu::sandbox::main);
|
XE_DEFINE_CONSOLE_APP("xenia-cpu-sandbox", xe::cpu::sandbox::main, "");
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||||
#include "xenia/cpu/hir/hir_builder.h"
|
#include "xenia/cpu/hir/hir_builder.h"
|
||||||
#include "xenia/cpu/ppc/ppc_context.h"
|
#include "xenia/cpu/ppc/ppc_context.h"
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
#include "xenia/ui/graphics_provider.h"
|
#include "xenia/ui/graphics_provider.h"
|
||||||
#include "xenia/ui/imgui_drawer.h"
|
#include "xenia/ui/imgui_drawer.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
|
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
|
||||||
|
|
||||||
|
@ -50,11 +51,12 @@ using xe::ui::UIEvent;
|
||||||
|
|
||||||
const std::string kBaseTitle = "Xenia Debugger";
|
const std::string kBaseTitle = "Xenia Debugger";
|
||||||
|
|
||||||
DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop)
|
DebugWindow::DebugWindow(Emulator* emulator,
|
||||||
|
xe::ui::WindowedAppContext& app_context)
|
||||||
: emulator_(emulator),
|
: emulator_(emulator),
|
||||||
processor_(emulator->processor()),
|
processor_(emulator->processor()),
|
||||||
loop_(loop),
|
app_context_(app_context),
|
||||||
window_(xe::ui::Window::Create(loop_, kBaseTitle)) {
|
window_(xe::ui::Window::Create(app_context_, kBaseTitle)) {
|
||||||
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||||
assert_always("Failed to initialize capstone");
|
assert_always("Failed to initialize capstone");
|
||||||
}
|
}
|
||||||
|
@ -63,16 +65,18 @@ DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop)
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugWindow::~DebugWindow() {
|
DebugWindow::~DebugWindow() {
|
||||||
loop_->PostSynchronous([this]() { window_.reset(); });
|
// Make sure pending functions referencing the DebugWindow are executed.
|
||||||
|
app_context_.ExecutePendingFunctionsFromUIThread();
|
||||||
|
|
||||||
if (capstone_handle_) {
|
if (capstone_handle_) {
|
||||||
cs_close(&capstone_handle_);
|
cs_close(&capstone_handle_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<DebugWindow> DebugWindow::Create(Emulator* emulator,
|
std::unique_ptr<DebugWindow> DebugWindow::Create(
|
||||||
xe::ui::Loop* loop) {
|
Emulator* emulator, xe::ui::WindowedAppContext& app_context) {
|
||||||
std::unique_ptr<DebugWindow> debug_window(new DebugWindow(emulator, loop));
|
std::unique_ptr<DebugWindow> debug_window(
|
||||||
|
new DebugWindow(emulator, app_context));
|
||||||
if (!debug_window->Initialize()) {
|
if (!debug_window->Initialize()) {
|
||||||
xe::FatalError("Failed to initialize debug window");
|
xe::FatalError("Failed to initialize debug window");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -87,8 +91,6 @@ bool DebugWindow::Initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); });
|
|
||||||
|
|
||||||
// Main menu.
|
// Main menu.
|
||||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||||
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
||||||
|
@ -1425,7 +1427,7 @@ void DebugWindow::UpdateCache() {
|
||||||
auto kernel_state = emulator_->kernel_state();
|
auto kernel_state = emulator_->kernel_state();
|
||||||
auto object_table = kernel_state->object_table();
|
auto object_table = kernel_state->object_table();
|
||||||
|
|
||||||
loop_->Post([this]() {
|
app_context_.CallInUIThread([this]() {
|
||||||
std::string title = kBaseTitle;
|
std::string title = kBaseTitle;
|
||||||
switch (processor_->execution_state()) {
|
switch (processor_->execution_state()) {
|
||||||
case cpu::ExecutionState::kEnded:
|
case cpu::ExecutionState::kEnded:
|
||||||
|
@ -1531,9 +1533,7 @@ Breakpoint* DebugWindow::LookupBreakpointAtAddress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::OnFocus() {
|
void DebugWindow::OnFocus() { Focus(); }
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugWindow::OnDetached() {
|
void DebugWindow::OnDetached() {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
|
@ -1546,30 +1546,34 @@ void DebugWindow::OnDetached() {
|
||||||
|
|
||||||
void DebugWindow::OnExecutionPaused() {
|
void DebugWindow::OnExecutionPaused() {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::OnExecutionContinued() {
|
void DebugWindow::OnExecutionContinued() {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::OnExecutionEnded() {
|
void DebugWindow::OnExecutionEnded() {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) {
|
void DebugWindow::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
SelectThreadStackFrame(thread_info, 0, true);
|
SelectThreadStackFrame(thread_info, 0, true);
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
||||||
cpu::ThreadDebugInfo* thread_info) {
|
cpu::ThreadDebugInfo* thread_info) {
|
||||||
UpdateCache();
|
UpdateCache();
|
||||||
SelectThreadStackFrame(thread_info, 0, true);
|
SelectThreadStackFrame(thread_info, 0, true);
|
||||||
loop_->Post([this]() { window_->set_focus(true); });
|
Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugWindow::Focus() const {
|
||||||
|
app_context_.CallInUIThread([this]() { window_->set_focus(true); });
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
#include "xenia/cpu/debug_listener.h"
|
#include "xenia/cpu/debug_listener.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
#include "xenia/ui/menu_item.h"
|
#include "xenia/ui/menu_item.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -31,11 +31,11 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
public:
|
public:
|
||||||
~DebugWindow();
|
~DebugWindow();
|
||||||
|
|
||||||
static std::unique_ptr<DebugWindow> Create(Emulator* emulator,
|
static std::unique_ptr<DebugWindow> Create(
|
||||||
xe::ui::Loop* loop);
|
Emulator* emulator, xe::ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
Emulator* emulator() const { return emulator_; }
|
||||||
xe::ui::Loop* loop() const { return loop_; }
|
xe::ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||||
xe::ui::Window* window() const { return window_.get(); }
|
xe::ui::Window* window() const { return window_.get(); }
|
||||||
|
|
||||||
void OnFocus() override;
|
void OnFocus() override;
|
||||||
|
@ -48,7 +48,8 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
cpu::ThreadDebugInfo* thread_info) override;
|
cpu::ThreadDebugInfo* thread_info) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
|
explicit DebugWindow(Emulator* emulator,
|
||||||
|
xe::ui::WindowedAppContext& app_context);
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
void DrawFrame();
|
void DrawFrame();
|
||||||
|
@ -86,9 +87,11 @@ class DebugWindow : public cpu::DebugListener {
|
||||||
cpu::Breakpoint* LookupBreakpointAtAddress(
|
cpu::Breakpoint* LookupBreakpointAtAddress(
|
||||||
cpu::Breakpoint::AddressType address_type, uint64_t address);
|
cpu::Breakpoint::AddressType address_type, uint64_t address);
|
||||||
|
|
||||||
|
void Focus() const;
|
||||||
|
|
||||||
Emulator* emulator_ = nullptr;
|
Emulator* emulator_ = nullptr;
|
||||||
cpu::Processor* processor_ = nullptr;
|
cpu::Processor* processor_ = nullptr;
|
||||||
xe::ui::Loop* loop_ = nullptr;
|
xe::ui::WindowedAppContext& app_context_;
|
||||||
std::unique_ptr<xe::ui::Window> window_;
|
std::unique_ptr<xe::ui::Window> window_;
|
||||||
uint64_t last_draw_tick_count_ = 0;
|
uint64_t last_draw_tick_count_ = 0;
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/vfs/devices/disc_image_device.h"
|
#include "xenia/vfs/devices/disc_image_device.h"
|
||||||
#include "xenia/vfs/devices/host_path_device.h"
|
#include "xenia/vfs/devices/host_path_device.h"
|
||||||
#include "xenia/vfs/devices/null_device.h"
|
#include "xenia/vfs/devices/null_device.h"
|
||||||
|
@ -233,7 +235,7 @@ X_STATUS Emulator::Setup(
|
||||||
|
|
||||||
if (display_window_) {
|
if (display_window_) {
|
||||||
// Finish initializing the display.
|
// Finish initializing the display.
|
||||||
display_window_->loop()->PostSynchronous([this]() {
|
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
||||||
Profiler::set_window(display_window_);
|
Profiler::set_window(display_window_);
|
||||||
});
|
});
|
||||||
|
@ -583,7 +585,7 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display a dialog telling the user the guest has crashed.
|
// Display a dialog telling the user the guest has crashed.
|
||||||
display_window()->loop()->PostSynchronous([&]() {
|
display_window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||||
display_window(), "Uh-oh!",
|
display_window(), "Uh-oh!",
|
||||||
"The guest has crashed.\n\n"
|
"The guest has crashed.\n\n"
|
||||||
|
|
|
@ -49,7 +49,7 @@ std::string D3D12GraphicsSystem::name() const {
|
||||||
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::Window* target_window) {
|
||||||
provider_ = xe::ui::d3d12::D3D12Provider::Create(target_window);
|
provider_ = xe::ui::d3d12::D3D12Provider::Create();
|
||||||
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
|
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
|
||||||
auto device = d3d12_provider->GetDevice();
|
auto device = d3d12_provider->GetDevice();
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||||
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
||||||
#include "xenia/gpu/trace_dump.h"
|
#include "xenia/gpu/trace_dump.h"
|
||||||
|
@ -54,6 +54,6 @@ int trace_dump_main(const std::vector<std::string>& args) {
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-dump",
|
XE_DEFINE_CONSOLE_APP("xenia-gpu-d3d12-trace-dump",
|
||||||
xe::gpu::d3d12::trace_dump_main, "some.trace",
|
xe::gpu::d3d12::trace_dump_main, "some.trace",
|
||||||
"target_trace_file");
|
"target_trace_file");
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||||
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
||||||
#include "xenia/gpu/trace_viewer.h"
|
#include "xenia/gpu/trace_viewer.h"
|
||||||
|
@ -17,10 +19,13 @@ namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace d3d12 {
|
namespace d3d12 {
|
||||||
|
|
||||||
using namespace xe::gpu::xenos;
|
class D3D12TraceViewer final : public TraceViewer {
|
||||||
|
|
||||||
class D3D12TraceViewer : public TraceViewer {
|
|
||||||
public:
|
public:
|
||||||
|
static std::unique_ptr<WindowedApp> Create(
|
||||||
|
xe::ui::WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<WindowedApp>(new D3D12TraceViewer(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() override {
|
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() override {
|
||||||
return std::unique_ptr<gpu::GraphicsSystem>(new D3D12GraphicsSystem());
|
return std::unique_ptr<gpu::GraphicsSystem>(new D3D12GraphicsSystem());
|
||||||
}
|
}
|
||||||
|
@ -45,17 +50,15 @@ class D3D12TraceViewer : public TraceViewer {
|
||||||
// TextureInfo/SamplerInfo which are going away.
|
// TextureInfo/SamplerInfo which are going away.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
int trace_viewer_main(const std::vector<std::string>& args) {
|
private:
|
||||||
D3D12TraceViewer trace_viewer;
|
explicit D3D12TraceViewer(xe::ui::WindowedAppContext& app_context)
|
||||||
return trace_viewer.Main(args);
|
: TraceViewer(app_context, "xenia-gpu-d3d12-trace-viewer") {}
|
||||||
}
|
};
|
||||||
|
|
||||||
} // namespace d3d12
|
} // namespace d3d12
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-viewer",
|
XE_DEFINE_WINDOWED_APP(xenia_gpu_d3d12_trace_viewer,
|
||||||
xe::gpu::d3d12::trace_viewer_main, "some.trace",
|
xe::gpu::d3d12::D3D12TraceViewer::Create);
|
||||||
"target_trace_file");
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ project("xenia-gpu-d3d12-trace-viewer")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"d3d12_trace_viewer_main.cc",
|
"d3d12_trace_viewer_main.cc",
|
||||||
"../../base/main_"..platform_suffix..".cc",
|
"../../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
-- Only create the .user file if it doesn't already exist.
|
-- Only create the .user file if it doesn't already exist.
|
||||||
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-viewer.vcxproj.user"
|
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-viewer.vcxproj.user"
|
||||||
|
@ -101,7 +101,7 @@ project("xenia-gpu-d3d12-trace-dump")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"d3d12_trace_dump_main.cc",
|
"d3d12_trace_dump_main.cc",
|
||||||
"../../base/main_"..platform_suffix..".cc",
|
"../../base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
-- Only create the .user file if it doesn't already exist.
|
-- Only create the .user file if it doesn't already exist.
|
||||||
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-dump.vcxproj.user"
|
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-dump.vcxproj.user"
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
#include "xenia/gpu/command_processor.h"
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/gpu_flags.h"
|
#include "xenia/gpu/gpu_flags.h"
|
||||||
#include "xenia/ui/graphics_provider.h"
|
#include "xenia/ui/graphics_provider.h"
|
||||||
#include "xenia/ui/loop.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
DEFINE_bool(
|
DEFINE_bool(
|
||||||
store_shaders, true,
|
store_shaders, true,
|
||||||
|
@ -57,22 +58,24 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
// This must happen on the UI thread.
|
// This must happen on the UI thread.
|
||||||
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
|
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
|
||||||
if (provider_) {
|
if (provider_) {
|
||||||
if (target_window_) {
|
// Setup the context the command processor will do all its drawing in.
|
||||||
target_window_->loop()->PostSynchronous([&]() {
|
bool contexts_initialized = true;
|
||||||
// Create the context used for presentation.
|
processor_context = provider()->CreateOffscreenContext();
|
||||||
assert_null(target_window->context());
|
if (processor_context) {
|
||||||
target_window_->set_context(provider_->CreateContext(target_window_));
|
if (target_window_) {
|
||||||
|
if (!target_window_->app_context().CallInUIThreadSynchronous([&]() {
|
||||||
// Setup the context the command processor will do all its drawing in.
|
// Create the context used for presentation.
|
||||||
// It's shared with the display context so that we can resolve
|
assert_null(target_window->context());
|
||||||
// framebuffers from it.
|
target_window_->set_context(
|
||||||
processor_context = provider()->CreateOffscreenContext();
|
provider_->CreateContext(target_window_));
|
||||||
});
|
})) {
|
||||||
|
contexts_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
processor_context = provider()->CreateOffscreenContext();
|
contexts_initialized = false;
|
||||||
}
|
}
|
||||||
|
if (!contexts_initialized) {
|
||||||
if (!processor_context) {
|
|
||||||
xe::FatalError(
|
xe::FatalError(
|
||||||
"Unable to initialize graphics context. Xenia requires Vulkan "
|
"Unable to initialize graphics context. Xenia requires Vulkan "
|
||||||
"support.\n"
|
"support.\n"
|
||||||
|
|
|
@ -26,7 +26,7 @@ X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
ui::Window* target_window) {
|
ui::Window* target_window) {
|
||||||
// This is a null graphics system, but we still setup vulkan because UI needs
|
// This is a null graphics system, but we still setup vulkan because UI needs
|
||||||
// it through us :|
|
// it through us :|
|
||||||
provider_ = xe::ui::vulkan::VulkanProvider::Create(target_window);
|
provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||||
|
|
||||||
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ project("xenia-gpu-shader-compiler")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"shader_compiler_main.cc",
|
"shader_compiler_main.cc",
|
||||||
"../base/main_"..platform_suffix..".cc",
|
"../base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
|
|
||||||
filter("platforms:Windows")
|
filter("platforms:Windows")
|
||||||
|
|
|
@ -13,9 +13,9 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/base/string_buffer.h"
|
#include "xenia/base/string_buffer.h"
|
||||||
|
@ -229,5 +229,6 @@ int shader_compiler_main(const std::vector<std::string>& args) {
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-gpu-shader-compiler", xe::gpu::shader_compiler_main,
|
XE_DEFINE_CONSOLE_APP("xenia-gpu-shader-compiler",
|
||||||
"shader.bin", "shader_input");
|
xe::gpu::shader_compiler_main, "shader.bin",
|
||||||
|
"shader_input");
|
||||||
|
|
|
@ -100,7 +100,7 @@ bool TraceDump::Setup() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
graphics_system_ = emulator_->graphics_system();
|
graphics_system_ = emulator_->graphics_system();
|
||||||
player_ = std::make_unique<TracePlayer>(nullptr, graphics_system_);
|
player_ = std::make_unique<TracePlayer>(graphics_system_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,8 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
TracePlayer::TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system)
|
TracePlayer::TracePlayer(GraphicsSystem* graphics_system)
|
||||||
: loop_(loop),
|
: graphics_system_(graphics_system),
|
||||||
graphics_system_(graphics_system),
|
|
||||||
current_frame_index_(0),
|
current_frame_index_(0),
|
||||||
current_command_index_(-1) {
|
current_command_index_(-1) {
|
||||||
// Need to allocate all of physical memory so that we can write to it during
|
// Need to allocate all of physical memory so that we can write to it during
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/gpu/trace_protocol.h"
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
#include "xenia/gpu/trace_reader.h"
|
#include "xenia/gpu/trace_reader.h"
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -30,7 +29,7 @@ enum class TracePlaybackMode {
|
||||||
|
|
||||||
class TracePlayer : public TraceReader {
|
class TracePlayer : public TraceReader {
|
||||||
public:
|
public:
|
||||||
TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system);
|
TracePlayer(GraphicsSystem* graphics_system);
|
||||||
~TracePlayer() override;
|
~TracePlayer() override;
|
||||||
|
|
||||||
GraphicsSystem* graphics_system() const { return graphics_system_; }
|
GraphicsSystem* graphics_system() const { return graphics_system_; }
|
||||||
|
@ -54,7 +53,6 @@ class TracePlayer : public TraceReader {
|
||||||
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
||||||
TracePlaybackMode playback_mode, bool clear_caches);
|
TracePlaybackMode playback_mode, bool clear_caches);
|
||||||
|
|
||||||
xe::ui::Loop* loop_;
|
|
||||||
GraphicsSystem* graphics_system_;
|
GraphicsSystem* graphics_system_;
|
||||||
int current_frame_index_;
|
int current_frame_index_;
|
||||||
int current_command_index_;
|
int current_command_index_;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -13,10 +13,12 @@
|
||||||
|
|
||||||
#include "third_party/half/include/half.hpp"
|
#include "third_party/half/include/half.hpp"
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
#include "xenia/base/system.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/gpu/command_processor.h"
|
#include "xenia/gpu/command_processor.h"
|
||||||
#include "xenia/gpu/gpu_flags.h"
|
#include "xenia/gpu/gpu_flags.h"
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
#include "xenia/ui/imgui_drawer.h"
|
#include "xenia/ui/imgui_drawer.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
DEFINE_path(target_trace_file, "", "Specifies the trace file to load.", "GPU");
|
DEFINE_path(target_trace_file, "", "Specifies the trace file to load.", "GPU");
|
||||||
|
@ -46,22 +49,16 @@ static const ImVec4 kColorComment =
|
||||||
static const ImVec4 kColorIgnored =
|
static const ImVec4 kColorIgnored =
|
||||||
ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f);
|
ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f);
|
||||||
|
|
||||||
TraceViewer::TraceViewer() = default;
|
TraceViewer::TraceViewer(xe::ui::WindowedAppContext& app_context,
|
||||||
|
const std::string_view name)
|
||||||
|
: xe::ui::WindowedApp(app_context, name, "some.trace") {
|
||||||
|
AddPositionalOption("target_trace_file");
|
||||||
|
}
|
||||||
|
|
||||||
TraceViewer::~TraceViewer() = default;
|
TraceViewer::~TraceViewer() = default;
|
||||||
|
|
||||||
int TraceViewer::Main(const std::vector<std::string>& args) {
|
bool TraceViewer::OnInitialize() {
|
||||||
// Grab path from the flag or unnamed argument.
|
std::filesystem::path path = cvars::target_trace_file;
|
||||||
std::filesystem::path path;
|
|
||||||
if (!cvars::target_trace_file.empty()) {
|
|
||||||
// Passed as a named argument.
|
|
||||||
// TODO(benvanik): find something better than gflags that supports
|
|
||||||
// unicode.
|
|
||||||
path = cvars::target_trace_file;
|
|
||||||
} else if (args.size() >= 2) {
|
|
||||||
// Passed as an unnamed argument.
|
|
||||||
path = xe::to_path(args[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no path passed, ask the user.
|
// If no path passed, ask the user.
|
||||||
if (path.empty()) {
|
if (path.empty()) {
|
||||||
|
@ -83,42 +80,37 @@ int TraceViewer::Main(const std::vector<std::string>& args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.empty()) {
|
if (path.empty()) {
|
||||||
xe::FatalError("No trace file specified");
|
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Warning,
|
||||||
return 1;
|
"No trace file specified");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the path and make absolute.
|
// Normalize the path and make absolute.
|
||||||
auto abs_path = std::filesystem::absolute(path);
|
auto abs_path = std::filesystem::absolute(path);
|
||||||
|
|
||||||
if (!Setup()) {
|
if (!Setup()) {
|
||||||
xe::FatalError("Unable to setup trace viewer");
|
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error,
|
||||||
return 1;
|
"Unable to setup trace viewer");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (!Load(std::move(abs_path))) {
|
if (!Load(std::move(abs_path))) {
|
||||||
xe::FatalError("Unable to load trace file; not found?");
|
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error,
|
||||||
return 1;
|
"Unable to load trace file; not found?");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
Run();
|
return true;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TraceViewer::Setup() {
|
bool TraceViewer::Setup() {
|
||||||
// Main display window.
|
// Main display window.
|
||||||
loop_ = ui::Loop::Create();
|
assert_true(app_context().IsInUIThread());
|
||||||
window_ = xe::ui::Window::Create(loop_.get(), "xenia-gpu-trace-viewer");
|
window_ = xe::ui::Window::Create(app_context(), "xenia-gpu-trace-viewer");
|
||||||
loop_->PostSynchronous([&]() {
|
if (!window_->Initialize()) {
|
||||||
xe::threading::set_name("Win32 Loop");
|
XELOGE("Failed to initialize main window");
|
||||||
if (!window_->Initialize()) {
|
return false;
|
||||||
xe::FatalError("Failed to initialize main window");
|
}
|
||||||
return;
|
window_->on_closed.AddListener(
|
||||||
}
|
[this](xe::ui::UIEvent* e) { app_context().QuitFromUIThread(); });
|
||||||
});
|
|
||||||
window_->on_closed.AddListener([&](xe::ui::UIEvent* e) {
|
|
||||||
loop_->Quit();
|
|
||||||
XELOGI("User-initiated death!");
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
loop_->on_quit.AddListener([&](xe::ui::UIEvent* e) { window_.reset(); });
|
|
||||||
window_->Resize(1920, 1200);
|
window_->Resize(1920, 1200);
|
||||||
|
|
||||||
// Create the emulator but don't initialize so we can setup the window.
|
// Create the emulator but don't initialize so we can setup the window.
|
||||||
|
@ -142,9 +134,9 @@ bool TraceViewer::Setup() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
player_ = std::make_unique<TracePlayer>(loop_.get(), graphics_system_);
|
player_ = std::make_unique<TracePlayer>(graphics_system_);
|
||||||
|
|
||||||
window_->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||||
DrawUI();
|
DrawUI();
|
||||||
|
|
||||||
// Continuous paint.
|
// Continuous paint.
|
||||||
|
@ -167,16 +159,6 @@ bool TraceViewer::Load(const std::filesystem::path& trace_file_path) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TraceViewer::Run() {
|
|
||||||
// Wait until we are exited.
|
|
||||||
loop_->AwaitQuit();
|
|
||||||
|
|
||||||
player_.reset();
|
|
||||||
emulator_.reset();
|
|
||||||
window_.reset();
|
|
||||||
loop_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TraceViewer::DrawMultilineString(const std::string_view str) {
|
void TraceViewer::DrawMultilineString(const std::string_view str) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
bool done = false;
|
bool done = false;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -18,13 +18,8 @@
|
||||||
#include "xenia/gpu/trace_protocol.h"
|
#include "xenia/gpu/trace_protocol.h"
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/memory.h"
|
#include "xenia/memory.h"
|
||||||
|
#include "xenia/ui/window.h"
|
||||||
namespace xe {
|
#include "xenia/ui/windowed_app.h"
|
||||||
namespace ui {
|
|
||||||
class Loop;
|
|
||||||
class Window;
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
@ -32,14 +27,15 @@ namespace gpu {
|
||||||
struct SamplerInfo;
|
struct SamplerInfo;
|
||||||
struct TextureInfo;
|
struct TextureInfo;
|
||||||
|
|
||||||
class TraceViewer {
|
class TraceViewer : public xe::ui::WindowedApp {
|
||||||
public:
|
public:
|
||||||
virtual ~TraceViewer();
|
virtual ~TraceViewer();
|
||||||
|
|
||||||
int Main(const std::vector<std::string>& args);
|
bool OnInitialize() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TraceViewer();
|
explicit TraceViewer(xe::ui::WindowedAppContext& app_context,
|
||||||
|
const std::string_view name);
|
||||||
|
|
||||||
virtual std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() = 0;
|
virtual std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() = 0;
|
||||||
|
|
||||||
|
@ -60,7 +56,6 @@ class TraceViewer {
|
||||||
|
|
||||||
virtual bool Setup();
|
virtual bool Setup();
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::Loop> loop_;
|
|
||||||
std::unique_ptr<xe::ui::Window> window_;
|
std::unique_ptr<xe::ui::Window> window_;
|
||||||
std::unique_ptr<Emulator> emulator_;
|
std::unique_ptr<Emulator> emulator_;
|
||||||
Memory* memory_ = nullptr;
|
Memory* memory_ = nullptr;
|
||||||
|
@ -75,7 +70,6 @@ class TraceViewer {
|
||||||
};
|
};
|
||||||
|
|
||||||
bool Load(const std::filesystem::path& trace_file_path);
|
bool Load(const std::filesystem::path& trace_file_path);
|
||||||
void Run();
|
|
||||||
|
|
||||||
void DrawUI();
|
void DrawUI();
|
||||||
void DrawControllerUI();
|
void DrawControllerUI();
|
||||||
|
|
|
@ -62,7 +62,7 @@ project("xenia-gpu-vulkan-trace-viewer")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"vulkan_trace_viewer_main.cc",
|
"vulkan_trace_viewer_main.cc",
|
||||||
"../../base/main_"..platform_suffix..".cc",
|
"../../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
|
|
||||||
filter("platforms:Linux")
|
filter("platforms:Linux")
|
||||||
|
@ -128,7 +128,7 @@ project("xenia-gpu-vulkan-trace-dump")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"vulkan_trace_dump_main.cc",
|
"vulkan_trace_dump_main.cc",
|
||||||
"../../base/main_"..platform_suffix..".cc",
|
"../../base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
|
|
||||||
filter("platforms:Linux")
|
filter("platforms:Linux")
|
||||||
|
|
|
@ -37,7 +37,7 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
kernel::KernelState* kernel_state,
|
kernel::KernelState* kernel_state,
|
||||||
ui::Window* target_window) {
|
ui::Window* target_window) {
|
||||||
// Must create the provider so we can create contexts.
|
// Must create the provider so we can create contexts.
|
||||||
auto provider = xe::ui::vulkan::VulkanProvider::Create(target_window);
|
auto provider = xe::ui::vulkan::VulkanProvider::Create();
|
||||||
device_ = provider->device();
|
device_ = provider->device();
|
||||||
provider_ = std::move(provider);
|
provider_ = std::move(provider);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/gpu/trace_dump.h"
|
#include "xenia/gpu/trace_dump.h"
|
||||||
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
||||||
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
|
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
|
||||||
|
@ -55,6 +55,6 @@ int trace_dump_main(const std::vector<std::string>& args) {
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-gpu-vulkan-trace-dump",
|
XE_DEFINE_CONSOLE_APP("xenia-gpu-vulkan-trace-dump",
|
||||||
xe::gpu::vulkan::trace_dump_main, "some.trace",
|
xe::gpu::vulkan::trace_dump_main, "some.trace",
|
||||||
"target_trace_file");
|
"target_trace_file");
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/gpu/trace_viewer.h"
|
#include "xenia/gpu/trace_viewer.h"
|
||||||
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
||||||
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
|
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
|
||||||
|
@ -19,8 +21,13 @@ namespace vulkan {
|
||||||
|
|
||||||
using namespace xe::gpu::xenos;
|
using namespace xe::gpu::xenos;
|
||||||
|
|
||||||
class VulkanTraceViewer : public TraceViewer {
|
class VulkanTraceViewer final : public TraceViewer {
|
||||||
public:
|
public:
|
||||||
|
static std::unique_ptr<WindowedApp> Create(
|
||||||
|
xe::ui::WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<WindowedApp>(new VulkanTraceViewer(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() override {
|
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() override {
|
||||||
return std::unique_ptr<gpu::GraphicsSystem>(new VulkanGraphicsSystem());
|
return std::unique_ptr<gpu::GraphicsSystem>(new VulkanGraphicsSystem());
|
||||||
}
|
}
|
||||||
|
@ -60,17 +67,15 @@ class VulkanTraceViewer : public TraceViewer {
|
||||||
// return static_cast<uintptr_t>(texture->handle);
|
// return static_cast<uintptr_t>(texture->handle);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
int trace_viewer_main(const std::vector<std::string>& args) {
|
private:
|
||||||
VulkanTraceViewer trace_viewer;
|
explicit VulkanTraceViewer(xe::ui::WindowedAppContext& app_context)
|
||||||
return trace_viewer.Main(args);
|
: TraceViewer(app_context, "xenia-gpu-vulkan-trace-viewer") {}
|
||||||
}
|
};
|
||||||
|
|
||||||
} // namespace vulkan
|
} // namespace vulkan
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-gpu-vulkan-trace-viewer",
|
XE_DEFINE_WINDOWED_APP(xenia_gpu_vulkan_trace_viewer,
|
||||||
xe::gpu::vulkan::trace_viewer_main, "some.trace",
|
xe::gpu::vulkan::VulkanTraceViewer::Create);
|
||||||
"target_trace_file");
|
|
||||||
|
|
|
@ -10,15 +10,17 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/hid/hid_flags.h"
|
#include "xenia/hid/hid_flags.h"
|
||||||
#include "xenia/hid/input_system.h"
|
#include "xenia/hid/input_system.h"
|
||||||
|
@ -26,6 +28,7 @@
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
// Available input drivers:
|
// Available input drivers:
|
||||||
#include "xenia/hid/nop/nop_hid.h"
|
#include "xenia/hid/nop/nop_hid.h"
|
||||||
|
@ -46,10 +49,36 @@ DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace hid {
|
namespace hid {
|
||||||
|
|
||||||
std::unique_ptr<xe::hid::InputSystem> input_system_;
|
class HidDemoApp final : public ui::WindowedApp {
|
||||||
bool is_active = true;
|
public:
|
||||||
|
static std::unique_ptr<ui::WindowedApp> Create(
|
||||||
|
ui::WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<ui::WindowedApp>(new HidDemoApp(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
bool OnInitialize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit HidDemoApp(ui::WindowedAppContext& app_context)
|
||||||
|
: ui::WindowedApp(app_context, "xenia-hid-demo") {}
|
||||||
|
|
||||||
|
static std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||||
|
ui::Window* window);
|
||||||
|
|
||||||
|
void DrawUserInputGetState(uint32_t user_index) const;
|
||||||
|
void DrawInputGetState() const;
|
||||||
|
void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||||
|
bool hide_repeats, bool clear_log) const;
|
||||||
|
void DrawInputGetKeystroke(bool poll, bool hide_repeats,
|
||||||
|
bool clear_log) const;
|
||||||
|
|
||||||
|
std::unique_ptr<ui::GraphicsProvider> graphics_provider_;
|
||||||
|
std::unique_ptr<ui::Window> window_;
|
||||||
|
std::unique_ptr<InputSystem> input_system_;
|
||||||
|
bool is_active_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<hid::InputDriver>> HidDemoApp::CreateInputDrivers(
|
||||||
ui::Window* window) {
|
ui::Window* window) {
|
||||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||||
if (cvars::hid.compare("nop") == 0) {
|
if (cvars::hid.compare("nop") == 0) {
|
||||||
|
@ -94,60 +123,46 @@ std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||||
return drivers;
|
return drivers;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::GraphicsProvider> CreateDemoGraphicsProvider(
|
bool HidDemoApp::OnInitialize() {
|
||||||
xe::ui::Window* window) {
|
// Create graphics provider that provides the context for the window.
|
||||||
return xe::ui::vulkan::VulkanProvider::Create(window);
|
graphics_provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||||
}
|
if (!graphics_provider_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void DrawInputGetState();
|
// Create the window.
|
||||||
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log);
|
window_ = xe::ui::Window::Create(app_context(), GetName());
|
||||||
|
if (!window_->Initialize()) {
|
||||||
int hid_demo_main(const std::vector<std::string>& args) {
|
XELOGE("Failed to initialize main window");
|
||||||
// Create run loop and the window.
|
return false;
|
||||||
auto loop = ui::Loop::Create();
|
}
|
||||||
auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name);
|
window_->on_closed.AddListener([this](xe::ui::UIEvent* e) {
|
||||||
loop->PostSynchronous([&window]() {
|
|
||||||
xe::threading::set_name("Win32 Loop");
|
|
||||||
if (!window->Initialize()) {
|
|
||||||
FatalError("Failed to initialize main window");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) {
|
|
||||||
loop->Quit();
|
|
||||||
XELOGI("User-initiated death!");
|
XELOGI("User-initiated death!");
|
||||||
exit(1);
|
app_context().QuitFromUIThread();
|
||||||
});
|
});
|
||||||
loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); });
|
|
||||||
|
|
||||||
// Initial size setting, done here so that it knows the menu exists.
|
// Initial size setting, done here so that it knows the menu exists.
|
||||||
window->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500);
|
window_->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500);
|
||||||
|
|
||||||
// Create the graphics context used for drawing and setup the window.
|
// Create the graphics context for the window. The window will finish
|
||||||
std::unique_ptr<xe::ui::GraphicsProvider> graphics_provider;
|
// initialization with the context (loading resources, etc).
|
||||||
loop->PostSynchronous([&window, &graphics_provider]() {
|
window_->set_context(graphics_provider_->CreateContext(window_.get()));
|
||||||
// Create context and give it to the window.
|
|
||||||
// The window will finish initialization wtih the context (loading
|
|
||||||
// resources, etc).
|
|
||||||
graphics_provider = CreateDemoGraphicsProvider(window.get());
|
|
||||||
window->set_context(graphics_provider->CreateContext(window.get()));
|
|
||||||
|
|
||||||
// Initialize input system and all drivers.
|
// Initialize input system and all drivers.
|
||||||
input_system_ = std::make_unique<xe::hid::InputSystem>(window.get());
|
input_system_ = std::make_unique<xe::hid::InputSystem>(window_.get());
|
||||||
auto drivers = CreateInputDrivers(window.get());
|
auto drivers = CreateInputDrivers(window_.get());
|
||||||
for (size_t i = 0; i < drivers.size(); ++i) {
|
for (size_t i = 0; i < drivers.size(); ++i) {
|
||||||
auto& driver = drivers[i];
|
auto& driver = drivers[i];
|
||||||
driver->set_is_active_callback([]() -> bool { return is_active; });
|
driver->set_is_active_callback([this]() -> bool { return is_active_; });
|
||||||
input_system_->AddDriver(std::move(driver));
|
input_system_->AddDriver(std::move(driver));
|
||||||
}
|
}
|
||||||
|
|
||||||
window->Invalidate();
|
window_->Invalidate();
|
||||||
});
|
|
||||||
|
|
||||||
window->set_imgui_input_enabled(true);
|
window_->set_imgui_input_enabled(true);
|
||||||
|
|
||||||
window->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||||
auto& io = window->imgui_drawer()->GetIO();
|
auto& io = window_->imgui_drawer()->GetIO();
|
||||||
|
|
||||||
const ImGuiWindowFlags wflags =
|
const ImGuiWindowFlags wflags =
|
||||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||||
|
@ -161,7 +176,7 @@ int hid_demo_main(const std::vector<std::string>& args) {
|
||||||
ImVec2(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL));
|
ImVec2(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL));
|
||||||
|
|
||||||
ImGui::Text("Input System (hid) = \"%s\"", cvars::hid.c_str());
|
ImGui::Text("Input System (hid) = \"%s\"", cvars::hid.c_str());
|
||||||
ImGui::Checkbox("is_active", &is_active);
|
ImGui::Checkbox("is_active", &is_active_);
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
@ -201,21 +216,13 @@ int hid_demo_main(const std::vector<std::string>& args) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
// Continuous paint.
|
// Continuous paint.
|
||||||
window->Invalidate();
|
window_->Invalidate();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait until we are exited.
|
return true;
|
||||||
loop->AwaitQuit();
|
|
||||||
|
|
||||||
input_system_.reset();
|
|
||||||
|
|
||||||
loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); });
|
|
||||||
window.reset();
|
|
||||||
loop.reset();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawUserInputGetState(uint32_t user_index) {
|
void HidDemoApp::DrawUserInputGetState(uint32_t user_index) const {
|
||||||
ImGui::Text("User %u:", user_index);
|
ImGui::Text("User %u:", user_index);
|
||||||
|
|
||||||
X_INPUT_STATE state;
|
X_INPUT_STATE state;
|
||||||
|
@ -274,7 +281,7 @@ void DrawUserInputGetState(uint32_t user_index) {
|
||||||
ImGui::Text(" ");
|
ImGui::Text(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawInputGetState() {
|
void HidDemoApp::DrawInputGetState() const {
|
||||||
ImGui::BeginChild("##input_get_state_scroll");
|
ImGui::BeginChild("##input_get_state_scroll");
|
||||||
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
||||||
DrawUserInputGetState(user_index);
|
DrawUserInputGetState(user_index);
|
||||||
|
@ -282,46 +289,48 @@ void DrawInputGetState() {
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty = {
|
void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||||
{ui::VirtualKey::kXInputPadA, "A"},
|
bool hide_repeats,
|
||||||
{ui::VirtualKey::kXInputPadB, "B"},
|
bool clear_log) const {
|
||||||
{ui::VirtualKey::kXInputPadX, "X"},
|
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
|
||||||
{ui::VirtualKey::kXInputPadY, "Y"},
|
{
|
||||||
{ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"},
|
{ui::VirtualKey::kXInputPadA, "A"},
|
||||||
{ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"},
|
{ui::VirtualKey::kXInputPadB, "B"},
|
||||||
{ui::VirtualKey::kXInputPadLTrigger, "L Trigger"},
|
{ui::VirtualKey::kXInputPadX, "X"},
|
||||||
{ui::VirtualKey::kXInputPadRTrigger, "R Trigger"},
|
{ui::VirtualKey::kXInputPadY, "Y"},
|
||||||
|
{ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"},
|
||||||
|
{ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"},
|
||||||
|
{ui::VirtualKey::kXInputPadLTrigger, "L Trigger"},
|
||||||
|
{ui::VirtualKey::kXInputPadRTrigger, "R Trigger"},
|
||||||
|
|
||||||
{ui::VirtualKey::kXInputPadDpadUp, "DPad up"},
|
{ui::VirtualKey::kXInputPadDpadUp, "DPad up"},
|
||||||
{ui::VirtualKey::kXInputPadDpadDown, "DPad down"},
|
{ui::VirtualKey::kXInputPadDpadDown, "DPad down"},
|
||||||
{ui::VirtualKey::kXInputPadDpadLeft, "DPad left"},
|
{ui::VirtualKey::kXInputPadDpadLeft, "DPad left"},
|
||||||
{ui::VirtualKey::kXInputPadDpadRight, "DPad right"},
|
{ui::VirtualKey::kXInputPadDpadRight, "DPad right"},
|
||||||
{ui::VirtualKey::kXInputPadStart, "Start"},
|
{ui::VirtualKey::kXInputPadStart, "Start"},
|
||||||
{ui::VirtualKey::kXInputPadBack, "Back"},
|
{ui::VirtualKey::kXInputPadBack, "Back"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"},
|
{ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"},
|
{ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"},
|
||||||
|
|
||||||
{ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"},
|
{ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"},
|
{ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"},
|
{ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"},
|
{ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"},
|
{ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"},
|
{ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"},
|
{ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"},
|
||||||
{ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"},
|
{ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"},
|
||||||
|
|
||||||
{ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"},
|
{ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"},
|
{ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"},
|
{ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"},
|
{ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"},
|
{ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"},
|
{ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"},
|
{ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"},
|
||||||
{ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"},
|
{ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"},
|
||||||
};
|
};
|
||||||
|
|
||||||
void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
|
||||||
bool hide_repeats, bool clear_log) {
|
|
||||||
const size_t maxLog = 128;
|
const size_t maxLog = 128;
|
||||||
static std::array<std::forward_list<std::string>, MAX_USERS> event_logs;
|
static std::array<std::forward_list<std::string>, MAX_USERS> event_logs;
|
||||||
static std::array<uint64_t, MAX_USERS> last_event_times = {};
|
static std::array<uint64_t, MAX_USERS> last_event_times = {};
|
||||||
|
@ -400,7 +409,8 @@ void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) {
|
void HidDemoApp::DrawInputGetKeystroke(bool poll, bool hide_repeats,
|
||||||
|
bool clear_log) const {
|
||||||
bool tab_bar = ImGui::BeginTabBar("DrawInputGetKeystroke");
|
bool tab_bar = ImGui::BeginTabBar("DrawInputGetKeystroke");
|
||||||
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
||||||
DrawUserInputGetKeystroke(user_index, poll, hide_repeats, clear_log);
|
DrawUserInputGetKeystroke(user_index, poll, hide_repeats, clear_log);
|
||||||
|
@ -411,4 +421,4 @@ void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) {
|
||||||
} // namespace hid
|
} // namespace hid
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-hid-demo", xe::hid::hid_demo_main, "");
|
XE_DEFINE_WINDOWED_APP(xenia_hid_demo, xe::hid::HidDemoApp::Create);
|
||||||
|
|
|
@ -32,7 +32,7 @@ project("xenia-hid-demo")
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
"hid_demo.cc",
|
"hid_demo.cc",
|
||||||
"../base/main_"..platform_suffix..".cc",
|
"../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
resincludedirs({
|
resincludedirs({
|
||||||
project_root,
|
project_root,
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "xenia/hid/hid_flags.h"
|
#include "xenia/hid/hid_flags.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
// TODO(joellinn) make this path relative to the config folder.
|
// TODO(joellinn) make this path relative to the config folder.
|
||||||
DEFINE_path(mappings_file, "gamecontrollerdb.txt",
|
DEFINE_path(mappings_file, "gamecontrollerdb.txt",
|
||||||
|
@ -43,6 +44,12 @@ SDLInputDriver::SDLInputDriver(xe::ui::Window* window)
|
||||||
keystroke_states_() {}
|
keystroke_states_() {}
|
||||||
|
|
||||||
SDLInputDriver::~SDLInputDriver() {
|
SDLInputDriver::~SDLInputDriver() {
|
||||||
|
// Make sure the CallInUIThread is executed before destroying the references.
|
||||||
|
if (sdl_pumpevents_queued_) {
|
||||||
|
window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||||
|
window()->app_context().ExecutePendingFunctionsFromUIThread();
|
||||||
|
});
|
||||||
|
}
|
||||||
for (size_t i = 0; i < controllers_.size(); i++) {
|
for (size_t i = 0; i < controllers_.size(); i++) {
|
||||||
if (controllers_.at(i).sdl) {
|
if (controllers_.at(i).sdl) {
|
||||||
SDL_GameControllerClose(controllers_.at(i).sdl);
|
SDL_GameControllerClose(controllers_.at(i).sdl);
|
||||||
|
@ -65,8 +72,9 @@ X_STATUS SDLInputDriver::Setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SDL_PumpEvents should only be run in the thread that initialized SDL - we
|
// SDL_PumpEvents should only be run in the thread that initialized SDL - we
|
||||||
// are hijacking the window loop thread for that.
|
// are hijacking the UI thread for that. If this function fails to be queued,
|
||||||
window()->loop()->PostSynchronous([&]() {
|
// the "initialized" variables will be false - that's handled safely.
|
||||||
|
window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||||
if (!xe::helper::sdl::SDLHelper::Prepare()) {
|
if (!xe::helper::sdl::SDLHelper::Prepare()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -129,7 +137,9 @@ X_STATUS SDLInputDriver::Setup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return sdl_events_initialized_ && sdl_gamecontroller_initialized_;
|
return (sdl_events_initialized_ && sdl_gamecontroller_initialized_)
|
||||||
|
? X_STATUS_SUCCESS
|
||||||
|
: X_STATUS_UNSUCCESSFUL;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
||||||
|
@ -693,7 +703,7 @@ void SDLInputDriver::QueueControllerUpdate() {
|
||||||
bool is_queued = false;
|
bool is_queued = false;
|
||||||
sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true);
|
sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true);
|
||||||
if (!is_queued) {
|
if (!is_queued) {
|
||||||
window()->loop()->Post([this]() {
|
window()->app_context().CallInUIThread([this]() {
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
sdl_pumpevents_queued_ = false;
|
sdl_pumpevents_queued_ = false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "xenia/kernel/xam/xam_private.h"
|
#include "xenia/kernel/xam/xam_private.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -50,15 +51,16 @@ dword_result_t XamShowNuiTroubleshooterUI(unknown_t unk1, unknown_t unk2,
|
||||||
|
|
||||||
auto display_window = kernel_state()->emulator()->display_window();
|
auto display_window = kernel_state()->emulator()->display_window();
|
||||||
xe::threading::Fence fence;
|
xe::threading::Fence fence;
|
||||||
display_window->loop()->PostSynchronous([&]() {
|
if (display_window->app_context().CallInUIThreadSynchronous([&]() {
|
||||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||||
display_window, "NUI Troubleshooter",
|
display_window, "NUI Troubleshooter",
|
||||||
"The game has indicated there is a problem with NUI (Kinect).")
|
"The game has indicated there is a problem with NUI (Kinect).")
|
||||||
->Then(&fence);
|
->Then(&fence);
|
||||||
});
|
})) {
|
||||||
++xam_dialogs_shown_;
|
++xam_dialogs_shown_;
|
||||||
fence.Wait();
|
fence.Wait();
|
||||||
--xam_dialogs_shown_;
|
--xam_dialogs_shown_;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "xenia/kernel/xam/xam_private.h"
|
#include "xenia/kernel/xam/xam_private.h"
|
||||||
#include "xenia/ui/imgui_dialog.h"
|
#include "xenia/ui/imgui_dialog.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -76,11 +77,16 @@ X_RESULT xeXamDispatchDialog(T* dialog,
|
||||||
result = close_callback(dialog);
|
result = close_callback(dialog);
|
||||||
});
|
});
|
||||||
xe::threading::Fence fence;
|
xe::threading::Fence fence;
|
||||||
kernel_state()->emulator()->display_window()->loop()->PostSynchronous(
|
xe::ui::WindowedAppContext& app_context =
|
||||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
kernel_state()->emulator()->display_window()->app_context();
|
||||||
++xam_dialogs_shown_;
|
if (app_context.CallInUIThreadSynchronous(
|
||||||
fence.Wait();
|
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||||
--xam_dialogs_shown_;
|
++xam_dialogs_shown_;
|
||||||
|
fence.Wait();
|
||||||
|
--xam_dialogs_shown_;
|
||||||
|
} else {
|
||||||
|
delete dialog;
|
||||||
|
}
|
||||||
// dialog should be deleted at this point!
|
// dialog should be deleted at this point!
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -117,11 +123,14 @@ X_RESULT xeXamDispatchDialogEx(
|
||||||
result = close_callback(dialog, extended_error, length);
|
result = close_callback(dialog, extended_error, length);
|
||||||
});
|
});
|
||||||
xe::threading::Fence fence;
|
xe::threading::Fence fence;
|
||||||
display_window->loop()->PostSynchronous(
|
if (display_window->app_context().CallInUIThreadSynchronous(
|
||||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||||
++xam_dialogs_shown_;
|
++xam_dialogs_shown_;
|
||||||
fence.Wait();
|
fence.Wait();
|
||||||
--xam_dialogs_shown_;
|
--xam_dialogs_shown_;
|
||||||
|
} else {
|
||||||
|
delete dialog;
|
||||||
|
}
|
||||||
// dialog should be deleted at this point!
|
// dialog should be deleted at this point!
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,10 +11,6 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#ifdef XE_PLATFORM_WIN32
|
|
||||||
#include <objbase.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/base/byte_stream.h"
|
#include "xenia/base/byte_stream.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
|
@ -383,20 +379,6 @@ X_STATUS XThread::Create() {
|
||||||
// Set name immediately, if we have one.
|
// Set name immediately, if we have one.
|
||||||
thread_->set_name(thread_name_);
|
thread_->set_name(thread_name_);
|
||||||
|
|
||||||
#ifdef XE_PLATFORM_WIN32
|
|
||||||
// Setup COM on this thread.
|
|
||||||
//
|
|
||||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
|
||||||
//
|
|
||||||
// "If any thread in the process calls CoInitialize[Ex] with the
|
|
||||||
// COINIT_MULTITHREADED flag, then that not only initializes the current
|
|
||||||
// thread as a member of the multi-threaded apartment, but it also says,
|
|
||||||
// "Any thread which has never called CoInitialize[Ex] is also part of the
|
|
||||||
// multi-threaded apartment."
|
|
||||||
#pragma warning(suppress : 6031)
|
|
||||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Profiler needs to know about the thread.
|
// Profiler needs to know about the thread.
|
||||||
xe::Profiler::ThreadEnter(thread_name_.c_str());
|
xe::Profiler::ThreadEnter(thread_name_.c_str());
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/string.h"
|
|
||||||
#include "xenia/cpu/export_resolver.h"
|
#include "xenia/cpu/export_resolver.h"
|
||||||
#include "xenia/kernel/fs/filesystem.h"
|
#include "xenia/kernel/fs/filesystem.h"
|
||||||
#include "xenia/kernel/objects/xfile.h"
|
#include "xenia/kernel/objects/xfile.h"
|
||||||
|
|
|
@ -7,17 +7,25 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "api_scanner_loader.h"
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
|
#include "xenia/base/string.h"
|
||||||
|
#include "xenia/tools/api_scanner_loader.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace tools {
|
namespace tools {
|
||||||
|
|
||||||
DEFINE_string(target, "", "List of file to extract imports from");
|
DEFINE_transient_string(target, "", "List of file to extract imports from");
|
||||||
|
|
||||||
|
// TODO(Triang3l): Change to std::string (currently doesn't even compile).
|
||||||
int api_scanner_main(std::vector<std::wstring>& args) {
|
int api_scanner_main(std::vector<std::wstring>& args) {
|
||||||
// XXX we need gflags to split multiple flags into arrays for us
|
// XXX we need gflags to split multiple flags into arrays for us
|
||||||
|
|
||||||
if (args.size() == 2 || !FLAGS_target.empty()) {
|
if (args.size() == 2 || !cvars::target.empty()) {
|
||||||
apiscanner_loader loader_;
|
apiscanner_loader loader_;
|
||||||
std::wstring target(cvars::target.empty() ? args[1]
|
std::wstring target(cvars::target.empty() ? args[1]
|
||||||
: xe::to_wstring(cvars::target));
|
: xe::to_wstring(cvars::target));
|
||||||
|
@ -41,5 +49,5 @@ int api_scanner_main(std::vector<std::wstring>& args) {
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT(L"api-scanner", L"api-scanner --target=<target file>",
|
XE_DEFINE_CONSOLE_APP("api-scanner", xe::tools::api_scanner_main,
|
||||||
xe::tools::api_scanner_main);
|
"[target file]", "target");
|
||||||
|
|
|
@ -47,8 +47,8 @@ bool D3D12Provider::IsD3D12APIAvailable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<D3D12Provider> D3D12Provider::Create(Window* main_window) {
|
std::unique_ptr<D3D12Provider> D3D12Provider::Create() {
|
||||||
std::unique_ptr<D3D12Provider> provider(new D3D12Provider(main_window));
|
std::unique_ptr<D3D12Provider> provider(new D3D12Provider);
|
||||||
if (!provider->Initialize()) {
|
if (!provider->Initialize()) {
|
||||||
xe::FatalError(
|
xe::FatalError(
|
||||||
"Unable to initialize Direct3D 12 graphics subsystem.\n"
|
"Unable to initialize Direct3D 12 graphics subsystem.\n"
|
||||||
|
@ -63,9 +63,6 @@ std::unique_ptr<D3D12Provider> D3D12Provider::Create(Window* main_window) {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
D3D12Provider::D3D12Provider(Window* main_window)
|
|
||||||
: GraphicsProvider(main_window) {}
|
|
||||||
|
|
||||||
D3D12Provider::~D3D12Provider() {
|
D3D12Provider::~D3D12Provider() {
|
||||||
if (graphics_analysis_ != nullptr) {
|
if (graphics_analysis_ != nullptr) {
|
||||||
graphics_analysis_->Release();
|
graphics_analysis_->Release();
|
||||||
|
|
|
@ -27,7 +27,7 @@ class D3D12Provider : public GraphicsProvider {
|
||||||
|
|
||||||
static bool IsD3D12APIAvailable();
|
static bool IsD3D12APIAvailable();
|
||||||
|
|
||||||
static std::unique_ptr<D3D12Provider> Create(Window* main_window);
|
static std::unique_ptr<D3D12Provider> Create();
|
||||||
|
|
||||||
std::unique_ptr<GraphicsContext> CreateContext(
|
std::unique_ptr<GraphicsContext> CreateContext(
|
||||||
Window* target_window) override;
|
Window* target_window) override;
|
||||||
|
@ -147,7 +147,7 @@ class D3D12Provider : public GraphicsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit D3D12Provider(Window* main_window);
|
D3D12Provider() = default;
|
||||||
|
|
||||||
static bool EnableIncreaseBasePriorityPrivilege();
|
static bool EnableIncreaseBasePriorityPrivilege();
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
|
@ -2,28 +2,44 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/ui/d3d12/d3d12_provider.h"
|
#include "xenia/ui/d3d12/d3d12_provider.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window_demo.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
namespace d3d12 {
|
||||||
|
|
||||||
int window_demo_main(const std::vector<std::string>& args);
|
class D3D12WindowDemoApp final : public WindowDemoApp {
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<WindowedApp> Create(WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<WindowedApp>(new D3D12WindowDemoApp(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<GraphicsProvider> CreateDemoGraphicsProvider(Window* window) {
|
protected:
|
||||||
return xe::ui::d3d12::D3D12Provider::Create(window);
|
std::unique_ptr<GraphicsProvider> CreateGraphicsProvider() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit D3D12WindowDemoApp(WindowedAppContext& app_context)
|
||||||
|
: WindowDemoApp(app_context, "xenia-ui-window-d3d12-demo") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<GraphicsProvider> D3D12WindowDemoApp::CreateGraphicsProvider()
|
||||||
|
const {
|
||||||
|
return D3D12Provider::Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace d3d12
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-ui-window-d3d12-demo", xe::ui::window_demo_main, "");
|
XE_DEFINE_WINDOWED_APP(xenia_ui_window_d3d12_demo,
|
||||||
|
xe::ui::d3d12::D3D12WindowDemoApp::Create);
|
||||||
|
|
|
@ -30,7 +30,7 @@ project("xenia-ui-window-d3d12-demo")
|
||||||
files({
|
files({
|
||||||
"../window_demo.cc",
|
"../window_demo.cc",
|
||||||
"d3d12_window_demo.cc",
|
"d3d12_window_demo.cc",
|
||||||
project_root.."/src/xenia/base/main_"..platform_suffix..".cc",
|
project_root.."/src/xenia/ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
resincludedirs({
|
resincludedirs({
|
||||||
project_root,
|
project_root,
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
|
|
||||||
|
// Microsoft headers after platform_win.h.
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
@ -106,32 +109,16 @@ Win32FilePicker::Win32FilePicker() = default;
|
||||||
|
|
||||||
Win32FilePicker::~Win32FilePicker() = default;
|
Win32FilePicker::~Win32FilePicker() = default;
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct com_ptr {
|
|
||||||
com_ptr() : value(nullptr) {}
|
|
||||||
~com_ptr() { reset(); }
|
|
||||||
void reset() {
|
|
||||||
if (value) {
|
|
||||||
value->Release();
|
|
||||||
value = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
operator T*() { return value; }
|
|
||||||
T* operator->() const { return value; }
|
|
||||||
T** addressof() { return &value; }
|
|
||||||
T* value;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool Win32FilePicker::Show(void* parent_window_handle) {
|
bool Win32FilePicker::Show(void* parent_window_handle) {
|
||||||
// TODO(benvanik): FileSaveDialog.
|
// TODO(benvanik): FileSaveDialog.
|
||||||
assert_true(mode() == Mode::kOpen);
|
assert_true(mode() == Mode::kOpen);
|
||||||
// TODO(benvanik): folder dialogs.
|
// TODO(benvanik): folder dialogs.
|
||||||
assert_true(type() == Type::kFile);
|
assert_true(type() == Type::kFile);
|
||||||
|
|
||||||
com_ptr<IFileDialog> file_dialog;
|
Microsoft::WRL::ComPtr<IFileDialog> file_dialog;
|
||||||
HRESULT hr =
|
HRESULT hr =
|
||||||
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
|
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
|
||||||
IID_PPV_ARGS(file_dialog.addressof()));
|
IID_PPV_ARGS(&file_dialog));
|
||||||
if (!SUCCEEDED(hr)) {
|
if (!SUCCEEDED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -180,14 +167,13 @@ bool Win32FilePicker::Show(void* parent_window_handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an event handling object, and hook it up to the dialog.
|
// Create an event handling object, and hook it up to the dialog.
|
||||||
com_ptr<IFileDialogEvents> file_dialog_events;
|
Microsoft::WRL::ComPtr<IFileDialogEvents> file_dialog_events;
|
||||||
hr = CDialogEventHandler_CreateInstance(
|
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&file_dialog_events));
|
||||||
IID_PPV_ARGS(file_dialog_events.addressof()));
|
|
||||||
if (!SUCCEEDED(hr)) {
|
if (!SUCCEEDED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DWORD cookie;
|
DWORD cookie;
|
||||||
hr = file_dialog->Advise(file_dialog_events, &cookie);
|
hr = file_dialog->Advise(file_dialog_events.Get(), &cookie);
|
||||||
if (!SUCCEEDED(hr)) {
|
if (!SUCCEEDED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -201,8 +187,8 @@ bool Win32FilePicker::Show(void* parent_window_handle) {
|
||||||
|
|
||||||
// Obtain the result once the user clicks the 'Open' button.
|
// Obtain the result once the user clicks the 'Open' button.
|
||||||
// The result is an IShellItem object.
|
// The result is an IShellItem object.
|
||||||
com_ptr<IShellItem> shell_item;
|
Microsoft::WRL::ComPtr<IShellItem> shell_item;
|
||||||
hr = file_dialog->GetResult(shell_item.addressof());
|
hr = file_dialog->GetResult(&shell_item);
|
||||||
if (!SUCCEEDED(hr)) {
|
if (!SUCCEEDED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,6 @@ class GraphicsProvider {
|
||||||
|
|
||||||
virtual ~GraphicsProvider() = default;
|
virtual ~GraphicsProvider() = default;
|
||||||
|
|
||||||
// The 'main' window of an application, used to query provider information.
|
|
||||||
Window* main_window() const { return main_window_; }
|
|
||||||
|
|
||||||
// Creates a new graphics context and swapchain for presenting to a window.
|
// Creates a new graphics context and swapchain for presenting to a window.
|
||||||
virtual std::unique_ptr<GraphicsContext> CreateContext(
|
virtual std::unique_ptr<GraphicsContext> CreateContext(
|
||||||
Window* target_window) = 0;
|
Window* target_window) = 0;
|
||||||
|
@ -48,9 +45,7 @@ class GraphicsProvider {
|
||||||
virtual std::unique_ptr<GraphicsContext> CreateOffscreenContext() = 0;
|
virtual std::unique_ptr<GraphicsContext> CreateOffscreenContext() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit GraphicsProvider(Window* main_window) : main_window_(main_window) {}
|
GraphicsProvider() = default;
|
||||||
|
|
||||||
Window* main_window_ = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
|
||||||
#include "xenia/base/threading.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
Loop::Loop() = default;
|
|
||||||
|
|
||||||
Loop::~Loop() = default;
|
|
||||||
|
|
||||||
void Loop::PostSynchronous(std::function<void()> fn) {
|
|
||||||
if (is_on_loop_thread()) {
|
|
||||||
// Prevent deadlock if we are executing on ourselves.
|
|
||||||
fn();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xe::threading::Fence fence;
|
|
||||||
Post([&fn, &fence]() {
|
|
||||||
fn();
|
|
||||||
fence.Signal();
|
|
||||||
});
|
|
||||||
fence.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
|
@ -1,45 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_UI_LOOP_H_
|
|
||||||
#define XENIA_UI_LOOP_H_
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "xenia/base/delegate.h"
|
|
||||||
#include "xenia/ui/ui_event.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class Loop {
|
|
||||||
public:
|
|
||||||
static std::unique_ptr<Loop> Create();
|
|
||||||
|
|
||||||
Loop();
|
|
||||||
virtual ~Loop();
|
|
||||||
|
|
||||||
// Returns true if the currently executing code is within the loop thread.
|
|
||||||
virtual bool is_on_loop_thread() = 0;
|
|
||||||
|
|
||||||
virtual void Post(std::function<void()> fn) = 0;
|
|
||||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
|
||||||
void PostSynchronous(std::function<void()> fn);
|
|
||||||
|
|
||||||
virtual void Quit() = 0;
|
|
||||||
virtual void AwaitQuit() = 0;
|
|
||||||
|
|
||||||
Delegate<UIEvent*> on_quit;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif // XENIA_UI_LOOP_H_
|
|
|
@ -1,81 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/ui/loop_gtk.h"
|
|
||||||
|
|
||||||
#include <gdk/gdkx.h>
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
std::unique_ptr<Loop> Loop::Create() { return std::make_unique<GTKLoop>(); }
|
|
||||||
|
|
||||||
GTKLoop::GTKLoop() : thread_id_() {
|
|
||||||
gtk_init(nullptr, nullptr);
|
|
||||||
xe::threading::Fence init_fence;
|
|
||||||
thread_ = std::thread([&init_fence, this]() {
|
|
||||||
xe::threading::set_name("GTK Loop");
|
|
||||||
|
|
||||||
thread_id_ = std::this_thread::get_id();
|
|
||||||
init_fence.Signal();
|
|
||||||
|
|
||||||
ThreadMain();
|
|
||||||
|
|
||||||
quit_fence_.Signal();
|
|
||||||
});
|
|
||||||
init_fence.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
GTKLoop::~GTKLoop() {
|
|
||||||
Quit();
|
|
||||||
thread_.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GTKLoop::ThreadMain() { gtk_main(); }
|
|
||||||
|
|
||||||
bool GTKLoop::is_on_loop_thread() {
|
|
||||||
return thread_id_ == std::this_thread::get_id();
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean _posted_fn_thunk(gpointer posted_fn) {
|
|
||||||
// convert gpointer back to std::function, call it, then free std::function
|
|
||||||
std::function<void()>* f =
|
|
||||||
reinterpret_cast<std::function<void()>*>(posted_fn);
|
|
||||||
std::function<void()>& func = *f;
|
|
||||||
func();
|
|
||||||
delete f;
|
|
||||||
// Tells GDK we don't want to run this again
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GTKLoop::Post(std::function<void()> fn) {
|
|
||||||
assert_true(thread_id_ != std::thread::id());
|
|
||||||
// converting std::function to a generic pointer for gdk
|
|
||||||
gdk_threads_add_idle(_posted_fn_thunk, reinterpret_cast<gpointer>(
|
|
||||||
new std::function<void()>(fn)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GTKLoop::PostDelayed(std::function<void()> fn, uint64_t delay_millis) {
|
|
||||||
gdk_threads_add_timeout(
|
|
||||||
delay_millis, _posted_fn_thunk,
|
|
||||||
reinterpret_cast<gpointer>(new std::function<void()>(fn)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GTKLoop::Quit() {
|
|
||||||
assert_true(thread_id_ != std::thread::id());
|
|
||||||
Post([]() { gtk_main_quit(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void GTKLoop::AwaitQuit() { quit_fence_.Wait(); }
|
|
||||||
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
|
@ -1,131 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/ui/loop_win.h"
|
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
std::unique_ptr<Loop> Loop::Create() { return std::make_unique<Win32Loop>(); }
|
|
||||||
|
|
||||||
Win32Loop::Win32Loop() : thread_id_(0) {
|
|
||||||
timer_queue_ = CreateTimerQueue();
|
|
||||||
|
|
||||||
xe::threading::Fence init_fence;
|
|
||||||
thread_ = std::thread([&init_fence, this]() {
|
|
||||||
xe::threading::set_name("Win32 Loop");
|
|
||||||
thread_id_ = GetCurrentThreadId();
|
|
||||||
|
|
||||||
// Make a Win32 call to enable the thread queue.
|
|
||||||
MSG msg;
|
|
||||||
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
|
||||||
|
|
||||||
init_fence.Signal();
|
|
||||||
|
|
||||||
ThreadMain();
|
|
||||||
|
|
||||||
quit_fence_.Signal();
|
|
||||||
});
|
|
||||||
init_fence.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
Win32Loop::~Win32Loop() {
|
|
||||||
Quit();
|
|
||||||
thread_.join();
|
|
||||||
|
|
||||||
DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE);
|
|
||||||
std::lock_guard<std::mutex> lock(pending_timers_mutex_);
|
|
||||||
while (!pending_timers_.empty()) {
|
|
||||||
auto timer = pending_timers_.back();
|
|
||||||
pending_timers_.pop_back();
|
|
||||||
delete timer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::ThreadMain() {
|
|
||||||
MSG msg;
|
|
||||||
while (!should_exit_ && GetMessage(&msg, NULL, 0, 0)) {
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
|
|
||||||
// Process queued functions.
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(posted_functions_mutex_);
|
|
||||||
for (auto it = posted_functions_.begin(); it != posted_functions_.end();) {
|
|
||||||
(*it).Call();
|
|
||||||
it = posted_functions_.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UIEvent e(nullptr);
|
|
||||||
on_quit(&e);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Win32Loop::is_on_loop_thread() {
|
|
||||||
return thread_id_ == GetCurrentThreadId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::Post(std::function<void()> fn) {
|
|
||||||
assert_true(thread_id_ != 0);
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(posted_functions_mutex_);
|
|
||||||
PostedFn posted_fn(fn);
|
|
||||||
posted_functions_.push_back(posted_fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) {
|
|
||||||
Sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::TimerQueueCallback(void* context, uint8_t) {
|
|
||||||
auto timer = reinterpret_cast<PendingTimer*>(context);
|
|
||||||
auto loop = timer->loop;
|
|
||||||
auto fn = std::move(timer->fn);
|
|
||||||
DeleteTimerQueueTimer(timer->timer_queue, timer->timer_handle, NULL);
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(loop->pending_timers_mutex_);
|
|
||||||
loop->pending_timers_.remove(timer);
|
|
||||||
}
|
|
||||||
delete timer;
|
|
||||||
loop->Post(std::move(fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::PostDelayed(std::function<void()> fn, uint64_t delay_millis) {
|
|
||||||
if (!delay_millis) {
|
|
||||||
Post(std::move(fn));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto timer = new PendingTimer();
|
|
||||||
timer->loop = this;
|
|
||||||
timer->timer_queue = timer_queue_;
|
|
||||||
timer->fn = std::move(fn);
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(pending_timers_mutex_);
|
|
||||||
pending_timers_.push_back(timer);
|
|
||||||
}
|
|
||||||
CreateTimerQueueTimer(&timer->timer_handle, timer_queue_,
|
|
||||||
WAITORTIMERCALLBACK(TimerQueueCallback), timer,
|
|
||||||
DWORD(delay_millis), 0,
|
|
||||||
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::Quit() {
|
|
||||||
assert_true(thread_id_ != 0);
|
|
||||||
should_exit_ = true;
|
|
||||||
while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) {
|
|
||||||
Sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32Loop::AwaitQuit() { quit_fence_.Wait(); }
|
|
||||||
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
|
@ -1,74 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_UI_LOOP_WIN_H_
|
|
||||||
#define XENIA_UI_LOOP_WIN_H_
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "xenia/base/platform_win.h"
|
|
||||||
#include "xenia/base/threading.h"
|
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace ui {
|
|
||||||
|
|
||||||
class Win32Loop : public Loop {
|
|
||||||
public:
|
|
||||||
Win32Loop();
|
|
||||||
~Win32Loop() override;
|
|
||||||
|
|
||||||
bool is_on_loop_thread() override;
|
|
||||||
|
|
||||||
void Post(std::function<void()> fn) override;
|
|
||||||
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;
|
|
||||||
|
|
||||||
void Quit() override;
|
|
||||||
void AwaitQuit() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct PendingTimer {
|
|
||||||
Win32Loop* loop;
|
|
||||||
HANDLE timer_queue;
|
|
||||||
HANDLE timer_handle;
|
|
||||||
std::function<void()> fn;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PostedFn {
|
|
||||||
public:
|
|
||||||
explicit PostedFn(std::function<void()> fn) : fn_(std::move(fn)) {}
|
|
||||||
void Call() { fn_(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::function<void()> fn_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ThreadMain();
|
|
||||||
|
|
||||||
static void TimerQueueCallback(void* context, uint8_t);
|
|
||||||
|
|
||||||
std::thread thread_;
|
|
||||||
DWORD thread_id_;
|
|
||||||
bool should_exit_ = false;
|
|
||||||
xe::threading::Fence quit_fence_;
|
|
||||||
|
|
||||||
HANDLE timer_queue_;
|
|
||||||
std::mutex pending_timers_mutex_;
|
|
||||||
std::list<PendingTimer*> pending_timers_;
|
|
||||||
|
|
||||||
std::recursive_mutex posted_functions_mutex_;
|
|
||||||
std::list<PostedFn> posted_functions_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ui
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif // XENIA_UI_LOOP_WIN_H_
|
|
|
@ -13,3 +13,4 @@ project("xenia-ui")
|
||||||
})
|
})
|
||||||
local_platform_files()
|
local_platform_files()
|
||||||
removefiles({"*_demo.cc"})
|
removefiles({"*_demo.cc"})
|
||||||
|
removefiles({"windowed_app_main_*.cc"})
|
||||||
|
|
|
@ -45,7 +45,7 @@ project("xenia-ui-window-vulkan-demo")
|
||||||
files({
|
files({
|
||||||
"../window_demo.cc",
|
"../window_demo.cc",
|
||||||
"vulkan_window_demo.cc",
|
"vulkan_window_demo.cc",
|
||||||
project_root.."/src/xenia/base/main_"..platform_suffix..".cc",
|
project_root.."/src/xenia/ui/windowed_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
resincludedirs({
|
resincludedirs({
|
||||||
project_root,
|
project_root,
|
||||||
|
|
|
@ -24,8 +24,8 @@ namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace vulkan {
|
namespace vulkan {
|
||||||
|
|
||||||
std::unique_ptr<VulkanProvider> VulkanProvider::Create(Window* main_window) {
|
std::unique_ptr<VulkanProvider> VulkanProvider::Create() {
|
||||||
std::unique_ptr<VulkanProvider> provider(new VulkanProvider(main_window));
|
std::unique_ptr<VulkanProvider> provider(new VulkanProvider);
|
||||||
if (!provider->Initialize()) {
|
if (!provider->Initialize()) {
|
||||||
xe::FatalError(
|
xe::FatalError(
|
||||||
"Unable to initialize Vulkan graphics subsystem.\n"
|
"Unable to initialize Vulkan graphics subsystem.\n"
|
||||||
|
@ -40,9 +40,6 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(Window* main_window) {
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanProvider::VulkanProvider(Window* main_window)
|
|
||||||
: GraphicsProvider(main_window) {}
|
|
||||||
|
|
||||||
VulkanProvider::~VulkanProvider() {
|
VulkanProvider::~VulkanProvider() {
|
||||||
device_.reset();
|
device_.reset();
|
||||||
instance_.reset();
|
instance_.reset();
|
||||||
|
|
|
@ -25,7 +25,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||||
public:
|
public:
|
||||||
~VulkanProvider() override;
|
~VulkanProvider() override;
|
||||||
|
|
||||||
static std::unique_ptr<VulkanProvider> Create(Window* main_window);
|
static std::unique_ptr<VulkanProvider> Create();
|
||||||
|
|
||||||
VulkanInstance* instance() const { return instance_.get(); }
|
VulkanInstance* instance() const { return instance_.get(); }
|
||||||
VulkanDevice* device() const { return device_.get(); }
|
VulkanDevice* device() const { return device_.get(); }
|
||||||
|
@ -35,7 +35,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||||
std::unique_ptr<GraphicsContext> CreateOffscreenContext() override;
|
std::unique_ptr<GraphicsContext> CreateOffscreenContext() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit VulkanProvider(Window* main_window);
|
VulkanProvider() = default;
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
|
|
|
@ -2,28 +2,44 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window_demo.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
namespace vulkan {
|
||||||
|
|
||||||
int window_demo_main(const std::vector<std::string>& args);
|
class VulkanWindowDemoApp final : public WindowDemoApp {
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<WindowedApp> Create(WindowedAppContext& app_context) {
|
||||||
|
return std::unique_ptr<WindowedApp>(new VulkanWindowDemoApp(app_context));
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<GraphicsProvider> CreateDemoGraphicsProvider(Window* window) {
|
protected:
|
||||||
return xe::ui::vulkan::VulkanProvider::Create(window);
|
std::unique_ptr<GraphicsProvider> CreateGraphicsProvider() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit VulkanWindowDemoApp(WindowedAppContext& app_context)
|
||||||
|
: WindowDemoApp(app_context, "xenia-ui-window-vulkan-demo") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<GraphicsProvider> VulkanWindowDemoApp::CreateGraphicsProvider()
|
||||||
|
const {
|
||||||
|
return VulkanProvider::Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace vulkan
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-ui-window-vulkan-demo", xe::ui::window_demo_main, "");
|
XE_DEFINE_WINDOWED_APP(xenia_ui_window_vulkan_demo,
|
||||||
|
xe::ui::vulkan::VulkanWindowDemoApp::Create);
|
||||||
|
|
|
@ -31,8 +31,8 @@ constexpr double kDoubleClickDistance = 5;
|
||||||
|
|
||||||
constexpr int32_t kMouseWheelDetent = 120;
|
constexpr int32_t kMouseWheelDetent = 120;
|
||||||
|
|
||||||
Window::Window(Loop* loop, const std::string& title)
|
Window::Window(WindowedAppContext& app_context, const std::string& title)
|
||||||
: loop_(loop), title_(title) {}
|
: app_context_(app_context), title_(title) {}
|
||||||
|
|
||||||
Window::~Window() {
|
Window::~Window() {
|
||||||
// Context must have been cleaned up already in OnDestroy.
|
// Context must have been cleaned up already in OnDestroy.
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
#include "xenia/base/delegate.h"
|
#include "xenia/base/delegate.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/ui/graphics_context.h"
|
#include "xenia/ui/graphics_context.h"
|
||||||
#include "xenia/ui/loop.h"
|
|
||||||
#include "xenia/ui/menu_item.h"
|
#include "xenia/ui/menu_item.h"
|
||||||
#include "xenia/ui/ui_event.h"
|
#include "xenia/ui/ui_event.h"
|
||||||
#include "xenia/ui/window_listener.h"
|
#include "xenia/ui/window_listener.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
@ -31,11 +31,13 @@ class ImGuiDrawer;
|
||||||
|
|
||||||
class Window {
|
class Window {
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<Window> Create(Loop* loop, const std::string& title);
|
static std::unique_ptr<Window> Create(WindowedAppContext& app_context_,
|
||||||
|
const std::string& title);
|
||||||
|
|
||||||
virtual ~Window();
|
virtual ~Window();
|
||||||
|
|
||||||
Loop* loop() const { return loop_; }
|
WindowedAppContext& app_context() const { return app_context_; }
|
||||||
|
|
||||||
virtual NativePlatformHandle native_platform_handle() const = 0;
|
virtual NativePlatformHandle native_platform_handle() const = 0;
|
||||||
virtual NativeWindowHandle native_handle() const = 0;
|
virtual NativeWindowHandle native_handle() const = 0;
|
||||||
|
|
||||||
|
@ -69,8 +71,11 @@ class Window {
|
||||||
virtual bool is_bordered() const { return false; }
|
virtual bool is_bordered() const { return false; }
|
||||||
virtual void set_bordered(bool enabled) {}
|
virtual void set_bordered(bool enabled) {}
|
||||||
|
|
||||||
virtual int get_dpi() const { return 96; }
|
virtual int get_medium_dpi() const { return 96; }
|
||||||
virtual float get_dpi_scale() const { return get_dpi() / 96.f; }
|
virtual int get_dpi() const { return get_medium_dpi(); }
|
||||||
|
virtual float get_dpi_scale() const {
|
||||||
|
return float(get_dpi()) / float(get_medium_dpi());
|
||||||
|
}
|
||||||
|
|
||||||
bool has_focus() const { return has_focus_; }
|
bool has_focus() const { return has_focus_; }
|
||||||
virtual void set_focus(bool value) { has_focus_ = value; }
|
virtual void set_focus(bool value) { has_focus_ = value; }
|
||||||
|
@ -78,6 +83,8 @@ class Window {
|
||||||
bool is_cursor_visible() const { return is_cursor_visible_; }
|
bool is_cursor_visible() const { return is_cursor_visible_; }
|
||||||
virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; }
|
virtual void set_cursor_visible(bool value) { is_cursor_visible_ = value; }
|
||||||
|
|
||||||
|
// TODO(Triang3l): Don't scale for guest output - use physical pixels. Use
|
||||||
|
// logical pixels only for the immediate drawer.
|
||||||
int32_t width() const { return width_; }
|
int32_t width() const { return width_; }
|
||||||
int32_t height() const { return height_; }
|
int32_t height() const { return height_; }
|
||||||
int32_t scaled_width() const { return int32_t(width_ * get_dpi_scale()); }
|
int32_t scaled_width() const { return int32_t(width_ * get_dpi_scale()); }
|
||||||
|
@ -135,7 +142,7 @@ class Window {
|
||||||
Delegate<MouseEvent*> on_mouse_wheel;
|
Delegate<MouseEvent*> on_mouse_wheel;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Window(Loop* loop, const std::string& title);
|
Window(WindowedAppContext& app_context, const std::string& title);
|
||||||
|
|
||||||
void ForEachListener(std::function<void(WindowListener*)> fn);
|
void ForEachListener(std::function<void(WindowListener*)> fn);
|
||||||
void TryForEachListener(std::function<bool(WindowListener*)> fn);
|
void TryForEachListener(std::function<bool(WindowListener*)> fn);
|
||||||
|
@ -170,7 +177,7 @@ class Window {
|
||||||
|
|
||||||
void OnKeyPress(KeyEvent* e, bool is_down, bool is_char);
|
void OnKeyPress(KeyEvent* e, bool is_down, bool is_char);
|
||||||
|
|
||||||
Loop* loop_ = nullptr;
|
WindowedAppContext& app_context_;
|
||||||
std::unique_ptr<MenuItem> main_menu_;
|
std::unique_ptr<MenuItem> main_menu_;
|
||||||
std::string title_;
|
std::string title_;
|
||||||
#ifdef XE_PLATFORM_GNU_LINUX
|
#ifdef XE_PLATFORM_GNU_LINUX
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -12,7 +12,6 @@
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/ui/graphics_provider.h"
|
#include "xenia/ui/graphics_provider.h"
|
||||||
|
@ -20,28 +19,29 @@
|
||||||
#include "xenia/ui/imgui_drawer.h"
|
#include "xenia/ui/imgui_drawer.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/window_demo.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
// Implemented in one of the window_*_demo.cc files under a subdir.
|
WindowDemoApp::~WindowDemoApp() { Profiler::Shutdown(); }
|
||||||
std::unique_ptr<GraphicsProvider> CreateDemoGraphicsProvider(Window* window);
|
|
||||||
|
|
||||||
int window_demo_main(const std::vector<std::string>& args) {
|
bool WindowDemoApp::OnInitialize() {
|
||||||
Profiler::Initialize();
|
Profiler::Initialize();
|
||||||
Profiler::ThreadEnter("main");
|
Profiler::ThreadEnter("Main");
|
||||||
|
|
||||||
// Create run loop and the window.
|
// Create graphics provider that provides the context for the window.
|
||||||
auto loop = ui::Loop::Create();
|
graphics_provider_ = CreateGraphicsProvider();
|
||||||
auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name);
|
if (!graphics_provider_) {
|
||||||
loop->PostSynchronous([&window]() {
|
return false;
|
||||||
xe::threading::set_name("Win32 Loop");
|
}
|
||||||
xe::Profiler::ThreadEnter("Win32 Loop");
|
|
||||||
if (!window->Initialize()) {
|
// Create the window.
|
||||||
FatalError("Failed to initialize main window");
|
window_ = xe::ui::Window::Create(app_context(), GetName());
|
||||||
return;
|
if (!window_->Initialize()) {
|
||||||
}
|
XELOGE("Failed to initialize main window");
|
||||||
});
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Main menu.
|
// Main menu.
|
||||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||||
|
@ -49,7 +49,7 @@ int window_demo_main(const std::vector<std::string>& args) {
|
||||||
{
|
{
|
||||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close",
|
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, "&Close",
|
||||||
"Alt+F4",
|
"Alt+F4",
|
||||||
[&window]() { window->Close(); }));
|
[this]() { window_->Close(); }));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(file_menu));
|
main_menu->AddChild(std::move(file_menu));
|
||||||
auto debug_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Debug");
|
auto debug_menu = MenuItem::Create(MenuItem::Type::kPopup, "&Debug");
|
||||||
|
@ -62,37 +62,28 @@ int window_demo_main(const std::vector<std::string>& args) {
|
||||||
[]() { Profiler::TogglePause(); }));
|
[]() { Profiler::TogglePause(); }));
|
||||||
}
|
}
|
||||||
main_menu->AddChild(std::move(debug_menu));
|
main_menu->AddChild(std::move(debug_menu));
|
||||||
window->set_main_menu(std::move(main_menu));
|
window_->set_main_menu(std::move(main_menu));
|
||||||
|
|
||||||
// Initial size setting, done here so that it knows the menu exists.
|
// Initial size setting, done here so that it knows the menu exists.
|
||||||
window->Resize(1920, 1200);
|
window_->Resize(1920, 1200);
|
||||||
|
|
||||||
// Create the graphics context used for drawing and setup the window.
|
// Create the graphics context for the window. The window will finish
|
||||||
std::unique_ptr<GraphicsProvider> graphics_provider;
|
// initialization with the context (loading resources, etc).
|
||||||
loop->PostSynchronous([&window, &graphics_provider]() {
|
window_->set_context(graphics_provider_->CreateContext(window_.get()));
|
||||||
// Create graphics provider and an initial context for the window.
|
|
||||||
// The window will finish initialization with the context (loading
|
|
||||||
// resources, etc).
|
|
||||||
graphics_provider = CreateDemoGraphicsProvider(window.get());
|
|
||||||
window->set_context(graphics_provider->CreateContext(window.get()));
|
|
||||||
|
|
||||||
// Setup the profiler display.
|
// Setup the profiler display.
|
||||||
GraphicsContextLock context_lock(window->context());
|
GraphicsContextLock context_lock(window_->context());
|
||||||
Profiler::set_window(window.get());
|
Profiler::set_window(window_.get());
|
||||||
|
|
||||||
// Enable imgui input.
|
// Enable imgui input.
|
||||||
window->set_imgui_input_enabled(true);
|
window_->set_imgui_input_enabled(true);
|
||||||
|
|
||||||
|
window_->on_closed.AddListener([this](xe::ui::UIEvent* e) {
|
||||||
|
XELOGI("User-initiated death!");
|
||||||
|
app_context().QuitFromUIThread();
|
||||||
});
|
});
|
||||||
|
|
||||||
window->on_closed.AddListener(
|
window_->on_key_down.AddListener([](xe::ui::KeyEvent* e) {
|
||||||
[&loop, &window, &graphics_provider](xe::ui::UIEvent* e) {
|
|
||||||
loop->Quit();
|
|
||||||
Profiler::Shutdown();
|
|
||||||
XELOGI("User-initiated death!");
|
|
||||||
});
|
|
||||||
loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); });
|
|
||||||
|
|
||||||
window->on_key_down.AddListener([](xe::ui::KeyEvent* e) {
|
|
||||||
switch (e->virtual_key()) {
|
switch (e->virtual_key()) {
|
||||||
case VirtualKey::kF3:
|
case VirtualKey::kF3:
|
||||||
Profiler::ToggleDisplay();
|
Profiler::ToggleDisplay();
|
||||||
|
@ -102,23 +93,19 @@ int window_demo_main(const std::vector<std::string>& args) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||||
auto& io = window->imgui_drawer()->GetIO();
|
auto& io = window_->imgui_drawer()->GetIO();
|
||||||
|
|
||||||
ImGui::ShowDemoWindow();
|
ImGui::ShowDemoWindow();
|
||||||
ImGui::ShowMetricsWindow();
|
ImGui::ShowMetricsWindow();
|
||||||
|
|
||||||
|
Profiler::Flip();
|
||||||
|
|
||||||
// Continuous paint.
|
// Continuous paint.
|
||||||
window->Invalidate();
|
window_->Invalidate();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait until we are exited.
|
return true;
|
||||||
loop->AwaitQuit();
|
|
||||||
|
|
||||||
window.reset();
|
|
||||||
loop.reset();
|
|
||||||
graphics_provider.reset();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOW_DEMO_H_
|
||||||
|
#define XENIA_UI_WINDOW_DEMO_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/ui/graphics_provider.h"
|
||||||
|
#include "xenia/ui/window.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class WindowDemoApp : public WindowedApp {
|
||||||
|
public:
|
||||||
|
~WindowDemoApp();
|
||||||
|
|
||||||
|
bool OnInitialize() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
explicit WindowDemoApp(WindowedAppContext& app_context,
|
||||||
|
const std::string_view name)
|
||||||
|
: WindowedApp(app_context, name) {}
|
||||||
|
|
||||||
|
virtual std::unique_ptr<GraphicsProvider> CreateGraphicsProvider() const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<GraphicsProvider> graphics_provider_;
|
||||||
|
std::unique_ptr<Window> window_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOW_DEMO_H_
|
|
@ -22,12 +22,13 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
std::unique_ptr<Window> Window::Create(Loop* loop, const std::string& title) {
|
std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
||||||
return std::make_unique<GTKWindow>(loop, title);
|
const std::string& title) {
|
||||||
|
return std::make_unique<GTKWindow>(app_context, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
GTKWindow::GTKWindow(Loop* loop, const std::string& title)
|
GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string& title)
|
||||||
: Window(loop, title) {}
|
: Window(app_context, title) {}
|
||||||
|
|
||||||
GTKWindow::~GTKWindow() {
|
GTKWindow::~GTKWindow() {
|
||||||
OnDestroy();
|
OnDestroy();
|
||||||
|
@ -92,7 +93,7 @@ gboolean close_callback(GtkWidget* widget, gpointer data) {
|
||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GTKWindow::Create() {
|
bool GTKWindow::OnCreate() {
|
||||||
// GTK optionally allows passing argv and argc here for parsing gtk specific
|
// GTK optionally allows passing argv and argc here for parsing gtk specific
|
||||||
// options. We won't bother
|
// options. We won't bother
|
||||||
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||||
|
@ -129,10 +130,7 @@ void GTKWindow::Create() {
|
||||||
gtk_box_pack_end(GTK_BOX(box_), drawing_area_, TRUE, TRUE, 0);
|
gtk_box_pack_end(GTK_BOX(box_), drawing_area_, TRUE, TRUE, 0);
|
||||||
gtk_container_add(GTK_CONTAINER(window_), box_);
|
gtk_container_add(GTK_CONTAINER(window_), box_);
|
||||||
gtk_widget_show_all(window_);
|
gtk_widget_show_all(window_);
|
||||||
}
|
|
||||||
|
|
||||||
bool GTKWindow::OnCreate() {
|
|
||||||
loop()->PostSynchronous([this]() { this->Create(); });
|
|
||||||
return super::OnCreate();
|
return super::OnCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class GTKWindow : public Window {
|
||||||
using super = Window;
|
using super = Window;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GTKWindow(Loop* loop, const std::string& title);
|
GTKWindow(WindowedAppContext& app_context, const std::string& title);
|
||||||
~GTKWindow() override;
|
~GTKWindow() override;
|
||||||
|
|
||||||
NativePlatformHandle native_platform_handle() const override {
|
NativePlatformHandle native_platform_handle() const override {
|
||||||
|
@ -74,7 +74,6 @@ class GTKWindow : public Window {
|
||||||
void OnResize(UIEvent* e) override;
|
void OnResize(UIEvent* e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Create();
|
|
||||||
GtkWidget* window_;
|
GtkWidget* window_;
|
||||||
GtkWidget* box_;
|
GtkWidget* box_;
|
||||||
GtkWidget* drawing_area_;
|
GtkWidget* drawing_area_;
|
||||||
|
|
|
@ -18,16 +18,19 @@
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/platform_win.h"
|
#include "xenia/base/platform_win.h"
|
||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
|
#include "xenia/ui/windowed_app_context_win.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
std::unique_ptr<Window> Window::Create(Loop* loop, const std::string& title) {
|
std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
||||||
return std::make_unique<Win32Window>(loop, title);
|
const std::string& title) {
|
||||||
|
return std::make_unique<Win32Window>(app_context, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Window::Win32Window(Loop* loop, const std::string& title)
|
Win32Window::Win32Window(WindowedAppContext& app_context,
|
||||||
: Window(loop, title) {}
|
const std::string& title)
|
||||||
|
: Window(app_context, title) {}
|
||||||
|
|
||||||
Win32Window::~Win32Window() {
|
Win32Window::~Win32Window() {
|
||||||
OnDestroy();
|
OnDestroy();
|
||||||
|
@ -43,37 +46,32 @@ Win32Window::~Win32Window() {
|
||||||
}
|
}
|
||||||
|
|
||||||
NativePlatformHandle Win32Window::native_platform_handle() const {
|
NativePlatformHandle Win32Window::native_platform_handle() const {
|
||||||
return ::GetModuleHandle(nullptr);
|
return static_cast<const Win32WindowedAppContext&>(app_context()).hinstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Win32Window::Initialize() { return OnCreate(); }
|
bool Win32Window::Initialize() { return OnCreate(); }
|
||||||
|
|
||||||
bool Win32Window::OnCreate() {
|
bool Win32Window::OnCreate() {
|
||||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
HINSTANCE hInstance =
|
||||||
|
static_cast<const Win32WindowedAppContext&>(app_context()).hinstance();
|
||||||
|
|
||||||
if (!SetProcessDpiAwareness_ || !GetDpiForMonitor_) {
|
// Per-monitor DPI awareness is expected to be enabled via the manifest, as
|
||||||
|
// that's the recommended way, which also doesn't require calling
|
||||||
|
// SetProcessDpiAwareness before doing anything that may depend on DPI
|
||||||
|
// awareness (so it's safe to use any Windows APIs before this code).
|
||||||
|
// TODO(Triang3l): Safe handling of per-monitor DPI awareness v2, with
|
||||||
|
// automatic scaling on DPI change.
|
||||||
|
if (!GetDpiForMonitor_) {
|
||||||
auto shcore = GetModuleHandleW(L"shcore.dll");
|
auto shcore = GetModuleHandleW(L"shcore.dll");
|
||||||
if (shcore) {
|
if (shcore) {
|
||||||
SetProcessDpiAwareness_ =
|
|
||||||
GetProcAddress(shcore, "SetProcessDpiAwareness");
|
|
||||||
GetDpiForMonitor_ = GetProcAddress(shcore, "GetDpiForMonitor");
|
GetDpiForMonitor_ = GetProcAddress(shcore, "GetDpiForMonitor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool has_registered_class = false;
|
static bool has_registered_class = false;
|
||||||
if (!has_registered_class) {
|
if (!has_registered_class) {
|
||||||
// Tell Windows that we're DPI aware.
|
WNDCLASSEXW wcex;
|
||||||
if (SetProcessDpiAwareness_) {
|
wcex.cbSize = sizeof(wcex);
|
||||||
auto spda = (decltype(&SetProcessDpiAwareness))SetProcessDpiAwareness_;
|
|
||||||
auto res = spda(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
||||||
if (res != S_OK) {
|
|
||||||
XELOGW("Failed to set process DPI awareness. (code = 0x{:08X})",
|
|
||||||
static_cast<uint32_t>(res));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WNDCLASSEX wcex;
|
|
||||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
||||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||||
wcex.lpfnWndProc = Win32Window::WndProcThunk;
|
wcex.lpfnWndProc = Win32Window::WndProcThunk;
|
||||||
wcex.cbClsExtra = 0;
|
wcex.cbClsExtra = 0;
|
||||||
|
@ -85,7 +83,7 @@ bool Win32Window::OnCreate() {
|
||||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||||
wcex.lpszMenuName = nullptr;
|
wcex.lpszMenuName = nullptr;
|
||||||
wcex.lpszClassName = L"XeniaWindowClass";
|
wcex.lpszClassName = L"XeniaWindowClass";
|
||||||
if (!RegisterClassEx(&wcex)) {
|
if (!RegisterClassExW(&wcex)) {
|
||||||
XELOGE("RegisterClassEx failed");
|
XELOGE("RegisterClassEx failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -216,7 +214,9 @@ bool Win32Window::SetIcon(const void* buffer, size_t size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset icon to default.
|
// Reset icon to default.
|
||||||
auto default_icon = LoadIconW(GetModuleHandle(nullptr), L"MAINICON");
|
auto default_icon = LoadIconW(
|
||||||
|
static_cast<const Win32WindowedAppContext&>(app_context()).hinstance(),
|
||||||
|
L"MAINICON");
|
||||||
SendMessageW(hwnd_, WM_SETICON, ICON_BIG,
|
SendMessageW(hwnd_, WM_SETICON, ICON_BIG,
|
||||||
reinterpret_cast<LPARAM>(default_icon));
|
reinterpret_cast<LPARAM>(default_icon));
|
||||||
SendMessageW(hwnd_, WM_SETICON, ICON_SMALL,
|
SendMessageW(hwnd_, WM_SETICON, ICON_SMALL,
|
||||||
|
@ -316,13 +316,22 @@ void Win32Window::set_bordered(bool enabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Win32Window::get_dpi() const {
|
int Win32Window::get_dpi() const {
|
||||||
|
// TODO(Triang3l): Cache until WM_DPICHANGED is received (which, with
|
||||||
|
// per-monitor awareness v2 will also receive the new suggested window size).
|
||||||
|
// According to MSDN, x and y are identical.
|
||||||
|
|
||||||
if (!GetDpiForMonitor_) {
|
if (!GetDpiForMonitor_) {
|
||||||
return 96;
|
HDC screen_hdc = GetDC(nullptr);
|
||||||
|
if (!screen_hdc) {
|
||||||
|
return get_medium_dpi();
|
||||||
|
}
|
||||||
|
int logical_pixels_x = GetDeviceCaps(screen_hdc, LOGPIXELSX);
|
||||||
|
ReleaseDC(nullptr, screen_hdc);
|
||||||
|
return logical_pixels_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY);
|
HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
|
||||||
// According to msdn, x and y are identical...
|
|
||||||
UINT dpi_x, dpi_y;
|
UINT dpi_x, dpi_y;
|
||||||
auto gdfm = (decltype(&GetDpiForMonitor))GetDpiForMonitor_;
|
auto gdfm = (decltype(&GetDpiForMonitor))GetDpiForMonitor_;
|
||||||
gdfm(monitor, MDT_DEFAULT, &dpi_x, &dpi_y);
|
gdfm(monitor, MDT_DEFAULT, &dpi_x, &dpi_y);
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Win32Window : public Window {
|
||||||
using super = Window;
|
using super = Window;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Win32Window(Loop* loop, const std::string& title);
|
Win32Window(WindowedAppContext& app_context, const std::string& title);
|
||||||
~Win32Window() override;
|
~Win32Window() override;
|
||||||
|
|
||||||
NativePlatformHandle native_platform_handle() const override;
|
NativePlatformHandle native_platform_handle() const override;
|
||||||
|
@ -90,7 +90,6 @@ class Win32Window : public Window {
|
||||||
WINDOWPLACEMENT windowed_pos_ = {0};
|
WINDOWPLACEMENT windowed_pos_ = {0};
|
||||||
POINT last_mouse_pos_ = {0};
|
POINT last_mouse_pos_ = {0};
|
||||||
|
|
||||||
void* SetProcessDpiAwareness_ = nullptr;
|
|
||||||
void* GetDpiForMonitor_ = nullptr;
|
void* GetDpiForMonitor_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOWED_APP_H_
|
||||||
|
#define XENIA_UI_WINDOWED_APP_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
#include <android/native_activity.h>
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context_android.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
// Interface between the platform's entry points (in the main, UI, thread that
|
||||||
|
// also runs the message loop) and the app that implements it.
|
||||||
|
class WindowedApp {
|
||||||
|
public:
|
||||||
|
// WindowedApps are expected to provide a static creation function, for
|
||||||
|
// creating an instance of the class (which may be called before
|
||||||
|
// initialization of platform-specific parts, should preferably be as simple
|
||||||
|
// as possible).
|
||||||
|
|
||||||
|
WindowedApp(const WindowedApp& app) = delete;
|
||||||
|
WindowedApp& operator=(const WindowedApp& app) = delete;
|
||||||
|
virtual ~WindowedApp() = default;
|
||||||
|
|
||||||
|
WindowedAppContext& app_context() const { return app_context_; }
|
||||||
|
|
||||||
|
// Same as the executable (project), xenia-library-app.
|
||||||
|
const std::string& GetName() const { return name_; }
|
||||||
|
const std::string& GetPositionalOptionsUsage() const {
|
||||||
|
return positional_options_usage_;
|
||||||
|
}
|
||||||
|
const std::vector<std::string>& GetPositionalOptions() const {
|
||||||
|
return positional_options_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called once before receiving other lifecycle callback invocations. Cvars
|
||||||
|
// will be initialized with the launch arguments. Returns whether the app has
|
||||||
|
// been initialized successfully (otherwise platform-specific code must call
|
||||||
|
// OnDestroy and refuse to continue running the app).
|
||||||
|
virtual bool OnInitialize() = 0;
|
||||||
|
// See OnDestroy for more info.
|
||||||
|
void InvokeOnDestroy() {
|
||||||
|
// For safety and convenience of referencing objects owned by the app in
|
||||||
|
// pending functions queued in or after OnInitialize, make sure they are
|
||||||
|
// executed before telling the app that destruction needs to happen.
|
||||||
|
app_context().ExecutePendingFunctionsFromUIThread();
|
||||||
|
OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Positional options should be initialized in the constructor if needed.
|
||||||
|
// Cvars will not have been initialized with the arguments at the moment of
|
||||||
|
// construction (as the result depends on construction).
|
||||||
|
explicit WindowedApp(
|
||||||
|
WindowedAppContext& app_context, const std::string_view name,
|
||||||
|
const std::string_view positional_options_usage = std::string_view())
|
||||||
|
: app_context_(app_context),
|
||||||
|
name_(name),
|
||||||
|
positional_options_usage_(positional_options_usage) {}
|
||||||
|
|
||||||
|
// For calling from the constructor.
|
||||||
|
void AddPositionalOption(const std::string_view option) {
|
||||||
|
positional_options_.emplace_back(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnDestroy entry point may be called (through InvokeOnDestroy) by the
|
||||||
|
// platform-specific lifecycle interface at request of either the app itself
|
||||||
|
// or the OS - thus should be possible for the lifecycle interface to call at
|
||||||
|
// any moment (not from inside other lifecycle callbacks though). The app will
|
||||||
|
// also be destroyed when that happens, so the destructor will also be called
|
||||||
|
// (but this is more safe with respect to exceptions). This is only guaranteed
|
||||||
|
// to be called if OnInitialize has already happened (successfully or not) -
|
||||||
|
// in case of an error before initialization, the destructor may be called
|
||||||
|
// alone as well. Context's pending functions will be executed before the
|
||||||
|
// call, so it's safe to destroy dependencies of them here (though it may
|
||||||
|
// still be possible to add more pending functions here depending on whether
|
||||||
|
// the context was explicitly shut down before this is invoked).
|
||||||
|
virtual void OnDestroy() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WindowedAppContext& app_context_;
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
std::string positional_options_usage_;
|
||||||
|
std::vector<std::string> positional_options_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
// Multiple apps in a single library. ANativeActivity_onCreate chosen via
|
||||||
|
// android.app.func_name of the NativeActivity of each app.
|
||||||
|
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
||||||
|
__attribute__((visibility("default"))) extern "C" void export_name( \
|
||||||
|
ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \
|
||||||
|
xe::ui::AndroidWindowedAppContext::StartAppOnNativeActivityCreate( \
|
||||||
|
activity, saved_state, saved_state_size, creator); \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Separate executables for each app.
|
||||||
|
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
|
||||||
|
WindowedAppContext& app_context);
|
||||||
|
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
||||||
|
std::unique_ptr<xe::ui::WindowedApp> (*xe::ui::GetWindowedAppCreator())( \
|
||||||
|
xe::ui::WindowedAppContext & app_context) { \
|
||||||
|
return creator; \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOWED_APP_H_
|
|
@ -0,0 +1,180 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
WindowedAppContext::~WindowedAppContext() {
|
||||||
|
// The UI thread is responsible for managing the lifetime of the context.
|
||||||
|
assert_true(IsInUIThread());
|
||||||
|
|
||||||
|
// It's okay to destroy the context from a platform's internal UI loop
|
||||||
|
// callback, primarily on platforms where the loop is run by the OS itself,
|
||||||
|
// and the context can't be created and destroyed in a RAII way, rather, it's
|
||||||
|
// created in an initialization handler and destroyed in a shutdown handler
|
||||||
|
// called by the OS. However, destruction must not be done from within the
|
||||||
|
// queued functions - as in this case, the pending function container, the
|
||||||
|
// mutex, will be accessed after having been destroyed already.
|
||||||
|
|
||||||
|
// Make sure CallInUIThreadDeferred doesn't call
|
||||||
|
// NotifyUILoopOfPendingFunctions, which is virtual.
|
||||||
|
is_in_destructor_ = true;
|
||||||
|
// Make sure the final ExecutePendingFunctionsFromUIThread doesn't call
|
||||||
|
// PlatformQuitFromUIThread, which is virtual.
|
||||||
|
has_quit_ = true;
|
||||||
|
|
||||||
|
// Platform-specific quit is expected to be performed by the subclass (the
|
||||||
|
// order of it vs. the final ExecutePendingFunctionsFromUIThread shouldn't
|
||||||
|
// matter anymore, the implementation may assume that no pending functions
|
||||||
|
// will be requested for execution specifically via the platform-specific
|
||||||
|
// loop, as there should be no more references to the context in other
|
||||||
|
// threads), can't call the virtual PlatformQuitFromUIThread anymore.
|
||||||
|
ExecutePendingFunctionsFromUIThread(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowedAppContext::CallInUIThreadDeferred(
|
||||||
|
std::function<void()> function) {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> pending_functions_lock(
|
||||||
|
pending_functions_mutex_);
|
||||||
|
if (!pending_functions_accepted_) {
|
||||||
|
// Will not be called as the loop will not be executed anymore.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pending_functions_.emplace_back(std::move(function));
|
||||||
|
}
|
||||||
|
// Notify unconditionally, even if currently running pending functions. It's
|
||||||
|
// possible for pending functions themselves to run inner platform message
|
||||||
|
// loops, such as when displaying dialogs - in this case, the notification is
|
||||||
|
// needed to run the new function from such an inner loop. A modal loop can be
|
||||||
|
// started even in leftovers happening during the quit, where there's still
|
||||||
|
// opportunity for enqueueing and executing new pending functions - so only
|
||||||
|
// checking if called in the destructor (it's safe to check this without
|
||||||
|
// locking a mutex as it's assumed that if the object is already being
|
||||||
|
// destroyed, no other threads can have references to it - any access would
|
||||||
|
// result in a race condition anyway) as the subclass has already been
|
||||||
|
// destroyed. Having pending_functions_mutex_ unlocked also means that
|
||||||
|
// NotifyUILoopOfPendingFunctions may be done while the UI thread is calling
|
||||||
|
// or has already called PlatformQuitFromUIThread - but it's better than
|
||||||
|
// keeping pending_functions_mutex_ locked as NotifyUILoopOfPendingFunctions
|
||||||
|
// may be implemented as pushing to a fixed-size pipe, in which case it will
|
||||||
|
// have to wait until free space is available, but if the UI thread tries to
|
||||||
|
// lock the mutex afterwards to execute pending functions (and encouters
|
||||||
|
// contention), nothing will be able to receive from the pipe anymore and thus
|
||||||
|
// free the space, causing a deadlock.
|
||||||
|
if (!is_in_destructor_) {
|
||||||
|
NotifyUILoopOfPendingFunctions();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowedAppContext::CallInUIThread(std::function<void()> function) {
|
||||||
|
if (IsInUIThread()) {
|
||||||
|
// The intention is just to make sure the code is executed in the UI thread,
|
||||||
|
// don't defer execution if no need to.
|
||||||
|
function();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return CallInUIThreadDeferred(std::move(function));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowedAppContext::CallInUIThreadSynchronous(
|
||||||
|
std::function<void()> function) {
|
||||||
|
if (IsInUIThread()) {
|
||||||
|
// Prevent deadlock if called from the UI thread.
|
||||||
|
function();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
xe::threading::Fence fence;
|
||||||
|
if (!CallInUIThreadDeferred([&function, &fence]() {
|
||||||
|
function();
|
||||||
|
fence.Signal();
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fence.Wait();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowedAppContext::QuitFromUIThread() {
|
||||||
|
assert_true(IsInUIThread());
|
||||||
|
bool has_quit_previously = has_quit_;
|
||||||
|
// Make sure PlatformQuitFromUIThread is called only once, not from nested
|
||||||
|
// pending function execution during the quit - otherwise it will be called
|
||||||
|
// when it's still possible to add new pending functions. This isn't as wrong
|
||||||
|
// as calling PlatformQuitFromUIThread from the destructor, but still a part
|
||||||
|
// of the contract for simplicity.
|
||||||
|
has_quit_ = true;
|
||||||
|
// Executing pending function unconditionally because it's the contract of
|
||||||
|
// this method that functions are executed immediately.
|
||||||
|
ExecutePendingFunctionsFromUIThread(true);
|
||||||
|
if (has_quit_previously) {
|
||||||
|
// Potentially calling QuitFromUIThread from inside a pending function (in
|
||||||
|
// the worst and dangerous case, from a pending function executed in the
|
||||||
|
// destructor - and PlatformQuitFromUIThread is virtual).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Call the platform-specific shutdown while letting it assume that no new
|
||||||
|
// functions will be queued anymore (but NotifyUILoopOfPendingFunctions may
|
||||||
|
// still be called after PlatformQuitFromUIThread as the two are not
|
||||||
|
// interlocked). This is different than the order in the destruction, but
|
||||||
|
// there this assumption is ensured by the expectation that there should be no
|
||||||
|
// more references to the context in other threads that would allow queueing
|
||||||
|
// new functions with calling NotifyUILoopOfPendingFunctions.
|
||||||
|
PlatformQuitFromUIThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowedAppContext::ExecutePendingFunctionsFromUIThread(bool is_final) {
|
||||||
|
assert_true(IsInUIThread());
|
||||||
|
std::unique_lock<std::mutex> pending_functions_lock(pending_functions_mutex_);
|
||||||
|
while (!pending_functions_.empty()) {
|
||||||
|
// Removing the function from the queue before executing it, as the function
|
||||||
|
// itself may call ExecutePendingFunctionsFromUIThread - if it's kept, the
|
||||||
|
// inner loop will try to execute it again, resulting in potentially endless
|
||||||
|
// recursion, and even if it's terminated, each level will be trying to
|
||||||
|
// remove the same function from the queue - instead, actually removing
|
||||||
|
// other functions, or even beyond the end of the queue.
|
||||||
|
std::function<void()> function = std::move(pending_functions_.front());
|
||||||
|
pending_functions_.pop_front();
|
||||||
|
// Call the function with the lock released as it may take an indefinitely
|
||||||
|
// long time to execute if it opens some dialog (possibly with its own
|
||||||
|
// platform message loop), and in that case, without unlocking, no other
|
||||||
|
// thread would be able to add new pending functions (which would result in
|
||||||
|
// unintended waits for user input). This also allows using std::mutex
|
||||||
|
// instead of std::recursive_mutex.
|
||||||
|
pending_functions_lock.unlock();
|
||||||
|
function();
|
||||||
|
pending_functions_lock.lock();
|
||||||
|
}
|
||||||
|
if (is_final) {
|
||||||
|
// Atomically with completion of the pending functions loop, disallow adding
|
||||||
|
// new functions after executing the existing ones - it was possible to
|
||||||
|
// enqueue new functions from the leftover ones as there still was
|
||||||
|
// opportunity to call them, so it wasn't necessary to disallow adding
|
||||||
|
// before executing, but now new functions will potentially never be
|
||||||
|
// executed. This is done even if this is just an inner pending functions
|
||||||
|
// execution and there's still potential possibility of adding and executing
|
||||||
|
// new functions in the outer loops - for simplicity and consistency (so
|
||||||
|
// QuitFromUIThread's behavior doesn't depend as much on the location of the
|
||||||
|
// call - inside a pending function or from some system callback of the
|
||||||
|
// window), assuming after a PlatformQuitFromUIThread call, it's not
|
||||||
|
// possible to add new pending functions anymore.
|
||||||
|
pending_functions_accepted_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,184 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_H_
|
||||||
|
#define XENIA_UI_WINDOWED_APP_CONTEXT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
// Context for inputs provided by the entry point and interacting with the
|
||||||
|
// platform's UI loop, to be implemented by platforms.
|
||||||
|
class WindowedAppContext {
|
||||||
|
public:
|
||||||
|
WindowedAppContext(const WindowedAppContext& context) = delete;
|
||||||
|
WindowedAppContext& operator=(const WindowedAppContext& context) = delete;
|
||||||
|
virtual ~WindowedAppContext();
|
||||||
|
|
||||||
|
// The thread where the object is created will be assumed to be the UI thread,
|
||||||
|
// for the purpose of being able to perform CallInUIThreadSynchronous before
|
||||||
|
// running the loop.
|
||||||
|
bool IsInUIThread() const {
|
||||||
|
return std::this_thread::get_id() == ui_thread_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallInUIThreadDeferred and CallInUIThread are fire and forget - will be
|
||||||
|
// executed at some point the future when the UI thread is running the loop
|
||||||
|
// and is not busy doing other things.
|
||||||
|
// Therefore, references to objects in the function may outlive the owners of
|
||||||
|
// those objects, so use-after-free is very easy to create if not being
|
||||||
|
// careful enough.
|
||||||
|
// There are two solutions to this issue:
|
||||||
|
// - Signaling a fence in the function, awaiting it before destroying objects
|
||||||
|
// referenced by the function (works for shutdown from non-UI threads, or
|
||||||
|
// for CallInUIThread, but not CallInUIThreadDeferred, in the UI thread).
|
||||||
|
// - Calling ExecutePendingFunctionsFromUIThread in the UI thread before
|
||||||
|
// destroying objects referenced by the function (works for shutdown from
|
||||||
|
// the UI thread, though CallInUIThreadSynchronous doing
|
||||||
|
// ExecutePendingFunctionsFromUIThread is also an option for shutdown from
|
||||||
|
// any thread).
|
||||||
|
// (These are not required if all the called function is doing is triggering a
|
||||||
|
// quit with the context pointer captured by value, as the only object
|
||||||
|
// involved will be the context itself, with a pointer that is valid until
|
||||||
|
// it's destroyed - the most late location of the pending function execution
|
||||||
|
// possible.)
|
||||||
|
// Returning true if the function has been enqueued (it will be called at some
|
||||||
|
// point, at worst, before exiting the loop) or called immediately, false if
|
||||||
|
// it was dropped (if calling after exiting the loop).
|
||||||
|
// It's okay to enqueue functions from queued functions already being
|
||||||
|
// executed. As execution may be happening during the destruction of the
|
||||||
|
// context as well, in this case, CallInUIThreadDeferred must make sure it
|
||||||
|
// doesn't call anything virtual in the destructor.
|
||||||
|
|
||||||
|
// Enqueues the function regardless of the current thread. Won't necessarily
|
||||||
|
// be executed directly from the platform main loop as
|
||||||
|
// ExecutePendingFunctionsFromUIThread may be called anywhere (for instance,
|
||||||
|
// to await pending functions before destroying what they are referencing).
|
||||||
|
bool CallInUIThreadDeferred(std::function<void()> function);
|
||||||
|
// Executes the function immediately if already in the UI thread, enqueues it
|
||||||
|
// otherwise.
|
||||||
|
bool CallInUIThread(std::function<void()> function);
|
||||||
|
bool CallInUIThreadSynchronous(std::function<void()> function);
|
||||||
|
// It's okay to call this function from the queued functions themselves (such
|
||||||
|
// as in the case of waiting for a pending async CallInUIThreadDeferred
|
||||||
|
// described above - the wait may be done inside a CallInUIThreadSynchronous
|
||||||
|
// function safely).
|
||||||
|
void ExecutePendingFunctionsFromUIThread() {
|
||||||
|
ExecutePendingFunctionsFromUIThread(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If on the target platform, the program itself is supposed to run the UI
|
||||||
|
// loop, this may be checked before doing blocking message waits as an
|
||||||
|
// additional safety measure beyond what PlatformQuitFromUIThread guarantees,
|
||||||
|
// and if true, the loop should be terminated (pending function will already
|
||||||
|
// have been executed). This doesn't imply that pending functions have been
|
||||||
|
// executed in all contexts, however - they can be executing from quitting
|
||||||
|
// itself (in the worst case, in the destructor where virtual methods can't be
|
||||||
|
// called), and in this case, this will be returning true.
|
||||||
|
bool HasQuitFromUIThread() const {
|
||||||
|
assert_true(IsInUIThread());
|
||||||
|
return has_quit_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately disallows adding new pending UI functions, executes the already
|
||||||
|
// queued ones, and makes sure that the UI loop is aware that it was asked to
|
||||||
|
// stop running. This must not destroy the context or the app directly - the
|
||||||
|
// actual app shutdown will be initiated at some point outside the scope of
|
||||||
|
// app's callbacks. May call virtual functions - invoke
|
||||||
|
// ExecutePendingFunctionsFromUIThread(true) in the destructor instead, and
|
||||||
|
// use the platform-specific destructor to invoke the needed
|
||||||
|
// PlatformQuitFromUIThread logic (it should be safe, in case of destruction,
|
||||||
|
// to perform platform-specific quit request logic before the common part -
|
||||||
|
// NotifyUILoopOfPendingFunctions won't be called after the platform-specific
|
||||||
|
// quit request logic in this case anyway as destruction expects that there
|
||||||
|
// are no references to the object in other threads). Safe to call from within
|
||||||
|
// pending functions - requesting quit from non-UI threads is possible via
|
||||||
|
// methods like CallInUIThreadSynchronous. For deferred, rather than
|
||||||
|
// immediate, quitting from the UI thread, CallInUIThreadDeferred may be used
|
||||||
|
// (but the context pointer should be captured by value not to require
|
||||||
|
// explicit completion forcing in case the storage of the pointer is lost
|
||||||
|
// before the function is called).
|
||||||
|
void QuitFromUIThread();
|
||||||
|
// Callable from any thread. This is a special case where a completely
|
||||||
|
// fire-and-forget CallInUIThreadDeferred is safe, as the function only
|
||||||
|
// references nonvirtual functions of the context itself, and will be called
|
||||||
|
// at most from the destructor. No need to return the result - it doesn't
|
||||||
|
// matter if has quit already or not, as that's the intention anyway.
|
||||||
|
void RequestDeferredQuit() {
|
||||||
|
CallInUIThreadDeferred([this] { QuitFromUIThread(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WindowedAppContext() : ui_thread_id_(std::this_thread::get_id()) {}
|
||||||
|
|
||||||
|
// Can be called from any thread (including the UI thread) to ask the OS to
|
||||||
|
// run an iteration of the UI loop (with or without processing internal UI
|
||||||
|
// messages, this is platform-dependent) so pending functions will be executed
|
||||||
|
// at some point. pending_functions_mutex_ will not be locked when this is
|
||||||
|
// called (to make sure that, for example, the caller, waiting for space in
|
||||||
|
// the OS's message queue, such as in case of a Linux pipe, won't be blocking
|
||||||
|
// the UI thread that has started executing pending messages while pumping
|
||||||
|
// that pipe, resulting in a deadlock) - implementations don't directly see
|
||||||
|
// anything protected by it anyway, and a spurious notification shouldn't be
|
||||||
|
// causing any damage, this is similar to how condition variables can be
|
||||||
|
// signaled outside the critical section (signaling inside the critical
|
||||||
|
// section may also cause contention if the thread waiting is woken up quickly
|
||||||
|
// enough). This, however, means that NotifyUILoopOfPendingFunctions may be
|
||||||
|
// called in a non-UI thread after the final pending message processing
|
||||||
|
// followed by PlatformQuitFromUIThread in the UI thread - so it can still be
|
||||||
|
// called after a quit.
|
||||||
|
virtual void NotifyUILoopOfPendingFunctions() = 0;
|
||||||
|
|
||||||
|
// Called when requesting a quit in the UI thread to tell the platform that
|
||||||
|
// the UI loop needs to be terminated. The pending function queue is assumed
|
||||||
|
// to be empty before this is called, and no new pending functions can be
|
||||||
|
// added (but NotifyUILoopOfPendingFunctions may still be called as it's not
|
||||||
|
// mutually exclusive with PlatformQuitFromUIThread - if this matters, the
|
||||||
|
// platform implementation itself should be resolving this case).
|
||||||
|
virtual void PlatformQuitFromUIThread() = 0;
|
||||||
|
|
||||||
|
std::thread::id ui_thread_id_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// May be called with is_final == true from the destructor - must not call
|
||||||
|
// anything virtual in this case.
|
||||||
|
void ExecutePendingFunctionsFromUIThread(bool is_final);
|
||||||
|
|
||||||
|
// Accessible by the UI thread.
|
||||||
|
bool has_quit_ = false;
|
||||||
|
bool is_in_destructor_ = false;
|
||||||
|
|
||||||
|
// Synchronizes producers with each other and with the consumer, as well as
|
||||||
|
// all of them with the pending_functions_accepted_ variable which indicates
|
||||||
|
// whether shutdown has not been performed yet, as after the shutdown, no new
|
||||||
|
// functions will be executed anymore, therefore no new function should be
|
||||||
|
// queued, as it will never be called (thus CallInUIThreadSynchronous for it
|
||||||
|
// will never return, for instance).
|
||||||
|
std::mutex pending_functions_mutex_;
|
||||||
|
std::deque<std::function<void()>> pending_functions_;
|
||||||
|
// Protected by pending_functions_mutex_, writable by the UI thread, readable
|
||||||
|
// by any thread. Must be set to false before exiting the main platform loop,
|
||||||
|
// but before that, all pending functions must be executed no matter what, as
|
||||||
|
// they may need to signal fences currently being awaited (like the
|
||||||
|
// CallInUIThreadSynchronous fence).
|
||||||
|
bool pending_functions_accepted_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOWED_APP_CONTEXT_H_
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context_android.h"
|
||||||
|
|
||||||
|
#include <android/native_activity.h>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
void AndroidWindowedAppContext::StartAppOnNativeActivityCreate(
|
||||||
|
ANativeActivity* activity, [[maybe_unused]] void* saved_state,
|
||||||
|
[[maybe_unused]] size_t saved_state_size,
|
||||||
|
std::unique_ptr<WindowedApp> (*app_creator)(
|
||||||
|
WindowedAppContext& app_context)) {
|
||||||
|
AndroidWindowedAppContext* app_context =
|
||||||
|
new AndroidWindowedAppContext(activity);
|
||||||
|
// The pointer is now held by the Activity as its ANativeActivity::instance,
|
||||||
|
// until the destruction.
|
||||||
|
if (!app_context->InitializeApp(app_creator)) {
|
||||||
|
delete app_context;
|
||||||
|
ANativeActivity_finish(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWindowedAppContext::~AndroidWindowedAppContext() {
|
||||||
|
// TODO(Triang3l): Unregister activity callbacks.
|
||||||
|
activity_->instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
|
// TODO(Triang3l): Request message processing in the UI thread.
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWindowedAppContext::PlatformQuitFromUIThread() {
|
||||||
|
ANativeActivity_finish(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity)
|
||||||
|
: activity_(activity) {
|
||||||
|
activity_->instance = this;
|
||||||
|
// TODO(Triang3l): Register activity callbacks.
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
|
||||||
|
*app_creator)(WindowedAppContext& app_context)) {
|
||||||
|
assert_null(app_);
|
||||||
|
app_ = app_creator(this);
|
||||||
|
if (!app_->OnInitialize()) {
|
||||||
|
app_->InvokeOnDestroy();
|
||||||
|
app_.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||||
|
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||||
|
|
||||||
|
#include <android/native_activity.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class WindowedApp;
|
||||||
|
|
||||||
|
class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
|
public:
|
||||||
|
// For calling from android.app.func_name exports.
|
||||||
|
static void StartAppOnNativeActivityCreate(
|
||||||
|
ANativeActivity* activity, void* saved_state, size_t saved_state_size,
|
||||||
|
std::unique_ptr<WindowedApp> (*app_creator)(
|
||||||
|
WindowedAppContext& app_context));
|
||||||
|
|
||||||
|
// Defined in the translation unit where WindowedApp is complete because of
|
||||||
|
// std::unique_ptr.
|
||||||
|
~AndroidWindowedAppContext();
|
||||||
|
|
||||||
|
ANativeActivity* activity() const { return activity_; }
|
||||||
|
WindowedApp* app() const { return app_.get(); }
|
||||||
|
|
||||||
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
|
||||||
|
void PlatformQuitFromUIThread() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit AndroidWindowedAppContext(ANativeActivity* activity);
|
||||||
|
bool InitializeApp(std::unique_ptr<WindowedApp> (*app_creator)(
|
||||||
|
WindowedAppContext& app_context));
|
||||||
|
|
||||||
|
// TODO(Triang3l): Switch from ANativeActivity to the context itself being the
|
||||||
|
// object for communication with the Java code when NativeActivity isn't used
|
||||||
|
// anymore as its functionality is heavily limited.
|
||||||
|
ANativeActivity* activity_;
|
||||||
|
std::unique_ptr<WindowedApp> app_;
|
||||||
|
|
||||||
|
// TODO(Triang3l): The rest of the context, including quit handler (and the
|
||||||
|
// destructor) calling `finish` on the activity, UI looper notification
|
||||||
|
// posting, etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context_gtk.h"
|
||||||
|
|
||||||
|
#include <gdk/gdk.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
GTKWindowedAppContext::~GTKWindowedAppContext() {
|
||||||
|
// Remove the idle sources as their data pointer (to this context) is now
|
||||||
|
// outdated.
|
||||||
|
if (quit_idle_pending_) {
|
||||||
|
g_source_remove(quit_idle_pending_);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Lock the mutex for a pending_functions_idle_pending_ access memory
|
||||||
|
// barrier, even though no other threads can access this object anymore.
|
||||||
|
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
|
||||||
|
pending_functions_idle_pending_mutex_);
|
||||||
|
if (pending_functions_idle_pending_) {
|
||||||
|
g_source_remove(pending_functions_idle_pending_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GTKWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
|
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
|
||||||
|
pending_functions_idle_pending_mutex_);
|
||||||
|
if (!pending_functions_idle_pending_) {
|
||||||
|
pending_functions_idle_pending_ =
|
||||||
|
gdk_threads_add_idle(PendingFunctionsSourceFunc, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GTKWindowedAppContext::PlatformQuitFromUIThread() {
|
||||||
|
if (quit_idle_pending_ || !gtk_main_level()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gtk_main_quit();
|
||||||
|
// Quit from all loops in the context, current inner, upcoming inner (this is
|
||||||
|
// why the idle function is added even at the main level of 1), until we can
|
||||||
|
// return to the tail of RunMainGTKLoop.
|
||||||
|
quit_idle_pending_ = gdk_threads_add_idle(QuitSourceFunc, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GTKWindowedAppContext::RunMainGTKLoop() {
|
||||||
|
// For safety, in case the quit request somehow happened before the loop.
|
||||||
|
if (HasQuitFromUIThread()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_zero(gtk_main_level());
|
||||||
|
gtk_main();
|
||||||
|
// Something else - not QuitFromUIThread - might have done gtk_main_quit for
|
||||||
|
// the outer loop. If it has exited for some reason, let the context know, so
|
||||||
|
// pending function won't be added pointlessly.
|
||||||
|
QuitFromUIThread();
|
||||||
|
// Don't interfere with main loops of other contexts that will possibly be
|
||||||
|
// executed in this thread later (if this is not the only app that will be
|
||||||
|
// executed in this process, for example, or if some WindowedAppContext-less
|
||||||
|
// dialog is opened after the windowed app has quit) - cancel the scheduled
|
||||||
|
// quit chain as we have quit already.
|
||||||
|
if (quit_idle_pending_) {
|
||||||
|
g_source_remove(quit_idle_pending_);
|
||||||
|
quit_idle_pending_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean GTKWindowedAppContext::PendingFunctionsSourceFunc(gpointer data) {
|
||||||
|
auto& app_context = *static_cast<GTKWindowedAppContext*>(data);
|
||||||
|
// Allow new pending function notifications to be made, and stop tracking the
|
||||||
|
// lifetime of the idle source that is already being executed and thus
|
||||||
|
// removed. This must be done before executing the functions (if done after,
|
||||||
|
// notifications may be missed if they happen between the execution of the
|
||||||
|
// functions and the reset of pending_functions_idle_pending_).
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
|
||||||
|
app_context.pending_functions_idle_pending_mutex_);
|
||||||
|
app_context.pending_functions_idle_pending_ = 0;
|
||||||
|
}
|
||||||
|
app_context.ExecutePendingFunctionsFromUIThread();
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean GTKWindowedAppContext::QuitSourceFunc(gpointer data) {
|
||||||
|
auto app_context = static_cast<GTKWindowedAppContext*>(data);
|
||||||
|
guint main_level = gtk_main_level();
|
||||||
|
assert_not_zero(main_level);
|
||||||
|
if (!main_level) {
|
||||||
|
app_context->quit_idle_pending_ = 0;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
gtk_main_quit();
|
||||||
|
// Quit from all loops in the context, current inner, upcoming inner (this is
|
||||||
|
// why the idle function is added even at the main level of 1), until we can
|
||||||
|
// return to the tail of RunMainGTKLoop.
|
||||||
|
app_context->quit_idle_pending_ =
|
||||||
|
gdk_threads_add_idle(QuitSourceFunc, app_context);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_
|
||||||
|
#define XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class GTKWindowedAppContext final : public WindowedAppContext {
|
||||||
|
public:
|
||||||
|
GTKWindowedAppContext() = default;
|
||||||
|
~GTKWindowedAppContext();
|
||||||
|
|
||||||
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
|
||||||
|
void PlatformQuitFromUIThread() override;
|
||||||
|
|
||||||
|
void RunMainGTKLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static gboolean PendingFunctionsSourceFunc(gpointer data);
|
||||||
|
|
||||||
|
static gboolean QuitSourceFunc(gpointer data);
|
||||||
|
|
||||||
|
std::mutex pending_functions_idle_pending_mutex_;
|
||||||
|
guint pending_functions_idle_pending_ = 0;
|
||||||
|
|
||||||
|
guint quit_idle_pending_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOWED_APP_CONTEXT_GTK_H_
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/ui/windowed_app_context_win.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
bool Win32WindowedAppContext::pending_functions_window_class_registered_;
|
||||||
|
|
||||||
|
Win32WindowedAppContext::~Win32WindowedAppContext() {
|
||||||
|
if (pending_functions_hwnd_) {
|
||||||
|
DestroyWindow(pending_functions_hwnd_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32WindowedAppContext::Initialize() {
|
||||||
|
// Logging possibly not initialized in this function yet.
|
||||||
|
// Create the message-only window for executing pending functions - using a
|
||||||
|
// window instead of executing them between iterations so non-main message
|
||||||
|
// loops, such as Windows modals, can execute pending functions too.
|
||||||
|
static const WCHAR kPendingFunctionsWindowClassName[] =
|
||||||
|
L"XeniaPendingFunctionsWindowClass";
|
||||||
|
if (!pending_functions_window_class_registered_) {
|
||||||
|
WNDCLASSEXW pending_functions_window_class = {};
|
||||||
|
pending_functions_window_class.cbSize =
|
||||||
|
sizeof(pending_functions_window_class);
|
||||||
|
pending_functions_window_class.lpfnWndProc = PendingFunctionsWndProc;
|
||||||
|
pending_functions_window_class.hInstance = hinstance_;
|
||||||
|
pending_functions_window_class.lpszClassName =
|
||||||
|
kPendingFunctionsWindowClassName;
|
||||||
|
if (!RegisterClassExW(&pending_functions_window_class)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pending_functions_window_class_registered_ = true;
|
||||||
|
}
|
||||||
|
pending_functions_hwnd_ = CreateWindowExW(
|
||||||
|
0, kPendingFunctionsWindowClassName, L"Xenia Pending Functions",
|
||||||
|
WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
|
HWND_MESSAGE, nullptr, hinstance_, this);
|
||||||
|
if (!pending_functions_hwnd_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32WindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
|
while (!PostMessageW(pending_functions_hwnd_,
|
||||||
|
kPendingFunctionsWindowClassMessageExecute, 0, 0)) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32WindowedAppContext::PlatformQuitFromUIThread() {
|
||||||
|
// Send WM_QUIT to whichever loop happens to process it - may be the loop of a
|
||||||
|
// built-in modal window, which is unaware of HasQuitFromUIThread, don't let
|
||||||
|
// it delay quitting indefinitely.
|
||||||
|
PostQuitMessage(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Win32WindowedAppContext::RunMainMessageLoop() {
|
||||||
|
int result = EXIT_SUCCESS;
|
||||||
|
MSG message;
|
||||||
|
// The HasQuitFromUIThread check is not absolutely required, but for
|
||||||
|
// additional safety in case WM_QUIT is not received for any reason.
|
||||||
|
while (!HasQuitFromUIThread()) {
|
||||||
|
BOOL message_result = GetMessageW(&message, nullptr, 0, 0);
|
||||||
|
if (message_result == 0 || message_result == -1) {
|
||||||
|
// WM_QUIT (0 - this is the primary message loop, no need to resend, also
|
||||||
|
// contains the result from PostQuitMessage in wParam) or an error
|
||||||
|
// (-1). Quitting the context will run the pending functions. Getting
|
||||||
|
// WM_QUIT doesn't imply that QuitFromUIThread has been called already, it
|
||||||
|
// may originate in some place other than PlatformQuitFromUIThread as
|
||||||
|
// well - call it to finish everything including the pending functions.
|
||||||
|
QuitFromUIThread();
|
||||||
|
result = message_result ? EXIT_FAILURE : int(message.wParam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TranslateMessage(&message);
|
||||||
|
DispatchMessageW(&message);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK Win32WindowedAppContext::PendingFunctionsWndProc(
|
||||||
|
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
|
||||||
|
if (message == WM_CLOSE) {
|
||||||
|
// Need the window for the entire context's lifetime, don't allow anything
|
||||||
|
// to close it.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (message == WM_NCCREATE) {
|
||||||
|
SetWindowLongPtrW(
|
||||||
|
hwnd, GWLP_USERDATA,
|
||||||
|
reinterpret_cast<LONG_PTR>(
|
||||||
|
reinterpret_cast<const CREATESTRUCTW*>(lparam)->lpCreateParams));
|
||||||
|
} else {
|
||||||
|
auto app_context = reinterpret_cast<Win32WindowedAppContext*>(
|
||||||
|
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||||
|
if (app_context) {
|
||||||
|
switch (message) {
|
||||||
|
case WM_DESTROY:
|
||||||
|
// The message-only window owned by the context is being destroyed,
|
||||||
|
// thus the context won't be able to execute pending functions
|
||||||
|
// anymore - can't continue functioning normally.
|
||||||
|
app_context->QuitFromUIThread();
|
||||||
|
break;
|
||||||
|
case kPendingFunctionsWindowClassMessageExecute:
|
||||||
|
app_context->ExecutePendingFunctionsFromUIThread();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DefWindowProcW(hwnd, message, wparam, lparam);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_
|
||||||
|
#define XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class Win32WindowedAppContext final : public WindowedAppContext {
|
||||||
|
public:
|
||||||
|
// Must call Initialize and check its result after creating to be able to
|
||||||
|
// perform pending function calls.
|
||||||
|
explicit Win32WindowedAppContext(HINSTANCE hinstance, int show_cmd)
|
||||||
|
: hinstance_(hinstance), show_cmd_(show_cmd) {}
|
||||||
|
~Win32WindowedAppContext();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
HINSTANCE hinstance() const { return hinstance_; }
|
||||||
|
int show_cmd() const { return show_cmd_; }
|
||||||
|
|
||||||
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
|
||||||
|
void PlatformQuitFromUIThread() override;
|
||||||
|
|
||||||
|
int RunMainMessageLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum : UINT {
|
||||||
|
kPendingFunctionsWindowClassMessageExecute = WM_USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
static LRESULT CALLBACK PendingFunctionsWndProc(HWND hwnd, UINT message,
|
||||||
|
WPARAM wparam, LPARAM lparam);
|
||||||
|
|
||||||
|
HINSTANCE hinstance_;
|
||||||
|
int show_cmd_;
|
||||||
|
HWND pending_functions_hwnd_ = nullptr;
|
||||||
|
|
||||||
|
static bool pending_functions_window_class_registered_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_UI_WINDOWED_APP_CONTEXT_WIN_H_
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
#include "xenia/ui/windowed_app_context_gtk.h"
|
||||||
|
|
||||||
|
extern "C" int main(int argc_pre_gtk, char** argv_pre_gtk) {
|
||||||
|
// Initialize GTK+, which will handle and remove its own arguments from argv.
|
||||||
|
// Both GTK+ and Xenia use --option=value argument format (see man
|
||||||
|
// gtk-options), however, it's meaningless to try to parse the same argument
|
||||||
|
// both as a GTK+ one and as a cvar. Make GTK+ options take precedence in case
|
||||||
|
// of a name collision, as there's an alternative way of setting Xenia options
|
||||||
|
// (the config).
|
||||||
|
int argc_post_gtk = argc_pre_gtk;
|
||||||
|
char** argv_post_gtk = argv_pre_gtk;
|
||||||
|
if (!gtk_init_check(&argc_post_gtk, &argv_post_gtk)) {
|
||||||
|
// Logging has not been initialized yet.
|
||||||
|
std::fputs("Failed to initialize GTK+\n", stderr);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
|
||||||
|
{
|
||||||
|
xe::ui::GTKWindowedAppContext app_context;
|
||||||
|
|
||||||
|
std::unique_ptr<xe::ui::WindowedApp> app =
|
||||||
|
xe::ui::GetWindowedAppCreator()(app_context);
|
||||||
|
|
||||||
|
cvar::ParseLaunchArguments(argc_post_gtk, argv_post_gtk,
|
||||||
|
app->GetPositionalOptionsUsage(),
|
||||||
|
app->GetPositionalOptions());
|
||||||
|
|
||||||
|
// Initialize logging. Needs parsed cvars.
|
||||||
|
xe::InitializeLogging(app->GetName());
|
||||||
|
|
||||||
|
if (app->OnInitialize()) {
|
||||||
|
app_context.RunMainGTKLoop();
|
||||||
|
result = EXIT_SUCCESS;
|
||||||
|
} else {
|
||||||
|
result = EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
app->InvokeOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging may still be needed in the destructors.
|
||||||
|
xe::ShutdownLogging();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/base/console.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
|
#include "xenia/base/main_win.h"
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
#include "xenia/ui/windowed_app_context_win.h"
|
||||||
|
|
||||||
|
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
||||||
|
"General");
|
||||||
|
|
||||||
|
int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE hinstance_prev,
|
||||||
|
LPWSTR command_line, int show_cmd) {
|
||||||
|
int result;
|
||||||
|
|
||||||
|
{
|
||||||
|
xe::ui::Win32WindowedAppContext app_context(hinstance, show_cmd);
|
||||||
|
// TODO(Triang3l): Initialize creates a window. Set DPI awareness via the
|
||||||
|
// manifest.
|
||||||
|
if (!app_context.Initialize()) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<xe::ui::WindowedApp> app =
|
||||||
|
xe::ui::GetWindowedAppCreator()(app_context);
|
||||||
|
|
||||||
|
if (!xe::ParseWin32LaunchArguments(false, app->GetPositionalOptionsUsage(),
|
||||||
|
app->GetPositionalOptions(), nullptr)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Triang3l): Rework this, need to initialize the console properly,
|
||||||
|
// disable has_console_attached_ by default in windowed apps, and attach
|
||||||
|
// only if needed.
|
||||||
|
if (cvars::enable_console) {
|
||||||
|
xe::AttachConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize COM on the UI thread with the apartment-threaded concurrency
|
||||||
|
// model, so dialogs can be used.
|
||||||
|
if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
xe::InitializeWin32App(app->GetName());
|
||||||
|
|
||||||
|
result =
|
||||||
|
app->OnInitialize() ? app_context.RunMainMessageLoop() : EXIT_FAILURE;
|
||||||
|
|
||||||
|
app->InvokeOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging may still be needed in the destructors.
|
||||||
|
xe::ShutdownWin32App();
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ project("xenia-vfs-dump")
|
||||||
|
|
||||||
files({
|
files({
|
||||||
"vfs_dump.cc",
|
"vfs_dump.cc",
|
||||||
project_root.."/src/xenia/base/main_"..platform_suffix..".cc",
|
project_root.."/src/xenia/base/console_app_main_"..platform_suffix..".cc",
|
||||||
})
|
})
|
||||||
resincludedirs({
|
resincludedirs({
|
||||||
project_root,
|
project_root,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
@ -11,8 +11,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
|
|
||||||
#include "xenia/vfs/devices/stfs_container_device.h"
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
|
@ -113,5 +114,5 @@ int vfs_dump_main(const std::vector<std::string>& args) {
|
||||||
} // namespace vfs
|
} // namespace vfs
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT("xenia-vfs-dump", xe::vfs::vfs_dump_main,
|
XE_DEFINE_CONSOLE_APP("xenia-vfs-dump", xe::vfs::vfs_dump_main,
|
||||||
"[source] [dump_path]", "source", "dump_path");
|
"[source] [dump_path]", "source", "dump_path");
|
||||||
|
|
|
@ -31,7 +31,7 @@ local function combined_test_suite(test_suite_name, project_root, base_path, con
|
||||||
})
|
})
|
||||||
files({
|
files({
|
||||||
project_root.."/"..build_tools_src.."/test_suite_main.cc",
|
project_root.."/"..build_tools_src.."/test_suite_main.cc",
|
||||||
project_root.."/src/xenia/base/main_"..platform_suffix..".cc",
|
project_root.."/src/xenia/base/console_app_main_"..platform_suffix..".cc",
|
||||||
base_path.."/**_test.cc",
|
base_path.."/**_test.cc",
|
||||||
})
|
})
|
||||||
filter("toolset:msc")
|
filter("toolset:msc")
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/console_app_main.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/base/main.h"
|
|
||||||
|
|
||||||
#define CATCH_CONFIG_RUNNER
|
#define CATCH_CONFIG_RUNNER
|
||||||
#include "third_party/catch/single_include/catch2/catch.hpp"
|
#include "third_party/catch/single_include/catch2/catch.hpp"
|
||||||
|
@ -43,5 +43,5 @@ int test_suite_main(const std::vector<std::string>& args) {
|
||||||
#error XE_TEST_SUITE_NAME is undefined!
|
#error XE_TEST_SUITE_NAME is undefined!
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DEFINE_ENTRY_POINT_TRANSPARENT(XE_TEST_SUITE_NAME,
|
XE_DEFINE_CONSOLE_APP_TRANSPARENT(XE_TEST_SUITE_NAME,
|
||||||
xe::test_suite::test_suite_main);
|
xe::test_suite::test_suite_main);
|
||||||
|
|
Loading…
Reference in New Issue