mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-10 14:07:25 +00:00
5f142c7926
Reduces the page entries from 32 bytes to 8 and rewrites `VirtualBuffer` to be more efficient in memory usage and specifically for large zero regions. The page table will now only reserve 1GiB instead of 4GiB and of this memory it should only use at most ~8MiB. This PR has the side effect of using Eden on Windows on low memory systems much more plausible since it would previously require ~10GiB of committable memory at front (despite using ~5-6 at most, inadvertently stalling other processes) where as now it should only require around the amount that it'll actually use. Co-authored-by: Lizzie <lizzie@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4219 Reviewed-by: Maufeat <sahyno1996@gmail.com> Reviewed-by: lizzie <lizzie@eden-emu.dev> Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
236 lines
9.0 KiB
C++
236 lines
9.0 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <vector>
|
|
#include <boost/container/small_vector.hpp>
|
|
|
|
#include "common/common_types.h"
|
|
#include "common/multi_level_page_table.h"
|
|
#include "common/range_map.h"
|
|
#include "common/scratch_buffer.h"
|
|
#include "common/sparse_large_vector.h"
|
|
#include "video_core/invalidation_accumulator.h"
|
|
#include "video_core/cache_types.h"
|
|
#include "video_core/host1x/gpu_device_memory_manager.h"
|
|
#include "video_core/pte_kind.h"
|
|
|
|
namespace VideoCore {
|
|
class RasterizerInterface;
|
|
}
|
|
|
|
namespace Core {
|
|
class System;
|
|
} // namespace Core
|
|
|
|
namespace Tegra {
|
|
|
|
class MemoryManager final {
|
|
public:
|
|
explicit MemoryManager(Core::System& system_, u64 address_space_bits_ = 40,
|
|
GPUVAddr split_address = 1ULL << 34, u64 big_page_bits_ = 16,
|
|
u64 page_bits_ = 12);
|
|
explicit MemoryManager(Core::System& system_, MaxwellDeviceMemoryManager& memory_,
|
|
u64 address_space_bits_ = 40, GPUVAddr split_address = 1ULL << 34,
|
|
u64 big_page_bits_ = 16, u64 page_bits_ = 12);
|
|
~MemoryManager();
|
|
|
|
static constexpr bool HAS_FLUSH_INVALIDATION = true;
|
|
|
|
inline size_t GetID() const noexcept {
|
|
return unique_identifier;
|
|
}
|
|
|
|
/// Binds a renderer to the memory manager.
|
|
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
|
|
|
[[nodiscard]] std::optional<DAddr> GpuToCpuAddress(GPUVAddr addr) const;
|
|
|
|
[[nodiscard]] std::optional<DAddr> GpuToCpuAddress(GPUVAddr addr, std::size_t size) const;
|
|
|
|
template <typename T>
|
|
[[nodiscard]] T Read(GPUVAddr addr) const;
|
|
|
|
template <typename T>
|
|
void Write(GPUVAddr addr, T data);
|
|
|
|
[[nodiscard]] u8* GetPointer(GPUVAddr addr);
|
|
[[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
|
|
|
|
template <typename T>
|
|
[[nodiscard]] inline T* GetPointer(GPUVAddr addr) noexcept {
|
|
const auto address = GpuToCpuAddress(addr);
|
|
if (!address)
|
|
return {};
|
|
return memory.GetPointer<T>(*address);
|
|
}
|
|
|
|
template <typename T>
|
|
[[nodiscard]] inline const T* GetPointer(GPUVAddr addr) const noexcept {
|
|
return GetPointer<T*>(addr);
|
|
}
|
|
|
|
/**
|
|
* ReadBlock and WriteBlock are full read and write operations over virtual
|
|
* GPU Memory. It's important to use these when GPU memory may not be continuous
|
|
* in the Host Memory counterpart. Note: This functions cause Host GPU Memory
|
|
* Flushes and Invalidations, respectively to each operation.
|
|
*/
|
|
void ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All) const;
|
|
void WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All);
|
|
void CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All);
|
|
|
|
/**
|
|
* ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and
|
|
* WriteBlock respectively. In this versions, no flushing or invalidation is actually
|
|
* done and their performance is similar to a memcpy. This functions can be used
|
|
* on either of this 2 scenarios instead of their safe counterpart:
|
|
* - Memory which is sure to never be represented in the Host GPU.
|
|
* - Memory Managed by a Cache Manager. Example: Texture Flushing should use
|
|
* WriteBlockUnsafe instead of WriteBlock since it shouldn't invalidate the texture
|
|
* being flushed.
|
|
*/
|
|
void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
|
|
void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
|
|
void WriteBlockCached(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
|
|
|
|
/**
|
|
* Checks if a gpu region can be simply read with a pointer.
|
|
*/
|
|
[[nodiscard]] bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const;
|
|
|
|
/**
|
|
* Checks if a gpu region is mapped by a single range of device addresses.
|
|
*/
|
|
[[nodiscard]] bool IsContinuousRange(GPUVAddr gpu_addr, std::size_t size) const;
|
|
|
|
/**
|
|
* Checks if a gpu region is mapped entirely.
|
|
*/
|
|
[[nodiscard]] bool IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) const;
|
|
|
|
/**
|
|
* Returns a vector with all the subranges of device addresses mapped beneath.
|
|
* if the region is continuous, a single pair will be returned. If it's unmapped, an empty
|
|
* vector will be returned;
|
|
*/
|
|
boost::container::small_vector<std::pair<GPUVAddr, std::size_t>, 32> GetSubmappedRange(
|
|
GPUVAddr gpu_addr, std::size_t size) const;
|
|
|
|
GPUVAddr Map(GPUVAddr gpu_addr, DAddr dev_addr, std::size_t size,
|
|
PTEKind kind = PTEKind::INVALID, bool is_big_pages = true);
|
|
GPUVAddr MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages = true);
|
|
void Unmap(GPUVAddr gpu_addr, std::size_t size);
|
|
|
|
void FlushRegion(GPUVAddr gpu_addr, size_t size,
|
|
VideoCommon::CacheType which = VideoCommon::CacheType::All) const;
|
|
|
|
void InvalidateRegion(GPUVAddr gpu_addr, size_t size,
|
|
VideoCommon::CacheType which = VideoCommon::CacheType::All) const;
|
|
|
|
bool IsMemoryDirty(GPUVAddr gpu_addr, size_t size,
|
|
VideoCommon::CacheType which = VideoCommon::CacheType::All) const;
|
|
|
|
size_t MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const;
|
|
|
|
bool IsWithinGPUAddressRange(GPUVAddr gpu_addr) const {
|
|
return gpu_addr < address_space_size;
|
|
}
|
|
|
|
PTEKind GetPageKind(GPUVAddr gpu_addr) const;
|
|
|
|
size_t GetMemoryLayoutSize(GPUVAddr gpu_addr,
|
|
size_t max_size = (std::numeric_limits<size_t>::max)()) const;
|
|
|
|
void FlushCaching();
|
|
|
|
const u8* GetSpan(const GPUVAddr src_addr, const std::size_t size) const;
|
|
u8* GetSpan(const GPUVAddr src_addr, const std::size_t size);
|
|
|
|
private:
|
|
template <typename FuncMapped, typename FuncReserved, typename FuncUnmapped>
|
|
inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, bool is_big_page, FuncMapped&& func_mapped, FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const;
|
|
|
|
void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which, bool unsafe) const;
|
|
void WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, VideoCommon::CacheType which, bool unsafe);
|
|
|
|
[[nodiscard]] std::size_t PageEntryIndex(GPUVAddr gpu_addr, bool is_big_page) const {
|
|
if (is_big_page) {
|
|
return (gpu_addr >> big_page_bits) & big_page_table_mask;
|
|
} else {
|
|
return (gpu_addr >> page_bits) & page_table_mask;
|
|
}
|
|
}
|
|
|
|
inline bool IsBigPageContinuous(size_t big_page_index) const;
|
|
inline void SetBigPageContinuous(size_t big_page_index, bool value);
|
|
|
|
template <bool is_gpu_address>
|
|
void GetSubmappedRangeImpl(
|
|
GPUVAddr gpu_addr, std::size_t size,
|
|
boost::container::small_vector<std::pair<std::conditional_t<is_gpu_address, GPUVAddr, DAddr>, std::size_t>, 32>& result) const;
|
|
|
|
Core::System& system;
|
|
MaxwellDeviceMemoryManager& memory;
|
|
|
|
const u64 address_space_bits;
|
|
GPUVAddr split_address;
|
|
const u64 page_bits;
|
|
u64 address_space_size;
|
|
u64 page_size;
|
|
u64 page_mask;
|
|
u64 page_table_mask;
|
|
static constexpr u64 cpu_page_bits{12};
|
|
|
|
const u64 big_page_bits;
|
|
u64 big_page_size;
|
|
u64 big_page_mask;
|
|
u64 big_page_table_mask;
|
|
|
|
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
|
|
|
enum class EntryType : u64 {
|
|
Free = 0,
|
|
Reserved = 1,
|
|
Mapped = 2,
|
|
};
|
|
|
|
std::vector<u64> entries;
|
|
std::vector<u64> big_entries;
|
|
|
|
GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, EntryType entry_type);
|
|
GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, EntryType entry_type);
|
|
|
|
inline EntryType GetEntry(size_t position, bool is_big_page) const;
|
|
inline void SetEntry(size_t position, EntryType entry, bool is_big_page);
|
|
|
|
Common::MultiLevelPageTable<u32> page_table;
|
|
Common::RangeMap<GPUVAddr, PTEKind> kind_map;
|
|
Common::SparseLargeVector<u32> big_page_table_dev;
|
|
|
|
std::vector<u64> big_page_continuous;
|
|
boost::container::small_vector<std::pair<DAddr, std::size_t>, 32> page_stash{};
|
|
boost::container::small_vector<std::pair<DAddr, std::size_t>, 32> page_stash2{};
|
|
|
|
mutable std::mutex guard;
|
|
|
|
static constexpr size_t continuous_bits = 64;
|
|
|
|
const size_t unique_identifier;
|
|
VideoCommon::InvalidationAccumulator accumulator;
|
|
|
|
static std::atomic<size_t> unique_identifier_generator;
|
|
|
|
Common::ScratchBuffer<u8> tmp_buffer;
|
|
};
|
|
|
|
} // namespace Tegra
|