[GPU] Add FXAA post-processing [UI] Add FidelityFX FSR and CAS post-processing [UI] Add blue noise dithering from 10bpc to 8bpc [GPU] Apply the DC PWL gamma ramp closer to the spec, supporting fully white color [UI] Allow the GPU CP thread to present on the host directly, bypassing the UI thread OS paint event [UI] Allow variable refresh rate (or tearing) [UI] Present the newest frame (restart) on DXGI [UI] Replace GraphicsContext with a far more advanced Presenter with more coherent surface connection and UI overlay state management [UI] Connect presentation to windows via the Surface class, not native window handles [Vulkan] Switch to simpler Vulkan setup with no instance/device separation due to interdependencies and to pass fewer objects around [Vulkan] Lower the minimum required Vulkan version to 1.0 [UI/GPU] Various cleanup, mainly ComPtr usage [UI] Support per-monitor DPI awareness v2 on Windows [UI] DPI-scale Dear ImGui [UI] Replace the remaining non-detachable window delegates with unified window event and input listeners [UI] Allow listeners to safely destroy or close the window, and to register/unregister listeners without use-after-free and the ABA problem [UI] Explicit Z ordering of input listeners and UI overlays, top-down for input, bottom-up for drawing [UI] Add explicit window lifecycle phases [UI] Replace Window virtual functions with explicit desired state, its application, actual state, its feedback [UI] GTK: Apply the initial size to the drawing area [UI] Limit internal UI frame rate to that of the monitor [UI] Hide the cursor using a timer instead of polling due to no repeated UI thread paints with GPU CP thread presentation, and only within the window
393 lines
12 KiB
C++
393 lines
12 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
// NOTE: this must be included before microprofile as macro expansion needs
|
|
// XELOGI.
|
|
#include "xenia/base/logging.h"
|
|
|
|
#include "third_party/fmt/include/fmt/printf.h"
|
|
|
|
// NOTE: microprofile must be setup first, before profiling.h is included.
|
|
#define MICROPROFILE_ENABLED 1
|
|
#define MICROPROFILEUI_ENABLED 1
|
|
#define MICROPROFILE_IMPL 1
|
|
#define MICROPROFILEUI_IMPL 1
|
|
#define MICROPROFILE_PER_THREAD_BUFFER_SIZE (1024 * 1024 * 10)
|
|
#define MICROPROFILE_USE_THREAD_NAME_CALLBACK 1
|
|
#define MICROPROFILE_WEBSERVER_MAXFRAMES 3
|
|
#define MICROPROFILE_PRINTF(...) \
|
|
do { \
|
|
auto xenia_profiler_formatted = fmt::sprintf(__VA_ARGS__); \
|
|
XELOGI("{}", xenia_profiler_formatted); \
|
|
} while (false);
|
|
#define MICROPROFILE_WEBSERVER 0
|
|
#define MICROPROFILE_DEBUG 0
|
|
#define MICROPROFILE_MAX_THREADS 128
|
|
#include "third_party/microprofile/microprofile.h"
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/profiling.h"
|
|
#include "xenia/ui/ui_event.h"
|
|
#include "xenia/ui/virtual_key.h"
|
|
#include "xenia/ui/window.h"
|
|
|
|
#if XE_OPTION_PROFILING
|
|
#include "third_party/microprofile/microprofileui.h"
|
|
#endif // XE_OPTION_PROFILING
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
#include "xenia/ui/microprofile_drawer.h"
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
DEFINE_bool(profiler_dpi_scaling, false,
|
|
"Apply window DPI scaling to the profiler.", "UI");
|
|
DEFINE_bool(show_profiler, false, "Show profiling UI by default.", "UI");
|
|
|
|
namespace xe {
|
|
|
|
#if XE_OPTION_PROFILING
|
|
|
|
Profiler::ProfilerWindowInputListener Profiler::input_listener_;
|
|
size_t Profiler::z_order_ = 0;
|
|
ui::Window* Profiler::window_ = nullptr;
|
|
#if XE_OPTION_PROFILING_UI
|
|
Profiler::ProfilerUIDrawer Profiler::ui_drawer_;
|
|
ui::Presenter* Profiler::presenter_ = nullptr;
|
|
std::unique_ptr<ui::MicroprofileDrawer> Profiler::drawer_;
|
|
bool Profiler::dpi_scaling_ = false;
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
bool Profiler::is_enabled() { return true; }
|
|
|
|
bool Profiler::is_visible() { return is_enabled() && MicroProfileIsDrawing(); }
|
|
|
|
void Profiler::Initialize() {
|
|
// Custom groups.
|
|
MicroProfileSetEnableAllGroups(false);
|
|
MicroProfileForceEnableGroup("apu", MicroProfileTokenTypeCpu);
|
|
MicroProfileForceEnableGroup("cpu", MicroProfileTokenTypeCpu);
|
|
MicroProfileForceEnableGroup("gpu", MicroProfileTokenTypeCpu);
|
|
MicroProfileForceEnableGroup("internal", MicroProfileTokenTypeCpu);
|
|
g_MicroProfile.nGroupMask = g_MicroProfile.nForceGroup;
|
|
g_MicroProfile.nActiveGroup = g_MicroProfile.nActiveGroupWanted =
|
|
g_MicroProfile.nGroupMask;
|
|
|
|
// Custom timers: time, average.
|
|
g_MicroProfile.nBars |= 0x1 | 0x2;
|
|
g_MicroProfile.nActiveBars |= 0x1 | 0x2;
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
dpi_scaling_ = cvars::profiler_dpi_scaling;
|
|
MicroProfileInitUI();
|
|
g_MicroProfileUI.bShowSpikes = true;
|
|
g_MicroProfileUI.nOpacityBackground = 0x40u << 24;
|
|
g_MicroProfileUI.nOpacityForeground = 0xc0u << 24;
|
|
if (cvars::show_profiler) {
|
|
MicroProfileSetDisplayMode(1);
|
|
}
|
|
#else
|
|
MicroProfileSetForceEnable(true);
|
|
MicroProfileSetEnableAllGroups(true);
|
|
MicroProfileSetForceMetaCounters(false);
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
}
|
|
|
|
void Profiler::Dump() {
|
|
#if XE_OPTION_PROFILING_UI
|
|
MicroProfileDumpTimers();
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
// MicroProfileDumpHtml("profile.html");
|
|
// MicroProfileDumpHtmlToFile();
|
|
}
|
|
|
|
void Profiler::Shutdown() {
|
|
SetUserIO(0, nullptr, nullptr, nullptr);
|
|
window_ = nullptr;
|
|
MicroProfileShutdown();
|
|
}
|
|
|
|
uint32_t Profiler::GetColor(const char* str) {
|
|
std::hash<std::string> fn;
|
|
size_t value = fn(str);
|
|
return value & 0xFFFFFF;
|
|
}
|
|
|
|
void Profiler::ThreadEnter(const char* name) {
|
|
MicroProfileOnThreadCreate(name);
|
|
}
|
|
|
|
void Profiler::ThreadExit() { MicroProfileOnThreadExit(); }
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnKeyDown(ui::KeyEvent& e) {
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
|
bool handled = true;
|
|
switch (e.virtual_key()) {
|
|
case ui::VirtualKey::kOem3: // `
|
|
MicroProfileTogglePause();
|
|
break;
|
|
#if XE_OPTION_PROFILING_UI
|
|
case ui::VirtualKey::kTab:
|
|
ToggleDisplay();
|
|
break;
|
|
case ui::VirtualKey::k1:
|
|
MicroProfileModKey(1);
|
|
break;
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
default:
|
|
handled = false;
|
|
break;
|
|
}
|
|
if (handled) {
|
|
e.set_handled(true);
|
|
}
|
|
PostInputEvent();
|
|
}
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
|
|
bool handled = true;
|
|
switch (e.virtual_key()) {
|
|
#if XE_OPTION_PROFILING_UI
|
|
case ui::VirtualKey::k1:
|
|
MicroProfileModKey(0);
|
|
break;
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
default:
|
|
handled = false;
|
|
break;
|
|
}
|
|
if (handled) {
|
|
e.set_handled(true);
|
|
}
|
|
PostInputEvent();
|
|
}
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnMouseDown(ui::MouseEvent& e) {
|
|
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
|
MicroProfileMouseButton(e.button() == ui::MouseEvent::Button::kLeft,
|
|
e.button() == ui::MouseEvent::Button::kRight);
|
|
e.set_handled(true);
|
|
PostInputEvent();
|
|
}
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnMouseUp(ui::MouseEvent& e) {
|
|
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
|
MicroProfileMouseButton(0, 0);
|
|
e.set_handled(true);
|
|
PostInputEvent();
|
|
}
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnMouseMove(ui::MouseEvent& e) {
|
|
Profiler::SetMousePosition(e.x(), e.y(), 0);
|
|
e.set_handled(true);
|
|
PostInputEvent();
|
|
}
|
|
|
|
void Profiler::ProfilerWindowInputListener::OnMouseWheel(ui::MouseEvent& e) {
|
|
Profiler::SetMousePosition(e.x(), e.y(), e.scroll_y());
|
|
e.set_handled(true);
|
|
PostInputEvent();
|
|
}
|
|
|
|
void Profiler::TogglePause() { MicroProfileTogglePause(); }
|
|
|
|
#else
|
|
|
|
void Profiler::TogglePause() {}
|
|
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
void Profiler::ToggleDisplay() {
|
|
bool was_visible = is_visible();
|
|
MicroProfileToggleDisplayMode();
|
|
if (is_visible() != was_visible) {
|
|
if (window_) {
|
|
if (was_visible) {
|
|
window_->RemoveInputListener(&input_listener_);
|
|
} else {
|
|
window_->AddInputListener(&input_listener_, z_order_);
|
|
}
|
|
}
|
|
#if XE_OPTION_PROFILING_UI
|
|
if (presenter_) {
|
|
if (was_visible) {
|
|
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
|
} else {
|
|
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
|
|
}
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
}
|
|
}
|
|
|
|
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
|
|
ui::Presenter* presenter,
|
|
ui::ImmediateDrawer* immediate_drawer) {
|
|
#if XE_OPTION_PROFILING_UI
|
|
if (presenter_ && is_visible()) {
|
|
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
|
}
|
|
drawer_.reset();
|
|
presenter_ = nullptr;
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
if (window_) {
|
|
if (is_visible()) {
|
|
window_->RemoveInputListener(&input_listener_);
|
|
}
|
|
window_ = nullptr;
|
|
}
|
|
|
|
if (!window) {
|
|
return;
|
|
}
|
|
|
|
z_order_ = z_order;
|
|
window_ = window;
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
if (presenter && immediate_drawer) {
|
|
presenter_ = presenter;
|
|
drawer_ = std::make_unique<ui::MicroprofileDrawer>(immediate_drawer);
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
if (is_visible()) {
|
|
window_->AddInputListener(&input_listener_, z_order_);
|
|
#if XE_OPTION_PROFILING_UI
|
|
if (presenter_) {
|
|
presenter_->AddUIDrawerFromUIThread(&ui_drawer_, z_order_);
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
}
|
|
}
|
|
|
|
void Profiler::Flip() {
|
|
MicroProfileFlip();
|
|
// This can be called from non-UI threads, so not trying to access the drawer
|
|
// to trigger redraw here as it's owned and managed exclusively by the UI
|
|
// thread. Relying on continuous painting currently.
|
|
}
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
void Profiler::ProfilerUIDrawer::Draw(ui::UIDrawContext& ui_draw_context) {
|
|
if (!window_ || !presenter_ || !drawer_) {
|
|
return;
|
|
}
|
|
SCOPE_profile_cpu_f("internal");
|
|
uint32_t coordinate_space_width = dpi_scaling_
|
|
? window_->GetActualLogicalWidth()
|
|
: window_->GetActualPhysicalWidth();
|
|
uint32_t coordinate_space_height = dpi_scaling_
|
|
? window_->GetActualLogicalHeight()
|
|
: window_->GetActualPhysicalHeight();
|
|
drawer_->Begin(ui_draw_context, coordinate_space_width,
|
|
coordinate_space_height);
|
|
MicroProfileDraw(coordinate_space_width, coordinate_space_height);
|
|
drawer_->End();
|
|
// Continuous repaint.
|
|
if (is_visible()) {
|
|
presenter_->RequestUIPaintFromUIThread();
|
|
}
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
#if XE_OPTION_PROFILING_UI
|
|
void Profiler::SetMousePosition(int32_t x, int32_t y, int32_t wheel_delta) {
|
|
if (!window_) {
|
|
return;
|
|
}
|
|
if (dpi_scaling_) {
|
|
x = window_->PositionToLogical(x);
|
|
y = window_->PositionToLogical(y);
|
|
}
|
|
MicroProfileMousePosition(uint32_t(std::max(int32_t(0), x)),
|
|
uint32_t(std::max(int32_t(0), y)), wheel_delta);
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
void Profiler::PostInputEvent() {
|
|
// The profiler can be hidden from within the profiler (Mode > Off).
|
|
if (!is_visible()) {
|
|
window_->RemoveInputListener(&input_listener_);
|
|
#if XE_OPTION_PROFILING_UI
|
|
if (presenter_) {
|
|
presenter_->RemoveUIDrawerFromUIThread(&ui_drawer_);
|
|
}
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
return;
|
|
}
|
|
// Relying on continuous painting currently, no need to request drawing.
|
|
}
|
|
|
|
#else
|
|
|
|
bool Profiler::is_enabled() { return false; }
|
|
bool Profiler::is_visible() { return false; }
|
|
void Profiler::Initialize() {}
|
|
void Profiler::Dump() {}
|
|
void Profiler::Shutdown() {}
|
|
uint32_t Profiler::GetColor(const char* str) { return 0; }
|
|
void Profiler::ThreadEnter(const char* name) {}
|
|
void Profiler::ThreadExit() {}
|
|
void Profiler::ToggleDisplay() {}
|
|
void Profiler::TogglePause() {}
|
|
void Profiler::SetUserIO(size_t z_order, ui::Window* window,
|
|
ui::Presenter* presenter,
|
|
ui::ImmediateDrawer* immediate_drawer) {}
|
|
void Profiler::Flip() {}
|
|
|
|
#endif // XE_OPTION_PROFILING
|
|
|
|
} // namespace xe
|
|
|
|
uint32_t MicroProfileGpuInsertTimeStamp() { return 0; }
|
|
|
|
const char* MicroProfileGetThreadName() { return "TODO: get thread name!"; }
|
|
|
|
#if XE_OPTION_PROFILING
|
|
#if XE_OPTION_PROFILING_UI
|
|
|
|
void MicroProfileDrawBox(int nX, int nY, int nX1, int nY1, uint32_t nColor,
|
|
MicroProfileBoxType type) {
|
|
auto drawer = xe::Profiler::drawer();
|
|
if (!drawer) {
|
|
return;
|
|
}
|
|
drawer->DrawBox(nX, nY, nX1, nY1, nColor,
|
|
static_cast<xe::ui::MicroprofileDrawer::BoxType>(type));
|
|
}
|
|
|
|
void MicroProfileDrawLine2D(uint32_t nVertices, float* pVertices,
|
|
uint32_t nColor) {
|
|
auto drawer = xe::Profiler::drawer();
|
|
if (!drawer) {
|
|
return;
|
|
}
|
|
drawer->DrawLine2D(nVertices, pVertices, nColor);
|
|
}
|
|
|
|
void MicroProfileDrawText(int nX, int nY, uint32_t nColor, const char* pText,
|
|
uint32_t nLen) {
|
|
auto drawer = xe::Profiler::drawer();
|
|
if (!drawer) {
|
|
return;
|
|
}
|
|
drawer->DrawTextString(nX, nY, nColor, pText, nLen);
|
|
}
|
|
|
|
#endif // XE_OPTION_PROFILING_UI
|
|
|
|
#endif // XE_OPTION_PROFILING
|