diff --git a/app/src/main/cpp/skyline/common/dirty_tracking.h b/app/src/main/cpp/skyline/common/dirty_tracking.h new file mode 100644 index 00000000..45b66b00 --- /dev/null +++ b/app/src/main/cpp/skyline/common/dirty_tracking.h @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "base.h" +#include "exception.h" +#include "logger.h" +#include "span.h" + +namespace skyline::dirty { + template + class Manager; + + /** + * @brief An opaque handle to a dirty subresource + */ + struct Handle { + private: + template + friend class Manager; + + bool *dirtyPtr; //!< Underlying target ptr + + public: + explicit Handle(bool *dirtyPtr) : dirtyPtr{dirtyPtr} {} + }; + + /** + * @brief Implements a way to track dirty subresources within a region of memory + * @tparam ManagedResourceSize Size of the managed resource in bytes + * @tparam Granularity Minimum granularity of a subresource in bytes + * @tparam OverlapPoolSize Size of the pool used to store handles when there are multiple bound to the same subresource + * @note This class is *NOT* thread-safe + */ + template + class Manager { + private: + struct BindingState { + enum class Type : u32 { + None, //!< No handles are bound + Inline, //!< `inlineDirtyPtr` contains a pointer to the single bound handle for the entry + OverlapSpan, //!< `overlapSpanDirtyPtrs` contains an array of pointers to handles bound to the entry + } type{Type::None}; + u32 overlapSpanSize{}; //!< Size of the array that overlapSpanDirtyPtrs points to + + union { + bool *inlineDirtyPtr; + bool **overlapSpanDirtyPtrs; + }; + + /** + * @return An array of pointers to handles bound to the entry + */ + span GetOverlapSpan() { + return {overlapSpanDirtyPtrs, overlapSpanSize}; + } + }; + + std::array overlapPool{}; //!< Backing pool for `overlapSpanDirtyPtrs` in entry + bool **freeOverlapPtr{}; //!< Pointer to the next free entry in `overlapPool` + + std::array states{}; //!< The dirty binding states for the entire managed resource + + uintptr_t managedResourceBaseAddress; //!< The base address of the managed resource + + public: + template requires (std::is_standard_layout_v && sizeof(ManagedResourceType) == ManagedResourceSize) + Manager(ManagedResourceType &managedResource) : managedResourceBaseAddress{reinterpret_cast(&managedResource)}, freeOverlapPtr{overlapPool.data()} {} + + /** + * @brief Binds a handle to a subresource, automatically handling overlaps + */ + void Bind(Handle handle, uintptr_t subresourceAddress, size_t subresourceSizeBytes) { + if (managedResourceBaseAddress > subresourceAddress) + throw exception("Dirty subresource address is below the managed resource base address"); + + size_t subresourceAddressOffset{subresourceAddress - managedResourceBaseAddress}; + + // Validate + if (subresourceAddressOffset < 0 || (subresourceAddressOffset + subresourceSizeBytes) >= ManagedResourceSize) + throw exception("Dirty subresource address is not within the managed resource"); + + if (subresourceSizeBytes % Granularity) + throw exception("Dirty subresource size isn't aligned to the tracking granularity"); + + if (subresourceAddressOffset % Granularity) + throw exception("Dirty subresource offset isn't aligned to the tracking granularity"); + + // Insert + size_t subresourceIndex{static_cast(subresourceAddressOffset / Granularity)}; + size_t subresourceSize{subresourceSizeBytes / Granularity}; + + for (size_t i{subresourceIndex}; i < subresourceIndex + subresourceSize; i++) { + auto &state{states[i]}; + + if (state.type == BindingState::Type::None) { + state.type = BindingState::Type::Inline; + state.inlineDirtyPtr = handle.dirtyPtr; + } else if (state.type == BindingState::Type::Inline) { + state.type = BindingState::Type::OverlapSpan; + + // Save the old inline dirty pointer since we'll need to insert it into the new overlap span and setting the overlap span ptr will overwrite it + bool *origDirtyPtr{state.inlineDirtyPtr}; + + // Point to a new pool allocation that can hold the overlap + state.overlapSpanDirtyPtrs = freeOverlapPtr; + state.overlapSpanSize = 2; // Existing inline handle + our new handle + + // Check if the pool allocation is valid + if (freeOverlapPtr + state.overlapSpanSize >= overlapPool.end()) + throw exception("Dirty overlap pool is full"); + + // Write overlap to our new pool allocation + *freeOverlapPtr++ = origDirtyPtr; + *freeOverlapPtr++ = handle.dirtyPtr; + } else if (state.type == BindingState::Type::OverlapSpan) { + auto originalOverlapSpan{state.GetOverlapSpan()}; + + // Point to a new pool allocation that can hold all the old overlaps + our new overlap + state.overlapSpanSize++; + state.overlapSpanDirtyPtrs = freeOverlapPtr; + + // Check if the pool allocation is valid + if (freeOverlapPtr + state.overlapSpanSize >= overlapPool.end()) + throw exception("Dirty overlap pool is full"); + + // Write all overlaps to our new pool allocation + auto newOverlapSpan{state.GetOverlapSpan()}; + newOverlapSpan.copy_from(originalOverlapSpan); // Copy old overlaps + *newOverlapSpan.last(1).data() = handle.dirtyPtr; // Write new overlap + freeOverlapPtr += state.overlapSpanSize; + } + } + } + + template requires std::is_standard_layout_v + void Bind(Handle handle, SubresourceType &subresource) { + Bind(handle, reinterpret_cast(&subresource), sizeof(SubresourceType)); + } + + template + void Bind(Handle handle, Args && ...subresources) { + (Bind(handle, subresources), ...); + } + + /** + * @brief Marks part of the managed resource as dirty + * @note This *MUST NOT* be called after any bound handles have been destroyed + */ + void MarkDirty(size_t index) { + auto &state{states[index]}; + if (state.type == BindingState::Type::None) { + return; + } else if (state.type == BindingState::Type::Inline) { + *state.inlineDirtyPtr = true; + } else if (state.type == BindingState::Type::OverlapSpan) [[unlikely]] { + for (auto &dirtyPtr : state.GetOverlapSpan()) + *dirtyPtr = true; + } + } + }; + + /** + * @brief Encapsulates a dirty subresource to allow for automatic binding on construction + */ + template + class BoundSubresource { + private: + const T subresource; + + public: + template + BoundSubresource(ManagerT &manager, dirty::Handle handle, const T &subresource) : subresource{subresource} { + subresource.DirtyBind(manager, handle); + } + + const T *operator->() const { + return &subresource; + } + }; + + class ManualDirty {}; + + /** + * @brief ManualDirty but with a `Refresh()` method that should be called for every `Update()`, irrespective of dirty state + */ + class RefreshableManualDirty : ManualDirty {}; + + /** + * @brief ManualDirty but with a `PurgeCaches()` method to purge caches that would usually be kept even after being marked dirty + */ + class CachedManualDirty : ManualDirty {}; + + /** + * @brief Wrapper around an object that holds dirty state and provides convinient functionality for dirty tracking + */ + template requires (std::is_base_of_v) + class ManualDirtyState { + private: + T value; //!< The underlying object + bool dirty{}; //!< Whether the value is dirty + + /** + * @return An opaque handle that can be used to modify dirty state + */ + Handle GetDirtyHandle() { + return Handle{&dirty}; + } + + public: + template + ManualDirtyState(Args &&... args) : value{GetDirtyHandle(), std::forward(args)...} {} + + /** + * @brief Cleans the object of its dirty state and refreshes it if necessary + * @note This *MUST* be called before any accesses to the underlying object without *ANY* calls to `MarkDirty()` in between + */ + template + void Update(Args &&... args) { + if (dirty) { + dirty = false; + value.Flush(std::forward(args)...); + } else if constexpr (std::is_base_of_v) { + value.Refresh(std::forward(args)...); + } + } + + /** + * @brief Marks the object as dirty + * @param purgeCaches Whether to purge caches of the object that would usually be kept even after being marked dirty + */ + void MarkDirty(bool purgeCaches) { + dirty = true; + + if constexpr (std::is_base_of_v) + if (purgeCaches) + value.PurgeCaches(); + } + + + /** + * @brief Returns a reference to the underlying object + * @note `Update()` *MUST* be called before calling this, without *ANY* calls to `MarkDirty()` in between + */ + T &Get() { + return value; + } + + template + T &UpdateGet(Args &&... args) { + Update(std::forward(args)...); + return value; + } + }; +}