diff --git a/dxvk.conf b/dxvk.conf index 45f5998b..ba924e8a 100644 --- a/dxvk.conf +++ b/dxvk.conf @@ -560,3 +560,12 @@ # - True/False # dxvk.enableDebugUtils = False + +# Memory limit for locked D3D9 textures +# +# How much virtual memory will be used for textures (in MB). +# 0 to disable the limit. +# THIS DOES NOT IMPACT ACTUAL MEMORY CONSUMPTION OR TEXTURE QUALITY. +# DO NOT CHANGE THIS UNLESS YOU HAVE A VERY GOOD REASON. + +# d3d9.textureMemory = 100 diff --git a/src/d3d9/d3d9_common_texture.cpp b/src/d3d9/d3d9_common_texture.cpp index b14ff99f..74100dfc 100644 --- a/src/d3d9/d3d9_common_texture.cpp +++ b/src/d3d9/d3d9_common_texture.cpp @@ -89,6 +89,8 @@ namespace dxvk { D3D9CommonTexture::~D3D9CommonTexture() { if (m_size != 0) m_device->ChangeReportedMemory(m_size); + + m_device->RemoveMappedTexture(this); } @@ -488,7 +490,7 @@ namespace dxvk { return D3D9_COMMON_TEXTURE_MAP_MODE_NONE; #ifdef D3D9_ALLOW_UNMAPPING - if (m_desc.Pool != D3DPOOL_DEFAULT) + if (m_device->GetOptions()->textureMemory != 0 && m_desc.Pool != D3DPOOL_DEFAULT) return D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE; #endif diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index fbc4c64b..57f22d6e 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -1,6 +1,7 @@ #include "d3d9_device.h" #include "d3d9_annotation.h" +#include "d3d9_common_texture.h" #include "d3d9_interface.h" #include "d3d9_swapchain.h" #include "d3d9_caps.h" @@ -4201,6 +4202,7 @@ namespace dxvk { pResource->GetBuffer(Subresource, !needsReadback); } + // Don't use MapTexture here to keep the mapped list small while the resource is still locked. mapPtr = pResource->GetData(Subresource); if (needsReadback) { @@ -4298,6 +4300,8 @@ namespace dxvk { pResource->SetLocked(Subresource, true); + UnmapTextures(); + const bool noDirtyUpdate = Flags & D3DLOCK_NO_DIRTY_UPDATE; if ((desc.Pool == D3DPOOL_DEFAULT || !noDirtyUpdate) && !readOnly) { if (pBox && MipLevel != 0) { @@ -4354,6 +4358,7 @@ namespace dxvk { if (unlikely(!pResource->GetLocked(Subresource))) return D3D_OK; + MapTexture(pResource, Subresource); // Add it to the list of mapped resources pResource->SetLocked(Subresource, false); // Flush image contents from staging if we aren't read only @@ -4380,6 +4385,7 @@ namespace dxvk { pResource->SetNeedsReadback(Subresource, true); } + UnmapTextures(); return D3D_OK; } @@ -4463,7 +4469,7 @@ namespace dxvk { + srcOffsetBlockCount.y * pitch + srcOffsetBlockCount.x * formatInfo->elementSize; - const void* mapPtr = pSrcTexture->GetData(SrcSubresource); + const void* mapPtr = MapTexture(pSrcTexture, SrcSubresource); VkDeviceSize dirtySize = extentBlockCount.width * extentBlockCount.height * extentBlockCount.depth * formatInfo->elementSize; D3D9BufferSlice slice = AllocStagingBuffer(dirtySize); const void* srcData = reinterpret_cast(mapPtr) + copySrcOffset; @@ -4489,7 +4495,7 @@ namespace dxvk { } else { const DxvkFormatInfo* formatInfo = lookupFormatInfo(pDestTexture->GetFormatMapping().FormatColor); - const void* mapPtr = pSrcTexture->GetData(SrcSubresource); + const void* mapPtr = MapTexture(pSrcTexture, SrcSubresource); // Add more blocks for the other planes that we might have. // TODO: PLEASE CLEAN ME @@ -4522,6 +4528,7 @@ namespace dxvk { image, dstLayers, slice.slice); } + UnmapTextures(); } void D3D9DeviceEx::EmitGenerateMips( @@ -4643,6 +4650,7 @@ namespace dxvk { pResource->SetMapFlags(Flags | oldFlags); pResource->IncrementLockCount(); + UnmapTextures(); return D3D_OK; } @@ -4675,7 +4683,8 @@ namespace dxvk { pResource->DirtyRange().Clear(); TrackBufferMappingBufferSequenceNumber(pResource); - return D3D_OK; + UnmapTextures(); + return D3D_OK; } @@ -7223,4 +7232,61 @@ namespace dxvk { return m_csChunk->empty() ? m_csSeqNum : m_csSeqNum + 1; } + + void* D3D9DeviceEx::MapTexture(D3D9CommonTexture* pTexture, UINT Subresource) { + // Will only be called inside the device lock + void *ptr = pTexture->GetData(Subresource); + +#ifdef D3D9_ALLOW_UNMAPPING + if (likely(pTexture->GetMapMode() == D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE)) { + m_mappedTextures.insert(pTexture); + } +#endif + + return ptr; + } + + void D3D9DeviceEx::TouchMappedTexture(D3D9CommonTexture* pTexture) { +#ifdef D3D9_ALLOW_UNMAPPING + if (pTexture->GetMapMode() != D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE) + return; + + D3D9DeviceLock lock = LockDevice(); + m_mappedTextures.touch(pTexture); +#endif + } + + void D3D9DeviceEx::RemoveMappedTexture(D3D9CommonTexture* pTexture) { +#ifdef D3D9_ALLOW_UNMAPPING + if (pTexture->GetMapMode() != D3D9_COMMON_TEXTURE_MAP_MODE_UNMAPPABLE) + return; + + D3D9DeviceLock lock = LockDevice(); + m_mappedTextures.remove(pTexture); +#endif + } + + void D3D9DeviceEx::UnmapTextures() { + // Will only be called inside the device lock + +#ifdef D3D9_ALLOW_UNMAPPING + uint32_t mappedMemory = m_memoryAllocator.MappedMemory(); + if (likely(mappedMemory < uint32_t(m_d3d9Options.textureMemory))) + return; + + uint32_t threshold = (m_d3d9Options.textureMemory / 4) * 3; + + auto iter = m_mappedTextures.leastRecentlyUsedIter(); + while (m_memoryAllocator.MappedMemory() >= threshold && iter != m_mappedTextures.leastRecentlyUsedEndIter()) { + if (unlikely((*iter)->IsAnySubresourceLocked() != 0)) { + iter++; + continue; + } + (*iter)->UnmapData(); + + iter = m_mappedTextures.remove(iter); + } +#endif + } + } diff --git a/src/d3d9/d3d9_device.h b/src/d3d9/d3d9_device.h index 2d1e3dcc..6b1d2641 100644 --- a/src/d3d9/d3d9_device.h +++ b/src/d3d9/d3d9_device.h @@ -28,10 +28,13 @@ #include "d3d9_shader_permutations.h" +#include #include #include #include +#include "../util/util_lru.h" + namespace dxvk { class D3D9InterfaceEx; @@ -934,6 +937,10 @@ namespace dxvk { return &m_memoryAllocator; } + void* MapTexture(D3D9CommonTexture* pTexture, UINT Subresource); + void TouchMappedTexture(D3D9CommonTexture* pTexture); + void RemoveMappedTexture(D3D9CommonTexture* pTexture); + private: DxvkCsChunkRef AllocCsChunk() { @@ -1142,6 +1149,8 @@ namespace dxvk { D3D9CommonTexture* pResource, UINT Subresource); + void UnmapTextures(); + uint64_t GetCurrentSequenceNumber(); Com m_parent; @@ -1282,6 +1291,9 @@ namespace dxvk { Direct3DState9 m_state; +#ifdef D3D9_ALLOW_UNMAPPING + lru_list m_mappedTextures; +#endif }; } diff --git a/src/d3d9/d3d9_options.cpp b/src/d3d9/d3d9_options.cpp index b7e80260..ec44bf24 100644 --- a/src/d3d9/d3d9_options.cpp +++ b/src/d3d9/d3d9_options.cpp @@ -73,6 +73,7 @@ namespace dxvk { this->deviceLocalConstantBuffers = config.getOption ("d3d9.deviceLocalConstantBuffers", false); this->allowDirectBufferMapping = config.getOption ("d3d9.allowDirectBufferMapping", true); this->seamlessCubes = config.getOption ("d3d9.seamlessCubes", false); + this->textureMemory = config.getOption ("d3d9.textureMemory", 100) << 20; // If we are not Nvidia, enable general hazards. this->generalHazards = adapter != nullptr diff --git a/src/d3d9/d3d9_options.h b/src/d3d9/d3d9_options.h index 33e3ebe5..34d3ae1f 100644 --- a/src/d3d9/d3d9_options.h +++ b/src/d3d9/d3d9_options.h @@ -157,6 +157,9 @@ namespace dxvk { /// Don't use non seamless cube maps bool seamlessCubes; + + /// How much virtual memory will be used for textures (in MB). + int32_t textureMemory; }; } diff --git a/src/d3d9/d3d9_texture.cpp b/src/d3d9/d3d9_texture.cpp index 811a6cd3..2f1d3ce6 100644 --- a/src/d3d9/d3d9_texture.cpp +++ b/src/d3d9/d3d9_texture.cpp @@ -90,6 +90,7 @@ namespace dxvk { if (m_texture.IsManaged()) m_texture.SetAllNeedUpload(); + m_parent->TouchMappedTexture(&m_texture); return D3D_OK; } @@ -174,6 +175,7 @@ namespace dxvk { if (m_texture.IsManaged()) m_texture.SetAllNeedUpload(); + m_parent->TouchMappedTexture(&m_texture); return D3D_OK; } @@ -267,6 +269,7 @@ namespace dxvk { } } + m_parent->TouchMappedTexture(&m_texture); return D3D_OK; }