Support sampling the stencil component

This uses an internal R8Uint texture, which holds the copy of stencil
data, for sampling the stencil component.

Bug: dawn:1827
Bug: dawn:1705

Change-Id: I0fb806e620bccab92b7c75841e7a3fc48d21b161
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/142349
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Peng Huang <penghuang@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
diff --git a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
index 879e5ae..7181789 100644
--- a/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
+++ b/src/dawn/native/d3d11/BindGroupTrackerD3D11.cpp
@@ -351,7 +351,13 @@
             case BindingInfoType::Texture: {
                 TextureView* view = ToBackend(group->GetBindingAsTextureView(bindingIndex));
                 ComPtr<ID3D11ShaderResourceView> srv;
-                DAWN_TRY_ASSIGN(srv, view->CreateD3D11ShaderResourceView());
+                // For sampling from stencil, we have to use an internal mirror 'R8Uint' texture.
+                if (view->GetAspects() == Aspect::Stencil) {
+                    DAWN_TRY_ASSIGN(
+                        srv, ToBackend(view->GetTexture())->GetStencilSRV(mCommandContext, view));
+                } else {
+                    DAWN_TRY_ASSIGN(srv, view->CreateD3D11ShaderResourceView());
+                }
                 if (bindingVisibility & wgpu::ShaderStage::Vertex) {
                     deviceContext1->VSSetShaderResources(bindingSlot, 1, srv.GetAddressOf());
                 }
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index 72c3eff..d43b82d 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -1059,6 +1059,81 @@
     return GetDevice()->GetLastSubmittedCommandSerial();
 }
 
+ResultOrError<ComPtr<ID3D11ShaderResourceView>> Texture::GetStencilSRV(
+    CommandRecordingContext* commandContext,
+    const TextureView* view) {
+    ASSERT(GetFormat().HasStencil());
+
+    if (!mTextureForStencilSampling.Get()) {
+        // Create an interim texture of R8Uint format.
+        TextureDescriptor desc = {};
+        desc.label = "InterimStencilTexture";
+        desc.dimension = GetDimension();
+        desc.size = GetSize();
+        desc.format = wgpu::TextureFormat::R8Uint;
+        desc.mipLevelCount = GetNumMipLevels();
+        desc.sampleCount = GetSampleCount();
+        desc.usage = wgpu::TextureUsage::TextureBinding;
+
+        DAWN_TRY_ASSIGN(mTextureForStencilSampling,
+                        CreateInternal(ToBackend(GetDevice()), &desc, Kind::Interim));
+    }
+
+    // Sync the stencil data of this texture to the interim stencil-view texture.
+    // TODO(dawn:1705): Improve to only sync as few as possible.
+    const auto range = view->GetSubresourceRange();
+    const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(range.aspects).block;
+    Extent3D size = GetMipLevelSubresourceVirtualSize(range.baseMipLevel);
+    uint32_t bytesPerRow = blockInfo.byteSize * size.width;
+    uint32_t rowsPerImage = size.height;
+    uint64_t byteLength;
+    DAWN_TRY_ASSIGN(byteLength,
+                    ComputeRequiredBytesInCopy(blockInfo, size, bytesPerRow, rowsPerImage));
+
+    std::vector<uint8_t> stagingData(byteLength);
+    for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount;
+         ++layer) {
+        for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
+             ++level) {
+            size = GetMipLevelSubresourceVirtualSize(level);
+            bytesPerRow = blockInfo.byteSize * size.width;
+            rowsPerImage = size.height;
+            auto singleRange = SubresourceRange::MakeSingle(range.aspects, layer, level);
+
+            Texture::ReadCallback callback = [&](const uint8_t* data, uint64_t offset,
+                                                 uint64_t length) -> MaybeError {
+                std::memcpy(static_cast<uint8_t*>(stagingData.data()) + offset, data, length);
+                return {};
+            };
+
+            // TODO(dawn:1705): Work out a way of GPU-GPU copy, rather than the CPU-GPU round trip.
+            commandContext->GetDevice()->EmitWarningOnce(
+                "Sampling the stencil component is rather slow now.");
+            DAWN_TRY(Read(commandContext, singleRange, {0, 0, 0}, size, bytesPerRow, rowsPerImage,
+                          callback));
+
+            DAWN_TRY(mTextureForStencilSampling->WriteInternal(commandContext, singleRange,
+                                                               {0, 0, 0}, size, stagingData.data(),
+                                                               bytesPerRow, rowsPerImage));
+        }
+    }
+
+    Ref<TextureViewBase> textureView;
+    TextureViewDescriptor viewDesc = {};
+    viewDesc.label = "InterimStencilTextureView";
+    viewDesc.format = wgpu::TextureFormat::R8Uint;
+    viewDesc.dimension = view->GetDimension();
+    viewDesc.baseArrayLayer = view->GetBaseArrayLayer();
+    viewDesc.arrayLayerCount = view->GetLayerCount();
+    viewDesc.baseMipLevel = view->GetBaseMipLevel();
+    viewDesc.mipLevelCount = view->GetLevelCount();
+    DAWN_TRY_ASSIGN(textureView, mTextureForStencilSampling->CreateView(&viewDesc));
+
+    ComPtr<ID3D11ShaderResourceView> srv;
+    DAWN_TRY_ASSIGN(srv, ToBackend(textureView)->CreateD3D11ShaderResourceView());
+    return srv;
+}
+
 // static
 Ref<TextureView> TextureView::Create(TextureBase* texture,
                                      const TextureViewDescriptor* descriptor) {
@@ -1100,8 +1175,7 @@
                         break;
                     case Aspect::Stencil:
                         srvDesc.Format = DXGI_FORMAT_X24_TYPELESS_G8_UINT;
-                        // TODO(dawn:1827) Support sampling the stencil component.
-                        return DAWN_UNIMPLEMENTED_ERROR("Sampling the stencil component.");
+                        break;
                     default:
                         UNREACHABLE();
                         break;
@@ -1124,8 +1198,7 @@
                         break;
                     case Aspect::Stencil:
                         srvDesc.Format = DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
-                        // TODO(dawn:1827) Support sampling the stencil component.
-                        return DAWN_UNIMPLEMENTED_ERROR("Sampling the stencil component.");
+                        break;
                     default:
                         UNREACHABLE();
                         break;
diff --git a/src/dawn/native/d3d11/TextureD3D11.h b/src/dawn/native/d3d11/TextureD3D11.h
index 7eb2788..b72cdab 100644
--- a/src/dawn/native/d3d11/TextureD3D11.h
+++ b/src/dawn/native/d3d11/TextureD3D11.h
@@ -36,6 +36,7 @@
 
 class CommandRecordingContext;
 class Device;
+class TextureView;
 
 MaybeError ValidateTextureCanBeWrapped(ID3D11Resource* d3d11Resource,
                                        const TextureDescriptor* descriptor);
@@ -82,6 +83,13 @@
 
     ResultOrError<ExecutionSerial> EndAccess() override;
 
+    // As D3D11 SRV doesn't support 'Shader4ComponentMapping' for depth-stencil textures, we can't
+    // sample the stencil component directly. As a workaround we create an internal R8Uint texture,
+    // holding the copy of its stencil data, and use the internal texture's SRV instead.
+    ResultOrError<ComPtr<ID3D11ShaderResourceView>> GetStencilSRV(
+        CommandRecordingContext* commandContext,
+        const TextureView* view);
+
   private:
     using Base = d3d::Texture;
 
@@ -160,6 +168,8 @@
 
     const Kind mKind = Kind::Normal;
     ComPtr<ID3D11Resource> mD3d11Resource;
+    // The internal 'R8Uint' texture for sampling stencil from depth-stencil textures.
+    Ref<Texture> mTextureForStencilSampling;
 };
 
 class TextureView final : public TextureViewBase {
diff --git a/src/dawn/tests/end2end/DepthStencilSamplingTests.cpp b/src/dawn/tests/end2end/DepthStencilSamplingTests.cpp
index e3f5c23..9816af9 100644
--- a/src/dawn/tests/end2end/DepthStencilSamplingTests.cpp
+++ b/src/dawn/tests/end2end/DepthStencilSamplingTests.cpp
@@ -313,8 +313,6 @@
                 inputViewDesc.aspect = wgpu::TextureAspect::DepthOnly;
                 break;
             case TestAspectAndSamplerType::StencilAsUint:
-                // TODO(dawn:1827): Suport SRV component mapping for stencil on D3D11.
-                DAWN_SUPPRESS_TEST_IF(IsD3D11());
                 inputViewDesc.aspect = wgpu::TextureAspect::StencilOnly;
                 break;
         }
@@ -372,8 +370,6 @@
                 inputViewDesc.aspect = wgpu::TextureAspect::DepthOnly;
                 break;
             case TestAspectAndSamplerType::StencilAsUint:
-                // TODO(dawn:1827): Suport SRV component mapping for stencil on D3D11.
-                DAWN_SUPPRESS_TEST_IF(IsD3D11());
                 inputViewDesc.aspect = wgpu::TextureAspect::StencilOnly;
                 break;
         }
@@ -749,9 +745,6 @@
     // In compat, you can't have different views of the same texture in the same draw command.
     DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode());
 
-    // TODO(dawn:1827): Suport SRV component mapping for stencil on D3D11.
-    DAWN_SUPPRESS_TEST_IF(IsD3D11());
-
     wgpu::TextureFormat format = GetParam().mTextureFormat;
 
     wgpu::SamplerDescriptor samplerDesc;
diff --git a/src/dawn/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp b/src/dawn/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
index 53410ae..7afde12 100644
--- a/src/dawn/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
+++ b/src/dawn/tests/end2end/ReadOnlyDepthStencilAttachmentTests.cpp
@@ -297,9 +297,6 @@
 };
 
 TEST_P(ReadOnlyStencilAttachmentTests, SampleFromAttachment) {
-    // TODO(dawn:1827): sampling from stencil attachment fails on D3D11.
-    DAWN_SUPPRESS_TEST_IF(IsD3D11());
-
     wgpu::Texture colorTexture =
         CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
                       wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);