mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +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
|
||||
// subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry).
|
||||
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;
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
@@ -135,6 +135,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
|
||||
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
|
||||
}
|
||||
|
||||
inline void InitBackendType() {
|
||||
|
||||
@@ -1341,7 +1341,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1409,9 +1410,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
imageInfo.samples = resolvedSampleCount;
|
||||
if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) {
|
||||
VkImageFormatProperties imageFormatProperties{};
|
||||
const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||
VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties(
|
||||
m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage,
|
||||
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 ||
|
||||
(isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
|
||||
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 <vk_mem_alloc.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class ITextureObject;
|
||||
@@ -384,6 +385,9 @@ private:
|
||||
TextureResource* resource = nullptr;
|
||||
};
|
||||
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, TextureResource, TextureIdentityHash> m_textureResources;
|
||||
Vector<Vector<TextureResource>> m_deferredReleases;
|
||||
|
||||
@@ -1622,6 +1622,63 @@ void main() {
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
Memcpy(rgba, source, sizeof(Float) * 4);
|
||||
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:
|
||||
return false;
|
||||
}
|
||||
@@ -7166,7 +7223,10 @@ void main() {
|
||||
// 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
|
||||
// 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.independentBlend = supportedDeviceFeatures.independentBlend;
|
||||
m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE;
|
||||
@@ -7196,6 +7256,15 @@ void main() {
|
||||
if (m_unformattedFloatStorageImagesEnabled) {
|
||||
deviceFeatures.shaderStorageImageReadWithoutFormat = 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.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect;
|
||||
|
||||
@@ -794,6 +794,33 @@ TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
|
||||
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) {
|
||||
using namespace MobileGL::MG_Backend::DirectVulkan;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user