GPUDevice: Add sampler cache to base class

Removes per-backend bookkeeping in D3D12 and Vulkan.
This commit is contained in:
Stenzek 2025-01-18 14:55:15 +10:00
parent da13579356
commit 609fa5c7d7
No known key found for this signature in database
8 changed files with 66 additions and 86 deletions

View File

@ -317,7 +317,6 @@ void D3D12Device::DestroyDevice()
m_main_swap_chain.reset(); m_main_swap_chain.reset();
DestroyDeferredObjects(m_current_fence_value); DestroyDeferredObjects(m_current_fence_value);
DestroySamplers();
DestroyTimestampQuery(); DestroyTimestampQuery();
DestroyBuffers(); DestroyBuffers();
DestroyDescriptorHeaps(); DestroyDescriptorHeaps();
@ -571,11 +570,11 @@ bool D3D12Device::CreateDescriptorHeaps(Error* error)
m_device->CreateUnorderedAccessView(nullptr, nullptr, &null_uav_desc, m_null_uav_descriptor.cpu_handle); m_device->CreateUnorderedAccessView(nullptr, nullptr, &null_uav_desc, m_null_uav_descriptor.cpu_handle);
// Same for samplers. // Same for samplers.
m_point_sampler = GetSampler(GPUSampler::GetNearestConfig(), error); GPUSampler* default_sampler = GetSampler(GPUSampler::GetNearestConfig(), error);
if (!m_point_sampler) [[unlikely]] if (!default_sampler) [[unlikely]]
return false; return false;
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++) for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
m_current_samplers[i] = m_point_sampler; m_current_samplers[i] = static_cast<D3D12Sampler*>(default_sampler)->GetDescriptor();
return true; return true;
} }
@ -2123,7 +2122,7 @@ void D3D12Device::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* s
} }
const D3D12DescriptorHandle& handle = const D3D12DescriptorHandle& handle =
sampler ? static_cast<D3D12Sampler*>(sampler)->GetDescriptor() : m_point_sampler; static_cast<D3D12Sampler*>(sampler ? sampler : m_nearest_sampler)->GetDescriptor();
if (m_current_samplers[slot] != handle) if (m_current_samplers[slot] != handle)
{ {
m_current_samplers[slot] = handle; m_current_samplers[slot] = handle;
@ -2295,7 +2294,7 @@ void D3D12Device::RenderTextureMipmap(D3D12Texture* texture, u32 dst_level, u32
cmdlist->SetPipelineState(pipeline.Get()); cmdlist->SetPipelineState(pipeline.Get());
cmdlist->SetGraphicsRootDescriptorTable(0, srv_handle); cmdlist->SetGraphicsRootDescriptorTable(0, srv_handle);
cmdlist->SetGraphicsRootDescriptorTable(1, static_cast<D3D12Sampler*>(m_linear_sampler.get())->GetDescriptor()); cmdlist->SetGraphicsRootDescriptorTable(1, static_cast<D3D12Sampler*>(m_linear_sampler)->GetDescriptor());
cmdlist->DrawInstanced(3, 1, 0, 0); cmdlist->DrawInstanced(3, 1, 0, 0);
cmdlist->EndRenderPass(); cmdlist->EndRenderPass();

View File

@ -246,8 +246,6 @@ private:
}; };
static_assert(sizeof(PIPELINE_CACHE_HEADER) == 16); static_assert(sizeof(PIPELINE_CACHE_HEADER) == 16);
using SamplerMap = std::unordered_map<u64, D3D12DescriptorHandle>;
void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr); void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr);
void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features); void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features);
@ -261,8 +259,6 @@ private:
void DestroyDescriptorHeaps(); void DestroyDescriptorHeaps();
bool CreateTimestampQuery(); bool CreateTimestampQuery();
void DestroyTimestampQuery(); void DestroyTimestampQuery();
D3D12DescriptorHandle GetSampler(const GPUSampler::Config& config, Error* error);
void DestroySamplers();
void DestroyDeferredObjects(u64 fence_value); void DestroyDeferredObjects(u64 fence_value);
void RenderBlankFrame(D3D12SwapChain* swap_chain); void RenderBlankFrame(D3D12SwapChain* swap_chain);
@ -320,7 +316,6 @@ private:
D3D12DescriptorHeapManager m_sampler_heap_manager; D3D12DescriptorHeapManager m_sampler_heap_manager;
D3D12DescriptorHandle m_null_srv_descriptor; D3D12DescriptorHandle m_null_srv_descriptor;
D3D12DescriptorHandle m_null_uav_descriptor; D3D12DescriptorHandle m_null_uav_descriptor;
D3D12DescriptorHandle m_point_sampler;
ComPtr<ID3D12QueryHeap> m_timestamp_query_heap; ComPtr<ID3D12QueryHeap> m_timestamp_query_heap;
ComPtr<ID3D12Resource> m_timestamp_query_buffer; ComPtr<ID3D12Resource> m_timestamp_query_buffer;
@ -342,7 +337,6 @@ private:
u32 m_uniform_buffer_position = 0; u32 m_uniform_buffer_position = 0;
bool m_in_render_pass = false; bool m_in_render_pass = false;
SamplerMap m_sampler_map;
ComPtr<ID3D12PipelineLibrary> m_pipeline_library; ComPtr<ID3D12PipelineLibrary> m_pipeline_library;
// Which bindings/state has to be updated before the next draw. // Which bindings/state has to be updated before the next draw.

View File

@ -699,7 +699,8 @@ D3D12Sampler::D3D12Sampler(D3D12DescriptorHandle descriptor) : m_descriptor(desc
D3D12Sampler::~D3D12Sampler() D3D12Sampler::~D3D12Sampler()
{ {
// Cleaned up by main class. D3D12Device& dev = D3D12Device::GetInstance();
dev.DeferDescriptorDestruction(dev.GetSamplerHeapManager(), &m_descriptor);
} }
#ifdef ENABLE_GPU_OBJECT_NAMES #ifdef ENABLE_GPU_OBJECT_NAMES
@ -710,12 +711,8 @@ void D3D12Sampler::SetDebugName(std::string_view name)
#endif #endif
D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config, Error* error) std::unique_ptr<GPUSampler> D3D12Device::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{ {
const auto it = m_sampler_map.find(config.key);
if (it != m_sampler_map.end())
return it->second;
static constexpr std::array<D3D12_TEXTURE_ADDRESS_MODE, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{ static constexpr std::array<D3D12_TEXTURE_ADDRESS_MODE, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{
D3D12_TEXTURE_ADDRESS_MODE_WRAP, // Repeat D3D12_TEXTURE_ADDRESS_MODE_WRAP, // Repeat
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // ClampToEdge D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // ClampToEdge
@ -756,31 +753,15 @@ D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config,
D3D12DescriptorHandle handle; D3D12DescriptorHandle handle;
if (m_sampler_heap_manager.Allocate(&handle)) [[likely]] if (m_sampler_heap_manager.Allocate(&handle)) [[likely]]
m_device->CreateSampler(&desc, handle);
else
Error::SetStringView(error, "Failed to allocate sampler handle.");
m_sampler_map.emplace(config.key, handle);
return handle;
}
void D3D12Device::DestroySamplers()
{
for (auto& it : m_sampler_map)
{ {
if (it.second) m_device->CreateSampler(&desc, handle);
m_sampler_heap_manager.Free(&it.second);
}
m_sampler_map.clear();
}
std::unique_ptr<GPUSampler> D3D12Device::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{
const D3D12DescriptorHandle handle = GetSampler(config, error);
if (!handle)
return {};
return std::unique_ptr<GPUSampler>(new D3D12Sampler(handle)); return std::unique_ptr<GPUSampler>(new D3D12Sampler(handle));
}
else
{
Error::SetStringView(error, "Failed to allocate sampler handle.");
return {};
}
} }
D3D12TextureBuffer::D3D12TextureBuffer(Format format, u32 size_in_elements) : GPUTextureBuffer(format, size_in_elements) D3D12TextureBuffer::D3D12TextureBuffer(Format format, u32 size_in_elements) : GPUTextureBuffer(format, size_in_elements)

View File

@ -650,12 +650,14 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
bool GPUDevice::CreateResources(Error* error) bool GPUDevice::CreateResources(Error* error)
{ {
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig(), error)) || if (!(m_nearest_sampler = GetSampler(GPUSampler::GetNearestConfig(), error)) ||
!(m_linear_sampler = CreateSampler(GPUSampler::GetLinearConfig(), error))) !(m_linear_sampler = GetSampler(GPUSampler::GetLinearConfig(), error)))
{ {
Error::AddPrefix(error, "Failed to create samplers: "); Error::AddPrefix(error, "Failed to create samplers: ");
return false; return false;
} }
GL_OBJECT_NAME(m_nearest_sampler, "Nearest Sampler");
GL_OBJECT_NAME(m_linear_sampler, "Nearest Sampler");
const RenderAPI render_api = GetRenderAPI(); const RenderAPI render_api = GetRenderAPI();
ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), m_features.dual_source_blend, ShaderGen shadergen(render_api, ShaderGen::GetShaderLanguageForAPI(render_api), m_features.dual_source_blend,
@ -717,8 +719,9 @@ void GPUDevice::DestroyResources()
m_imgui_pipeline.reset(); m_imgui_pipeline.reset();
m_linear_sampler.reset(); m_linear_sampler = nullptr;
m_nearest_sampler.reset(); m_nearest_sampler = nullptr;
m_sampler_map.clear();
m_shader_cache.Close(); m_shader_cache.Close();
} }
@ -773,7 +776,7 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
clip = FlipToLowerLeft(clip, post_rotated_height); clip = FlipToLowerLeft(clip, post_rotated_height);
SetScissor(clip); SetScissor(clip);
SetTextureSampler(0, reinterpret_cast<GPUTexture*>(pcmd->TextureId), m_linear_sampler.get()); SetTextureSampler(0, reinterpret_cast<GPUTexture*>(pcmd->TextureId), m_linear_sampler);
if (pcmd->UserCallback) [[unlikely]] if (pcmd->UserCallback) [[unlikely]]
{ {
@ -1015,6 +1018,25 @@ GSVector4i GPUDevice::FlipToLowerLeft(GSVector4i rc, s32 target_height)
return rc; return rc;
} }
GPUSampler* GPUDevice::GetSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{
auto it = m_sampler_map.find(config.key);
if (it != m_sampler_map.end())
{
if (!it->second) [[unlikely]]
Error::SetStringView(error, "Sampler previously failed creation.");
return it->second.get();
}
std::unique_ptr<GPUSampler> sampler = g_gpu_device->CreateSampler(config, error);
if (sampler)
GL_OBJECT_NAME_FMT(sampler, "Sampler {:016X}", config.key);
it = m_sampler_map.emplace(config.key, std::move(sampler)).first;
return it->second.get();
}
bool GPUDevice::IsTexturePoolType(GPUTexture::Type type) bool GPUDevice::IsTexturePoolType(GPUTexture::Type type)
{ {
return (type == GPUTexture::Type::Texture); return (type == GPUTexture::Type::Texture);

View File

@ -716,8 +716,8 @@ public:
ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); } ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); }
ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); } ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); }
ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler.get(); } ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler; }
ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); } ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler; }
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; } ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
@ -757,6 +757,8 @@ public:
virtual std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements, virtual std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) = 0; Error* error = nullptr) = 0;
GPUSampler* GetSampler(const GPUSampler::Config& config, Error* error = nullptr);
// Texture pooling. // Texture pooling.
std::unique_ptr<GPUTexture> FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, std::unique_ptr<GPUTexture> FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags, GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
@ -918,11 +920,11 @@ protected:
u32 m_max_multisamples = 0; u32 m_max_multisamples = 0;
std::unique_ptr<GPUSwapChain> m_main_swap_chain; std::unique_ptr<GPUSwapChain> m_main_swap_chain;
GPUSampler* m_nearest_sampler = nullptr;
GPUSampler* m_linear_sampler = nullptr;
GPUShaderCache m_shader_cache; GPUShaderCache m_shader_cache;
std::unique_ptr<GPUSampler> m_nearest_sampler;
std::unique_ptr<GPUSampler> m_linear_sampler;
private: private:
static constexpr u32 MAX_TEXTURE_POOL_SIZE = 125; static constexpr u32 MAX_TEXTURE_POOL_SIZE = 125;
@ -957,6 +959,7 @@ private:
}; };
using TexturePool = std::deque<TexturePoolEntry>; using TexturePool = std::deque<TexturePoolEntry>;
using SamplerMap = std::unordered_map<u64, std::unique_ptr<GPUSampler>>;
#ifdef __APPLE__ #ifdef __APPLE__
// We have to define these in the base class, because they're in Objective C++. // We have to define these in the base class, because they're in Objective C++.
@ -976,11 +979,14 @@ private:
std::unique_ptr<GPUPipeline> m_imgui_pipeline; std::unique_ptr<GPUPipeline> m_imgui_pipeline;
std::unique_ptr<GPUTexture> m_imgui_font_texture; std::unique_ptr<GPUTexture> m_imgui_font_texture;
SamplerMap m_sampler_map;
TexturePool m_texture_pool; TexturePool m_texture_pool;
TexturePool m_target_pool; TexturePool m_target_pool;
size_t m_pool_vram_usage = 0; size_t m_pool_vram_usage = 0;
u32 m_texture_pool_counter = 0; u32 m_texture_pool_counter = 0;
protected: protected:
static Statistics s_stats; static Statistics s_stats;

View File

@ -1604,6 +1604,12 @@ void VulkanDevice::DeferPersistentDescriptorSetDestruction(VkDescriptorSet objec
m_cleanup_objects.emplace_back(GetCurrentFenceCounter(), [this, object]() { FreePersistentDescriptorSet(object); }); m_cleanup_objects.emplace_back(GetCurrentFenceCounter(), [this, object]() { FreePersistentDescriptorSet(object); });
} }
void VulkanDevice::DeferSamplerDestruction(VkSampler object)
{
m_cleanup_objects.emplace_back(GetCurrentFenceCounter(),
[this, object]() { vkDestroySampler(m_device, object, nullptr); });
}
VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT messageType, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
@ -2064,7 +2070,6 @@ void VulkanDevice::DestroyDevice()
m_cleanup_objects.clear(); m_cleanup_objects.clear();
DestroyPersistentDescriptorSets(); DestroyPersistentDescriptorSets();
DestroyBuffers(); DestroyBuffers();
DestroySamplers();
DestroyPersistentDescriptorPool(); DestroyPersistentDescriptorPool();
DestroyPipelineLayouts(); DestroyPipelineLayouts();
@ -2790,15 +2795,15 @@ bool VulkanDevice::CreateNullTexture(Error* error)
Vulkan::SetObjectName(m_device, m_null_texture->GetView(), "Null texture view"); Vulkan::SetObjectName(m_device, m_null_texture->GetView(), "Null texture view");
// Bind null texture and point sampler state to all. // Bind null texture and point sampler state to all.
const VkSampler point_sampler = GetSampler(GPUSampler::GetNearestConfig(), error); GPUSampler* point_sampler = GetSampler(GPUSampler::GetNearestConfig(), error);
if (point_sampler == VK_NULL_HANDLE) if (!point_sampler)
{ {
Error::AddPrefix(error, "Failed to get nearest sampler for init bind: "); Error::AddPrefix(error, "Failed to get nearest sampler for init bind: ");
return false; return false;
} }
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++) for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
m_current_samplers[i] = point_sampler; m_current_samplers[i] = static_cast<VulkanSampler*>(point_sampler)->GetSampler();
return true; return true;
} }
@ -3568,7 +3573,7 @@ void VulkanDevice::SetInitialPipelineState()
void VulkanDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* sampler) void VulkanDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* sampler)
{ {
VulkanTexture* T = static_cast<VulkanTexture*>(texture); VulkanTexture* T = static_cast<VulkanTexture*>(texture);
const VkSampler vsampler = static_cast<VulkanSampler*>(sampler ? sampler : m_nearest_sampler.get())->GetSampler(); const VkSampler vsampler = static_cast<VulkanSampler*>(sampler ? sampler : m_nearest_sampler)->GetSampler();
if (m_current_textures[slot] != T || m_current_samplers[slot] != vsampler) if (m_current_textures[slot] != T || m_current_samplers[slot] != vsampler)
{ {
m_current_textures[slot] = T; m_current_textures[slot] = T;

View File

@ -222,6 +222,7 @@ public:
void DeferPipelineDestruction(VkPipeline object); void DeferPipelineDestruction(VkPipeline object);
void DeferBufferViewDestruction(VkBufferView object); void DeferBufferViewDestruction(VkBufferView object);
void DeferPersistentDescriptorSetDestruction(VkDescriptorSet object); void DeferPersistentDescriptorSetDestruction(VkDescriptorSet object);
void DeferSamplerDestruction(VkSampler object);
// Wait for a fence to be completed. // Wait for a fence to be completed.
// Also invokes callbacks for completion. // Also invokes callbacks for completion.
@ -315,7 +316,6 @@ private:
}; };
using CleanupObjectFunction = void (*)(VulkanDevice& dev, void* obj); using CleanupObjectFunction = void (*)(VulkanDevice& dev, void* obj);
using SamplerMap = std::unordered_map<u64, VkSampler>;
// Helper method to create a Vulkan instance. // Helper method to create a Vulkan instance.
static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils, static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils,
@ -363,8 +363,6 @@ private:
void DestroyPipelineLayouts(); void DestroyPipelineLayouts();
bool CreatePersistentDescriptorSets(); bool CreatePersistentDescriptorSets();
void DestroyPersistentDescriptorSets(); void DestroyPersistentDescriptorSets();
VkSampler GetSampler(const GPUSampler::Config& config, Error* error = nullptr);
void DestroySamplers();
void RenderBlankFrame(VulkanSwapChain* swap_chain); void RenderBlankFrame(VulkanSwapChain* swap_chain);
@ -457,8 +455,6 @@ private:
VkDescriptorSet m_ubo_descriptor_set = VK_NULL_HANDLE; VkDescriptorSet m_ubo_descriptor_set = VK_NULL_HANDLE;
u32 m_uniform_buffer_position = 0; u32 m_uniform_buffer_position = 0;
SamplerMap m_sampler_map;
// Which bindings/state has to be updated before the next draw. // Which bindings/state has to be updated before the next draw.
u32 m_dirty_flags = ALL_DIRTY_STATE; u32 m_dirty_flags = ALL_DIRTY_STATE;

View File

@ -776,7 +776,7 @@ VulkanSampler::VulkanSampler(VkSampler sampler) : m_sampler(sampler)
VulkanSampler::~VulkanSampler() VulkanSampler::~VulkanSampler()
{ {
// Cleaned up by main class. VulkanDevice::GetInstance().DeferSamplerDestruction(m_sampler);
} }
#ifdef ENABLE_GPU_OBJECT_NAMES #ifdef ENABLE_GPU_OBJECT_NAMES
@ -788,12 +788,8 @@ void VulkanSampler::SetDebugName(std::string_view name)
#endif #endif
VkSampler VulkanDevice::GetSampler(const GPUSampler::Config& config, Error* error) std::unique_ptr<GPUSampler> VulkanDevice::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{ {
const auto it = m_sampler_map.find(config.key);
if (it != m_sampler_map.end())
return it->second;
static constexpr std::array<VkSamplerAddressMode, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{ static constexpr std::array<VkSamplerAddressMode, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{
VK_SAMPLER_ADDRESS_MODE_REPEAT, // Repeat VK_SAMPLER_ADDRESS_MODE_REPEAT, // Repeat
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // ClampToEdge VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // ClampToEdge
@ -867,29 +863,10 @@ VkSampler VulkanDevice::GetSampler(const GPUSampler::Config& config, Error* erro
{ {
LOG_VULKAN_ERROR(res, "vkCreateSampler() failed: "); LOG_VULKAN_ERROR(res, "vkCreateSampler() failed: ");
Vulkan::SetErrorObject(error, "vkCreateSampler() failed: ", res); Vulkan::SetErrorObject(error, "vkCreateSampler() failed: ", res);
}
m_sampler_map.emplace(config.key, sampler);
return sampler;
}
void VulkanDevice::DestroySamplers()
{
for (auto& it : m_sampler_map)
{
if (it.second != VK_NULL_HANDLE)
vkDestroySampler(m_device, it.second, nullptr);
}
m_sampler_map.clear();
}
std::unique_ptr<GPUSampler> VulkanDevice::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{
const VkSampler vsampler = GetSampler(config, error);
if (vsampler == VK_NULL_HANDLE)
return {}; return {};
}
return std::unique_ptr<GPUSampler>(new VulkanSampler(vsampler)); return std::unique_ptr<GPUSampler>(new VulkanSampler(sampler));
} }
VulkanTextureBuffer::VulkanTextureBuffer(Format format, u32 size_in_elements) VulkanTextureBuffer::VulkanTextureBuffer(Format format, u32 size_in_elements)