mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-04-05 16:40:17 +02:00
[d3d9] Route operations on unmappable memory through allocator
Fixes an extremely race condition that can happen when freeing a chunk.
This commit is contained in:
parent
92523fc0dd
commit
4282829f38
@ -19,6 +19,7 @@ namespace dxvk {
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetSystemInfo(&sysInfo);
|
||||
m_allocationGranularity = sysInfo.dwAllocationGranularity;
|
||||
m_mappingGranularity = m_allocationGranularity * 16;
|
||||
}
|
||||
|
||||
D3D9Memory D3D9MemoryAllocator::Alloc(uint32_t Size) {
|
||||
@ -26,7 +27,7 @@ namespace dxvk {
|
||||
|
||||
uint32_t alignedSize = align(Size, CACHE_LINE_SIZE);
|
||||
for (auto& chunk : m_chunks) {
|
||||
D3D9Memory memory = chunk->Alloc(alignedSize);
|
||||
D3D9Memory memory = chunk->AllocLocked(alignedSize);
|
||||
if (memory) {
|
||||
m_usedMemory += memory.GetSize();
|
||||
return memory;
|
||||
@ -38,16 +39,26 @@ namespace dxvk {
|
||||
|
||||
D3D9MemoryChunk* chunk = new D3D9MemoryChunk(this, chunkSize);
|
||||
std::unique_ptr<D3D9MemoryChunk> uniqueChunk(chunk);
|
||||
D3D9Memory memory = uniqueChunk->Alloc(alignedSize);
|
||||
D3D9Memory memory = uniqueChunk->AllocLocked(alignedSize);
|
||||
m_usedMemory += memory.GetSize();
|
||||
|
||||
m_chunks.push_back(std::move(uniqueChunk));
|
||||
return memory;
|
||||
}
|
||||
|
||||
void D3D9MemoryAllocator::FreeChunk(D3D9MemoryChunk *Chunk) {
|
||||
void D3D9MemoryAllocator::Free(D3D9Memory *Memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
|
||||
D3D9MemoryChunk* chunk = Memory->GetChunk();
|
||||
chunk->FreeLocked(Memory);
|
||||
m_usedMemory -= Memory->GetSize();
|
||||
if (chunk->IsEmpty())
|
||||
FreeChunk(chunk);
|
||||
}
|
||||
|
||||
void D3D9MemoryAllocator::FreeChunk(D3D9MemoryChunk *Chunk) {
|
||||
// Has to be called in the lock
|
||||
|
||||
m_allocatedMemory -= Chunk->Size();
|
||||
|
||||
m_chunks.erase(std::remove_if(m_chunks.begin(), m_chunks.end(), [&](auto& item) {
|
||||
@ -55,57 +66,66 @@ namespace dxvk {
|
||||
}), m_chunks.end());
|
||||
}
|
||||
|
||||
void D3D9MemoryAllocator::NotifyMapped(uint32_t Size) {
|
||||
m_mappedMemory += Size;
|
||||
void* D3D9MemoryAllocator::Map(D3D9Memory* Memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
|
||||
D3D9MemoryChunk* chunk = Memory->GetChunk();
|
||||
uint32_t memoryMapped;
|
||||
void* ptr = chunk->MapLocked(Memory, memoryMapped);
|
||||
m_mappedMemory += memoryMapped;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void D3D9MemoryAllocator::NotifyUnmapped(uint32_t Size) {
|
||||
m_mappedMemory -= Size;
|
||||
void D3D9MemoryAllocator::Unmap(D3D9Memory* Memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
|
||||
D3D9MemoryChunk* chunk = Memory->GetChunk();
|
||||
m_mappedMemory -= chunk->UnmapLocked(Memory);
|
||||
}
|
||||
|
||||
void D3D9MemoryAllocator::NotifyFreed(uint32_t Size) {
|
||||
m_usedMemory -= Size;
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::MappedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::MappedMemory() const {
|
||||
return m_mappedMemory.load();
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::UsedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::UsedMemory() const {
|
||||
return m_usedMemory.load();
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::AllocatedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::AllocatedMemory() const {
|
||||
return m_allocatedMemory.load();
|
||||
}
|
||||
|
||||
D3D9MemoryChunk::D3D9MemoryChunk(D3D9MemoryAllocator* Allocator, uint32_t Size)
|
||||
: m_allocator(Allocator), m_size(Size), m_mappingGranularity(m_allocator->MemoryGranularity() * 16) {
|
||||
: m_allocator(Allocator), m_size(Size) {
|
||||
m_mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE | SEC_COMMIT, 0, Size, nullptr);
|
||||
m_freeRanges.push_back({ 0, Size });
|
||||
m_mappingRanges.resize(((Size + m_mappingGranularity - 1) / m_mappingGranularity));
|
||||
uint32_t mappingGranularity = Allocator->MappingGranularity();
|
||||
m_mappingRanges.resize(((Size + mappingGranularity - 1) / mappingGranularity));
|
||||
}
|
||||
|
||||
D3D9MemoryChunk::~D3D9MemoryChunk() {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
CloseHandle(m_mapping);
|
||||
}
|
||||
|
||||
void* D3D9MemoryChunk::Map(D3D9Memory* memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
void* D3D9MemoryChunk::MapLocked(D3D9Memory* Memory, uint32_t& mappedSize) {
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
uint32_t alignedOffset = alignDown(memory->GetOffset(), m_mappingGranularity);
|
||||
uint32_t alignmentDelta = memory->GetOffset() - alignedOffset;
|
||||
uint32_t alignedSize = memory->GetSize() + alignmentDelta;
|
||||
if (alignedSize > m_mappingGranularity) {
|
||||
mappedSize = 0;
|
||||
uint32_t mappingGranularity = m_allocator->MappingGranularity();
|
||||
|
||||
uint32_t alignedOffset = alignDown(Memory->GetOffset(), mappingGranularity);
|
||||
uint32_t alignmentDelta = Memory->GetOffset() - alignedOffset;
|
||||
uint32_t alignedSize = Memory->GetSize() + alignmentDelta;
|
||||
if (alignedSize > mappingGranularity) {
|
||||
// The allocation crosses the boundary of the internal mapping page it's a part of
|
||||
// so we map it on it's own.
|
||||
alignedOffset = alignDown(memory->GetOffset(), m_allocator->MemoryGranularity());
|
||||
alignmentDelta = memory->GetOffset() - alignedOffset;
|
||||
alignedSize = memory->GetSize() + alignmentDelta;
|
||||
alignedOffset = alignDown(Memory->GetOffset(), m_allocator->AllocationGranularity());
|
||||
alignmentDelta = Memory->GetOffset() - alignedOffset;
|
||||
alignedSize = Memory->GetSize() + alignmentDelta;
|
||||
|
||||
m_allocator->NotifyMapped(alignedSize);
|
||||
mappedSize = alignedSize;
|
||||
uint8_t* basePtr = static_cast<uint8_t*>(MapViewOfFile(m_mapping, FILE_MAP_ALL_ACCESS, 0, alignedOffset, alignedSize));
|
||||
if (unlikely(basePtr == nullptr)) {
|
||||
DWORD error = GetLastError();
|
||||
@ -117,10 +137,10 @@ namespace dxvk {
|
||||
|
||||
// For small allocations we map the entire mapping page to minimize the overhead from having the align the offset to 65k bytes.
|
||||
// This should hopefully also reduce the amount of MapViewOfFile calls we do for tiny allocations.
|
||||
auto& mappingRange = m_mappingRanges[memory->GetOffset() / m_mappingGranularity];
|
||||
auto& mappingRange = m_mappingRanges[Memory->GetOffset() / mappingGranularity];
|
||||
if (unlikely(mappingRange.refCount == 0)) {
|
||||
m_allocator->NotifyMapped(m_mappingGranularity);
|
||||
mappingRange.ptr = static_cast<uint8_t*>(MapViewOfFile(m_mapping, FILE_MAP_ALL_ACCESS, 0, alignedOffset, m_mappingGranularity));
|
||||
mappedSize = mappingGranularity;
|
||||
mappingRange.ptr = static_cast<uint8_t*>(MapViewOfFile(m_mapping, FILE_MAP_ALL_ACCESS, 0, alignedOffset, m_allocator->MappingGranularity()));
|
||||
if (unlikely(mappingRange.ptr == nullptr)) {
|
||||
DWORD error = GetLastError();
|
||||
LPTSTR buffer = nullptr;
|
||||
@ -136,34 +156,36 @@ namespace dxvk {
|
||||
return basePtr + alignmentDelta;
|
||||
}
|
||||
|
||||
void D3D9MemoryChunk::Unmap(D3D9Memory* memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
uint32_t D3D9MemoryChunk::UnmapLocked(D3D9Memory* Memory) {
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
uint32_t alignedOffset = alignDown(memory->GetOffset(), m_mappingGranularity);
|
||||
uint32_t alignmentDelta = memory->GetOffset() - alignedOffset;
|
||||
uint32_t alignedSize = memory->GetSize() + alignmentDelta;
|
||||
if (alignedSize > m_mappingGranularity) {
|
||||
uint32_t mappingGranularity = m_allocator->MappingGranularity();
|
||||
|
||||
uint32_t alignedOffset = alignDown(Memory->GetOffset(), mappingGranularity);
|
||||
uint32_t alignmentDelta = Memory->GetOffset() - alignedOffset;
|
||||
uint32_t alignedSize = Memory->GetSize() + alignmentDelta;
|
||||
if (alignedSize > mappingGranularity) {
|
||||
// Single use mapping
|
||||
alignedOffset = alignDown(memory->GetOffset(), m_allocator->MemoryGranularity());
|
||||
alignmentDelta = memory->GetOffset() - alignedOffset;
|
||||
alignedSize = memory->GetSize() + alignmentDelta;
|
||||
alignedOffset = alignDown(Memory->GetOffset(), m_allocator->AllocationGranularity());
|
||||
alignmentDelta = Memory->GetOffset() - alignedOffset;
|
||||
alignedSize = Memory->GetSize() + alignmentDelta;
|
||||
|
||||
uint8_t* basePtr = static_cast<uint8_t*>(memory->Ptr()) - alignmentDelta;
|
||||
uint8_t* basePtr = static_cast<uint8_t*>(Memory->Ptr()) - alignmentDelta;
|
||||
UnmapViewOfFile(basePtr);
|
||||
m_allocator->NotifyUnmapped(alignedSize);
|
||||
return;
|
||||
return alignedSize;
|
||||
}
|
||||
auto& mappingRange = m_mappingRanges[memory->GetOffset() / m_mappingGranularity];
|
||||
auto& mappingRange = m_mappingRanges[Memory->GetOffset() / mappingGranularity];
|
||||
mappingRange.refCount--;
|
||||
if (unlikely(mappingRange.refCount == 0)) {
|
||||
UnmapViewOfFile(mappingRange.ptr);
|
||||
mappingRange.ptr = nullptr;
|
||||
m_allocator->NotifyUnmapped(m_mappingGranularity);
|
||||
return mappingGranularity;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
D3D9Memory D3D9MemoryChunk::Alloc(uint32_t Size) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
D3D9Memory D3D9MemoryChunk::AllocLocked(uint32_t Size) {
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
uint32_t offset = 0;
|
||||
uint32_t size = 0;
|
||||
@ -188,8 +210,8 @@ namespace dxvk {
|
||||
return {};
|
||||
}
|
||||
|
||||
void D3D9MemoryChunk::Free(D3D9Memory *Memory) {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
void D3D9MemoryChunk::FreeLocked(D3D9Memory *Memory) {
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
uint32_t offset = Memory->GetOffset();
|
||||
uint32_t size = Memory->GetSize();
|
||||
@ -211,11 +233,10 @@ namespace dxvk {
|
||||
}
|
||||
|
||||
m_freeRanges.push_back({ offset, size });
|
||||
m_allocator->NotifyFreed(Memory->GetSize());
|
||||
}
|
||||
|
||||
bool D3D9MemoryChunk::IsEmpty() {
|
||||
std::lock_guard<dxvk::mutex> lock(m_mutex);
|
||||
bool D3D9MemoryChunk::IsEmpty() const {
|
||||
// Has to be protected by the allocator lock
|
||||
|
||||
return m_freeRanges.size() == 1
|
||||
&& m_freeRanges[0].length == m_size;
|
||||
@ -225,10 +246,6 @@ namespace dxvk {
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
HANDLE D3D9MemoryChunk::FileHandle() const {
|
||||
return m_mapping;
|
||||
}
|
||||
|
||||
|
||||
D3D9Memory::D3D9Memory(D3D9MemoryChunk* Chunk, size_t Offset, size_t Size)
|
||||
: m_chunk(Chunk), m_offset(Offset), m_size(Size) {}
|
||||
@ -260,11 +277,7 @@ namespace dxvk {
|
||||
if (m_ptr != nullptr)
|
||||
Unmap();
|
||||
|
||||
m_chunk->Free(this);
|
||||
if (m_chunk->IsEmpty()) {
|
||||
D3D9MemoryAllocator* allocator = m_chunk->Allocator();
|
||||
allocator->FreeChunk(m_chunk);
|
||||
}
|
||||
m_chunk->Allocator()->Free(this);
|
||||
m_chunk = nullptr;
|
||||
}
|
||||
|
||||
@ -275,14 +288,14 @@ namespace dxvk {
|
||||
if (unlikely(m_chunk == nullptr))
|
||||
return;
|
||||
|
||||
m_ptr = m_chunk->Map(this);
|
||||
m_ptr = m_chunk->Allocator()->Map(this);
|
||||
}
|
||||
|
||||
void D3D9Memory::Unmap() {
|
||||
if (unlikely(m_ptr == nullptr))
|
||||
return;
|
||||
|
||||
m_chunk->Unmap(this);
|
||||
m_chunk->Allocator()->Unmap(this);
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
|
||||
@ -298,15 +311,15 @@ namespace dxvk {
|
||||
return memory;
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::MappedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::MappedMemory() const {
|
||||
return m_allocatedMemory.load();
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::UsedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::UsedMemory() const {
|
||||
return m_allocatedMemory.load();
|
||||
}
|
||||
|
||||
uint32_t D3D9MemoryAllocator::AllocatedMemory() {
|
||||
uint32_t D3D9MemoryAllocator::AllocatedMemory() const {
|
||||
return m_allocatedMemory.load();
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ namespace dxvk {
|
||||
friend D3D9MemoryAllocator;
|
||||
|
||||
public:
|
||||
D3D9MemoryChunk(D3D9MemoryAllocator* Allocator, uint32_t Size);
|
||||
~D3D9MemoryChunk();
|
||||
|
||||
D3D9MemoryChunk (const D3D9MemoryChunk&) = delete;
|
||||
@ -47,29 +48,27 @@ namespace dxvk {
|
||||
D3D9MemoryChunk (D3D9MemoryChunk&& other) = delete;
|
||||
D3D9MemoryChunk& operator = (D3D9MemoryChunk&& other) = delete;
|
||||
|
||||
D3D9Memory Alloc(uint32_t Size);
|
||||
void Free(D3D9Memory* Memory);
|
||||
bool IsEmpty();
|
||||
uint32_t Size() const { return m_size; }
|
||||
D3D9MemoryAllocator* Allocator() const;
|
||||
HANDLE FileHandle() const;
|
||||
void* Map(D3D9Memory* memory);
|
||||
void Unmap(D3D9Memory* memory);
|
||||
|
||||
private:
|
||||
D3D9MemoryChunk(D3D9MemoryAllocator* Allocator, uint32_t Size);
|
||||
bool IsEmpty() const;
|
||||
uint32_t Size() const { return m_size; }
|
||||
|
||||
D3D9Memory AllocLocked(uint32_t Size);
|
||||
void FreeLocked(D3D9Memory* Memory);
|
||||
void* MapLocked(D3D9Memory* memory, uint32_t& mappedSize);
|
||||
uint32_t UnmapLocked(D3D9Memory* memory);
|
||||
|
||||
dxvk::mutex m_mutex;
|
||||
D3D9MemoryAllocator* m_allocator;
|
||||
HANDLE m_mapping;
|
||||
uint32_t m_size;
|
||||
uint32_t m_mappingGranularity;
|
||||
std::vector<D3D9MemoryRange> m_freeRanges;
|
||||
std::vector<D3D9MappingRange> m_mappingRanges;
|
||||
};
|
||||
|
||||
class D3D9Memory {
|
||||
friend D3D9MemoryChunk;
|
||||
friend D3D9MemoryAllocator;
|
||||
|
||||
public:
|
||||
D3D9Memory() {}
|
||||
@ -86,13 +85,13 @@ namespace dxvk {
|
||||
void Map();
|
||||
void Unmap();
|
||||
void* Ptr();
|
||||
D3D9MemoryChunk* GetChunk() const { return m_chunk; }
|
||||
size_t GetOffset() const { return m_offset; }
|
||||
size_t GetSize() const { return m_size; }
|
||||
|
||||
private:
|
||||
D3D9Memory(D3D9MemoryChunk* Chunk, size_t Offset, size_t Size);
|
||||
void Free();
|
||||
D3D9MemoryChunk* GetChunk() const { return m_chunk; }
|
||||
size_t GetOffset() const { return m_offset; }
|
||||
size_t GetSize() const { return m_size; }
|
||||
|
||||
D3D9MemoryChunk* m_chunk = nullptr;
|
||||
void* m_ptr = nullptr;
|
||||
@ -107,22 +106,26 @@ namespace dxvk {
|
||||
D3D9MemoryAllocator();
|
||||
~D3D9MemoryAllocator() = default;
|
||||
D3D9Memory Alloc(uint32_t Size);
|
||||
void FreeChunk(D3D9MemoryChunk* Chunk);
|
||||
void NotifyMapped(uint32_t Size);
|
||||
void NotifyUnmapped(uint32_t Size);
|
||||
void NotifyFreed(uint32_t Size);
|
||||
uint32_t MappedMemory();
|
||||
uint32_t UsedMemory();
|
||||
uint32_t AllocatedMemory();
|
||||
uint32_t MemoryGranularity() { return m_allocationGranularity; }
|
||||
D3D9Memory AllocFromChunk(D3D9MemoryChunk* Chunk, uint32_t Size);
|
||||
void Free(D3D9Memory* Memory);
|
||||
void* Map(D3D9Memory* Memory);
|
||||
void Unmap(D3D9Memory* Memory);
|
||||
uint32_t MappedMemory() const;
|
||||
uint32_t UsedMemory() const;
|
||||
uint32_t AllocatedMemory() const;
|
||||
uint32_t AllocationGranularity() const { return m_allocationGranularity; }
|
||||
uint32_t MappingGranularity() const { return m_mappingGranularity; }
|
||||
|
||||
private:
|
||||
void FreeChunk(D3D9MemoryChunk* Chunk);
|
||||
|
||||
dxvk::mutex m_mutex;
|
||||
std::vector<std::unique_ptr<D3D9MemoryChunk>> m_chunks;
|
||||
std::atomic<size_t> m_mappedMemory = 0;
|
||||
std::atomic<size_t> m_allocatedMemory = 0;
|
||||
std::atomic<size_t> m_usedMemory = 0;
|
||||
uint32_t m_allocationGranularity;
|
||||
uint32_t m_mappingGranularity;
|
||||
};
|
||||
|
||||
#else
|
||||
@ -144,7 +147,6 @@ namespace dxvk {
|
||||
void Map() {}
|
||||
void Unmap() {}
|
||||
void* Ptr() { return m_ptr; }
|
||||
size_t GetSize() const { return m_size; }
|
||||
|
||||
private:
|
||||
D3D9Memory(D3D9MemoryAllocator* pAllocator, size_t Size);
|
||||
@ -159,9 +161,9 @@ namespace dxvk {
|
||||
|
||||
public:
|
||||
D3D9Memory Alloc(uint32_t Size);
|
||||
uint32_t MappedMemory();
|
||||
uint32_t UsedMemory();
|
||||
uint32_t AllocatedMemory();
|
||||
uint32_t MappedMemory() const;
|
||||
uint32_t UsedMemory() const;
|
||||
uint32_t AllocatedMemory() const;
|
||||
void NotifyFreed(uint32_t Size) {
|
||||
m_allocatedMemory -= Size;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user