[UI] Loop thread to main thread WindowedAppContext

This commit is contained in:
Triang3l 2021-08-28 19:38:24 +03:00
parent f540c188bf
commit 6ce5330f5f
95 changed files with 2343 additions and 1235 deletions

View File

@ -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",
}) })

View File

@ -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" },

View File

@ -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;

View File

@ -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({

View File

@ -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");

View File

@ -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

View File

@ -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;

View File

@ -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>

22
src/xenia/base/console.h Normal file
View File

@ -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_

View File

@ -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_

View File

@ -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"

View File

@ -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;
} }

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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.

View File

@ -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.

View File

@ -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 {

View File

@ -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

29
src/xenia/base/main_win.h Normal file
View File

@ -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_

View File

@ -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",
}) })

View File

@ -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");

View File

@ -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");

View File

@ -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",

View File

@ -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, "");

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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();

View File

@ -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");

View 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");

View 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"

View File

@ -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"

View File

@ -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);
} }

View File

@ -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")

View File

@ -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");

View File

@ -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;
} }

View File

@ -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

View File

@ -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_;

View File

@ -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;

View File

@ -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();

View File

@ -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")

View File

@ -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);

View File

@ -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");

View 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");

View 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);

View File

@ -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,

View File

@ -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;
}); });

View File

@ -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;
} }

View File

@ -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;
}; };

View File

@ -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());

View File

@ -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"

View File

@ -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");

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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,

View File

@ -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;
} }

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -13,3 +13,4 @@ project("xenia-ui")
}) })
local_platform_files() local_platform_files()
removefiles({"*_demo.cc"}) removefiles({"*_demo.cc"})
removefiles({"windowed_app_main_*.cc"})

View File

@ -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,

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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();
} }

View File

@ -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_;

View File

@ -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);

View File

@ -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;
}; };

129
src/xenia/ui/windowed_app.h Normal file
View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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");

View File

@ -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")

View File

@ -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);