#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 #include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX #endif #include #undef min #undef max #elif defined(__APPLE__) #include #elif defined(__linux__) #include #include #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(pthread_mach_thread_np(pthread_self())); #elif defined(__linux__) static thread_local const uint32_t tid = static_cast(::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 [[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const { const auto [start, end] = GetPageRange(m_cpu_addr + offset, size); const auto& bits = GetBits(); return RegionBits(bits, start, end).Any(); } template 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(); if constexpr (enable) { bits.SetRange(start, end); } else { bits.UnsetRange(start, end); } if constexpr (source == DirtySource::Cpu) { UpdateCpuProtection(); } else { UpdateGpuProtection(); } } template void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) { const auto [start, end] = GetPageRange(vaddr, size); RegionBits mask(GetBits(), start, end); if constexpr (clear) { GetBits().UnsetRange(start, end); } if constexpr (source == DirtySource::Cpu && clear) { UpdateCpuProtection(); ForEachRange(mask, std::forward(func)); return; } if constexpr (source == DirtySource::Gpu && clear) { UpdateGpuProtection(); } ForEachRange(mask, std::forward(func)); } TrackingSpinLock lock; private: template void UpdateCpuProtection() { auto mask = m_cpu_dirty ^ m_writable; m_writable = m_cpu_dirty; if (mask.None()) { return; } m_page_manager.UpdatePageWatchersForRegion(m_cpu_addr, mask); } template 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(m_cpu_addr, mask); } else { m_page_manager.UpdatePageWatchersForRegion(m_cpu_addr, mask); } } template RegionBits& GetBits() { if constexpr (source == DirtySource::Cpu) { return m_cpu_dirty; } else { return m_gpu_dirty; } } template const RegionBits& GetBits() const { if constexpr (source == DirtySource::Cpu) { return m_cpu_dirty; } else { return m_gpu_dirty; } } [[nodiscard]] std::pair 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(offset / TRACKER_PAGE_SIZE), static_cast((offset + size + TRACKER_PAGE_SIZE - 1) / TRACKER_PAGE_SIZE)}; } template 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_