From 154cb4f722a9996a4134463e64c85c6bfa25d8bf Mon Sep 17 00:00:00 2001 From: Scott Mansell Date: Mon, 30 Jan 2023 22:36:25 +1300 Subject: [PATCH] Introduce an Event system to VideoCommon A lot of the remaining complexity in Renderer is the massive Swap function which tries to handle a bunch of FrameBegin/FrameEnd events. Rather than create a new place for it. This event system will try to distribute it all over the place --- Source/Core/Common/CMakeLists.txt | 2 + Source/Core/Common/EventHook.h | 79 ++++++++++++++++++++++++ Source/Core/Common/StringLiteral.h | 17 ++++++ Source/Core/DolphinLib.props | 3 + Source/Core/VideoCommon/CMakeLists.txt | 1 + Source/Core/VideoCommon/VideoEvents.h | 84 ++++++++++++++++++++++++++ 6 files changed, 186 insertions(+) create mode 100644 Source/Core/Common/EventHook.h create mode 100644 Source/Core/Common/StringLiteral.h create mode 100644 Source/Core/VideoCommon/VideoEvents.h diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 6e49f7b7a6..bab78766da 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(common EnumFormatter.h EnumMap.h Event.h + EventHook.h FatFsUtil.cpp FatFsUtil.h FileSearch.cpp @@ -115,6 +116,7 @@ add_library(common SocketContext.cpp SocketContext.h SPSCQueue.h + StringLiteral.h StringUtil.cpp StringUtil.h SymbolDB.cpp diff --git a/Source/Core/Common/EventHook.h b/Source/Core/Common/EventHook.h new file mode 100644 index 0000000000..265013827a --- /dev/null +++ b/Source/Core/Common/EventHook.h @@ -0,0 +1,79 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Logging/Log.h" +#include "Common/StringLiteral.h" + +#include +#include +#include +#include +#include + +// A hookable event system. + +// Define Events in a header as: +// +// using MyLoveyEvent = Event<"My lovely event", std::string>; +// +// Register listeners anywhere you need them as: +// EventHook myHook = MyLoveyEvent::Register([](std::string foo) { +// // Do something +// }, "Name of the hook"); +// +// The hook will be automatically unregistered when the EventHook object goes out of scope. +// Trigger events by doing: +// +// MyLoveyEvent::Trigger("Hello world"); +// + +struct HookBase +{ + virtual ~HookBase() = default; +}; + +using EventHook = std::unique_ptr; + +template +class Event +{ +public: + using CallbackType = std::function; + +private: + struct HookImpl : public HookBase + { + ~HookImpl() override { Event::Remove(this); } + HookImpl(CallbackType callback, std::string name) : m_fn(callback), m_name(name){ } + CallbackType m_fn; + std::string m_name; + }; +public: + + // Returns a handle that will unregister the listener when destroyed. + static EventHook Register(CallbackType callback, std::string name) + { + DEBUG_LOG_FMT(COMMON, "Registering {} handler at {} event hook", name, EventName.value); + auto handle = std::make_unique(callback, name); + m_listeners.push_back(handle.get()); + return handle; + } + + static void Trigger(CallbackArgs... args) + { + for (auto& handle : m_listeners) + handle->m_fn(args...); + } + +private: + static void Remove(HookImpl* handle) + { + auto it = std::find(m_listeners.begin(), m_listeners.end(), handle); + if (it != m_listeners.end()) + m_listeners.erase(it); + } + + inline static std::vector m_listeners = {}; +}; diff --git a/Source/Core/Common/StringLiteral.h b/Source/Core/Common/StringLiteral.h new file mode 100644 index 0000000000..6227d48ed6 --- /dev/null +++ b/Source/Core/Common/StringLiteral.h @@ -0,0 +1,17 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// A useful template for passing string literals as arguments to templates +// from: https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/ +template +struct StringLiteral { + consteval StringLiteral(const char (&str)[N]) { + std::copy_n(str, N, value); + } + + char value[N]; +}; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 9bf63bbb51..470d303e21 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -46,6 +46,7 @@ + @@ -145,6 +146,7 @@ + @@ -713,6 +715,7 @@ + diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 4507904afd..5428facc55 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -163,6 +163,7 @@ add_library(videocommon VertexShaderManager.h VideoBackendBase.cpp VideoBackendBase.h + VideoEvents.h VideoCommon.h VideoConfig.cpp VideoConfig.h diff --git a/Source/Core/VideoCommon/VideoEvents.h b/Source/Core/VideoCommon/VideoEvents.h new file mode 100644 index 0000000000..bb95296ebd --- /dev/null +++ b/Source/Core/VideoCommon/VideoEvents.h @@ -0,0 +1,84 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "Common/EventHook.h" + + +// Called when certain video config setting are changed +using ConfigChangedEvent = Event<"ConfigChanged", u32>; + +// An event called just before the first draw call of a frame +using BeforeFrameEvent = Event<"BeforeFrame">; + +// An event called after the frame XFB copy begins processing on the host GPU. +// Useful for "once per frame" usecases. +// Note: In a few rare cases, games do multiple XFB copies per frame and join them while presenting. +// If this matters to your usecase, you should use BeforePresent instead. +using AfterFrameEvent = Event<"AfterFrame">; + +struct PresentInfo +{ + enum class PresentReason + { + Immediate, // FIFO is Presenting the XFB immediately, straight after the XFB copy + VideoInterface, // VideoInterface has triggered a present with a new frame + VideoInterfaceDuplicate, // VideoInterface has triggered a present with a duplicate frame + }; + + // The number of (unique) frames since the emulated console booted + u64 frame_count; + + + // The number of presents since the video backend was initialized. + // never goes backwards. + u64 present_count; + + // The frame is identical to the previous frame + PresentReason reason; + + // The exact emulated time of the when real hardware would have presented this frame + // FIXME: Immediate should predict the timestamp of this present + u64 emulated_timestamp; + + // TODO: + // u64 intended_present_time; + + // AfterPresent only: The actual time the frame was presented + u64 actual_present_time = 0; + + enum class PresentTimeAccuracy + { + // The Driver/OS has given us an exact timestamp of when the first line of the frame started + // scanning out to the monitor + PresentOnScreenExact, + + // An approximate timestamp of scanout. + PresentOnScreen, + + // Dolphin doesn't have visibility of the present time. But the present operation has + // been queued with the GPU driver and will happen in the near future. + PresentInProgress, + + // Not implemented + Unimplemented, + }; + + // Accuracy of actual_present_time + PresentTimeAccuracy present_time_accuracy = PresentTimeAccuracy::Unimplemented; +}; + +// An event called just as a frame is queued for presentation. +// The exact timing of this event depends on the "Immediately Present XFB" option. +// +// If enabled, this event will trigger immediately after AfterFrame +// If disabled, this event won't trigger until the emulated interface starts drawing out a new frame. +// +// frame_count: The number of frames +using BeforePresentEvent = Event<"BeforePresent", PresentInfo&>; + +// An event that is triggered after a frame is presented. +// The exact timing of this event depends on backend/driver support. +using AfterPresentEvent = Event<"AfterPresent", PresentInfo&>;