Files
KytyPS5/src/graphics/host_gpu/regionManager.h
T

212 lines
5.9 KiB
C++

#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_
#include "common/assert.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include <atomic>
#include <mutex>
#include <utility>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#undef min
#undef max
#elif defined(__APPLE__)
#include <pthread.h>
#elif defined(__linux__)
#include <sys/syscall.h>
#include <unistd.h>
#endif
namespace Libs::Graphics {
class TrackingSpinLock final {
public:
void lock() noexcept {
const auto thread = CurrentThread();
if (m_owner.load(std::memory_order_relaxed) == thread) {
EXIT("recursive region tracking lock\n");
}
while (m_lock.test_and_set(std::memory_order_acquire)) {
if (m_owner.load(std::memory_order_relaxed) == thread) {
EXIT("recursive region tracking lock while contended\n");
}
std::atomic_signal_fence(std::memory_order_seq_cst);
}
m_owner.store(thread, std::memory_order_relaxed);
}
void unlock() noexcept {
if (m_owner.load(std::memory_order_relaxed) != CurrentThread()) {
EXIT("region tracking lock released by non-owner\n");
}
m_owner.store(0, std::memory_order_relaxed);
m_lock.clear(std::memory_order_release);
}
private:
static uint32_t CurrentThread() noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return GetCurrentThreadId();
#elif defined(__APPLE__)
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
#elif defined(__linux__)
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
return tid;
#else
EXIT("region tracking thread identity is unsupported on this platform\n");
#endif
}
std::atomic_flag m_lock = ATOMIC_FLAG_INIT;
std::atomic_uint32_t m_owner {0};
};
static_assert(std::atomic_uint32_t::is_always_lock_free);
class RegionManager final {
public:
RegionManager(PageManager& page_manager, uint64_t cpu_addr)
: m_page_manager(page_manager), m_cpu_addr(cpu_addr) {
if (m_cpu_addr % TRACKER_REGION_SIZE != 0) {
EXIT("invalid region tracking manager construction\n");
}
m_cpu_dirty.Fill();
m_writable.Fill();
m_readable.Fill();
}
KYTY_CLASS_NO_COPY(RegionManager);
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
template <DirtySource source>
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
const auto& bits = GetBits<source>();
return RegionBits(bits, start, end).Any();
}
template <DirtySource source, bool enable>
void ChangeState(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
if constexpr (source == DirtySource::Cpu && enable) {
if (RegionBits(m_gpu_dirty, start, end).Any()) {
EXIT("CPU dirty state conflicts with GPU dirty state\n");
}
}
if constexpr (source == DirtySource::Gpu && enable) {
if (RegionBits(m_cpu_dirty, start, end).Any()) {
EXIT("GPU dirty state conflicts with CPU dirty state\n");
}
}
auto& bits = GetBits<source>();
if constexpr (enable) {
bits.SetRange(start, end);
} else {
bits.UnsetRange(start, end);
}
if constexpr (source == DirtySource::Cpu) {
UpdateCpuProtection<!enable>();
} else {
UpdateGpuProtection<enable>();
}
}
template <DirtySource source, bool clear, typename Func>
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
const auto [start, end] = GetPageRange(vaddr, size);
RegionBits mask(GetBits<source>(), start, end);
if constexpr (clear) {
GetBits<source>().UnsetRange(start, end);
}
if constexpr (source == DirtySource::Cpu && clear) {
UpdateCpuProtection<true>();
ForEachRange(mask, std::forward<Func>(func));
return;
}
if constexpr (source == DirtySource::Gpu && clear) {
UpdateGpuProtection<false>();
}
ForEachRange(mask, std::forward<Func>(func));
}
TrackingSpinLock lock;
private:
template <bool track>
void UpdateCpuProtection() {
auto mask = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
if (mask.None()) {
return;
}
m_page_manager.UpdatePageWatchersForRegion<track>(m_cpu_addr, mask);
}
template <bool track>
void UpdateGpuProtection() {
auto readable = ~m_gpu_dirty;
auto mask = readable ^ m_readable;
m_readable = readable;
if (mask.None()) {
return;
}
if constexpr (track) {
m_page_manager.UpdatePageWatchersForRegion<true, true>(m_cpu_addr, mask);
} else {
m_page_manager.UpdatePageWatchersForRegion<false, true>(m_cpu_addr, mask);
}
}
template <DirtySource source>
RegionBits& GetBits() {
if constexpr (source == DirtySource::Cpu) {
return m_cpu_dirty;
} else {
return m_gpu_dirty;
}
}
template <DirtySource source>
const RegionBits& GetBits() const {
if constexpr (source == DirtySource::Cpu) {
return m_cpu_dirty;
} else {
return m_gpu_dirty;
}
}
[[nodiscard]] std::pair<size_t, size_t> GetPageRange(uint64_t vaddr, uint64_t size) const {
if (size == 0 || vaddr < m_cpu_addr || vaddr >= m_cpu_addr + TRACKER_REGION_SIZE ||
size > m_cpu_addr + TRACKER_REGION_SIZE - vaddr) {
EXIT("range lies outside its tracking region\n");
}
const auto offset = vaddr - m_cpu_addr;
return {static_cast<size_t>(offset / TRACKER_PAGE_SIZE),
static_cast<size_t>((offset + size + TRACKER_PAGE_SIZE - 1) / TRACKER_PAGE_SIZE)};
}
template <typename Func>
void ForEachRange(const RegionBits& bits, Func&& func) const {
for (const auto [start, end]: bits) {
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
}
}
PageManager& m_page_manager;
uint64_t m_cpu_addr = 0;
RegionBits m_cpu_dirty;
RegionBits m_gpu_dirty;
RegionBits m_writable;
RegionBits m_readable;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_