Introduce resource based dirty tracking infrastructure

This will be heavily used by the upcoming GPU rework. It provides an intuitive way to track dirtiness based on using the underlying pointers of objects, as opposed to other methods which often need an enum entry per dirty state and don't support overlaps. Wrappers for dirty state objects are also provided to abstract as much of the dirty tracking as possible from user code. The pointer based mechanism also serves to avoid having to handle dirty bindings on the user side of the dirty resources, allowing them to bind things internally instead.
This commit is contained in:
Billy Laws 2022-09-01 15:57:03 +01:00
parent 8471ab754d
commit d7eab40f1c

View File

@ -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<size_t, size_t, size_t>
class Manager;
/**
* @brief An opaque handle to a dirty subresource
*/
struct Handle {
private:
template<size_t, size_t, size_t>
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<size_t ManagedResourceSize, size_t Granularity, size_t OverlapPoolSize = 0x400>
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<bool *> GetOverlapSpan() {
return {overlapSpanDirtyPtrs, overlapSpanSize};
}
};
std::array<bool *, OverlapPoolSize> overlapPool{}; //!< Backing pool for `overlapSpanDirtyPtrs` in entry
bool **freeOverlapPtr{}; //!< Pointer to the next free entry in `overlapPool`
std::array<BindingState, ManagedResourceSize / Granularity> states{}; //!< The dirty binding states for the entire managed resource
uintptr_t managedResourceBaseAddress; //!< The base address of the managed resource
public:
template<typename ManagedResourceType> requires (std::is_standard_layout_v<ManagedResourceType> && sizeof(ManagedResourceType) == ManagedResourceSize)
Manager(ManagedResourceType &managedResource) : managedResourceBaseAddress{reinterpret_cast<uintptr_t>(&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<size_t>(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<typename SubresourceType> requires std::is_standard_layout_v<SubresourceType>
void Bind(Handle handle, SubresourceType &subresource) {
Bind(handle, reinterpret_cast<uintptr_t>(&subresource), sizeof(SubresourceType));
}
template<typename... Args>
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<typename T>
class BoundSubresource {
private:
const T subresource;
public:
template<typename ManagerT>
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<typename T> requires (std::is_base_of_v<ManualDirty, T>)
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<typename... Args>
ManualDirtyState(Args &&... args) : value{GetDirtyHandle(), std::forward<Args>(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<typename... Args>
void Update(Args &&... args) {
if (dirty) {
dirty = false;
value.Flush(std::forward<Args>(args)...);
} else if constexpr (std::is_base_of_v<RefreshableManualDirty, T>) {
value.Refresh(std::forward<Args>(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<CachedManualDirty, T>)
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<typename... Args>
T &UpdateGet(Args &&... args) {
Update(std::forward<Args>(args)...);
return value;
}
};
}