Implement "rg11b10ufloat-renderable" feature

Implement "rg11b10ufloat-renderable" feature that allows
the RENDER_ATTACHMENT usage on textures with format
"rg11b10ufloat", and also allows textures of that format
to be multisampled.

Bug: dawn:1518
Change-Id: I4109dc0e9d90f4c0803219292edea554927a187a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/102000
Commit-Queue: Takahiro <hogehoge@gachapin.jp>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index 2f0fcb3..d8f8a34 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1386,6 +1386,7 @@
             {"value": 7, "name": "texture compression ASTC"},
             {"value": 8, "name": "indirect first instance"},
             {"value": 9, "name": "shader f16"},
+            {"value": 10, "name": "RG11B10 ufloat renderable"},
             {"value": 1001, "name": "dawn shader float 16", "tags": ["dawn"]},
             {"value": 1002, "name": "dawn internal usages", "tags": ["dawn"]},
             {"value": 1003, "name": "dawn multi planar formats", "tags": ["dawn"]},
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 4589f1c..c7d00b7 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -68,6 +68,11 @@
      {"shader-f16", "Supports the \"enable f16;\" directive in WGSL",
       "https://bugs.chromium.org/p/dawn/issues/detail?id=1510",
       FeatureInfo::FeatureState::Experimental}},
+    {Feature::RG11B10UfloatRenderable,
+     {"rg11b10ufloat-renderable",
+      "Allows the RENDER_ATTACHMENT usage on textures with format \"rg11b10ufloat\", and also "
+      "allows textures of that format to be multisampled.",
+      "https://bugs.chromium.org/p/dawn/issues/detail?id=1518", FeatureInfo::FeatureState::Stable}},
     {Feature::DawnInternalUsages,
      {"dawn-internal-usages",
       "Add internal usages to resources to affect how the texture is allocated, but not "
@@ -119,6 +124,8 @@
             return Feature::ChromiumExperimentalDp4a;
         case wgpu::FeatureName::ShaderF16:
             return Feature::ShaderF16;
+        case wgpu::FeatureName::RG11B10UfloatRenderable:
+            return Feature::RG11B10UfloatRenderable;
     }
     return Feature::InvalidEnum;
 }
@@ -151,6 +158,8 @@
             return wgpu::FeatureName::ChromiumExperimentalDp4a;
         case Feature::ShaderF16:
             return wgpu::FeatureName::ShaderF16;
+        case Feature::RG11B10UfloatRenderable:
+            return wgpu::FeatureName::RG11B10UfloatRenderable;
 
         case Feature::EnumCount:
             break;
diff --git a/src/dawn/native/Features.h b/src/dawn/native/Features.h
index 9413da6..4bcb93a 100644
--- a/src/dawn/native/Features.h
+++ b/src/dawn/native/Features.h
@@ -37,6 +37,7 @@
     ChromiumExperimentalDp4a,
     IndirectFirstInstance,
     ShaderF16,
+    RG11B10UfloatRenderable,
 
     // Dawn-specific
     DawnInternalUsages,
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index ef9a7d2..fdfe477 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -363,7 +363,8 @@
         AddColorFormat(wgpu::TextureFormat::BGRA8UnormSrgb, true, false, true, true, 4, kAnyFloat, 4, wgpu::TextureFormat::BGRA8Unorm);
         AddColorFormat(wgpu::TextureFormat::RGB10A2Unorm, true, false, true, true, 4, kAnyFloat, 4);
 
-        AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, false, false, false, false, 4, kAnyFloat, 3);
+        bool isRG11B10UfloatRenderable = device->HasFeature(Feature::RG11B10UfloatRenderable);
+        AddColorFormat(wgpu::TextureFormat::RG11B10Ufloat, isRG11B10UfloatRenderable, false, isRG11B10UfloatRenderable, false, 4, kAnyFloat, 3);
         AddColorFormat(wgpu::TextureFormat::RGB9E5Ufloat, false, false, false, false, 4, kAnyFloat, 3);
 
         // 8 bytes color formats
diff --git a/src/dawn/native/d3d12/AdapterD3D12.cpp b/src/dawn/native/d3d12/AdapterD3D12.cpp
index bcd40fc..842e49e 100644
--- a/src/dawn/native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn/native/d3d12/AdapterD3D12.cpp
@@ -137,6 +137,7 @@
     mSupportedFeatures.EnableFeature(Feature::MultiPlanarFormats);
     mSupportedFeatures.EnableFeature(Feature::Depth32FloatStencil8);
     mSupportedFeatures.EnableFeature(Feature::IndirectFirstInstance);
+    mSupportedFeatures.EnableFeature(Feature::RG11B10UfloatRenderable);
 
     if (GetBackend()->GetFunctions()->IsDXCAvailable()) {
         uint64_t dxcVersion = 0;
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 9d56480..76dbce8 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -370,8 +370,8 @@
         }
 
         mSupportedFeatures.EnableFeature(Feature::IndirectFirstInstance);
-
         mSupportedFeatures.EnableFeature(Feature::ShaderF16);
+        mSupportedFeatures.EnableFeature(Feature::RG11B10UfloatRenderable);
 
         return {};
     }
diff --git a/src/dawn/native/vulkan/AdapterVk.cpp b/src/dawn/native/vulkan/AdapterVk.cpp
index ab0342b..883f9d3 100644
--- a/src/dawn/native/vulkan/AdapterVk.cpp
+++ b/src/dawn/native/vulkan/AdapterVk.cpp
@@ -181,6 +181,16 @@
         mSupportedFeatures.EnableFeature(Feature::DepthClipControl);
     }
 
+    VkFormatProperties properties;
+    mVulkanInstance->GetFunctions().GetPhysicalDeviceFormatProperties(
+        mPhysicalDevice, VK_FORMAT_B10G11R11_UFLOAT_PACK32, &properties);
+
+    if (IsSubset(static_cast<VkFormatFeatureFlags>(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
+                                                   VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT),
+                 properties.optimalTilingFeatures)) {
+        mSupportedFeatures.EnableFeature(Feature::RG11B10UfloatRenderable);
+    }
+
 #if defined(DAWN_USE_SYNC_FDS)
     // TODO(chromium:1258986): Precisely enable the feature by querying the device's format
     // features.
diff --git a/src/dawn/tests/end2end/TextureFormatTests.cpp b/src/dawn/tests/end2end/TextureFormatTests.cpp
index c0e9a31..34c841d 100644
--- a/src/dawn/tests/end2end/TextureFormatTests.cpp
+++ b/src/dawn/tests/end2end/TextureFormatTests.cpp
@@ -111,6 +111,83 @@
     std::vector<uint16_t> mExpected;
 };
 
+// An expectation for RG11B10Ufloat buffer content that can correctly compare different NaN values
+class ExpectRG11B10Ufloat : public detail::Expectation {
+  public:
+    explicit ExpectRG11B10Ufloat(std::vector<uint32_t> expected) : mExpected(std::move(expected)) {}
+
+    testing::AssertionResult Check(const void* data, size_t size) override {
+        ASSERT(size == sizeof(uint32_t) * mExpected.size());
+
+        const uint32_t* actual = static_cast<const uint32_t*>(data);
+
+        for (size_t i = 0; i < mExpected.size(); ++i) {
+            uint32_t expectedValue = mExpected[i];
+            uint32_t actualValue = actual[i];
+
+            if (!RG11B10UfloatMatch(expectedValue, actualValue)) {
+                testing::AssertionResult result = testing::AssertionFailure()
+                                                  << "Expected data[" << i << "] to be "
+                                                  << expectedValue << ", actual " << actualValue
+                                                  << std::endl;
+                return result;
+            }
+        }
+        return testing::AssertionSuccess();
+    }
+
+  private:
+    bool RG11B10UfloatMatch(uint32_t expected, uint32_t actual) {
+        const uint32_t expectedR = expected & 0x7FF;
+        const uint32_t expectedG = (expected >> 11) & 0x7FF;
+        const uint32_t expectedB = (expected >> 22) & 0x3FF;
+
+        const uint32_t actualR = actual & 0x7FF;
+        const uint32_t actualG = (actual >> 11) & 0x7FF;
+        const uint32_t actualB = (actual >> 22) & 0x3FF;
+
+        return Float11Match(expectedR, actualR) && Float11Match(expectedG, actualG) &&
+               Float10Match(expectedB, actualB);
+    }
+
+    bool Float11Match(uint32_t expected, uint32_t actual) {
+        ASSERT((expected & ~0x7FF) == 0);
+        ASSERT((actual & ~0x7FF) == 0);
+
+        if (IsFloat11NaN(expected)) {
+            return IsFloat11NaN(actual);
+        }
+
+        return expected == actual;
+    }
+
+    bool Float10Match(uint32_t expected, uint32_t actual) {
+        ASSERT((expected & ~0x3FF) == 0);
+        ASSERT((actual & ~0x3FF) == 0);
+
+        if (IsFloat10NaN(expected)) {
+            return IsFloat10NaN(actual);
+        }
+
+        return expected == actual;
+    }
+
+    // The number is NaN if exponent bits are all 1 and mantissa is non-zero
+    bool IsFloat11NaN(uint32_t value) {
+        ASSERT((value & ~0x7FF) == 0);
+
+        return ((value & 0x7C0) == 0x7C0) && ((value & 0x3F) != 0);
+    }
+
+    bool IsFloat10NaN(uint32_t value) {
+        ASSERT((value & ~0x3FF) == 0);
+
+        return ((value & 0x3E0) == 0x3E0) && ((value & 0x1F) != 0);
+    }
+
+    std::vector<uint32_t> mExpected;
+};
+
 class TextureFormatTest : public DawnTest {
   protected:
     // Structure containing all the information that tests need to know about the format.
@@ -436,6 +513,22 @@
         DoFormatRenderingTest(formatInfo, uncompressedData, textureData,
                               new ExpectFloat16(textureData));
     }
+
+    // For "rg11b10ufloat-renderable" feature test
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        if (SupportsFeatures({wgpu::FeatureName::RG11B10UfloatRenderable})) {
+            mIsRG11B10UfloatRenderableSupported = true;
+            return {wgpu::FeatureName::RG11B10UfloatRenderable};
+        } else {
+            mIsRG11B10UfloatRenderableSupported = false;
+            return {};
+        }
+    }
+
+    bool IsRG11B10UfloatRenderableSupported() { return mIsRG11B10UfloatRenderableSupported; }
+
+  private:
+    bool mIsRG11B10UfloatRenderableSupported = false;
 };
 
 // Test the R8Unorm format
@@ -740,7 +833,20 @@
     DoFloatFormatSamplingTest(
         {wgpu::TextureFormat::RG11B10Ufloat, 4, wgpu::TextureComponentType::Float, 4}, textureData,
         uncompressedData);
-    // This format is not renderable.
+
+    // This format is renderable if "rg11b10ufloat-renderable" feature is enabled
+    if (IsRG11B10UfloatRenderableSupported()) {
+        // TODO(https://crbug.com/swiftshader/147) Rendering INFINITY and NaN isn't handled
+        // correctly by swiftshader
+        if ((IsVulkan() && IsSwiftshader()) || IsANGLE()) {
+            dawn::WarningLog() << "Skip Rendering test because Swiftshader doesn't render INFINITY "
+                                  "and NaN correctly for RG11B10Ufloat texture format.";
+        } else {
+            DoFormatRenderingTest(
+                {wgpu::TextureFormat::RG11B10Ufloat, 4, wgpu::TextureComponentType::Float, 4},
+                uncompressedData, textureData, new ExpectRG11B10Ufloat(textureData));
+        }
+    }
 }
 
 // Test the RGB9E5Ufloat format
diff --git a/src/dawn/tests/unittests/validation/TextureValidationTests.cpp b/src/dawn/tests/unittests/validation/TextureValidationTests.cpp
index d40fce4..9708ef4 100644
--- a/src/dawn/tests/unittests/validation/TextureValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/TextureValidationTests.cpp
@@ -885,6 +885,29 @@
     }
 }
 
+class RG11B10UfloatTextureFormatsValidationTests : public TextureValidationTest {
+  protected:
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override {
+        wgpu::DeviceDescriptor descriptor;
+        wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::RG11B10UfloatRenderable};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeaturesCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+};
+
+// Test that RG11B10Ufloat format is valid as render attachment and also it allows
+// multisampling if "rg11b10ufloat-renderable" feature is enabled
+TEST_F(RG11B10UfloatTextureFormatsValidationTests, RenderableFeature) {
+    wgpu::TextureDescriptor descriptor;
+    descriptor.size = {1, 1, 1};
+    descriptor.usage = wgpu::TextureUsage::RenderAttachment;
+
+    descriptor.format = wgpu::TextureFormat::RG11B10Ufloat;
+    descriptor.sampleCount = 4;
+    device.CreateTexture(&descriptor);
+}
+
 static void CheckTextureMatchesDescriptor(const wgpu::Texture& tex,
                                           const wgpu::TextureDescriptor& desc) {
     EXPECT_EQ(desc.size.width, tex.GetWidth());
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 6358405..59ab14f 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -37,6 +37,7 @@
         case WGPUFeatureName_DawnMultiPlanarFormats:
         case WGPUFeatureName_ChromiumExperimentalDp4a:
         case WGPUFeatureName_ShaderF16:
+        case WGPUFeatureName_RG11B10UfloatRenderable:
             return true;
     }