Merge branch 'master' into vulkan
This commit is contained in:
@@ -47,8 +47,8 @@ bool D3D12Provider::IsD3D12APIAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<D3D12Provider> D3D12Provider::Create(Window* main_window) {
|
||||
std::unique_ptr<D3D12Provider> provider(new D3D12Provider(main_window));
|
||||
std::unique_ptr<D3D12Provider> D3D12Provider::Create() {
|
||||
std::unique_ptr<D3D12Provider> provider(new D3D12Provider);
|
||||
if (!provider->Initialize()) {
|
||||
xe::FatalError(
|
||||
"Unable to initialize Direct3D 12 graphics subsystem.\n"
|
||||
@@ -63,9 +63,6 @@ std::unique_ptr<D3D12Provider> D3D12Provider::Create(Window* main_window) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
D3D12Provider::D3D12Provider(Window* main_window)
|
||||
: GraphicsProvider(main_window) {}
|
||||
|
||||
D3D12Provider::~D3D12Provider() {
|
||||
if (graphics_analysis_ != nullptr) {
|
||||
graphics_analysis_->Release();
|
||||
|
||||
@@ -27,7 +27,7 @@ class D3D12Provider : public GraphicsProvider {
|
||||
|
||||
static bool IsD3D12APIAvailable();
|
||||
|
||||
static std::unique_ptr<D3D12Provider> Create(Window* main_window);
|
||||
static std::unique_ptr<D3D12Provider> Create();
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateHostContext(
|
||||
Window* target_window) override;
|
||||
@@ -147,7 +147,7 @@ class D3D12Provider : public GraphicsProvider {
|
||||
}
|
||||
|
||||
private:
|
||||
explicit D3D12Provider(Window* main_window);
|
||||
D3D12Provider() = default;
|
||||
|
||||
static bool EnableIncreaseBasePriorityPrivilege();
|
||||
bool Initialize();
|
||||
|
||||
@@ -2,28 +2,44 @@
|
||||
******************************************************************************
|
||||
* 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. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/main.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 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) {
|
||||
return xe::ui::d3d12::D3D12Provider::Create(window);
|
||||
protected:
|
||||
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 xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-ui-window-d3d12-demo", xe::ui::window_demo_main, "");
|
||||
XE_DEFINE_WINDOWED_APP(xenia_ui_window_d3d12_demo,
|
||||
xe::ui::d3d12::D3D12WindowDemoApp::Create);
|
||||
|
||||
@@ -30,7 +30,7 @@ project("xenia-ui-window-d3d12-demo")
|
||||
files({
|
||||
"../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({
|
||||
project_root,
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
||||
// Microsoft headers after platform_win.h.
|
||||
#include <wrl/client.h>
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
@@ -106,32 +109,16 @@ 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) {
|
||||
// TODO(benvanik): FileSaveDialog.
|
||||
assert_true(mode() == Mode::kOpen);
|
||||
// TODO(benvanik): folder dialogs.
|
||||
assert_true(type() == Type::kFile);
|
||||
|
||||
com_ptr<IFileDialog> file_dialog;
|
||||
Microsoft::WRL::ComPtr<IFileDialog> file_dialog;
|
||||
HRESULT hr =
|
||||
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(file_dialog.addressof()));
|
||||
IID_PPV_ARGS(&file_dialog));
|
||||
if (!SUCCEEDED(hr)) {
|
||||
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.
|
||||
com_ptr<IFileDialogEvents> file_dialog_events;
|
||||
hr = CDialogEventHandler_CreateInstance(
|
||||
IID_PPV_ARGS(file_dialog_events.addressof()));
|
||||
Microsoft::WRL::ComPtr<IFileDialogEvents> file_dialog_events;
|
||||
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&file_dialog_events));
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
DWORD cookie;
|
||||
hr = file_dialog->Advise(file_dialog_events, &cookie);
|
||||
hr = file_dialog->Advise(file_dialog_events.Get(), &cookie);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
@@ -201,8 +187,8 @@ bool Win32FilePicker::Show(void* parent_window_handle) {
|
||||
|
||||
// Obtain the result once the user clicks the 'Open' button.
|
||||
// The result is an IShellItem object.
|
||||
com_ptr<IShellItem> shell_item;
|
||||
hr = file_dialog->GetResult(shell_item.addressof());
|
||||
Microsoft::WRL::ComPtr<IShellItem> shell_item;
|
||||
hr = file_dialog->GetResult(&shell_item);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -36,9 +36,6 @@ class GraphicsProvider {
|
||||
|
||||
virtual ~GraphicsProvider() = default;
|
||||
|
||||
// The 'main' window of an application, used to query provider information.
|
||||
Window* main_window() const { return main_window_; }
|
||||
|
||||
// Creates a new host-side graphics context and swapchain, possibly presenting
|
||||
// to a window and using the immediate drawer.
|
||||
virtual std::unique_ptr<GraphicsContext> CreateHostContext(
|
||||
@@ -48,9 +45,7 @@ class GraphicsProvider {
|
||||
virtual std::unique_ptr<GraphicsContext> CreateEmulationContext() = 0;
|
||||
|
||||
protected:
|
||||
explicit GraphicsProvider(Window* main_window) : main_window_(main_window) {}
|
||||
|
||||
Window* main_window_ = nullptr;
|
||||
GraphicsProvider() = default;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
Loop::Loop() = default;
|
||||
|
||||
Loop::~Loop() = default;
|
||||
|
||||
void Loop::PostSynchronous(std::function<void()> fn) {
|
||||
if (is_on_loop_thread()) {
|
||||
// Prevent deadlock if we are executing on ourselves.
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
xe::threading::Fence fence;
|
||||
Post([&fn, &fence]() {
|
||||
fn();
|
||||
fence.Signal();
|
||||
});
|
||||
fence.Wait();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_LOOP_H_
|
||||
#define XENIA_UI_LOOP_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Loop {
|
||||
public:
|
||||
static std::unique_ptr<Loop> Create();
|
||||
|
||||
Loop();
|
||||
virtual ~Loop();
|
||||
|
||||
// Returns true if the currently executing code is within the loop thread.
|
||||
virtual bool is_on_loop_thread() = 0;
|
||||
|
||||
virtual void Post(std::function<void()> fn) = 0;
|
||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
||||
void PostSynchronous(std::function<void()> fn);
|
||||
|
||||
virtual void Quit() = 0;
|
||||
virtual void AwaitQuit() = 0;
|
||||
|
||||
Delegate<UIEvent*> on_quit;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_LOOP_H_
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/loop_gtk.h"
|
||||
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class PostedFn {
|
||||
public:
|
||||
explicit PostedFn(std::function<void()> fn) : fn_(std::move(fn)) {}
|
||||
void Call() { fn_(); }
|
||||
|
||||
private:
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
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) {
|
||||
PostedFn* Fn = reinterpret_cast<PostedFn*>(posted_fn);
|
||||
Fn->Call();
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void GTKLoop::Post(std::function<void()> fn) {
|
||||
assert_true(thread_id_ != std::thread::id());
|
||||
gdk_threads_add_idle(_posted_fn_thunk,
|
||||
reinterpret_cast<gpointer>(new PostedFn(std::move(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 PostedFn(std::move(fn))));
|
||||
}
|
||||
|
||||
void GTKLoop::Quit() { assert_true(thread_id_ != std::thread::id()); }
|
||||
|
||||
void GTKLoop::AwaitQuit() { quit_fence_.Wait(); }
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/loop_win.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
std::unique_ptr<Loop> Loop::Create() { return std::make_unique<Win32Loop>(); }
|
||||
|
||||
Win32Loop::Win32Loop() : thread_id_(0) {
|
||||
timer_queue_ = CreateTimerQueue();
|
||||
|
||||
xe::threading::Fence init_fence;
|
||||
thread_ = std::thread([&init_fence, this]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
thread_id_ = GetCurrentThreadId();
|
||||
|
||||
// Make a Win32 call to enable the thread queue.
|
||||
MSG msg;
|
||||
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
||||
|
||||
init_fence.Signal();
|
||||
|
||||
ThreadMain();
|
||||
|
||||
quit_fence_.Signal();
|
||||
});
|
||||
init_fence.Wait();
|
||||
}
|
||||
|
||||
Win32Loop::~Win32Loop() {
|
||||
Quit();
|
||||
thread_.join();
|
||||
|
||||
DeleteTimerQueueEx(timer_queue_, INVALID_HANDLE_VALUE);
|
||||
std::lock_guard<std::mutex> lock(pending_timers_mutex_);
|
||||
while (!pending_timers_.empty()) {
|
||||
auto timer = pending_timers_.back();
|
||||
pending_timers_.pop_back();
|
||||
delete timer;
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Loop::ThreadMain() {
|
||||
MSG msg;
|
||||
while (!should_exit_ && GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
|
||||
// Process queued functions.
|
||||
std::lock_guard<std::recursive_mutex> lock(posted_functions_mutex_);
|
||||
for (auto it = posted_functions_.begin(); it != posted_functions_.end();) {
|
||||
(*it).Call();
|
||||
it = posted_functions_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
UIEvent e(nullptr);
|
||||
on_quit(&e);
|
||||
}
|
||||
|
||||
bool Win32Loop::is_on_loop_thread() {
|
||||
return thread_id_ == GetCurrentThreadId();
|
||||
}
|
||||
|
||||
void Win32Loop::Post(std::function<void()> fn) {
|
||||
assert_true(thread_id_ != 0);
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(posted_functions_mutex_);
|
||||
PostedFn posted_fn(fn);
|
||||
posted_functions_.push_back(posted_fn);
|
||||
}
|
||||
|
||||
while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) {
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Loop::TimerQueueCallback(void* context, uint8_t) {
|
||||
auto timer = reinterpret_cast<PendingTimer*>(context);
|
||||
auto loop = timer->loop;
|
||||
auto fn = std::move(timer->fn);
|
||||
DeleteTimerQueueTimer(timer->timer_queue, timer->timer_handle, NULL);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(loop->pending_timers_mutex_);
|
||||
loop->pending_timers_.remove(timer);
|
||||
}
|
||||
delete timer;
|
||||
loop->Post(std::move(fn));
|
||||
}
|
||||
|
||||
void Win32Loop::PostDelayed(std::function<void()> fn, uint64_t delay_millis) {
|
||||
if (!delay_millis) {
|
||||
Post(std::move(fn));
|
||||
return;
|
||||
}
|
||||
auto timer = new PendingTimer();
|
||||
timer->loop = this;
|
||||
timer->timer_queue = timer_queue_;
|
||||
timer->fn = std::move(fn);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pending_timers_mutex_);
|
||||
pending_timers_.push_back(timer);
|
||||
}
|
||||
CreateTimerQueueTimer(&timer->timer_handle, timer_queue_,
|
||||
WAITORTIMERCALLBACK(TimerQueueCallback), timer,
|
||||
DWORD(delay_millis), 0,
|
||||
WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE);
|
||||
}
|
||||
|
||||
void Win32Loop::Quit() {
|
||||
assert_true(thread_id_ != 0);
|
||||
should_exit_ = true;
|
||||
while (!PostThreadMessage(thread_id_, WM_NULL, 0, 0)) {
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Win32Loop::AwaitQuit() { quit_fence_.Wait(); }
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_LOOP_WIN_H_
|
||||
#define XENIA_UI_LOOP_WIN_H_
|
||||
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class Win32Loop : public Loop {
|
||||
public:
|
||||
Win32Loop();
|
||||
~Win32Loop() override;
|
||||
|
||||
bool is_on_loop_thread() override;
|
||||
|
||||
void Post(std::function<void()> fn) override;
|
||||
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;
|
||||
|
||||
void Quit() override;
|
||||
void AwaitQuit() override;
|
||||
|
||||
private:
|
||||
struct PendingTimer {
|
||||
Win32Loop* loop;
|
||||
HANDLE timer_queue;
|
||||
HANDLE timer_handle;
|
||||
std::function<void()> fn;
|
||||
};
|
||||
|
||||
class PostedFn {
|
||||
public:
|
||||
explicit PostedFn(std::function<void()> fn) : fn_(std::move(fn)) {}
|
||||
void Call() { fn_(); }
|
||||
|
||||
private:
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
void ThreadMain();
|
||||
|
||||
static void TimerQueueCallback(void* context, uint8_t);
|
||||
|
||||
std::thread thread_;
|
||||
DWORD thread_id_;
|
||||
bool should_exit_ = false;
|
||||
xe::threading::Fence quit_fence_;
|
||||
|
||||
HANDLE timer_queue_;
|
||||
std::mutex pending_timers_mutex_;
|
||||
std::list<PendingTimer*> pending_timers_;
|
||||
|
||||
std::recursive_mutex posted_functions_mutex_;
|
||||
std::list<PostedFn> posted_functions_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_LOOP_WIN_H_
|
||||
@@ -13,3 +13,4 @@ project("xenia-ui")
|
||||
})
|
||||
local_platform_files()
|
||||
removefiles({"*_demo.cc"})
|
||||
removefiles({"windowed_app_main_*.cc"})
|
||||
|
||||
@@ -30,7 +30,7 @@ project("xenia-ui-window-vulkan-demo")
|
||||
files({
|
||||
"../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({
|
||||
project_root,
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
std::unique_ptr<VulkanProvider> VulkanProvider::Create(Window* main_window) {
|
||||
std::unique_ptr<VulkanProvider> provider(new VulkanProvider(main_window));
|
||||
std::unique_ptr<VulkanProvider> VulkanProvider::Create() {
|
||||
std::unique_ptr<VulkanProvider> provider(new VulkanProvider);
|
||||
if (!provider->Initialize()) {
|
||||
xe::FatalError(
|
||||
"Unable to initialize Vulkan graphics subsystem.\n"
|
||||
@@ -57,9 +57,6 @@ std::unique_ptr<VulkanProvider> VulkanProvider::Create(Window* main_window) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
VulkanProvider::VulkanProvider(Window* main_window)
|
||||
: GraphicsProvider(main_window) {}
|
||||
|
||||
VulkanProvider::~VulkanProvider() {
|
||||
for (size_t i = 0; i < size_t(HostSampler::kCount); ++i) {
|
||||
if (host_samplers_[i] != VK_NULL_HANDLE) {
|
||||
|
||||
@@ -49,7 +49,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||
public:
|
||||
~VulkanProvider() override;
|
||||
|
||||
static std::unique_ptr<VulkanProvider> Create(Window* main_window);
|
||||
static std::unique_ptr<VulkanProvider> Create();
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateHostContext(
|
||||
Window* target_window) override;
|
||||
@@ -269,7 +269,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||
}
|
||||
|
||||
private:
|
||||
explicit VulkanProvider(Window* main_window);
|
||||
VulkanProvider() = default;
|
||||
|
||||
bool Initialize();
|
||||
|
||||
|
||||
@@ -2,28 +2,44 @@
|
||||
******************************************************************************
|
||||
* 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. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/main.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 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) {
|
||||
return xe::ui::vulkan::VulkanProvider::Create(window);
|
||||
protected:
|
||||
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 xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-ui-window-vulkan-demo", xe::ui::window_demo_main, "");
|
||||
XE_DEFINE_WINDOWED_APP(xenia_ui_window_vulkan_demo,
|
||||
xe::ui::vulkan::VulkanWindowDemoApp::Create);
|
||||
|
||||
@@ -31,8 +31,8 @@ constexpr double kDoubleClickDistance = 5;
|
||||
|
||||
constexpr int32_t kMouseWheelDetent = 120;
|
||||
|
||||
Window::Window(Loop* loop, const std::string& title)
|
||||
: loop_(loop), title_(title) {}
|
||||
Window::Window(WindowedAppContext& app_context, const std::string& title)
|
||||
: app_context_(app_context), title_(title) {}
|
||||
|
||||
Window::~Window() {
|
||||
// Context must have been cleaned up already in OnDestroy.
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/ui_event.h"
|
||||
#include "xenia/ui/window_listener.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
@@ -30,11 +31,13 @@ class ImGuiDrawer;
|
||||
|
||||
class Window {
|
||||
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();
|
||||
|
||||
Loop* loop() const { return loop_; }
|
||||
WindowedAppContext& app_context() const { return app_context_; }
|
||||
|
||||
virtual NativePlatformHandle native_platform_handle() const = 0;
|
||||
virtual NativeWindowHandle native_handle() const = 0;
|
||||
|
||||
@@ -68,8 +71,11 @@ class Window {
|
||||
virtual bool is_bordered() const { return false; }
|
||||
virtual void set_bordered(bool enabled) {}
|
||||
|
||||
virtual int get_dpi() const { return 96; }
|
||||
virtual float get_dpi_scale() const { return get_dpi() / 96.f; }
|
||||
virtual int get_medium_dpi() const { return 96; }
|
||||
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_; }
|
||||
virtual void set_focus(bool value) { has_focus_ = value; }
|
||||
@@ -77,6 +83,8 @@ class Window {
|
||||
bool is_cursor_visible() const { return is_cursor_visible_; }
|
||||
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 height() const { return height_; }
|
||||
int32_t scaled_width() const { return int32_t(width_ * get_dpi_scale()); }
|
||||
@@ -134,7 +142,7 @@ class Window {
|
||||
Delegate<MouseEvent*> on_mouse_wheel;
|
||||
|
||||
protected:
|
||||
Window(Loop* loop, const std::string& title);
|
||||
Window(WindowedAppContext& app_context, const std::string& title);
|
||||
|
||||
void ForEachListener(std::function<void(WindowListener*)> fn);
|
||||
void TryForEachListener(std::function<bool(WindowListener*)> fn);
|
||||
@@ -169,11 +177,19 @@ class Window {
|
||||
|
||||
void OnKeyPress(KeyEvent* e, bool is_down, bool is_char);
|
||||
|
||||
Loop* loop_ = nullptr;
|
||||
WindowedAppContext& app_context_;
|
||||
std::unique_ptr<MenuItem> main_menu_;
|
||||
std::string title_;
|
||||
#ifdef XE_PLATFORM_GNU_LINUX
|
||||
// GTK must have a default value here that isn't 0
|
||||
// TODO(Triang3l): Cleanup and unify this. May need to get the first resize
|
||||
// message on various platforms.
|
||||
int32_t width_ = 1280;
|
||||
int32_t height_ = 720;
|
||||
#else
|
||||
int32_t width_ = 0;
|
||||
int32_t height_ = 0;
|
||||
#endif
|
||||
bool has_focus_ = true;
|
||||
bool is_cursor_visible_ = true;
|
||||
bool is_imgui_input_enabled_ = false;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* 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. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
@@ -20,28 +19,29 @@
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/window_demo.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
// Implemented in one of the window_*_demo.cc files under a subdir.
|
||||
std::unique_ptr<GraphicsProvider> CreateDemoGraphicsProvider(Window* window);
|
||||
WindowDemoApp::~WindowDemoApp() { Profiler::Shutdown(); }
|
||||
|
||||
int window_demo_main(const std::vector<std::string>& args) {
|
||||
bool WindowDemoApp::OnInitialize() {
|
||||
Profiler::Initialize();
|
||||
Profiler::ThreadEnter("main");
|
||||
Profiler::ThreadEnter("Main");
|
||||
|
||||
// Create run loop and the window.
|
||||
auto loop = ui::Loop::Create();
|
||||
auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name);
|
||||
loop->PostSynchronous([&window]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
xe::Profiler::ThreadEnter("Win32 Loop");
|
||||
if (!window->Initialize()) {
|
||||
FatalError("Failed to initialize main window");
|
||||
return;
|
||||
}
|
||||
});
|
||||
// Create graphics provider that provides the context for the window.
|
||||
graphics_provider_ = CreateGraphicsProvider();
|
||||
if (!graphics_provider_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the window.
|
||||
window_ = xe::ui::Window::Create(app_context(), GetName());
|
||||
if (!window_->Initialize()) {
|
||||
XELOGE("Failed to initialize main window");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Main menu.
|
||||
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",
|
||||
"Alt+F4",
|
||||
[&window]() { window->Close(); }));
|
||||
[this]() { window_->Close(); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(file_menu));
|
||||
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(); }));
|
||||
}
|
||||
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.
|
||||
window->Resize(1920, 1200);
|
||||
window_->Resize(1920, 1200);
|
||||
|
||||
// Create the graphics context used for drawing and setup the window.
|
||||
std::unique_ptr<GraphicsProvider> graphics_provider;
|
||||
loop->PostSynchronous([&window, &graphics_provider]() {
|
||||
// Create graphics provider and an initial context for the window.
|
||||
// The window will finish initialization wtih the context (loading
|
||||
// resources, etc).
|
||||
graphics_provider = CreateDemoGraphicsProvider(window.get());
|
||||
window->set_context(graphics_provider->CreateHostContext(window.get()));
|
||||
// Create the graphics context for the window. The window will finish
|
||||
// initialization with the context (loading resources, etc).
|
||||
window_->set_context(graphics_provider_->CreateHostContext(window_.get()));
|
||||
|
||||
// Setup the profiler display.
|
||||
GraphicsContextLock context_lock(window->context());
|
||||
Profiler::set_window(window.get());
|
||||
// Setup the profiler display.
|
||||
GraphicsContextLock context_lock(window_->context());
|
||||
Profiler::set_window(window_.get());
|
||||
|
||||
// Enable imgui input.
|
||||
window->set_imgui_input_enabled(true);
|
||||
// Enable imgui input.
|
||||
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(
|
||||
[&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) {
|
||||
window_->on_key_down.AddListener([](xe::ui::KeyEvent* e) {
|
||||
switch (e->virtual_key()) {
|
||||
case VirtualKey::kF3:
|
||||
Profiler::ToggleDisplay();
|
||||
@@ -102,23 +93,19 @@ int window_demo_main(const std::vector<std::string>& args) {
|
||||
}
|
||||
});
|
||||
|
||||
window->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
||||
auto& io = window->imgui_drawer()->GetIO();
|
||||
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||
auto& io = window_->imgui_drawer()->GetIO();
|
||||
|
||||
ImGui::ShowDemoWindow();
|
||||
ImGui::ShowMetricsWindow();
|
||||
|
||||
Profiler::Flip();
|
||||
|
||||
// Continuous paint.
|
||||
window->Invalidate();
|
||||
window_->Invalidate();
|
||||
});
|
||||
|
||||
// Wait until we are exited.
|
||||
loop->AwaitQuit();
|
||||
|
||||
window.reset();
|
||||
loop.reset();
|
||||
graphics_provider.reset();
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
44
src/xenia/ui/window_demo.h
Normal file
44
src/xenia/ui/window_demo.h
Normal 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_
|
||||
@@ -7,9 +7,13 @@
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <X11/Xlib-xcb.h>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform_linux.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
@@ -18,33 +22,27 @@
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
class FnWrapper {
|
||||
public:
|
||||
explicit FnWrapper(std::function<void()> fn) : fn_(std::move(fn)) {}
|
||||
void Call() { fn_(); }
|
||||
|
||||
private:
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Window> Window::Create(Loop* loop, const std::string& title) {
|
||||
return std::make_unique<GTKWindow>(loop, title);
|
||||
std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
||||
const std::string& title) {
|
||||
return std::make_unique<GTKWindow>(app_context, title);
|
||||
}
|
||||
|
||||
GTKWindow::GTKWindow(Loop* loop, const std::string& title)
|
||||
: Window(loop, title) {}
|
||||
GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string& title)
|
||||
: Window(app_context, title) {}
|
||||
|
||||
GTKWindow::~GTKWindow() {
|
||||
OnDestroy();
|
||||
if (window_) {
|
||||
gtk_widget_destroy(window_);
|
||||
if (GTK_IS_WIDGET(window_)) {
|
||||
gtk_widget_destroy(window_);
|
||||
}
|
||||
window_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool GTKWindow::Initialize() { return OnCreate(); }
|
||||
|
||||
void gtk_event_handler_(GtkWidget* widget, GdkEvent* event, gpointer data) {
|
||||
gboolean gtk_event_handler(GtkWidget* widget, GdkEvent* event, gpointer data) {
|
||||
GTKWindow* window = reinterpret_cast<GTKWindow*>(data);
|
||||
switch (event->type) {
|
||||
case GDK_OWNER_CHANGE:
|
||||
@@ -58,34 +56,81 @@ void gtk_event_handler_(GtkWidget* widget, GdkEvent* event, gpointer data) {
|
||||
window->HandleKeyboard(&(event->key));
|
||||
break;
|
||||
case GDK_SCROLL:
|
||||
case GDK_BUTTON_PRESS:
|
||||
case GDK_MOTION_NOTIFY:
|
||||
case GDK_BUTTON_PRESS:
|
||||
case GDK_BUTTON_RELEASE:
|
||||
window->HandleMouse(&(event->any));
|
||||
break;
|
||||
case GDK_FOCUS_CHANGE:
|
||||
window->HandleWindowFocus(&(event->focus_change));
|
||||
break;
|
||||
case GDK_CONFIGURE:
|
||||
window->HandleWindowResize(&(event->configure));
|
||||
// Only handle the event for the drawing area so we don't save
|
||||
// a width and height that includes the menu bar on the full window
|
||||
if (event->configure.window ==
|
||||
gtk_widget_get_window(window->drawing_area_)) {
|
||||
window->HandleWindowResize(&(event->configure));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
return;
|
||||
break;
|
||||
}
|
||||
// Propagate the event to other handlers
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
void GTKWindow::Create() {
|
||||
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(window_), (gchar*)title_.c_str());
|
||||
gtk_window_set_default_size(GTK_WINDOW(window_), 1280, 720);
|
||||
gtk_widget_show_all(window_);
|
||||
g_signal_connect(G_OBJECT(window_), "destroy", G_CALLBACK(gtk_main_quit),
|
||||
NULL);
|
||||
g_signal_connect(G_OBJECT(window_), "event", G_CALLBACK(gtk_event_handler_),
|
||||
reinterpret_cast<gpointer>(this));
|
||||
gboolean draw_callback(GtkWidget* widget, GdkFrameClock* frame_clock,
|
||||
gpointer data) {
|
||||
GTKWindow* window = reinterpret_cast<GTKWindow*>(data);
|
||||
window->HandleWindowPaint();
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
gboolean close_callback(GtkWidget* widget, gpointer data) {
|
||||
GTKWindow* window = reinterpret_cast<GTKWindow*>(data);
|
||||
window->Close();
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
bool GTKWindow::OnCreate() {
|
||||
loop()->PostSynchronous([this]() { this->Create(); });
|
||||
// GTK optionally allows passing argv and argc here for parsing gtk specific
|
||||
// options. We won't bother
|
||||
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_resizable(GTK_WINDOW(window_), true);
|
||||
gtk_window_set_title(GTK_WINDOW(window_), title_.c_str());
|
||||
gtk_window_set_default_size(GTK_WINDOW(window_), width_, height_);
|
||||
// Drawing area is where we will attach our vulkan/gl context
|
||||
drawing_area_ = gtk_drawing_area_new();
|
||||
// tick callback is for the refresh rate of the window
|
||||
gtk_widget_add_tick_callback(drawing_area_, draw_callback,
|
||||
reinterpret_cast<gpointer>(this), nullptr);
|
||||
// Attach our event handler to both the main window (for keystrokes) and the
|
||||
// drawing area (for mouse input, resize event, etc)
|
||||
g_signal_connect(G_OBJECT(drawing_area_), "event",
|
||||
G_CALLBACK(gtk_event_handler),
|
||||
reinterpret_cast<gpointer>(this));
|
||||
|
||||
GdkDisplay* gdk_display = gtk_widget_get_display(window_);
|
||||
assert(GDK_IS_X11_DISPLAY(gdk_display));
|
||||
connection_ = XGetXCBConnection(gdk_x11_display_get_xdisplay(gdk_display));
|
||||
|
||||
g_signal_connect(G_OBJECT(window_), "event", G_CALLBACK(gtk_event_handler),
|
||||
reinterpret_cast<gpointer>(this));
|
||||
// When the window manager kills the window (ie, the user hits X)
|
||||
g_signal_connect(G_OBJECT(window_), "destroy", G_CALLBACK(close_callback),
|
||||
reinterpret_cast<gpointer>(this));
|
||||
// Enable only keyboard events (so no mouse) for the top window
|
||||
gtk_widget_set_events(window_, GDK_KEY_PRESS | GDK_KEY_RELEASE);
|
||||
// Enable all events for the drawing area
|
||||
gtk_widget_add_events(drawing_area_, GDK_ALL_EVENTS_MASK);
|
||||
// Place the drawing area in a container (which later will hold the menu)
|
||||
// then let it fill the whole area
|
||||
box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_box_pack_end(GTK_BOX(box_), drawing_area_, TRUE, TRUE, 0);
|
||||
gtk_container_add(GTK_CONTAINER(window_), box_);
|
||||
gtk_widget_show_all(window_);
|
||||
|
||||
return super::OnCreate();
|
||||
}
|
||||
|
||||
@@ -94,8 +139,6 @@ void GTKWindow::OnDestroy() { super::OnDestroy(); }
|
||||
void GTKWindow::OnClose() {
|
||||
if (!closing_ && window_) {
|
||||
closing_ = true;
|
||||
gtk_widget_destroy(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
super::OnClose();
|
||||
}
|
||||
@@ -123,7 +166,6 @@ void GTKWindow::ToggleFullscreen(bool fullscreen) {
|
||||
}
|
||||
|
||||
fullscreen_ = fullscreen;
|
||||
|
||||
if (fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(window_));
|
||||
} else {
|
||||
@@ -140,7 +182,6 @@ void GTKWindow::set_bordered(bool enabled) {
|
||||
// Don't screw with the borders if we're fullscreen.
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_window_set_decorated(GTK_WINDOW(window_), enabled);
|
||||
}
|
||||
|
||||
@@ -171,31 +212,30 @@ void GTKWindow::set_focus(bool value) {
|
||||
}
|
||||
|
||||
void GTKWindow::Resize(int32_t width, int32_t height) {
|
||||
if (is_fullscreen()) {
|
||||
// Cannot resize while in fullscreen.
|
||||
return;
|
||||
}
|
||||
gtk_window_resize(GTK_WINDOW(window_), width, height);
|
||||
super::Resize(width, height);
|
||||
}
|
||||
|
||||
void GTKWindow::Resize(int32_t left, int32_t top, int32_t right,
|
||||
int32_t bottom) {
|
||||
// TODO(dougvj) Verify that this is the desired behavior from this call
|
||||
if (is_fullscreen()) {
|
||||
// Cannot resize while in fullscreen.
|
||||
return;
|
||||
}
|
||||
gtk_window_move(GTK_WINDOW(window_), left, top);
|
||||
gtk_window_resize(GTK_WINDOW(window_), left - right, top - bottom);
|
||||
gtk_window_resize(GTK_WINDOW(window_), right - left, bottom - top);
|
||||
super::Resize(left, top, right, bottom);
|
||||
}
|
||||
|
||||
void GTKWindow::OnResize(UIEvent* e) {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
gtk_window_get_size(GTK_WINDOW(window_), &width, &height);
|
||||
if (width != width_ || height != height_) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
Layout();
|
||||
}
|
||||
super::OnResize(e);
|
||||
}
|
||||
void GTKWindow::OnResize(UIEvent* e) { super::OnResize(e); }
|
||||
|
||||
void GTKWindow::Invalidate() {
|
||||
// gtk_widget_queue_draw(drawing_area_);
|
||||
super::Invalidate();
|
||||
// TODO(dougvj) I am not sure what this is supposed to do
|
||||
}
|
||||
|
||||
void GTKWindow::Close() {
|
||||
@@ -203,20 +243,22 @@ void GTKWindow::Close() {
|
||||
return;
|
||||
}
|
||||
closing_ = true;
|
||||
Close();
|
||||
OnClose();
|
||||
gtk_widget_destroy(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
void GTKWindow::OnMainMenuChange() {
|
||||
// We need to store the old handle for detachment
|
||||
static GtkWidget* box = nullptr;
|
||||
static int count = 0;
|
||||
auto main_menu = reinterpret_cast<GTKMenuItem*>(main_menu_.get());
|
||||
if (main_menu && !is_fullscreen()) {
|
||||
if (box) gtk_widget_destroy(box);
|
||||
GtkWidget* menu = main_menu->handle();
|
||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
|
||||
gtk_box_pack_start(GTK_BOX(box), menu, FALSE, FALSE, 3);
|
||||
gtk_container_add(GTK_CONTAINER(window_), box);
|
||||
if (main_menu && main_menu->handle()) {
|
||||
if (!is_fullscreen()) {
|
||||
gtk_box_pack_start(GTK_BOX(box_), main_menu->handle(), FALSE, FALSE, 0);
|
||||
gtk_widget_show_all(window_);
|
||||
} else {
|
||||
gtk_container_remove(GTK_CONTAINER(box_), main_menu->handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,9 +276,22 @@ bool GTKWindow::HandleWindowOwnerChange(GdkEventOwnerChange* event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GTKWindow::HandleWindowPaint() {
|
||||
auto e = UIEvent(this);
|
||||
OnPaint(&e);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GTKWindow::HandleWindowResize(GdkEventConfigure* event) {
|
||||
if (event->type == GDK_CONFIGURE) {
|
||||
int32_t width = event->width;
|
||||
int32_t height = event->height;
|
||||
auto e = UIEvent(this);
|
||||
if (width != width_ || height != height_) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
Layout();
|
||||
}
|
||||
OnResize(&e);
|
||||
return true;
|
||||
}
|
||||
@@ -351,6 +406,7 @@ bool GTKWindow::HandleKeyboard(GdkEventKey* event) {
|
||||
bool ctrl_pressed = modifiers & GDK_CONTROL_MASK;
|
||||
bool alt_pressed = modifiers & GDK_META_MASK;
|
||||
bool super_pressed = modifiers & GDK_SUPER_MASK;
|
||||
uint32_t key_char = gdk_keyval_to_unicode(event->keyval);
|
||||
// TODO(Triang3l): event->hardware_keycode to VirtualKey translation.
|
||||
auto e = KeyEvent(this, VirtualKey(event->hardware_keycode), 1,
|
||||
event->type == GDK_KEY_RELEASE, shift_pressed, ctrl_pressed,
|
||||
@@ -358,12 +414,13 @@ bool GTKWindow::HandleKeyboard(GdkEventKey* event) {
|
||||
switch (event->type) {
|
||||
case GDK_KEY_PRESS:
|
||||
OnKeyDown(&e);
|
||||
if (key_char > 0) {
|
||||
OnKeyChar(&e);
|
||||
}
|
||||
break;
|
||||
case GDK_KEY_RELEASE:
|
||||
OnKeyUp(&e);
|
||||
break;
|
||||
// TODO(dougvj) GDK doesn't have a KEY CHAR event, so we will have to
|
||||
// figure out its equivalent here to call OnKeyChar(&e);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -377,23 +434,35 @@ std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type,
|
||||
return std::make_unique<GTKMenuItem>(type, text, hotkey, callback);
|
||||
}
|
||||
|
||||
static void _menu_activate_callback(GtkWidget* menu, gpointer data) {
|
||||
auto fn = reinterpret_cast<FnWrapper*>(data);
|
||||
fn->Call();
|
||||
delete fn;
|
||||
static void _menu_activate_callback(GtkWidget* gtk_menu, gpointer data) {
|
||||
GTKMenuItem* menu = reinterpret_cast<GTKMenuItem*>(data);
|
||||
menu->Activate();
|
||||
}
|
||||
|
||||
void GTKMenuItem::Activate() {
|
||||
try {
|
||||
callback_();
|
||||
} catch (const std::bad_function_call& e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
GTKMenuItem::GTKMenuItem(Type type, const std::string& text,
|
||||
const std::string& hotkey,
|
||||
std::function<void()> callback)
|
||||
: MenuItem(type, text, hotkey, std::move(callback)) {
|
||||
std::string label = text;
|
||||
// TODO(dougvj) Would we ever need to escape underscores?
|
||||
// Replace & with _ for gtk to see the memonic
|
||||
std::replace(label.begin(), label.end(), '&', '_');
|
||||
const gchar* gtk_label = reinterpret_cast<const gchar*>(label.c_str());
|
||||
switch (type) {
|
||||
case MenuItem::Type::kNormal:
|
||||
default:
|
||||
menu_ = gtk_menu_bar_new();
|
||||
break;
|
||||
case MenuItem::Type::kPopup:
|
||||
menu_ = gtk_menu_item_new_with_label((gchar*)text.c_str());
|
||||
menu_ = gtk_menu_item_new_with_mnemonic(gtk_label);
|
||||
break;
|
||||
case MenuItem::Type::kSeparator:
|
||||
menu_ = gtk_separator_menu_item_new();
|
||||
@@ -403,12 +472,12 @@ GTKMenuItem::GTKMenuItem(Type type, const std::string& text,
|
||||
if (!hotkey.empty()) {
|
||||
full_name += "\t" + hotkey;
|
||||
}
|
||||
menu_ = gtk_menu_item_new_with_label((gchar*)full_name.c_str());
|
||||
menu_ = gtk_menu_item_new_with_mnemonic(gtk_label);
|
||||
break;
|
||||
}
|
||||
if (GTK_IS_MENU_ITEM(menu_))
|
||||
g_signal_connect(menu_, "activate", G_CALLBACK(_menu_activate_callback),
|
||||
(gpointer) new FnWrapper(callback));
|
||||
(gpointer)this);
|
||||
}
|
||||
|
||||
GTKMenuItem::~GTKMenuItem() {
|
||||
@@ -456,4 +525,5 @@ void GTKMenuItem::OnChildRemoved(MenuItem* generic_child_item) {
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
} // namespace xe
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "xenia/base/platform_linux.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
@@ -27,13 +28,14 @@ class GTKWindow : public Window {
|
||||
using super = Window;
|
||||
|
||||
public:
|
||||
GTKWindow(Loop* loop, const std::string& title);
|
||||
GTKWindow(WindowedAppContext& app_context, const std::string& title);
|
||||
~GTKWindow() override;
|
||||
|
||||
NativePlatformHandle native_platform_handle() const override {
|
||||
return nullptr;
|
||||
return connection_;
|
||||
}
|
||||
NativeWindowHandle native_handle() const override { return window_; }
|
||||
GtkWidget* native_window_handle() const { return drawing_area_; }
|
||||
|
||||
void EnableMainMenu() override {}
|
||||
void DisableMainMenu() override {}
|
||||
@@ -72,16 +74,23 @@ class GTKWindow : public Window {
|
||||
void OnResize(UIEvent* e) override;
|
||||
|
||||
private:
|
||||
void Create();
|
||||
GtkWidget* window_;
|
||||
GtkWidget* box_;
|
||||
GtkWidget* drawing_area_;
|
||||
xcb_connection_t* connection_;
|
||||
|
||||
// C Callback shims for GTK
|
||||
friend gboolean gtk_event_handler(GtkWidget*, GdkEvent*, gpointer);
|
||||
friend gboolean close_callback(GtkWidget*, gpointer);
|
||||
friend gboolean draw_callback(GtkWidget*, GdkFrameClock*, gpointer);
|
||||
|
||||
friend void gtk_event_handler_(GtkWidget*, GdkEvent*, gpointer);
|
||||
bool HandleMouse(GdkEventAny* event);
|
||||
bool HandleKeyboard(GdkEventKey* event);
|
||||
bool HandleWindowResize(GdkEventConfigure* event);
|
||||
bool HandleWindowFocus(GdkEventFocus* event);
|
||||
bool HandleWindowVisibility(GdkEventVisibility* event);
|
||||
bool HandleWindowOwnerChange(GdkEventOwnerChange* event);
|
||||
bool HandleWindowPaint();
|
||||
|
||||
bool closing_ = false;
|
||||
bool fullscreen_ = false;
|
||||
@@ -95,6 +104,7 @@ class GTKMenuItem : public MenuItem {
|
||||
|
||||
GtkWidget* handle() { return menu_; }
|
||||
using MenuItem::OnSelected;
|
||||
void Activate();
|
||||
|
||||
void EnableMenuItem(Window& window) override {}
|
||||
void DisableMenuItem(Window& window) override {}
|
||||
|
||||
@@ -18,16 +18,19 @@
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/windowed_app_context_win.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
||||
std::unique_ptr<Window> Window::Create(Loop* loop, const std::string& title) {
|
||||
return std::make_unique<Win32Window>(loop, title);
|
||||
std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
||||
const std::string& title) {
|
||||
return std::make_unique<Win32Window>(app_context, title);
|
||||
}
|
||||
|
||||
Win32Window::Win32Window(Loop* loop, const std::string& title)
|
||||
: Window(loop, title) {}
|
||||
Win32Window::Win32Window(WindowedAppContext& app_context,
|
||||
const std::string& title)
|
||||
: Window(app_context, title) {}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
OnDestroy();
|
||||
@@ -43,37 +46,32 @@ Win32Window::~Win32Window() {
|
||||
}
|
||||
|
||||
NativePlatformHandle Win32Window::native_platform_handle() const {
|
||||
return ::GetModuleHandle(nullptr);
|
||||
return static_cast<const Win32WindowedAppContext&>(app_context()).hinstance();
|
||||
}
|
||||
|
||||
bool Win32Window::Initialize() { return 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");
|
||||
if (shcore) {
|
||||
SetProcessDpiAwareness_ =
|
||||
GetProcAddress(shcore, "SetProcessDpiAwareness");
|
||||
GetDpiForMonitor_ = GetProcAddress(shcore, "GetDpiForMonitor");
|
||||
}
|
||||
}
|
||||
|
||||
static bool has_registered_class = false;
|
||||
if (!has_registered_class) {
|
||||
// Tell Windows that we're DPI aware.
|
||||
if (SetProcessDpiAwareness_) {
|
||||
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);
|
||||
WNDCLASSEXW wcex;
|
||||
wcex.cbSize = sizeof(wcex);
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wcex.lpfnWndProc = Win32Window::WndProcThunk;
|
||||
wcex.cbClsExtra = 0;
|
||||
@@ -85,7 +83,7 @@ bool Win32Window::OnCreate() {
|
||||
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = L"XeniaWindowClass";
|
||||
if (!RegisterClassEx(&wcex)) {
|
||||
if (!RegisterClassExW(&wcex)) {
|
||||
XELOGE("RegisterClassEx failed");
|
||||
return false;
|
||||
}
|
||||
@@ -216,7 +214,9 @@ bool Win32Window::SetIcon(const void* buffer, size_t size) {
|
||||
}
|
||||
|
||||
// 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,
|
||||
reinterpret_cast<LPARAM>(default_icon));
|
||||
SendMessageW(hwnd_, WM_SETICON, ICON_SMALL,
|
||||
@@ -316,13 +316,22 @@ void Win32Window::set_bordered(bool enabled) {
|
||||
}
|
||||
|
||||
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_) {
|
||||
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);
|
||||
|
||||
// According to msdn, x and y are identical...
|
||||
UINT dpi_x, dpi_y;
|
||||
auto gdfm = (decltype(&GetDpiForMonitor))GetDpiForMonitor_;
|
||||
gdfm(monitor, MDT_DEFAULT, &dpi_x, &dpi_y);
|
||||
@@ -447,6 +456,7 @@ void Win32Window::Close() {
|
||||
closing_ = true;
|
||||
OnClose();
|
||||
DestroyWindow(hwnd_);
|
||||
hwnd_ = nullptr;
|
||||
}
|
||||
|
||||
void Win32Window::OnMainMenuChange() {
|
||||
|
||||
@@ -24,7 +24,7 @@ class Win32Window : public Window {
|
||||
using super = Window;
|
||||
|
||||
public:
|
||||
Win32Window(Loop* loop, const std::string& title);
|
||||
Win32Window(WindowedAppContext& app_context, const std::string& title);
|
||||
~Win32Window() override;
|
||||
|
||||
NativePlatformHandle native_platform_handle() const override;
|
||||
@@ -90,7 +90,6 @@ class Win32Window : public Window {
|
||||
WINDOWPLACEMENT windowed_pos_ = {0};
|
||||
POINT last_mouse_pos_ = {0};
|
||||
|
||||
void* SetProcessDpiAwareness_ = nullptr;
|
||||
void* GetDpiForMonitor_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
129
src/xenia/ui/windowed_app.h
Normal file
129
src/xenia/ui/windowed_app.h
Normal 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_
|
||||
180
src/xenia/ui/windowed_app_context.cc
Normal file
180
src/xenia/ui/windowed_app_context.cc
Normal 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
|
||||
184
src/xenia/ui/windowed_app_context.h
Normal file
184
src/xenia/ui/windowed_app_context.h
Normal 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_
|
||||
67
src/xenia/ui/windowed_app_context_android.cc
Normal file
67
src/xenia/ui/windowed_app_context_android.cc
Normal 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
|
||||
61
src/xenia/ui/windowed_app_context_android.h
Normal file
61
src/xenia/ui/windowed_app_context_android.h
Normal 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_
|
||||
112
src/xenia/ui/windowed_app_context_gtk.cc
Normal file
112
src/xenia/ui/windowed_app_context_gtk.cc
Normal 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
|
||||
46
src/xenia/ui/windowed_app_context_gtk.h
Normal file
46
src/xenia/ui/windowed_app_context_gtk.h
Normal 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_
|
||||
130
src/xenia/ui/windowed_app_context_win.cc
Normal file
130
src/xenia/ui/windowed_app_context_win.cc
Normal 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
|
||||
58
src/xenia/ui/windowed_app_context_win.h
Normal file
58
src/xenia/ui/windowed_app_context_win.h
Normal 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_
|
||||
64
src/xenia/ui/windowed_app_main_posix.cc
Normal file
64
src/xenia/ui/windowed_app_main_posix.cc
Normal 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;
|
||||
}
|
||||
70
src/xenia/ui/windowed_app_main_win.cc
Normal file
70
src/xenia/ui/windowed_app_main_win.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user