mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
Merge branch 'dev' of https://github.com/MobileGL-Dev/MobileGL into dev
This commit is contained in:
@@ -232,6 +232,9 @@ namespace MobileGL {
|
|||||||
|
|
||||||
struct DynamicBackendParameters {
|
struct DynamicBackendParameters {
|
||||||
SizeT UniformBufferOffsetAlignment = 256;
|
SizeT UniformBufferOffsetAlignment = 256;
|
||||||
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
|
||||||
|
// which is also why the extension is not advertised in that case.
|
||||||
|
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||||
Float AliasedLineWidthRangeMin = 1.0f;
|
Float AliasedLineWidthRangeMin = 1.0f;
|
||||||
Float AliasedLineWidthRangeMax = 1.0f;
|
Float AliasedLineWidthRangeMax = 1.0f;
|
||||||
Float SmoothLineWidthRangeMin = 1.0f;
|
Float SmoothLineWidthRangeMin = 1.0f;
|
||||||
|
|||||||
@@ -605,9 +605,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
{
|
{
|
||||||
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
||||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||||
// Baseline advertisement (no timer queries yet); reconciled once
|
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
||||||
// the ES capabilities exist, see UpdateAdvertisedTimerQueryExtension.
|
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||||
.Extensions = BuildAdvertisedExtensions(false),
|
.Extensions = BuildAdvertisedExtensions(false, false),
|
||||||
.IsCompatibilityProfile = false // Is Compatibility Profile
|
.IsCompatibilityProfile = false // Is Compatibility Profile
|
||||||
},
|
},
|
||||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
||||||
@@ -627,8 +627,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// thread can only observe the extension string after the
|
// thread can only observe the extension string after the
|
||||||
// advertisement for its context has settled; rebuilding the whole
|
// advertisement for its context has settled; rebuilding the whole
|
||||||
// list keeps the re-run after a context recreation idempotent.
|
// list keeps the re-run after a context recreation idempotent.
|
||||||
void UpdateAdvertisedTimerQueryExtension() {
|
void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) {
|
||||||
MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions(AreTimerQueriesSupported());
|
MutableRendererInfo().RendererGLInfo.Extensions =
|
||||||
|
BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -672,11 +673,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
|
DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
|
||||||
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query,
|
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and
|
||||||
// reconcile the E_GL_ARB_timer_query advertisement (see the comment on
|
// GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on
|
||||||
// UpdateAdvertisedTimerQueryExtension for why it cannot happen when
|
// UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension
|
||||||
// the extension list is first built).
|
// list is first built).
|
||||||
UpdateAdvertisedTimerQueryExtension();
|
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy);
|
||||||
UpdateDynamicBackendParameters();
|
UpdateDynamicBackendParameters();
|
||||||
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
|
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
|
||||||
PrintFormatCapabilities(GetFormatCapabilities());
|
PrintFormatCapabilities(GetFormatCapabilities());
|
||||||
@@ -818,7 +819,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return MutableRendererInfo();
|
return MutableRendererInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported) {
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
|
||||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||||
@@ -836,6 +837,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
||||||
extensions.push_back(E_GL_ARB_timer_query);
|
extensions.push_back(E_GL_ARB_timer_query);
|
||||||
}
|
}
|
||||||
|
// Only advertised when the host ES driver actually filters anisotropically: the sampler
|
||||||
|
// state is accepted regardless, but forwarding it would be a no-op without the extension,
|
||||||
|
// and an app that trusts the string (LWJGL builds GLCapabilities from it) would silently
|
||||||
|
// get plain trilinear.
|
||||||
|
if (anisotropicFilteringSupported) {
|
||||||
|
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
|
||||||
|
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
|
||||||
|
}
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -940,6 +949,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
void BackendObject_DirectGLES::UpdateDynamicBackendParameters() {
|
void BackendObject_DirectGLES::UpdateDynamicBackendParameters() {
|
||||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment;
|
m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment;
|
||||||
|
m_dynamicParameters.MaxTextureMaxAnisotropy = m_GLESCapabilities.MaxTextureMaxAnisotropy;
|
||||||
m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin;
|
m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin;
|
||||||
m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax;
|
m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax;
|
||||||
m_dynamicParameters.SmoothLineWidthRangeMin = m_GLESCapabilities.SmoothLineWidthRangeMin;
|
m_dynamicParameters.SmoothLineWidthRangeMin = m_GLESCapabilities.SmoothLineWidthRangeMin;
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const RendererInfo& GetRendererIdentity();
|
const RendererInfo& GetRendererIdentity();
|
||||||
|
|
||||||
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
||||||
// for a device whose timer queries are (or are not) usable. The
|
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
|
||||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported);
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
|
||||||
|
|
||||||
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
||||||
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
||||||
|
|||||||
@@ -488,14 +488,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.TargetGLSLVersion = {4, 6, 0},
|
.TargetGLSLVersion = {4, 6, 0},
|
||||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||||
.Extensions = BuildAdvertisedExtensions(false, false),
|
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||||
.IsCompatibilityProfile = false
|
.IsCompatibilityProfile = false
|
||||||
},
|
},
|
||||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||||
return rendererInfo;
|
return rendererInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported) {
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||||
|
Bool anisotropicFilteringSupported) {
|
||||||
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
Vector<GLExtension> extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32,
|
||||||
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader,
|
||||||
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||||
@@ -516,6 +517,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
|
||||||
extensions.push_back(E_GL_ARB_timer_query);
|
extensions.push_back(E_GL_ARB_timer_query);
|
||||||
}
|
}
|
||||||
|
// Only advertised when the samplerAnisotropy device feature was granted: without it the
|
||||||
|
// sampler state is accepted but never applied, and an app trusting the string (LWJGL builds
|
||||||
|
// GLCapabilities from it) would think it enabled anisotropic filtering.
|
||||||
|
if (anisotropicFilteringSupported) {
|
||||||
|
extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
|
||||||
|
extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
|
||||||
|
}
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +638,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// run without a renderer; no timer query is advertised then. Rebuilding
|
// run without a renderer; no timer query is advertised then. Rebuilding
|
||||||
// the whole list keeps re-runs idempotent.
|
// the whole list keeps re-runs idempotent.
|
||||||
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||||
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported());
|
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
|
||||||
|
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||||
@@ -680,6 +689,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment;
|
||||||
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
|
m_dynamicParameters.AliasedLineWidthRangeMin = m_vulkanCaps.AliasedLineWidthRangeMin;
|
||||||
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
|
m_dynamicParameters.AliasedLineWidthRangeMax = m_vulkanCaps.AliasedLineWidthRangeMax;
|
||||||
|
// Without the samplerAnisotropy feature the limit is unusable, so report 1.0 (no anisotropy)
|
||||||
|
// rather than a maximum the sampler manager will never apply.
|
||||||
|
m_dynamicParameters.MaxTextureMaxAnisotropy =
|
||||||
|
(pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()) ? m_vulkanCaps.MaxSamplerAnisotropy
|
||||||
|
: 1.0f;
|
||||||
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
m_dynamicParameters.SmoothLineWidthRangeMin = m_vulkanCaps.SmoothLineWidthRangeMin;
|
||||||
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
m_dynamicParameters.SmoothLineWidthRangeMax = m_vulkanCaps.SmoothLineWidthRangeMax;
|
||||||
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
m_dynamicParameters.SmoothLineWidthGranularity = m_vulkanCaps.SmoothLineWidthGranularity;
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
|
// a device with the given raw capabilities. The MOBILEGL_DISABLE_SUBGROUP and
|
||||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
||||||
// the detected device support (passing an already-gated value is harmless).
|
// the detected device support (passing an already-gated value is harmless).
|
||||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported);
|
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||||
|
Bool anisotropicFilteringSupported);
|
||||||
|
|
||||||
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
||||||
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
||||||
|
|||||||
@@ -58,11 +58,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
m_device = initInfo.device;
|
m_device = initInfo.device;
|
||||||
m_config = initInfo.config;
|
m_config = initInfo.config;
|
||||||
|
m_samplerAnisotropySupported = initInfo.samplerAnisotropySupported;
|
||||||
|
m_maxSamplerAnisotropy = std::max(initInfo.maxSamplerAnisotropy, 1.0f);
|
||||||
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
|
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_config != nullptr,
|
||||||
"VkSamplerManager::Initialize failed: invalid initialization info");
|
"VkSamplerManager::Initialize failed: invalid initialization info");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const MG_State::GLState::SamplerObject& sampler) const {
|
||||||
|
if (!m_samplerAnisotropySupported) return 1.0f;
|
||||||
|
// VUID-VkSamplerCreateInfo-anisotropyEnable-01071/01072: anisotropy requires both filters to
|
||||||
|
// be LINEAR and the value to sit within [1, limits.maxSamplerAnisotropy].
|
||||||
|
if (sampler.GetMinFilter() != SamplerFilterMode::Linear ||
|
||||||
|
sampler.GetMagFilter() != SamplerFilterMode::Linear) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
return std::clamp(sampler.GetMaxAnisotropy(), 1.0f, m_maxSamplerAnisotropy);
|
||||||
|
}
|
||||||
|
|
||||||
void VkSamplerManager::Shutdown() {
|
void VkSamplerManager::Shutdown() {
|
||||||
for (auto& [_, sampler] : m_samplers) {
|
for (auto& [_, sampler] : m_samplers) {
|
||||||
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
if (m_device != VK_NULL_HANDLE && sampler.handle != VK_NULL_HANDLE) {
|
||||||
@@ -99,9 +112,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||||
const auto lodBias = sampler.GetLodBias();
|
const auto lodBias = sampler.GetLodBias();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
||||||
// Anisotropy is currently an accepted frontend-only state on DirectVulkan.
|
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan
|
||||||
// Keep it out of the key so changing this no-op does not manufacture duplicate
|
// will not apply (NEAREST filtering, or requests past the device limit) must still share one
|
||||||
// VkSamplers while sampler versioning still exposes the new frontend value.
|
// VkSampler, while two samplers that really do differ must not collide onto the first one's.
|
||||||
|
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler);
|
||||||
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||||
const auto compareMode = sampler.GetCompareMode();
|
const auto compareMode = sampler.GetCompareMode();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||||
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
const auto compareFunc = ResolveCompareFunc(sampler, texture);
|
||||||
@@ -128,10 +143,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
|
||||||
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
|
||||||
samplerInfo.mipLodBias = sampler.GetLodBias();
|
samplerInfo.mipLodBias = sampler.GetLodBias();
|
||||||
// DirectVulkan does not yet plumb samplerAnisotropy feature/limit discovery;
|
// Must use the same resolver as BuildSamplerKey - a divergence would either collide two
|
||||||
// preserve the accepted frontend state without requesting an unsupported feature.
|
// different samplers or silently create duplicates.
|
||||||
samplerInfo.anisotropyEnable = VK_FALSE;
|
const Float maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler);
|
||||||
samplerInfo.maxAnisotropy = 1.0f;
|
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
|
||||||
|
samplerInfo.maxAnisotropy = maxAnisotropy;
|
||||||
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
|
||||||
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
samplerInfo.compareOp = ToVkCompareOp(ResolveCompareFunc(sampler, texture));
|
||||||
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
samplerInfo.maxLod = ResolveEffectiveMaxLod(sampler);
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ public:
|
|||||||
struct InitInfo {
|
struct InitInfo {
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig* config = nullptr;
|
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);
|
Bool Initialize(const InitInfo& initInfo);
|
||||||
@@ -49,9 +53,16 @@ private:
|
|||||||
const MG_State::GLState::ITextureObject& texture);
|
const MG_State::GLState::ITextureObject& texture);
|
||||||
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
static VkBorderColor ResolveVkBorderColor(const MG_State::GLState::SamplerObject& sampler,
|
||||||
const MG_State::GLState::ITextureObject& texture);
|
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) const;
|
||||||
|
|
||||||
VkDevice m_device = VK_NULL_HANDLE;
|
VkDevice m_device = VK_NULL_HANDLE;
|
||||||
const VulkanRendererConfig* m_config = nullptr;
|
const VulkanRendererConfig* m_config = nullptr;
|
||||||
|
Bool m_samplerAnisotropySupported = false;
|
||||||
|
Float m_maxSamplerAnisotropy = 1.0f;
|
||||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1890,7 +1890,8 @@ void main() {
|
|||||||
|
|
||||||
m_samplerManager = MakeUnique<VkSamplerManager>();
|
m_samplerManager = MakeUnique<VkSamplerManager>();
|
||||||
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
|
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
|
||||||
succeeded = m_samplerManager->Initialize({m_device, &m_config});
|
succeeded = m_samplerManager->Initialize({m_device, &m_config, m_samplerAnisotropyFeatureEnabled,
|
||||||
|
m_physicalDevice.properties.limits.maxSamplerAnisotropy});
|
||||||
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
|
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
|
||||||
succeeded = InitializeBlitResources();
|
succeeded = InitializeBlitResources();
|
||||||
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
|
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
|
||||||
@@ -6508,6 +6509,10 @@ void main() {
|
|||||||
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
||||||
m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE;
|
m_multiDrawIndirectFeatureEnabled = deviceFeatures.multiDrawIndirect == VK_TRUE;
|
||||||
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
||||||
|
// Backs GL_TEXTURE_MAX_ANISOTROPY_EXT; optional in Vulkan, so the sampler manager falls back
|
||||||
|
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
|
||||||
|
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
|
||||||
|
m_samplerAnisotropyFeatureEnabled = deviceFeatures.samplerAnisotropy == VK_TRUE;
|
||||||
|
|
||||||
VkDeviceCreateInfo deviceCreateInfo{};
|
VkDeviceCreateInfo deviceCreateInfo{};
|
||||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||||
|
|||||||
@@ -227,6 +227,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// frontend. Timestamp support (queue timestampValidBits > 0 and a
|
// frontend. Timestamp support (queue timestampValidBits > 0 and a
|
||||||
// non-zero timestampPeriod) is cached at device creation.
|
// non-zero timestampPeriod) is cached at device creation.
|
||||||
Bool IsTimerQuerySupported() const;
|
Bool IsTimerQuerySupported() const;
|
||||||
|
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
|
||||||
|
// honored rather than accepted-and-ignored.
|
||||||
|
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
|
||||||
// Ensures the frame command buffer is recording (same lazy pattern as
|
// Ensures the frame command buffer is recording (same lazy pattern as
|
||||||
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
|
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
|
||||||
// frame's pool. Null when unsupported or the pool is exhausted.
|
// frame's pool. Null when unsupported or the pool is exhausted.
|
||||||
@@ -359,6 +362,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool m_indexTypeUint8ExtensionEnabled = false;
|
Bool m_indexTypeUint8ExtensionEnabled = false;
|
||||||
Bool m_logicOpFeatureEnabled = false;
|
Bool m_logicOpFeatureEnabled = false;
|
||||||
Bool m_multiDrawIndirectFeatureEnabled = false;
|
Bool m_multiDrawIndirectFeatureEnabled = false;
|
||||||
|
Bool m_samplerAnisotropyFeatureEnabled = false;
|
||||||
Bool m_shaderDrawParametersExtensionEnabled = false;
|
Bool m_shaderDrawParametersExtensionEnabled = false;
|
||||||
Bool m_shaderDrawParametersFeatureEnabled = false;
|
Bool m_shaderDrawParametersFeatureEnabled = false;
|
||||||
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
|
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
|
||||||
|
|||||||
@@ -529,6 +529,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
params[1] = dynamicParameters.AliasedLineWidthRangeMax;
|
params[1] = dynamicParameters.AliasedLineWidthRangeMax;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: {
|
||||||
|
// EXT_texture_filter_anisotropic queries this as a float; the integer path below widens
|
||||||
|
// from here, so this case is the authoritative one.
|
||||||
|
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
||||||
|
params[0] = dynamicParameters.MaxTextureMaxAnisotropy;
|
||||||
|
return;
|
||||||
|
}
|
||||||
case GL_ALIASED_POINT_SIZE_RANGE:
|
case GL_ALIASED_POINT_SIZE_RANGE:
|
||||||
case GL_POINT_SIZE_RANGE: {
|
case GL_POINT_SIZE_RANGE: {
|
||||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
||||||
@@ -1959,6 +1966,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_SAMPLES:
|
case GL_MAX_SAMPLES:
|
||||||
*params = std::max(dynamicParameters.MaxSamples, kFrontendMaxSamples);
|
*params = std::max(dynamicParameters.MaxSamples, kFrontendMaxSamples);
|
||||||
break;
|
break;
|
||||||
|
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
// Float state (see GetFloatv); rounded to nearest for the integer query per GL 3.3 6.1.2.
|
||||||
|
*params = static_cast<GLint>(std::lround(dynamicParameters.MaxTextureMaxAnisotropy));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname);
|
MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname);
|
||||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <MG_Backend/DirectGLES/BackendObject_DirectGLES.h>
|
||||||
|
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
|
||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
|
|
||||||
// ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver:
|
// ProbeIndirectInstanceIdIncludesBaseInstance is driven against a fake GLES driver:
|
||||||
@@ -31,6 +35,11 @@ namespace {
|
|||||||
GLenum pendingError = GL_NO_ERROR;
|
GLenum pendingError = GL_NO_ERROR;
|
||||||
std::vector<std::string> extensions;
|
std::vector<std::string> extensions;
|
||||||
|
|
||||||
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT the fake reports, and whether it was ever asked:
|
||||||
|
// querying it on a driver without the extension would raise GL_INVALID_ENUM.
|
||||||
|
GLfloat maxTextureMaxAnisotropy = 16.0f;
|
||||||
|
bool maxTextureMaxAnisotropyQueried = false;
|
||||||
|
|
||||||
GLuint nextBufferId = 1;
|
GLuint nextBufferId = 1;
|
||||||
GLuint nextShaderId = 1;
|
GLuint nextShaderId = 1;
|
||||||
GLuint nextProgramId = 1;
|
GLuint nextProgramId = 1;
|
||||||
@@ -122,6 +131,10 @@ namespace {
|
|||||||
};
|
};
|
||||||
funcs.glGetFloatv = [](GLenum pname, GLfloat* data) {
|
funcs.glGetFloatv = [](GLenum pname, GLfloat* data) {
|
||||||
switch (pname) {
|
switch (pname) {
|
||||||
|
case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
|
||||||
|
g_fake.maxTextureMaxAnisotropyQueried = true;
|
||||||
|
data[0] = g_fake.maxTextureMaxAnisotropy;
|
||||||
|
break;
|
||||||
// Two-component range queries.
|
// Two-component range queries.
|
||||||
case GL_ALIASED_LINE_WIDTH_RANGE:
|
case GL_ALIASED_LINE_WIDTH_RANGE:
|
||||||
case GL_SMOOTH_LINE_WIDTH_RANGE:
|
case GL_SMOOTH_LINE_WIDTH_RANGE:
|
||||||
@@ -404,6 +417,51 @@ TEST(IndirectInstanceIdProbe, FillInCapabilitiesWiresProbeResult) {
|
|||||||
ExpectProbeReleasedAllObjects();
|
ExpectProbeReleasedAllObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The extension string is what apps gate on (LWJGL builds GLCapabilities from it), so advertising
|
||||||
|
// it on a driver that cannot filter anisotropically would leave them silently on trilinear.
|
||||||
|
TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSupportsIt) {
|
||||||
|
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
|
||||||
|
MobileGL::GLExtension wanted) {
|
||||||
|
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false);
|
||||||
|
EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||||
|
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||||
|
|
||||||
|
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true);
|
||||||
|
EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||||
|
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||||
|
|
||||||
|
// Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature.
|
||||||
|
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false);
|
||||||
|
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||||
|
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true);
|
||||||
|
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||||
|
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexSsboBlocks = 0;
|
||||||
|
const auto funcs = MakeFakeGLESFunctions();
|
||||||
|
|
||||||
|
MobileGL::MG_External::GLESCapabilities absentCaps;
|
||||||
|
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(absentCaps, funcs));
|
||||||
|
// Never probed (it would be GL_INVALID_ENUM), and reported as "no anisotropy".
|
||||||
|
EXPECT_FALSE(g_fake.maxTextureMaxAnisotropyQueried);
|
||||||
|
EXPECT_FLOAT_EQ(absentCaps.MaxTextureMaxAnisotropy, 1.0f);
|
||||||
|
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexSsboBlocks = 0;
|
||||||
|
g_fake.maxTextureMaxAnisotropy = 16.0f;
|
||||||
|
g_fake.extensions.emplace_back("GL_EXT_texture_filter_anisotropic");
|
||||||
|
MobileGL::MG_External::GLESCapabilities presentCaps;
|
||||||
|
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(presentCaps, funcs));
|
||||||
|
EXPECT_TRUE(g_fake.maxTextureMaxAnisotropyQueried);
|
||||||
|
EXPECT_FLOAT_EQ(presentCaps.MaxTextureMaxAnisotropy, 16.0f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) {
|
TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) {
|
||||||
ResetFakeDriver();
|
ResetFakeDriver();
|
||||||
g_fake.maxVertexSsboBlocks = 0;
|
g_fake.maxVertexSsboBlocks = 0;
|
||||||
|
|||||||
@@ -556,6 +556,81 @@ TEST_F(ProgramUtilTest, CompileSimpleVertexShader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Legacy desktop sources are normalized to "#version 330 core", which is stricter than the 460 they
|
||||||
|
// used to be forced to. A shader declaring 330 while using 420-era syntax without the matching
|
||||||
|
// #extension line is accepted by real drivers, so CompileShader retries it at 460 instead of failing.
|
||||||
|
TEST_F(ProgramUtilTest, CompileShaderRetriesAt460WhenLegacyVersionRejects420Syntax) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
String source = R"(#version 330
|
||||||
|
layout(binding = 0) uniform sampler2D InSampler;
|
||||||
|
in vec2 texCoord;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
fragColor = texture(InSampler, texCoord);
|
||||||
|
})";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
// The normal path still emits 330 - the retry must not become the default.
|
||||||
|
ASSERT_EQ(source.find("#version 330 core"), 0u);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same source compiled for the OpenGL environment must take the retry too.
|
||||||
|
ShaderAttrib glAttrib{
|
||||||
|
.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source, .flags = ShaderCompileBits::CompileForOpenGL};
|
||||||
|
auto glRes = ShaderCompiler::CompileShader(glAttrib);
|
||||||
|
if (!glRes) {
|
||||||
|
FAIL() << "errc: " << glRes.error().errc << "\nlog: " << glRes.error().log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, CompileShaderStillFailsWithOriginalDiagnosticsWhenRetryCannotHelp) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
String source = R"(#version 330
|
||||||
|
in vec2 texCoord;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
fragColor = thisFunctionDoesNotExist(texCoord);
|
||||||
|
})";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
ASSERT_FALSE(res);
|
||||||
|
EXPECT_EQ(res.error().errc, -2);
|
||||||
|
EXPECT_NE(res.error().log.find("thisFunctionDoesNotExist"), String::npos) << res.error().log;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, RetargetLegacyVersionDirectiveOnlyTouchesNormalizedDesktopCore) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String normalized = "#version 330 core\nvoid main() {}\n";
|
||||||
|
EXPECT_TRUE(RetargetLegacyVersionDirectiveTo460(normalized));
|
||||||
|
EXPECT_EQ(normalized.find("#version 460 core"), 0u);
|
||||||
|
|
||||||
|
// Already modern: nothing to retarget.
|
||||||
|
String modern = "#version 460 core\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(modern));
|
||||||
|
EXPECT_EQ(modern.find("#version 460 core"), 0u);
|
||||||
|
|
||||||
|
// ES and compatibility sources keep what they declared.
|
||||||
|
String es = "#version 300 es\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(es));
|
||||||
|
EXPECT_EQ(es.find("#version 300 es"), 0u);
|
||||||
|
|
||||||
|
String compat = "#version 330 compatibility\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(compat));
|
||||||
|
EXPECT_EQ(compat.find("#version 330 compatibility"), 0u);
|
||||||
|
|
||||||
|
// A commented-out directive is not the real one.
|
||||||
|
String commented = "// #version 330 core\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(commented));
|
||||||
|
EXPECT_EQ(commented.find("#version 460"), String::npos);
|
||||||
|
}
|
||||||
|
|
||||||
const char* fs = R"(#version 150
|
const char* fs = R"(#version 150
|
||||||
|
|
||||||
uniform sampler2D InSampler;
|
uniform sampler2D InSampler;
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ namespace {
|
|||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
|
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
|
||||||
|
return MutableDynamicParameters();
|
||||||
|
}
|
||||||
|
// Lets a test stand in a backend limit (e.g. GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT).
|
||||||
|
static MobileGL::MG_Backend::DynamicBackendParameters& MutableDynamicParameters() {
|
||||||
static MobileGL::MG_Backend::DynamicBackendParameters params = {};
|
static MobileGL::MG_Backend::DynamicBackendParameters params = {};
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
@@ -165,6 +169,30 @@ TEST_F(TextureTest, CreateTexturesCreatesObjectsWithoutBinding) {
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT is float state that must answer every numeric query: GetFloatv
|
||||||
|
// is authoritative and GetIntegerv would otherwise fall through to its INVALID_ENUM default.
|
||||||
|
TEST_F(TextureTest, MaxTextureMaxAnisotropyIsAnsweredFromTheBackendLimit) {
|
||||||
|
auto backend = MakeUnique<FormatCapabilityBackend>();
|
||||||
|
FormatCapabilityBackend::MutableDynamicParameters().MaxTextureMaxAnisotropy = 16.0f;
|
||||||
|
ScopedBackendOverride override(Move(backend));
|
||||||
|
|
||||||
|
GLfloat floatValue = 0.0f;
|
||||||
|
MG_Impl::GLImpl::GetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
|
||||||
|
EXPECT_FLOAT_EQ(floatValue, 16.0f);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
GLint integerValue = 0;
|
||||||
|
MG_Impl::GLImpl::GetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &integerValue);
|
||||||
|
EXPECT_EQ(integerValue, 16);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
// A backend without anisotropy reports the no-anisotropy floor rather than erroring.
|
||||||
|
FormatCapabilityBackend::MutableDynamicParameters().MaxTextureMaxAnisotropy = 1.0f;
|
||||||
|
MG_Impl::GLImpl::GetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
|
||||||
|
EXPECT_FLOAT_EQ(floatValue, 1.0f);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TextureTest, TextureMaxAnisotropyDefaultsToOneAndRoundTripsWithoutRedundantVersionBumps) {
|
TEST_F(TextureTest, TextureMaxAnisotropyDefaultsToOneAndRoundTripsWithoutRedundantVersionBumps) {
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
MG_Impl::GLImpl::GenTextures(1, &texture);
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
|||||||
@@ -911,6 +911,13 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
|
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
|
||||||
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
|
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
|
||||||
|
// Only legal to query once the extension has been seen in the loop above, hence not batched
|
||||||
|
// with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM.
|
||||||
|
if (caps.SupportsTextureFilterAnisotropy) {
|
||||||
|
GLfloat maxTextureMaxAnisotropy = 1.0f;
|
||||||
|
glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy);
|
||||||
|
caps.MaxTextureMaxAnisotropy = std::max(maxTextureMaxAnisotropy, 1.0f);
|
||||||
|
}
|
||||||
caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0];
|
caps.AliasedLineWidthRangeMin = aliasedLineWidthRange[0];
|
||||||
caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1];
|
caps.AliasedLineWidthRangeMax = aliasedLineWidthRange[1];
|
||||||
caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0];
|
caps.SmoothLineWidthRangeMin = smoothLineWidthRange[0];
|
||||||
|
|||||||
@@ -1034,6 +1034,9 @@ namespace MobileGL {
|
|||||||
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
|
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
|
||||||
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
|
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
|
||||||
Bool SupportsTextureFilterAnisotropy = false;
|
Bool SupportsTextureFilterAnisotropy = false;
|
||||||
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT of the host driver; only queried when the
|
||||||
|
// extension above is present, and left at 1.0 (no anisotropy) otherwise.
|
||||||
|
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||||
Bool SupportsBaseInstance = false;
|
Bool SupportsBaseInstance = false;
|
||||||
// GL_EXT_disjoint_timer_query is present in the extension string.
|
// GL_EXT_disjoint_timer_query is present in the extension string.
|
||||||
Bool SupportsDisjointTimerQuery = false;
|
Bool SupportsDisjointTimerQuery = false;
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment);
|
caps.UniformBufferOffsetAlignment = static_cast<int>(p.limits.minUniformBufferOffsetAlignment);
|
||||||
caps.AliasedLineWidthRangeMin = p.limits.lineWidthRange[0];
|
caps.AliasedLineWidthRangeMin = p.limits.lineWidthRange[0];
|
||||||
caps.AliasedLineWidthRangeMax = p.limits.lineWidthRange[1];
|
caps.AliasedLineWidthRangeMax = p.limits.lineWidthRange[1];
|
||||||
|
caps.MaxSamplerAnisotropy = p.limits.maxSamplerAnisotropy;
|
||||||
caps.SmoothLineWidthRangeMin = p.limits.lineWidthRange[0];
|
caps.SmoothLineWidthRangeMin = p.limits.lineWidthRange[0];
|
||||||
caps.SmoothLineWidthRangeMax = p.limits.lineWidthRange[1];
|
caps.SmoothLineWidthRangeMax = p.limits.lineWidthRange[1];
|
||||||
caps.SmoothLineWidthGranularity = p.limits.lineWidthGranularity;
|
caps.SmoothLineWidthGranularity = p.limits.lineWidthGranularity;
|
||||||
@@ -208,6 +209,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment);
|
caps.UniformBufferOffsetAlignment = static_cast<int>(properties.limits.minUniformBufferOffsetAlignment);
|
||||||
caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0];
|
caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0];
|
||||||
caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1];
|
caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1];
|
||||||
|
caps.MaxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
|
||||||
caps.SmoothLineWidthRangeMin = properties.limits.lineWidthRange[0];
|
caps.SmoothLineWidthRangeMin = properties.limits.lineWidthRange[0];
|
||||||
caps.SmoothLineWidthRangeMax = properties.limits.lineWidthRange[1];
|
caps.SmoothLineWidthRangeMax = properties.limits.lineWidthRange[1];
|
||||||
caps.SmoothLineWidthGranularity = properties.limits.lineWidthGranularity;
|
caps.SmoothLineWidthGranularity = properties.limits.lineWidthGranularity;
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ namespace MobileGL {
|
|||||||
Int UniformBufferOffsetAlignment = 256;
|
Int UniformBufferOffsetAlignment = 256;
|
||||||
Float AliasedLineWidthRangeMin = 1.0f;
|
Float AliasedLineWidthRangeMin = 1.0f;
|
||||||
Float AliasedLineWidthRangeMax = 1.0f;
|
Float AliasedLineWidthRangeMax = 1.0f;
|
||||||
|
// VkPhysicalDeviceLimits::maxSamplerAnisotropy. Whether it can be used at all depends on
|
||||||
|
// the samplerAnisotropy feature, which the renderer decides at device creation.
|
||||||
|
Float MaxSamplerAnisotropy = 1.0f;
|
||||||
Float SmoothLineWidthRangeMin = 1.0f;
|
Float SmoothLineWidthRangeMin = 1.0f;
|
||||||
Float SmoothLineWidthRangeMax = 1.0f;
|
Float SmoothLineWidthRangeMax = 1.0f;
|
||||||
Float SmoothLineWidthGranularity = 1.0f;
|
Float SmoothLineWidthGranularity = 1.0f;
|
||||||
|
|||||||
@@ -548,8 +548,8 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
if (summary.capsValid) {
|
if (summary.capsValid) {
|
||||||
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
|
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
|
||||||
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
|
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
|
||||||
advertisedExtensions = JoinAdvertisedExtensions(
|
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions(
|
||||||
MG_Backend::DirectGLES::BuildAdvertisedExtensions(summary.caps.SupportsDisjointTimerQuery));
|
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy));
|
||||||
}
|
}
|
||||||
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
|
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
|
||||||
advertisedExtensions);
|
advertisedExtensions);
|
||||||
@@ -841,6 +841,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
String driverVersionString; // raw hex, vendor-encoded (see RunVulkanDriverPost)
|
String driverVersionString; // raw hex, vendor-encoded (see RunVulkanDriverPost)
|
||||||
Bool shaderSubgroupUsable = false;
|
Bool shaderSubgroupUsable = false;
|
||||||
Bool timerQueriesSupported = false;
|
Bool timerQueriesSupported = false;
|
||||||
|
Bool samplerAnisotropySupported = false;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -1107,6 +1108,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
|
|
||||||
VkPhysicalDeviceFeatures features{};
|
VkPhysicalDeviceFeatures features{};
|
||||||
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
|
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
|
||||||
|
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
|
||||||
if (features.multiDrawIndirect == VK_TRUE) {
|
if (features.multiDrawIndirect == VK_TRUE) {
|
||||||
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
|
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
|
||||||
} else {
|
} else {
|
||||||
@@ -1279,7 +1281,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
|
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
|
||||||
summary.deviceName, summary.apiVersionString, summary.driverVersionString);
|
summary.deviceName, summary.apiVersionString, summary.driverVersionString);
|
||||||
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
|
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
|
||||||
summary.shaderSubgroupUsable, summary.timerQueriesSupported));
|
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported));
|
||||||
}
|
}
|
||||||
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
|
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
|
||||||
advertisedExtensions);
|
advertisedExtensions);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "spirv-tools/libspirv.h"
|
#include "spirv-tools/libspirv.h"
|
||||||
#include "spirv-tools/optimizer.hpp"
|
#include "spirv-tools/optimizer.hpp"
|
||||||
|
|
||||||
|
#include "ShaderSourceProcessor.h"
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/GLToGlslang/ProgramEnumConverter.h>
|
#include <MG_Util/Converters/GLToGlslang/ProgramEnumConverter.h>
|
||||||
|
|
||||||
@@ -133,27 +134,23 @@ namespace MobileGL {
|
|||||||
return Resources;
|
return Resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<SharedPtr<glslang::TShader>> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) {
|
// One parse attempt. A glslang::TShader cannot be re-parsed, so a retry has to build a
|
||||||
auto shaderType = attrib.shaderType;
|
// fresh one with byte-identical setup - hence a single factored body rather than two
|
||||||
auto& sourceStr = attrib.sourceStr;
|
// copies that could drift apart.
|
||||||
|
static Result<SharedPtr<glslang::TShader>> ParseShaderSource(EShLanguage lang, GLenum shaderType,
|
||||||
auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType);
|
const String& source,
|
||||||
if (lang == EShLanguage::EShLangCount) {
|
Flags<ShaderCompileBits> flags) {
|
||||||
ResultInfo r;
|
|
||||||
r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType);
|
|
||||||
r.errc = -1;
|
|
||||||
return std::unexpected(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPtr<glslang::TShader> res;
|
SharedPtr<glslang::TShader> res;
|
||||||
auto& tshader = res;
|
auto& tshader = res;
|
||||||
tshader = MakeShared<glslang::TShader>(lang);
|
tshader = MakeShared<glslang::TShader>(lang);
|
||||||
const char* src[] = {sourceStr.data()};
|
// setStrings gets no length array, so it relies on NUL termination: source must be an
|
||||||
|
// owning buffer that outlives parse(), never a StringView's substring.
|
||||||
|
const char* src[] = {source.c_str()};
|
||||||
tshader->setStrings(src, 1);
|
tshader->setStrings(src, 1);
|
||||||
tshader->setNanMinMaxClamp(true);
|
tshader->setNanMinMaxClamp(true);
|
||||||
tshader->setInvertY(true);
|
tshader->setInvertY(true);
|
||||||
tshader->setPreamble("#undef VULKAN\n");
|
tshader->setPreamble("#undef VULKAN\n");
|
||||||
if (attrib.flags & ShaderCompileBits::CompileForOpenGL) {
|
if (flags & ShaderCompileBits::CompileForOpenGL) {
|
||||||
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
|
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
|
||||||
tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450);
|
tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450);
|
||||||
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
|
||||||
@@ -182,6 +179,39 @@ namespace MobileGL {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<SharedPtr<glslang::TShader>> ShaderCompiler::CompileShader(const ShaderAttrib& attrib) {
|
||||||
|
auto shaderType = attrib.shaderType;
|
||||||
|
|
||||||
|
auto lang = MG_Util::ConvertGLEnumToEShLanguage(shaderType);
|
||||||
|
if (lang == EShLanguage::EShLangCount) {
|
||||||
|
ResultInfo r;
|
||||||
|
r.log += "Error: [Preprocess] Unsupported shader type: " + ConvertGLEnumToString(shaderType);
|
||||||
|
r.errc = -1;
|
||||||
|
return std::unexpected(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String source(attrib.sourceStr);
|
||||||
|
auto result = ParseShaderSource(lang, shaderType, source, attrib.flags);
|
||||||
|
if (result) return result;
|
||||||
|
|
||||||
|
// Legacy desktop sources are normalized to "#version 330 core", which parses under
|
||||||
|
// stricter rules than the 460 they used to be forced to: a shader declaring 330 while
|
||||||
|
// using e.g. layout(binding=...) without the matching #extension line compiles on real
|
||||||
|
// drivers but is rejected here. Retry once at 460 before reporting failure; a genuinely
|
||||||
|
// broken shader fails both attempts and keeps its original diagnostics.
|
||||||
|
String retrySource = source;
|
||||||
|
if (!MG_Util::ShaderTranspiler::RetargetLegacyVersionDirectiveTo460(retrySource)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto retryResult = ParseShaderSource(lang, shaderType, retrySource, attrib.flags);
|
||||||
|
if (!retryResult) return result;
|
||||||
|
|
||||||
|
MGLOG_D("CompileShader: %s only parsed after retargeting its legacy #version to 460",
|
||||||
|
ConvertGLEnumToString(shaderType).c_str());
|
||||||
|
return retryResult;
|
||||||
|
}
|
||||||
|
|
||||||
Result<SharedPtr<glslang::TProgram>> ShaderCompiler::LinkProgram(const ProgramAttrib& attrib) {
|
Result<SharedPtr<glslang::TProgram>> ShaderCompiler::LinkProgram(const ProgramAttrib& attrib) {
|
||||||
SharedPtr<glslang::TProgram> program = MakeShared<glslang::TProgram>();
|
SharedPtr<glslang::TProgram> program = MakeShared<glslang::TProgram>();
|
||||||
for (auto& s : attrib.shaders) {
|
for (auto& s : attrib.shaders) {
|
||||||
|
|||||||
@@ -633,6 +633,21 @@ namespace MobileGL {
|
|||||||
InjectDepthRangeBuiltinShim(stage, source);
|
InjectDepthRangeBuiltinShim(stage, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool RetargetLegacyVersionDirectiveTo460(String& source) {
|
||||||
|
// Re-inspect rather than searching for the literal directive: it is not necessarily at
|
||||||
|
// offset 0 (a BOM or comments may precede it) and a commented-out "#version" elsewhere
|
||||||
|
// must not be mistaken for the real one.
|
||||||
|
const ShaderLanguageInfo info = InspectShaderLanguage(source);
|
||||||
|
if (!info.HasVersionDirective()) return false;
|
||||||
|
// Only the set NormalizeVersionDirective downgraded: desktop core below 400. ES and
|
||||||
|
// compatibility shaders keep whatever they declared.
|
||||||
|
if (info.profile != ShaderProfile::Core || info.version >= 400) return false;
|
||||||
|
|
||||||
|
source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart,
|
||||||
|
"#version 460 core\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ShaderTranspiler
|
} // namespace ShaderTranspiler
|
||||||
} // namespace MG_Util
|
} // namespace MG_Util
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ namespace MobileGL {
|
|||||||
namespace MG_Util {
|
namespace MG_Util {
|
||||||
namespace ShaderTranspiler {
|
namespace ShaderTranspiler {
|
||||||
void PreprocessShaderSource(ShaderStage stage, String& source);
|
void PreprocessShaderSource(ShaderStage stage, String& source);
|
||||||
|
|
||||||
|
// Rewrites a "#version 330 core" directive that PreprocessShaderSource normalized down
|
||||||
|
// from a legacy desktop version back up to "#version 460 core". Returns false (leaving
|
||||||
|
// the source untouched) for anything else: ES, compatibility, or an already-modern
|
||||||
|
// declaration. Exists so a shader that only parses under the laxer 460 rules - e.g. it
|
||||||
|
// uses 420-era syntax without the matching #extension line, which real drivers tend to
|
||||||
|
// accept - can be retried instead of failing to compile.
|
||||||
|
Bool RetargetLegacyVersionDirectiveTo460(String& source);
|
||||||
} // namespace ShaderTranspiler
|
} // namespace ShaderTranspiler
|
||||||
} // namespace MG_Util
|
} // namespace MG_Util
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
Reference in New Issue
Block a user