mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (DirectVulkan): fall back to immutable images when the mutable probe fails, gate robustBufferAccess behind MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS, decode R32/RG32/R16-class readback formats, and warn when shaderStorageImage*WithoutFormat is unavailable
This commit is contained in:
@@ -80,6 +80,10 @@ namespace MobileGL::MG_Config {
|
|||||||
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
// rewrites the recognized workgroup prefix-scan template on Qualcomm devices with
|
||||||
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
||||||
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto;
|
||||||
|
// MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS: leave the Vulkan robustBufferAccess device
|
||||||
|
// feature off. It is enabled by default to match GL's defined out-of-range fetch
|
||||||
|
// behavior; this escape hatch exists to measure or dodge its GPU cost on a device.
|
||||||
|
Bool DisableRobustBufferAccess = false;
|
||||||
};
|
};
|
||||||
extern FeaturesTable Features;
|
extern FeaturesTable Features;
|
||||||
} // namespace MobileGL::MG_Config
|
} // namespace MobileGL::MG_Config
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
||||||
|
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void InitBackendType() {
|
inline void InitBackendType() {
|
||||||
|
|||||||
@@ -1341,7 +1341,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
||||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||||
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
|
VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags;
|
||||||
if (supportsStorageImage && IsMutableStorageImageFormat(format)) {
|
if (supportsStorageImage && IsMutableStorageImageFormat(format) &&
|
||||||
|
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
||||||
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1409,9 +1410,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
imageInfo.samples = resolvedSampleCount;
|
imageInfo.samples = resolvedSampleCount;
|
||||||
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
||||||
VkImageFormatProperties imageFormatProperties{};
|
VkImageFormatProperties imageFormatProperties{};
|
||||||
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||||
imageInfo.flags, &imageFormatProperties);
|
imageInfo.flags, &imageFormatProperties);
|
||||||
|
if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture &&
|
||||||
|
(imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
||||||
|
// Losing reinterpreted views only degrades the formatless-image feature for
|
||||||
|
// this texture; failing creation would lose the texture entirely, so retry
|
||||||
|
// as a plain immutable-format image.
|
||||||
|
MGLOG_W("%s: mutable image format=%d is unsupported for textureId=%d; creating "
|
||||||
|
"without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT (format reinterpretation "
|
||||||
|
"will be unavailable for it)",
|
||||||
|
__func__, static_cast<Int>(format), texture.GetExternalIndex());
|
||||||
|
// Remember the verdict so later syncs of same-format textures neither retry
|
||||||
|
// the probe nor flag-mismatch against this image and recreate it.
|
||||||
|
m_mutableFormatUnsupported.insert(format);
|
||||||
|
imageInfo.flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||||
|
imageCreateFlags = imageInfo.flags;
|
||||||
|
imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||||
|
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||||
|
imageInfo.flags, &imageFormatProperties);
|
||||||
|
}
|
||||||
if (imageFormatResult != VK_SUCCESS ||
|
if (imageFormatResult != VK_SUCCESS ||
|
||||||
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
||||||
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s "
|
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s "
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
class ITextureObject;
|
class ITextureObject;
|
||||||
@@ -384,6 +385,9 @@ private:
|
|||||||
TextureResource* resource = nullptr;
|
TextureResource* resource = nullptr;
|
||||||
};
|
};
|
||||||
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
Vector<DrawSyncedTexture> m_drawSyncedThisDraw;
|
||||||
|
// Formats whose mutable-image probe failed on this device; their images are created
|
||||||
|
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
||||||
|
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||||
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
std::unordered_map<TextureIdentity, WeakPtr<MG_State::GLState::ITextureObject>, TextureIdentityHash> m_aliveObjects;
|
||||||
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
std::unordered_map<TextureIdentity, TextureResource, TextureIdentityHash> m_textureResources;
|
||||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||||
|
|||||||
@@ -1622,6 +1622,63 @@ void main() {
|
|||||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||||
Memcpy(rgba, source, sizeof(Float) * 4);
|
Memcpy(rgba, source, sizeof(Float) * 4);
|
||||||
return true;
|
return true;
|
||||||
|
// Single- and dual-channel formats the reinterpretation feature makes common
|
||||||
|
// as readback sources (iterationRP custom images are R32F/R32UI-class).
|
||||||
|
// Missing channels take GL's defaults: 0 for GB, 1 for alpha.
|
||||||
|
case VK_FORMAT_R32_SFLOAT: {
|
||||||
|
Float value = 0.0f;
|
||||||
|
Memcpy(&value, source, sizeof(value));
|
||||||
|
rgba[0] = value;
|
||||||
|
rgba[1] = 0.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case VK_FORMAT_R32G32_SFLOAT: {
|
||||||
|
Float values[2] = {0.0f, 0.0f};
|
||||||
|
Memcpy(values, source, sizeof(values));
|
||||||
|
rgba[0] = values[0];
|
||||||
|
rgba[1] = values[1];
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case VK_FORMAT_R32_UINT: {
|
||||||
|
Uint32 value = 0;
|
||||||
|
Memcpy(&value, source, sizeof(value));
|
||||||
|
rgba[0] = static_cast<Float>(value);
|
||||||
|
rgba[1] = 0.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case VK_FORMAT_R32_SINT: {
|
||||||
|
Int32 value = 0;
|
||||||
|
Memcpy(&value, source, sizeof(value));
|
||||||
|
rgba[0] = static_cast<Float>(value);
|
||||||
|
rgba[1] = 0.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case VK_FORMAT_R16_SFLOAT: {
|
||||||
|
Uint16 value = 0;
|
||||||
|
Memcpy(&value, source, sizeof(value));
|
||||||
|
rgba[0] = MG_Util::DecodeHalfBitsToFloat(value);
|
||||||
|
rgba[1] = 0.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case VK_FORMAT_R16G16_SFLOAT:
|
||||||
|
for (SizeT component = 0; component < 2; ++component) {
|
||||||
|
Uint16 value = 0;
|
||||||
|
Memcpy(&value, source + component * sizeof(value), sizeof(value));
|
||||||
|
rgba[component] = MG_Util::DecodeHalfBitsToFloat(value);
|
||||||
|
}
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -7166,7 +7223,10 @@ void main() {
|
|||||||
// Match GL's robust buffer-fetch behavior where the Vulkan device supports it. This covers
|
// Match GL's robust buffer-fetch behavior where the Vulkan device supports it. This covers
|
||||||
// out-of-range fetches; arbitrary GL vertex strides/offsets still need the explicit tight
|
// out-of-range fetches; arbitrary GL vertex strides/offsets still need the explicit tight
|
||||||
// repack in VertexInputStateFactory when they violate Vulkan's address-alignment rules.
|
// repack in VertexInputStateFactory when they violate Vulkan's address-alignment rules.
|
||||||
deviceFeatures.robustBufferAccess = supportedDeviceFeatures.robustBufferAccess;
|
// MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS leaves it off to measure or dodge its GPU cost.
|
||||||
|
deviceFeatures.robustBufferAccess = MG_Config::Features.DisableRobustBufferAccess
|
||||||
|
? VK_FALSE
|
||||||
|
: supportedDeviceFeatures.robustBufferAccess;
|
||||||
deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader;
|
deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader;
|
||||||
deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend;
|
deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend;
|
||||||
m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE;
|
m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE;
|
||||||
@@ -7196,6 +7256,15 @@ void main() {
|
|||||||
if (m_unformattedFloatStorageImagesEnabled) {
|
if (m_unformattedFloatStorageImagesEnabled) {
|
||||||
deviceFeatures.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
deviceFeatures.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
||||||
deviceFeatures.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
deviceFeatures.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||||
|
} else {
|
||||||
|
// Surface the degradation instead of failing silently: shader packs that bind a
|
||||||
|
// float storage image with a format different from its declaration (e.g.
|
||||||
|
// iterationRP) will render incorrectly on this device.
|
||||||
|
MGLOG_W("CreateLogicalDeviceAndQueues: shaderStorageImage*WithoutFormat unavailable "
|
||||||
|
"(read=%d write=%d); float storage-image format reinterpretation is disabled "
|
||||||
|
"and packs relying on it may misrender",
|
||||||
|
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat,
|
||||||
|
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat);
|
||||||
}
|
}
|
||||||
deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance;
|
deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance;
|
||||||
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
||||||
|
|||||||
@@ -794,6 +794,33 @@ TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
|
|||||||
EXPECT_FLOAT_EQ(rgba16fFloatResult[3], 1.0f);
|
EXPECT_FLOAT_EQ(rgba16fFloatResult[3], 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DirectVulkanSanity, ReadbackDecodesSingleChannel32BitFormats) {
|
||||||
|
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
||||||
|
|
||||||
|
// The reinterpretation feature makes R32F/R32UI-class images common readback sources
|
||||||
|
// (iterationRP custom images). Missing channels take GL defaults: 0 for GB, 1 for alpha.
|
||||||
|
const MobileGL::Float r32f[] = {0.75f, -2.0f};
|
||||||
|
MobileGL::Float r32fResult[8]{};
|
||||||
|
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
|
||||||
|
reinterpret_cast<const MobileGL::Uint8*>(r32f), VK_FORMAT_R32_SFLOAT,
|
||||||
|
2, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 8,
|
||||||
|
reinterpret_cast<MobileGL::Uint8*>(r32fResult)));
|
||||||
|
EXPECT_FLOAT_EQ(r32fResult[0], 0.75f);
|
||||||
|
EXPECT_FLOAT_EQ(r32fResult[1], 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(r32fResult[2], 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(r32fResult[3], 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(r32fResult[4], -2.0f);
|
||||||
|
|
||||||
|
const MobileGL::Uint32 r32ui[] = {12345u};
|
||||||
|
MobileGL::Float r32uiResult[4]{};
|
||||||
|
ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(
|
||||||
|
reinterpret_cast<const MobileGL::Uint8*>(r32ui), VK_FORMAT_R32_UINT,
|
||||||
|
1, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 4,
|
||||||
|
reinterpret_cast<MobileGL::Uint8*>(r32uiResult)));
|
||||||
|
EXPECT_FLOAT_EQ(r32uiResult[0], 12345.0f);
|
||||||
|
EXPECT_FLOAT_EQ(r32uiResult[3], 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) {
|
TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) {
|
||||||
using namespace MobileGL::MG_Backend::DirectVulkan;
|
using namespace MobileGL::MG_Backend::DirectVulkan;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user