mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
SamplerParameters defaulted compareFunc to ALWAYS, but GL 4.6 core table 23.18 and GLES 3.2 table 21.16 both say the initial value is LEQUAL - for sampler objects and for the sampler state a texture object carries alike. Every freshly created texture and sampler therefore answered GL_ALWAYS to glGetTextureParameteriv(GL_TEXTURE_COMPARE_FUNC). The Vulkan backend had been papering over it: ResolveCompareFunc substituted LESS_EQUAL whenever a depth texture was sampled in compare mode and the func still read ALWAYS, which fixed the rendering but also made an explicitly requested GL_ALWAYS unreachable. With the default corrected that special case is both unnecessary and wrong, so it is gone and the compare op is taken straight from the sampler. Takes direct_state_access.textures_defaults from failing to passing on both backends.
91 lines
4.4 KiB
C++
91 lines
4.4 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#pragma once
|
|
|
|
#include "../VkIncludes.h"
|
|
#include "../VulkanRendererConfig.h"
|
|
#include <Includes.h>
|
|
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
|
|
|
namespace MobileGL::MG_State::GLState {
|
|
class SamplerObject;
|
|
class ITextureObject;
|
|
}
|
|
|
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
class VkSamplerManager {
|
|
public:
|
|
struct InitInfo {
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
const VulkanRendererConfig* config = nullptr;
|
|
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
|
|
Bool samplerAnisotropySupported = false;
|
|
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
|
|
Float maxSamplerAnisotropy = 1.0f;
|
|
};
|
|
|
|
Bool Initialize(const InitInfo& initInfo);
|
|
void Shutdown();
|
|
|
|
// viewLevelCount is the mip-level count of the image view this sampler will be paired
|
|
// with; 0 means "unknown, do not narrow". See GetOrCreateSampler for why it matters.
|
|
VkSampler GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
|
const MG_State::GLState::ITextureObject& texture,
|
|
Bool forceNearestFiltering = false,
|
|
Uint32 viewLevelCount = 0);
|
|
// Frame boundary hook: ages the sampler cache and destroys samplers not used
|
|
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
|
|
// anisotropy), so an app animating those would otherwise mint an unbounded
|
|
// stream of never-destroyed VkSamplers and eventually exhaust the device's
|
|
// maxSamplerAllocationCount. A sampler idle for over a thousand frame
|
|
// boundaries cannot be referenced by any in-flight command buffer (frames in
|
|
// flight are single digits), and every descriptor set the GPU consumes is
|
|
// written that same frame with live handles (the per-binding resolve memo and
|
|
// descriptor-set reuse are both frame-reset), so destruction here needs no
|
|
// fence wait. Self-gated: one counter bump and compare except on sweep
|
|
// boundaries.
|
|
void OnFrameBoundary();
|
|
|
|
private:
|
|
struct SamplerCacheEntry {
|
|
VkSampler handle = VK_NULL_HANDLE;
|
|
Uint externalIndex = 0;
|
|
Uint16 version = 0;
|
|
// Frame boundary of the last cache hit; entries idle past the
|
|
// OnFrameBoundary retirement age have their VkSampler destroyed.
|
|
Uint64 lastUsedFrameBoundary = 0;
|
|
};
|
|
|
|
Uint64 BuildSamplerKey(const MG_State::GLState::SamplerObject& sampler,
|
|
const MG_State::GLState::ITextureObject& texture,
|
|
Bool forceNearestFiltering, Bool singleLevelView) const;
|
|
static VkFilter ToVkFilter(SamplerFilterMode mode);
|
|
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
|
|
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
|
|
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
|
|
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
|
const MG_State::GLState::ITextureObject& texture);
|
|
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
|
|
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
|
|
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
|
|
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
|
|
Float ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler,
|
|
Bool forceNearestFiltering) const;
|
|
|
|
VkDevice m_device = VK_NULL_HANDLE;
|
|
const VulkanRendererConfig* m_config = nullptr;
|
|
Bool m_samplerAnisotropySupported = false;
|
|
Float m_maxSamplerAnisotropy = 1.0f;
|
|
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
|
Uint64 m_frameBoundaryCounter = 0;
|
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
|
};
|
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|