1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-12 04:08:52 +01:00

[dxvk] Implement sampler pool

Deduplicates redundant sampler objects and makes sampler creation
as well as lifetime tracking a bit more efficient.
This commit is contained in:
Philip Rebohle 2024-09-26 23:33:28 +02:00 committed by Philip Rebohle
parent 5f9f43e658
commit 4635397bb1
15 changed files with 546 additions and 228 deletions

View File

@ -9,59 +9,54 @@ namespace dxvk {
const D3D11_SAMPLER_DESC& desc) const D3D11_SAMPLER_DESC& desc)
: D3D11StateObject<ID3D11SamplerState>(device), : D3D11StateObject<ID3D11SamplerState>(device),
m_desc(desc), m_d3d10(this) { m_desc(desc), m_d3d10(this) {
DxvkSamplerCreateInfo info; DxvkSamplerKey info = { };
// While D3D11_FILTER is technically an enum, its value bits // While D3D11_FILTER is technically an enum, its value bits
// can be used to decode the filter properties more efficiently. // can be used to decode the filter properties more efficiently.
const uint32_t filterBits = uint32_t(desc.Filter); const uint32_t filterBits = uint32_t(desc.Filter);
info.magFilter = (filterBits & 0x04) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
info.minFilter = (filterBits & 0x10) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; VkFilter minFilter = (filterBits & 0x10) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
VkFilter magFilter = (filterBits & 0x04) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
info.setFilter(minFilter, magFilter,
(filterBits & 0x01) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
// Enforce LOD bias specified in the device options
float lodBias = desc.MipLODBias;
if (minFilter == VK_FILTER_LINEAR && magFilter == VK_FILTER_LINEAR) {
lodBias += device->GetOptions()->samplerLodBias;
if (device->GetOptions()->clampNegativeLodBias)
lodBias = std::max(lodBias, 0.0f);
}
info.setLodRange(desc.MinLOD, desc.MaxLOD, lodBias);
// Enforce anisotropy specified in the device options
uint32_t anisotropy = (filterBits & 0x40) ? desc.MaxAnisotropy : 0u;
int32_t samplerAnisotropyOption = device->GetOptions()->samplerAnisotropy;
if (samplerAnisotropyOption >= 0 && minFilter == VK_FILTER_LINEAR)
anisotropy = samplerAnisotropyOption > 0;
info.setAniso(anisotropy);
// Set up the remaining properties, which are // Set up the remaining properties, which are
// stored directly in the sampler description // stored directly in the sampler description
info.mipmapMode = (filterBits & 0x01) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST; info.setAddressModes(
info.mipmapLodBias = desc.MipLODBias; DecodeAddressMode(desc.AddressU),
info.mipmapLodMin = desc.MinLOD; DecodeAddressMode(desc.AddressV),
info.mipmapLodMax = desc.MaxLOD; DecodeAddressMode(desc.AddressW));
info.useAnisotropy = (filterBits & 0x40) ? VK_TRUE : VK_FALSE; info.setDepthCompare((filterBits & 0x180) == 0x80,
info.maxAnisotropy = float(desc.MaxAnisotropy); DecodeCompareOp(desc.ComparisonFunc));
info.addressModeU = DecodeAddressMode(desc.AddressU); info.setReduction(DecodeReductionMode(filterBits));
info.addressModeV = DecodeAddressMode(desc.AddressV);
info.addressModeW = DecodeAddressMode(desc.AddressW);
info.compareToDepth = (filterBits & 0x180) == 0x80 ? VK_TRUE : VK_FALSE;
info.compareOp = DecodeCompareOp(desc.ComparisonFunc);
info.reductionMode = DecodeReductionMode(filterBits);
for (uint32_t i = 0; i < 4; i++) for (uint32_t i = 0; i < 4; i++)
info.borderColor.float32[i] = desc.BorderColor[i]; info.borderColor.float32[i] = desc.BorderColor[i];
info.usePixelCoord = VK_FALSE; // Not supported in D3D11
info.nonSeamless = VK_FALSE;
// Make sure to use a valid anisotropy value
if (desc.MaxAnisotropy < 1) info.maxAnisotropy = 1.0f;
if (desc.MaxAnisotropy > 16) info.maxAnisotropy = 16.0f;
// Enforce LOD bias specified in the device options
if (info.minFilter == VK_FILTER_LINEAR && info.magFilter == VK_FILTER_LINEAR) {
info.mipmapLodBias += device->GetOptions()->samplerLodBias;
if (device->GetOptions()->clampNegativeLodBias)
info.mipmapLodBias = std::max(info.mipmapLodBias, 0.0f);
}
// Enforce anisotropy specified in the device options
int32_t samplerAnisotropyOption = device->GetOptions()->samplerAnisotropy;
if (samplerAnisotropyOption >= 0 && info.minFilter == VK_FILTER_LINEAR) {
info.useAnisotropy = samplerAnisotropyOption > 0;
info.maxAnisotropy = float(samplerAnisotropyOption);
}
m_sampler = device->GetDXVKDevice()->createSampler(info); m_sampler = device->GetDXVKDevice()->createSampler(info);
} }

View File

@ -6690,42 +6690,37 @@ namespace dxvk {
auto mipFilter = DecodeMipFilter(cKey.MipFilter); auto mipFilter = DecodeMipFilter(cKey.MipFilter);
DxvkSamplerCreateInfo info; DxvkSamplerKey info = { };
info.addressModeU = DecodeAddressMode(cKey.AddressU);
info.addressModeV = DecodeAddressMode(cKey.AddressV); info.setFilter(
info.addressModeW = DecodeAddressMode(cKey.AddressW); DecodeFilter(cKey.MinFilter),
info.compareToDepth = cKey.Depth; DecodeFilter(cKey.MagFilter),
info.compareOp = cKey.Depth ? VK_COMPARE_OP_LESS_OR_EQUAL : VK_COMPARE_OP_NEVER; mipFilter.MipFilter);
info.magFilter = DecodeFilter(cKey.MagFilter);
info.minFilter = DecodeFilter(cKey.MinFilter); info.setAddressModes(
info.mipmapMode = mipFilter.MipFilter; DecodeAddressMode(cKey.AddressU),
info.maxAnisotropy = float(cKey.MaxAnisotropy); DecodeAddressMode(cKey.AddressV),
info.useAnisotropy = cKey.MaxAnisotropy > 1; DecodeAddressMode(cKey.AddressW));
info.setDepthCompare(cKey.Depth,
VK_COMPARE_OP_LESS_OR_EQUAL);
info.setAniso(cKey.MaxAnisotropy);
float lodBias = cKey.MipmapLodBias + m_d3d9Options.samplerLodBias;
info.mipmapLodBias = cKey.MipmapLodBias + m_d3d9Options.samplerLodBias;
if (m_d3d9Options.clampNegativeLodBias) if (m_d3d9Options.clampNegativeLodBias)
info.mipmapLodBias = std::max(info.mipmapLodBias, 0.0f); lodBias = std::max(lodBias, 0.0f);
info.mipmapLodMin = mipFilter.MipsEnabled ? float(cKey.MaxMipLevel) : 0; info.setLodRange(
info.mipmapLodMax = mipFilter.MipsEnabled ? FLT_MAX : 0; mipFilter.MipsEnabled ? float(cKey.MaxMipLevel) : 0.0f,
info.reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE; mipFilter.MipsEnabled ? FLT_MAX : 0.0f,
info.usePixelCoord = VK_FALSE; lodBias);
info.nonSeamless = m_dxvkDevice->features().extNonSeamlessCubeMap.nonSeamlessCubeMap && !m_d3d9Options.seamlessCubes;
info.setLegacyCubeFilter(!m_d3d9Options.seamlessCubes);
DecodeD3DCOLOR(cKey.BorderColor, info.borderColor.float32); DecodeD3DCOLOR(cKey.BorderColor, info.borderColor.float32);
if (!m_dxvkDevice->features().extCustomBorderColor.customBorderColorWithoutFormat) {
// HACK: Let's get OPAQUE_WHITE border color over
// TRANSPARENT_BLACK if the border RGB is white.
if (info.borderColor.float32[0] == 1.0f
&& info.borderColor.float32[1] == 1.0f
&& info.borderColor.float32[2] == 1.0f
&& !m_dxvkDevice->features().extCustomBorderColor.customBorderColors) {
// Then set the alpha to 1.
info.borderColor.float32[3] = 1.0f;
}
}
try { try {
auto sampler = m_dxvkDevice->createSampler(info); auto sampler = m_dxvkDevice->createSampler(info);

View File

@ -281,6 +281,10 @@ namespace dxvk {
m_resources.trackResource(DxvkLifetime<DxvkResource>(rc, Access)); m_resources.trackResource(DxvkLifetime<DxvkResource>(rc, Access));
} }
void trackSampler(const Rc<DxvkSampler>& sampler) {
m_resources.trackSampler(sampler);
}
/** /**
* \brief Tracks a GPU event * \brief Tracks a GPU event
* *

View File

@ -5260,7 +5260,7 @@ namespace dxvk {
descriptorInfo.image.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; descriptorInfo.image.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (m_rcTracked.set(binding.resourceBinding)) if (m_rcTracked.set(binding.resourceBinding))
m_cmd->trackResource<DxvkAccess::None>(res.sampler); m_cmd->trackSampler(res.sampler);
} else { } else {
descriptorInfo.image.sampler = m_common->dummyResources().samplerHandle(); descriptorInfo.image.sampler = m_common->dummyResources().samplerHandle();
descriptorInfo.image.imageView = VK_NULL_HANDLE; descriptorInfo.image.imageView = VK_NULL_HANDLE;
@ -5312,7 +5312,7 @@ namespace dxvk {
descriptorInfo.image.imageLayout = res.imageView->image()->info().layout; descriptorInfo.image.imageLayout = res.imageView->image()->info().layout;
if (m_rcTracked.set(binding.resourceBinding)) { if (m_rcTracked.set(binding.resourceBinding)) {
m_cmd->trackResource<DxvkAccess::None>(res.sampler); m_cmd->trackSampler(res.sampler);
m_cmd->trackResource<DxvkAccess::Read>(res.imageView->image()); m_cmd->trackResource<DxvkAccess::Read>(res.imageView->image());
} }
} else { } else {

View File

@ -172,8 +172,8 @@ namespace dxvk {
Rc<DxvkSampler> DxvkDevice::createSampler( Rc<DxvkSampler> DxvkDevice::createSampler(
const DxvkSamplerCreateInfo& createInfo) { const DxvkSamplerKey& createInfo) {
return new DxvkSampler(this, createInfo); return m_objects.samplerPool().createSampler(createInfo);
} }

View File

@ -330,7 +330,7 @@ namespace dxvk {
* \returns Newly created sampler object * \returns Newly created sampler object
*/ */
Rc<DxvkSampler> createSampler( Rc<DxvkSampler> createSampler(
const DxvkSamplerCreateInfo& createInfo); const DxvkSamplerKey& createInfo);
/** /**
* \brief Creates local allocation cache * \brief Creates local allocation cache

View File

@ -9,6 +9,7 @@ namespace dxvk {
void DxvkLifetimeTracker::reset() { void DxvkLifetimeTracker::reset() {
m_resources.clear(); m_resources.clear();
m_allocations.clear(); m_allocations.clear();
m_samplers.clear();
} }
} }

View File

@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include "dxvk_resource.h" #include "dxvk_resource.h"
#include "dxvk_sampler.h"
namespace dxvk { namespace dxvk {
@ -106,6 +107,14 @@ namespace dxvk {
DxvkLifetimeTracker(); DxvkLifetimeTracker();
~DxvkLifetimeTracker(); ~DxvkLifetimeTracker();
/**
* \brief Adds a sampler to track
* \param [in] res The sampler to track
*/
void trackSampler(const Rc<DxvkSampler>& res) {
m_samplers.push_back(res);
}
/** /**
* \brief Adds a resource to track * \brief Adds a resource to track
* \param [in] res The resource to track * \param [in] res The resource to track
@ -132,6 +141,8 @@ namespace dxvk {
private: private:
std::vector<Rc<DxvkSampler>> m_samplers;
std::vector<DxvkLifetime<DxvkResource>> m_resources; std::vector<DxvkLifetime<DxvkResource>> m_resources;
std::vector<DxvkLifetime<DxvkResourceAllocation>> m_allocations; std::vector<DxvkLifetime<DxvkResourceAllocation>> m_allocations;

View File

@ -11,6 +11,7 @@
#include "dxvk_meta_resolve.h" #include "dxvk_meta_resolve.h"
#include "dxvk_pipemanager.h" #include "dxvk_pipemanager.h"
#include "dxvk_renderpass.h" #include "dxvk_renderpass.h"
#include "dxvk_sampler.h"
#include "dxvk_unbound.h" #include "dxvk_unbound.h"
#include "../util/util_lazy.h" #include "../util/util_lazy.h"
@ -25,6 +26,7 @@ namespace dxvk {
: m_device (device), : m_device (device),
m_memoryManager (device), m_memoryManager (device),
m_pipelineManager (device), m_pipelineManager (device),
m_samplerPool (device),
m_eventPool (device), m_eventPool (device),
m_queryPool (device), m_queryPool (device),
m_dummyResources (device) { m_dummyResources (device) {
@ -39,6 +41,10 @@ namespace dxvk {
return m_pipelineManager; return m_pipelineManager;
} }
DxvkSamplerPool& samplerPool() {
return m_samplerPool;
}
DxvkGpuEventPool& eventPool() { DxvkGpuEventPool& eventPool() {
return m_eventPool; return m_eventPool;
} }
@ -78,6 +84,7 @@ namespace dxvk {
DxvkMemoryAllocator m_memoryManager; DxvkMemoryAllocator m_memoryManager;
DxvkPipelineManager m_pipelineManager; DxvkPipelineManager m_pipelineManager;
DxvkSamplerPool m_samplerPool;
DxvkGpuEventPool m_eventPool; DxvkGpuEventPool m_eventPool;
DxvkGpuQueryPool m_queryPool; DxvkGpuQueryPool m_queryPool;

View File

@ -4,82 +4,216 @@
namespace dxvk { namespace dxvk {
DxvkSampler::DxvkSampler( DxvkSampler::DxvkSampler(
DxvkDevice* device, DxvkSamplerPool* pool,
const DxvkSamplerCreateInfo& info) const DxvkSamplerKey& key)
: m_vkd(device->vkd()) { : m_pool(pool), m_key(key) {
auto vk = m_pool->m_device->vkd();
VkSamplerCustomBorderColorCreateInfoEXT borderColorInfo = { VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT }; VkSamplerCustomBorderColorCreateInfoEXT borderColorInfo = { VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT };
borderColorInfo.customBorderColor = info.borderColor; borderColorInfo.customBorderColor = key.borderColor;
VkSamplerReductionModeCreateInfo reductionInfo = { VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO }; VkSamplerReductionModeCreateInfo reductionInfo = { VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO };
reductionInfo.reductionMode = info.reductionMode; reductionInfo.reductionMode = VkSamplerReductionMode(key.u.p.reduction);
VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
samplerInfo.flags = info.nonSeamless ? VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT : 0; samplerInfo.magFilter = VkFilter(key.u.p.magFilter);
samplerInfo.magFilter = info.magFilter; samplerInfo.minFilter = VkFilter(key.u.p.minFilter);
samplerInfo.minFilter = info.minFilter; samplerInfo.mipmapMode = VkSamplerMipmapMode(key.u.p.mipMode);
samplerInfo.mipmapMode = info.mipmapMode; samplerInfo.addressModeU = VkSamplerAddressMode(key.u.p.addressU);
samplerInfo.addressModeU = info.addressModeU; samplerInfo.addressModeV = VkSamplerAddressMode(key.u.p.addressV);
samplerInfo.addressModeV = info.addressModeV; samplerInfo.addressModeW = VkSamplerAddressMode(key.u.p.addressW);
samplerInfo.addressModeW = info.addressModeW; samplerInfo.mipLodBias = bit::decodeFixed<int32_t, 6, 8>(key.u.p.lodBias);
samplerInfo.mipLodBias = info.mipmapLodBias; samplerInfo.anisotropyEnable = key.u.p.anisotropy > 0u;
samplerInfo.anisotropyEnable = info.useAnisotropy; samplerInfo.maxAnisotropy = float(key.u.p.anisotropy);
samplerInfo.maxAnisotropy = info.maxAnisotropy; samplerInfo.compareEnable = key.u.p.compareEnable != 0u;
samplerInfo.compareEnable = info.compareToDepth; samplerInfo.compareOp = VkCompareOp(key.u.p.compareOp);
samplerInfo.compareOp = info.compareOp; samplerInfo.minLod = bit::decodeFixed<uint32_t, 4, 8>(key.u.p.minLod);
samplerInfo.minLod = info.mipmapLodMin; samplerInfo.maxLod = bit::decodeFixed<uint32_t, 4, 8>(key.u.p.maxLod);
samplerInfo.maxLod = info.mipmapLodMax;
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
samplerInfo.unnormalizedCoordinates = info.usePixelCoord; samplerInfo.unnormalizedCoordinates = key.u.p.pixelCoord;
if (!device->features().core.features.samplerAnisotropy) if (key.u.p.legacyCube && m_pool->m_device->features().extNonSeamlessCubeMap.nonSeamlessCubeMap)
samplerInfo.flags |= VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT;
if (!m_pool->m_device->features().core.features.samplerAnisotropy)
samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.anisotropyEnable = VK_FALSE;
if (samplerInfo.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER if (key.u.p.hasBorder)
|| samplerInfo.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER samplerInfo.borderColor = determineBorderColorType();
|| samplerInfo.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER)
samplerInfo.borderColor = getBorderColor(device, info);
if (samplerInfo.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT) if (samplerInfo.borderColor == VK_BORDER_COLOR_FLOAT_CUSTOM_EXT
|| samplerInfo.borderColor == VK_BORDER_COLOR_INT_CUSTOM_EXT)
borderColorInfo.pNext = std::exchange(samplerInfo.pNext, &borderColorInfo); borderColorInfo.pNext = std::exchange(samplerInfo.pNext, &borderColorInfo);
if (reductionInfo.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE) if (reductionInfo.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE)
reductionInfo.pNext = std::exchange(samplerInfo.pNext, &reductionInfo); reductionInfo.pNext = std::exchange(samplerInfo.pNext, &reductionInfo);
if (m_vkd->vkCreateSampler(m_vkd->device(), if (vk->vkCreateSampler(vk->device(),
&samplerInfo, nullptr, &m_sampler) != VK_SUCCESS) &samplerInfo, nullptr, &m_sampler) != VK_SUCCESS)
throw DxvkError("DxvkSampler::DxvkSampler: Failed to create sampler"); throw DxvkError("DxvkSampler::DxvkSampler: Failed to create sampler");
} }
DxvkSampler::~DxvkSampler() { DxvkSampler::~DxvkSampler() {
m_vkd->vkDestroySampler( auto vk = m_pool->m_device->vkd();
m_vkd->device(), m_sampler, nullptr);
vk->vkDestroySampler(vk->device(), m_sampler, nullptr);
} }
VkBorderColor DxvkSampler::getBorderColor(const Rc<DxvkDevice>& device, const DxvkSamplerCreateInfo& info) { void DxvkSampler::release() {
static const std::array<std::pair<VkClearColorValue, VkBorderColor>, 3> s_borderColors = {{ m_pool->releaseSampler(this);
}
VkBorderColor DxvkSampler::determineBorderColorType() const {
static const std::array<std::pair<VkClearColorValue, VkBorderColor>, 4> s_borderColors = {{
{ { { 0.0f, 0.0f, 0.0f, 0.0f } }, VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK }, { { { 0.0f, 0.0f, 0.0f, 0.0f } }, VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK },
{ { { 0.0f, 0.0f, 0.0f, 1.0f } }, VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK }, { { { 0.0f, 0.0f, 0.0f, 1.0f } }, VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK },
{ { { 1.0f, 1.0f, 1.0f, 1.0f } }, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE }, { { { 1.0f, 1.0f, 1.0f, 1.0f } }, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE },
}}; }};
// Ignore G/B/A components for shadow samplers // Iterate over border colors and try to find an exact match
size_t size = !info.compareToDepth uint32_t componentCount = m_key.u.p.compareEnable ? 1u : 4u;
? sizeof(VkClearColorValue)
: sizeof(float);
for (const auto& e : s_borderColors) { for (const auto& e : s_borderColors) {
if (!std::memcmp(&e.first, &info.borderColor, size)) bool allEqual = true;
for (uint32_t i = 0; i < componentCount; i++)
allEqual &= m_key.borderColor.float32[i] == e.first.float32[i];
if (allEqual)
return e.second; return e.second;
} }
if (!device->features().extCustomBorderColor.customBorderColorWithoutFormat) { // If custom border colors are supported, use that
if (m_pool->m_device->features().extCustomBorderColor.customBorderColorWithoutFormat)
return VK_BORDER_COLOR_FLOAT_CUSTOM_EXT;
// Otherwise, use the sum of absolute differences to find the
// closest fallback value. Some D3D9 games may rely on this.
Logger::warn("DXVK: Custom border colors not supported"); Logger::warn("DXVK: Custom border colors not supported");
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
VkBorderColor result = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
float minSad = -1.0f;
for (const auto& e : s_borderColors) {
float sad = 0.0f;
for (uint32_t i = 0; i < componentCount; i++)
sad += std::abs(m_key.borderColor.float32[i] - e.first.float32[i]);
if (sad < minSad || minSad < 0.0f) {
minSad = sad;
result = e.second;
}
} }
return VK_BORDER_COLOR_FLOAT_CUSTOM_EXT; return result;
}
DxvkSamplerPool::DxvkSamplerPool(DxvkDevice* device)
: m_device(device) {
}
DxvkSamplerPool::~DxvkSamplerPool() {
m_samplers.clear();
}
Rc<DxvkSampler> DxvkSamplerPool::createSampler(const DxvkSamplerKey& key) {
std::unique_lock lock(m_mutex);
auto entry = m_samplers.find(key);
if (entry != m_samplers.end()) {
DxvkSampler* sampler = &entry->second;
// Remove the sampler from the LRU list if it's in there. Due
// to the way releasing samplers is implemented upon reaching
// a ref count of 0, it is possible that we reach this before
// the releasing thread inserted the list into the LRU list.
if (!sampler->m_refCount.fetch_add(1u, std::memory_order_acquire)) {
if (sampler->m_lruPrev)
sampler->m_lruPrev->m_lruNext = sampler->m_lruNext;
else if (m_lruHead == sampler)
m_lruHead = sampler->m_lruNext;
if (sampler->m_lruNext)
sampler->m_lruNext->m_lruPrev = sampler->m_lruPrev;
else if (m_lruTail == sampler)
m_lruTail = sampler->m_lruPrev;
sampler->m_lruPrev = nullptr;
sampler->m_lruNext = nullptr;
}
// We already took a reference, forward the pointer as-is
return Rc<DxvkSampler>::unsafeCreate(sampler);
}
// If we're spamming sampler allocations, we might need
// to clean up unused ones here to stay within the limit
if (m_samplers.size() >= MaxSamplerCount)
destroyLeastRecentlyUsedSampler();
// Create new sampler object
return &m_samplers.emplace(std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(this, key)).first->second;
}
void DxvkSamplerPool::releaseSampler(DxvkSampler* sampler) {
std::unique_lock lock(m_mutex);
// Back off if another thread has re-aquired the sampler. This is
// safe since the ref count can only be incremented from zero when
// the pool is locked.
if (sampler->m_refCount.load())
return;
// It is also possible that two threads end up here while the ref
// count is zero. Make sure to not add the sampler to the LRU list
// more than once in that case.
if (sampler->m_lruPrev || m_lruHead == sampler)
return;
// Add sampler to the end of the LRU list
sampler->m_lruPrev = m_lruTail;
sampler->m_lruNext = nullptr;
if (m_lruTail)
m_lruTail->m_lruNext = sampler;
else
m_lruHead = sampler;
m_lruTail = sampler;
// Try to keep some samplers available for subsequent allocations
if (m_samplers.size() > MinSamplerCount)
destroyLeastRecentlyUsedSampler();
}
void DxvkSamplerPool::destroyLeastRecentlyUsedSampler() {
DxvkSampler* sampler = m_lruHead;
if (sampler) {
m_lruHead = sampler->m_lruNext;
if (m_lruHead)
m_lruHead->m_lruPrev = nullptr;
else
m_lruTail = nullptr;
m_samplers.erase(sampler->key());
}
} }
} }

View File

@ -1,51 +1,132 @@
#pragma once #pragma once
#include <unordered_map>
#include "../util/util_bit.h"
#include "../util/thread.h"
#include "dxvk_resource.h" #include "dxvk_resource.h"
namespace dxvk { namespace dxvk {
class DxvkDevice; class DxvkDevice;
class DxvkSamplerPool;
/** /**
* \brief Sampler properties * \brief Sampler key
*
* Stores packed sampler properties and in a way that
* can be reasonably efficiently used with a hash map.
*/ */
struct DxvkSamplerCreateInfo { struct DxvkSamplerKey {
/// Texture filter propertoes union {
VkFilter magFilter; struct {
VkFilter minFilter; uint32_t minFilter : 1;
uint32_t magFilter : 1;
uint32_t mipMode : 1;
uint32_t anisotropy : 5;
/// Mipmapping properties uint32_t addressU : 3;
VkSamplerMipmapMode mipmapMode; uint32_t addressV : 3;
float mipmapLodBias; uint32_t addressW : 3;
float mipmapLodMin; uint32_t hasBorder : 1;
float mipmapLodMax;
/// Anisotropic filtering uint32_t lodBias : 14;
VkBool32 useAnisotropy;
float maxAnisotropy;
/// Address modes uint32_t minLod : 12;
VkSamplerAddressMode addressModeU; uint32_t maxLod : 12;
VkSamplerAddressMode addressModeV;
VkSamplerAddressMode addressModeW;
/// Compare op for shadow textures uint32_t compareEnable : 1;
VkBool32 compareToDepth; uint32_t compareOp : 3;
VkCompareOp compareOp; uint32_t reduction : 2;
uint32_t pixelCoord : 1;
uint32_t legacyCube : 1;
} p;
/// Reduction mode for min/max samplers uint32_t properties[2] = { 0u, 0u };
VkSamplerReductionMode reductionMode; } u;
/// Texture border color VkClearColorValue borderColor = { };
VkClearColorValue borderColor;
/// Enables unnormalized coordinates void setFilter(VkFilter min, VkFilter mag, VkSamplerMipmapMode mip) {
VkBool32 usePixelCoord; u.p.minFilter = uint32_t(min);
u.p.magFilter = uint32_t(mag);
u.p.mipMode = uint32_t(mip);
}
void setAniso(uint32_t anisotropy) {
u.p.anisotropy = std::min(anisotropy, 16u);
}
void setDepthCompare(bool enable, VkCompareOp op) {
u.p.compareEnable = uint32_t(enable);
u.p.compareOp = enable ? uint32_t(op) : 0u;
}
void setReduction(VkSamplerReductionMode reduction) {
u.p.reduction = uint32_t(reduction);
}
void setUsePixelCoordinates(bool enable) {
u.p.pixelCoord = uint32_t(enable);
}
void setLegacyCubeFilter(bool enable) {
u.p.legacyCube = uint32_t(enable);
}
void setAddressModes(VkSamplerAddressMode u_, VkSamplerAddressMode v_, VkSamplerAddressMode w_) {
u.p.addressU = u_;
u.p.addressV = v_;
u.p.addressW = w_;
u.p.hasBorder = uint32_t(u_ == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER
|| v_ == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER
|| w_ == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
}
void setLodRange(float min, float max, float bias) {
u.p.minLod = bit::encodeFixed<uint32_t, 4, 8>(min);
u.p.maxLod = bit::encodeFixed<uint32_t, 4, 8>(max);
u.p.lodBias = bit::encodeFixed<int32_t, 6, 8>(bias);
}
void setBorderColor(VkClearColorValue color) {
borderColor = color;
}
bool eq(const DxvkSamplerKey& other) const {
bool eq = u.properties[0] == other.u.properties[0]
&& u.properties[1] == other.u.properties[1];
if (eq && u.p.hasBorder) {
eq = borderColor.uint32[0] == other.borderColor.uint32[0]
&& borderColor.uint32[1] == other.borderColor.uint32[1]
&& borderColor.uint32[2] == other.borderColor.uint32[2]
&& borderColor.uint32[3] == other.borderColor.uint32[3];
}
return eq;
}
size_t hash() const {
DxvkHashState hash;
hash.add(u.properties[0]);
hash.add(u.properties[1]);
if (u.p.hasBorder) {
hash.add(borderColor.uint32[0]);
hash.add(borderColor.uint32[1]);
hash.add(borderColor.uint32[2]);
hash.add(borderColor.uint32[3]);
}
return hash;
}
/// Enables non seamless cube map filtering
VkBool32 nonSeamless;
}; };
static_assert(sizeof(DxvkSamplerKey) == 24u);
/** /**
* \brief Sampler * \brief Sampler
@ -54,15 +135,33 @@ namespace dxvk {
* a pipeline. Sampler objects provide parameters * a pipeline. Sampler objects provide parameters
* for texture lookups within a shader. * for texture lookups within a shader.
*/ */
class DxvkSampler : public DxvkResource { class DxvkSampler {
friend class DxvkSamplerPool;
public: public:
DxvkSampler( DxvkSampler(
DxvkDevice* device, DxvkSamplerPool* pool,
const DxvkSamplerCreateInfo& info); const DxvkSamplerKey& key);
~DxvkSampler(); ~DxvkSampler();
/**
* \brief Increments reference count
*/
force_inline void incRef() {
m_refCount.fetch_add(1u, std::memory_order_acquire);
}
/**
* \brief Decrements reference count
*
* Recycles the sampler once the ref count reaches zero.
*/
force_inline void decRef() {
if (m_refCount.fetch_sub(1u, std::memory_order_relaxed) == 1u)
release();
}
/** /**
* \brief Sampler handle * \brief Sampler handle
* \returns Sampler handle * \returns Sampler handle
@ -71,15 +170,77 @@ namespace dxvk {
return m_sampler; return m_sampler;
} }
/**
* \brief Sampler key
* \returns Sampler properties
*/
const DxvkSamplerKey& key() const {
return m_key;
}
private: private:
Rc<vk::DeviceFn> m_vkd; std::atomic<uint32_t> m_refCount = { 0u };
DxvkSamplerPool* m_pool = nullptr;
DxvkSamplerKey m_key = { };
VkSampler m_sampler = VK_NULL_HANDLE; VkSampler m_sampler = VK_NULL_HANDLE;
static VkBorderColor getBorderColor( DxvkSampler* m_lruPrev = nullptr;
const Rc<DxvkDevice>& device, DxvkSampler* m_lruNext = nullptr;
const DxvkSamplerCreateInfo& info);
void release();
VkBorderColor determineBorderColorType() const;
}; };
/**
* \brief Sampler pool
*
* Manages unique samplers within a device.
*/
class DxvkSamplerPool {
friend DxvkSampler;
public:
// The Vulkan limit for samplers is at least 4000.
// Keep some objects available for internal use.
constexpr static uint32_t MaxSamplerCount = 3584u;
// Minimum number of samplers to keep alive.
constexpr static uint32_t MinSamplerCount = 1024u;
DxvkSamplerPool(DxvkDevice* device);
~DxvkSamplerPool();
/**
* \brief Creates sampler
*
* \param [in] key Sampler key
* \returns Sampler object
*/
Rc<DxvkSampler> createSampler(const DxvkSamplerKey& key);
private:
DxvkDevice* m_device;
dxvk::mutex m_mutex;
std::unordered_map<DxvkSamplerKey,
DxvkSampler, DxvkHash, DxvkEq> m_samplers;
DxvkSampler* m_lruHead = nullptr;
DxvkSampler* m_lruTail = nullptr;
void releaseSampler(DxvkSampler* sampler);
void destroyLeastRecentlyUsedSampler();
};
} }

View File

@ -286,30 +286,23 @@ namespace dxvk {
void DxvkSwapchainBlitter::createSampler() { void DxvkSwapchainBlitter::createSampler() {
DxvkSamplerCreateInfo samplerInfo; DxvkSamplerKey samplerInfo = { };
samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.setFilter(VK_FILTER_LINEAR, VK_FILTER_LINEAR,
samplerInfo.minFilter = VK_FILTER_LINEAR; VK_SAMPLER_MIPMAP_MODE_NEAREST);
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerInfo.setAddressModes(
samplerInfo.mipmapLodBias = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
samplerInfo.mipmapLodMin = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
samplerInfo.mipmapLodMax = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
samplerInfo.useAnisotropy = VK_FALSE; samplerInfo.setUsePixelCoordinates(true);
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.compareToDepth = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE;
samplerInfo.borderColor = VkClearColorValue();
samplerInfo.usePixelCoord = VK_TRUE;
samplerInfo.nonSeamless = VK_FALSE;
m_samplerPresent = m_device->createSampler(samplerInfo); m_samplerPresent = m_device->createSampler(samplerInfo);
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.setAddressModes(
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
samplerInfo.usePixelCoord = VK_FALSE; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
samplerInfo.setUsePixelCoordinates(false);
m_samplerGamma = m_device->createSampler(samplerInfo); m_samplerGamma = m_device->createSampler(samplerInfo);
} }

View File

@ -54,24 +54,14 @@ namespace dxvk {
Rc<DxvkSampler> DxvkUnboundResources::createSampler() { Rc<DxvkSampler> DxvkUnboundResources::createSampler() {
DxvkSamplerCreateInfo info; DxvkSamplerKey info;
info.minFilter = VK_FILTER_LINEAR; info.setFilter(VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_LINEAR);
info.magFilter = VK_FILTER_LINEAR; info.setLodRange(-256.0f, 256.0f, 0.0f);
info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; info.setAddressModes(
info.mipmapLodBias = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
info.mipmapLodMin = -256.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
info.mipmapLodMax = 256.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
info.useAnisotropy = VK_FALSE; info.setReduction(VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE);
info.maxAnisotropy = 1.0f;
info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.compareToDepth = VK_FALSE;
info.compareOp = VK_COMPARE_OP_NEVER;
info.reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE;
info.borderColor = VkClearColorValue();
info.usePixelCoord = VK_FALSE;
info.nonSeamless = VK_FALSE;
return m_device->createSampler(info); return m_device->createSampler(info);
} }

View File

@ -321,24 +321,14 @@ namespace dxvk::hud {
Rc<DxvkSampler> HudRenderer::createFontSampler() { Rc<DxvkSampler> HudRenderer::createFontSampler() {
DxvkSamplerCreateInfo info; DxvkSamplerKey info = { };
info.magFilter = VK_FILTER_LINEAR; info.setFilter(VK_FILTER_LINEAR,
info.minFilter = VK_FILTER_LINEAR; VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST);
info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; info.setAddressModes(
info.mipmapLodBias = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
info.mipmapLodMin = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
info.mipmapLodMax = 0.0f; VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
info.useAnisotropy = VK_FALSE; info.setUsePixelCoordinates(true);
info.maxAnisotropy = 1.0f;
info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.compareToDepth = VK_FALSE;
info.compareOp = VK_COMPARE_OP_NEVER;
info.reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE;
info.borderColor = VkClearColorValue();
info.usePixelCoord = VK_TRUE;
info.nonSeamless = VK_FALSE;
return m_device->createSampler(info); return m_device->createSampler(info);
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint>
#include <functional> #include <functional>
#include <ostream> #include <ostream>
#include <utility> #include <utility>
@ -107,19 +109,47 @@ namespace dxvk {
return m_object != nullptr; return m_object != nullptr;
} }
/**
* \brief Sets pointer without acquiring a reference
*
* Must only be use when a reference has been taken via
* other means.
* \param [in] object Object pointer
*/
void unsafeInsert(T* object) { void unsafeInsert(T* object) {
this->decRef(); this->decRef();
m_object = object; m_object = object;
} }
/**
* \brief Extracts raw pointer
*
* Sets the smart pointer to null without decrementing the
* reference count. Must only be used when the reference
* count is decremented in some other way.
* \returns Pointer to owned object
*/
T* unsafeExtract() { T* unsafeExtract() {
return std::exchange(m_object, nullptr); return std::exchange(m_object, nullptr);
} }
/**
* \brief Creates smart pointer without taking reference
*
* Must only be used when a refernece has been obtained via other means.
* \param [in] object Pointer to object to take ownership of
*/
static Rc<T> unsafeCreate(T* object) {
return Rc<T>(object, false);
}
private: private:
T* m_object = nullptr; T* m_object = nullptr;
explicit Rc(T* object, bool)
: m_object(object) { }
force_inline void incRef() const { force_inline void incRef() const {
if (m_object != nullptr) if (m_object != nullptr)
m_object->incRef(); m_object->incRef();
@ -145,6 +175,13 @@ namespace dxvk {
template<typename Tx, typename Ty> template<typename Tx, typename Ty>
bool operator != (Tx* a, const Rc<Ty>& b) { return b != a; } bool operator != (Tx* a, const Rc<Ty>& b) { return b != a; }
struct RcHash {
template<typename T>
size_t operator () (const Rc<T>& rc) const {
return reinterpret_cast<uintptr_t>(rc.ptr()) / sizeof(T);
}
};
} }
template<typename T> template<typename T>