D3D11: Cache last created SRV and RTV in the Texture class.

It's a common scenario that users create a texture's view transiently
once per frame and never cache it.
e.g:
void renderStep() {
    // Create a view and attach it to a render pass.
    wgpu::RenderPassDescriptor renderPassDesc = ...;
    renderPassDesc.colorAttachments[0].view = texture.CreateView();

    // Draw the render pass.
}

In this case, D3D11 has to repeatedly re-create the texture's D3D native
view every frame. This is not a cheap operation. Unlike metal backend,
where the view creation could be skipped entirely in some simple
scenarios:
See: https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/src/dawn/native/metal/TextureMTL.mm;drc=5ca64904963dc4676216ddfd226779522c271540;l=104

This CL adds a small optimization for this scenario by:
- Caching last created D3D views within the wgpu::Texture object.
- Checking the cache for an existing D3D view before creating a new one
when a wgpu::TextureView is initialized.

Bug: chromium:377716220
Bug: 409035452
Change-Id: Id99c38f8c91dc441f9a6ba92ca888dea3e8d37dc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/234254
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index 7fbe37c..ea1f482 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -146,6 +146,34 @@
 
 }  // namespace
 
+// TODO(409035452): Consider using the real LRU cache for caching the views.
+template <typename K, typename T>
+ComPtr<T> Texture::ViewCache<K, T>::Get(const K& key) const {
+    for (auto ite = mViews.begin(); ite != mViews.end(); ++ite) {
+        if (ite->key == key) {
+            return ite->view;
+        }
+    }
+
+    return nullptr;
+}
+
+template <typename K, typename T>
+void Texture::ViewCache<K, T>::Set(const K& key, ComPtr<T> view) {
+    if (mViews.size() == kMaxCacheSize) {
+        // Remove oldest entry.
+        mViews.erase(mViews.begin());
+    }
+
+    DAWN_ASSERT(mViews.size() < kMaxCacheSize);
+    mViews.emplace_back(key, std::move(view));
+}
+
+template <typename K, typename T>
+void Texture::ViewCache<K, T>::Clear() {
+    mViews.clear();
+}
+
 // static
 ResultOrError<Ref<Texture>> Texture::Create(Device* device,
                                             const UnpackedPtr<TextureDescriptor>& descriptor) {
@@ -320,27 +348,30 @@
     mD3d11Resource = nullptr;
     mKeyedMutex = nullptr;
     mTextureForStencilSampling = nullptr;
+    mCachedRTVs.Clear();
+    mCachedSRVs.Clear();
 }
 
 ID3D11Resource* Texture::GetD3D11Resource() const {
     return mD3d11Resource.Get();
 }
 
-ResultOrError<ComPtr<ID3D11RenderTargetView>> Texture::CreateD3D11RenderTargetView(
-    wgpu::TextureFormat format,
-    uint32_t mipLevel,
-    uint32_t baseSlice,
-    uint32_t sliceCount,
-    uint32_t planeSlice) const {
+ResultOrError<ComPtr<ID3D11RenderTargetView1>> Texture::GetOrCreateD3D11RenderTargetView(
+    const RTVKey& key) {
+    auto cachedRTV = mCachedRTVs.Get(key);
+    if (cachedRTV) {
+        return std::move(cachedRTV);
+    }
+
     D3D11_RENDER_TARGET_VIEW_DESC1 rtvDesc;
-    rtvDesc.Format = d3d::DXGITextureFormat(GetDevice(), format);
+    rtvDesc.Format = d3d::DXGITextureFormat(GetDevice(), key.viewFormat);
     if (IsMultisampledTexture()) {
         DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
         DAWN_ASSERT(GetNumMipLevels() == 1);
-        DAWN_ASSERT(mipLevel == 0);
-        DAWN_ASSERT(baseSlice == 0);
-        DAWN_ASSERT(sliceCount == 1);
-        DAWN_ASSERT(planeSlice == 0);
+        DAWN_ASSERT(key.mipLevel == 0);
+        DAWN_ASSERT(key.baseSlice == 0);
+        DAWN_ASSERT(key.sliceCount == 1);
+        DAWN_ASSERT(key.planeSlice == 0);
         rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
     } else {
         switch (GetDimension()) {
@@ -354,20 +385,20 @@
                 // https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ns-d3d11-d3d11_tex2d_array
                 // _rtv
                 rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
-                rtvDesc.Texture2DArray.MipSlice = mipLevel;
-                rtvDesc.Texture2DArray.FirstArraySlice = baseSlice;
-                rtvDesc.Texture2DArray.ArraySize = sliceCount;
-                rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
+                rtvDesc.Texture2DArray.MipSlice = key.mipLevel;
+                rtvDesc.Texture2DArray.FirstArraySlice = key.baseSlice;
+                rtvDesc.Texture2DArray.ArraySize = key.sliceCount;
+                rtvDesc.Texture2DArray.PlaneSlice = key.planeSlice;
                 break;
             case wgpu::TextureDimension::e3D:
                 rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
-                rtvDesc.Texture3D.MipSlice = mipLevel;
-                rtvDesc.Texture3D.FirstWSlice = baseSlice;
-                rtvDesc.Texture3D.WSize = sliceCount;
+                rtvDesc.Texture3D.MipSlice = key.mipLevel;
+                rtvDesc.Texture3D.FirstWSlice = key.baseSlice;
+                rtvDesc.Texture3D.WSize = key.sliceCount;
                 break;
             case wgpu::TextureDimension::e1D:
                 rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1D;
-                rtvDesc.Texture1D.MipSlice = mipLevel;
+                rtvDesc.Texture1D.MipSlice = key.mipLevel;
                 break;
         }
     }
@@ -378,7 +409,9 @@
                               ->CreateRenderTargetView1(GetD3D11Resource(), &rtvDesc, &rtv),
                           "CreateRenderTargetView"));
 
-    return {std::move(rtv)};
+    mCachedRTVs.Set(key, rtv);
+
+    return std::move(rtv);
 }
 
 ResultOrError<ComPtr<ID3D11DepthStencilView>> Texture::CreateD3D11DepthStencilView(
@@ -519,12 +552,12 @@
                     // RTV, we can use the 'layer' as baseSlice and the 'depthOrArrayLayers' as
                     // sliceCount to create RTV without checking the dimension.
                     DAWN_TRY_ASSIGN(d3d11RTV,
-                                    CreateD3D11RenderTargetView(
-                                        format, clearRange.baseMipLevel, clearRange.baseArrayLayer,
-                                        GetMipLevelSingleSubresourceVirtualSize(
-                                            clearRange.baseMipLevel, clearRange.aspects)
-                                            .depthOrArrayLayers,
-                                        GetAspectIndex(aspect)));
+                                    GetOrCreateD3D11RenderTargetView(
+                                        {format, clearRange.baseMipLevel, clearRange.baseArrayLayer,
+                                         GetMipLevelSingleSubresourceVirtualSize(
+                                             clearRange.baseMipLevel, clearRange.aspects)
+                                             .depthOrArrayLayers,
+                                         GetAspectIndex(aspect)}));
                     commandContext->ClearRenderTargetView(d3d11RTV.Get(), d3d11ClearValue.color);
                 }
             }
@@ -1148,6 +1181,85 @@
     return srv;
 }
 
+ResultOrError<ComPtr<ID3D11ShaderResourceView1>> Texture::GetOrCreateSRV(const SRVKey& key) {
+    auto cacheSRV = mCachedSRVs.Get(key);
+    if (cacheSRV) {
+        return std::move(cacheSRV);
+    }
+
+    D3D11_SHADER_RESOURCE_VIEW_DESC1 srvDesc;
+    srvDesc.Format = d3d::D3DShaderResourceViewFormat(
+        GetDevice(), GetFormat(), GetDevice()->GetValidInternalFormat(key.viewFormat), key.aspects);
+
+    // Currently we always use D3D11_TEX2D_ARRAY_SRV because we cannot specify base array
+    // layer and layer count in D3D11_TEX2D_SRV. For 2D texture views, we treat them as
+    // 1-layer 2D array textures. Multisampled textures may only be one array layer, so we
+    // use D3D11_SRV_DIMENSION_TEXTURE2DMS.
+    // https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ns-d3d11-d3d11_tex2d_srv
+    // https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ns-d3d11-d3d11_tex2d_array_srv
+    if (IsMultisampledTexture()) {
+        switch (key.viewDimension) {
+            case wgpu::TextureViewDimension::e2DArray:
+                DAWN_ASSERT(GetArrayLayers() == 1);
+                [[fallthrough]];
+            case wgpu::TextureViewDimension::e2D:
+                DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
+                break;
+
+            default:
+                DAWN_UNREACHABLE();
+        }
+    } else {
+        switch (key.viewDimension) {
+            case wgpu::TextureViewDimension::e1D:
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
+                srvDesc.Texture1D.MipLevels = key.levelCount;
+                srvDesc.Texture1D.MostDetailedMip = key.mipLevel;
+                break;
+
+            case wgpu::TextureViewDimension::e2D:
+            case wgpu::TextureViewDimension::e2DArray:
+                DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
+                srvDesc.Texture2DArray.ArraySize = key.sliceCount;
+                srvDesc.Texture2DArray.FirstArraySlice = key.baseSlice;
+                srvDesc.Texture2DArray.MipLevels = key.levelCount;
+                srvDesc.Texture2DArray.MostDetailedMip = key.mipLevel;
+                srvDesc.Texture2DArray.PlaneSlice = GetAspectIndex(key.aspects);
+                break;
+            case wgpu::TextureViewDimension::Cube:
+            case wgpu::TextureViewDimension::CubeArray:
+                DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
+                DAWN_ASSERT(key.sliceCount % 6 == 0);
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBEARRAY;
+                srvDesc.TextureCubeArray.First2DArrayFace = key.baseSlice;
+                srvDesc.TextureCubeArray.NumCubes = key.sliceCount / 6;
+                srvDesc.TextureCubeArray.MipLevels = key.levelCount;
+                srvDesc.TextureCubeArray.MostDetailedMip = key.mipLevel;
+                break;
+            case wgpu::TextureViewDimension::e3D:
+                DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e3D);
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
+                srvDesc.Texture3D.MostDetailedMip = key.mipLevel;
+                srvDesc.Texture3D.MipLevels = key.levelCount;
+                break;
+
+            case wgpu::TextureViewDimension::Undefined:
+                DAWN_UNREACHABLE();
+        }
+    }
+
+    ComPtr<ID3D11ShaderResourceView1> srv;
+    DAWN_TRY(CheckHRESULT(ToBackend(GetDevice())
+                              ->GetD3D11Device3()
+                              ->CreateShaderResourceView1(GetD3D11Resource(), &srvDesc, &srv),
+                          "CreateShaderResourceView1"));
+
+    mCachedSRVs.Set(key, srv);
+    return std::move(srv);
+}
+
 // static
 Ref<TextureView> TextureView::Create(TextureBase* texture,
                                      const UnpackedPtr<TextureViewDescriptor>& descriptor) {
@@ -1169,75 +1281,11 @@
         return mD3d11SharedResourceView.Get();
     }
 
-    Device* device = ToBackend(GetDevice());
-    D3D11_SHADER_RESOURCE_VIEW_DESC1 srvDesc;
-    srvDesc.Format = d3d::D3DShaderResourceViewFormat(GetDevice(), GetTexture()->GetFormat(),
-                                                      GetFormat(), GetAspects());
-
-    // Currently we always use D3D11_TEX2D_ARRAY_SRV because we cannot specify base array
-    // layer and layer count in D3D11_TEX2D_SRV. For 2D texture views, we treat them as
-    // 1-layer 2D array textures. Multisampled textures may only be one array layer, so we
-    // use D3D11_SRV_DIMENSION_TEXTURE2DMS.
-    // https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ns-d3d11-d3d11_tex2d_srv
-    // https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ns-d3d11-d3d11_tex2d_array_srv
-    if (GetTexture()->IsMultisampledTexture()) {
-        switch (GetDimension()) {
-            case wgpu::TextureViewDimension::e2DArray:
-                DAWN_ASSERT(GetTexture()->GetArrayLayers() == 1);
-                [[fallthrough]];
-            case wgpu::TextureViewDimension::e2D:
-                DAWN_ASSERT(GetTexture()->GetDimension() == wgpu::TextureDimension::e2D);
-                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
-                break;
-
-            default:
-                DAWN_UNREACHABLE();
-        }
-    } else {
-        switch (GetDimension()) {
-            case wgpu::TextureViewDimension::e1D:
-                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
-                srvDesc.Texture1D.MipLevels = GetLevelCount();
-                srvDesc.Texture1D.MostDetailedMip = GetBaseMipLevel();
-                break;
-
-            case wgpu::TextureViewDimension::e2D:
-            case wgpu::TextureViewDimension::e2DArray:
-                DAWN_ASSERT(GetTexture()->GetDimension() == wgpu::TextureDimension::e2D);
-                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
-                srvDesc.Texture2DArray.ArraySize = GetLayerCount();
-                srvDesc.Texture2DArray.FirstArraySlice = GetBaseArrayLayer();
-                srvDesc.Texture2DArray.MipLevels = GetLevelCount();
-                srvDesc.Texture2DArray.MostDetailedMip = GetBaseMipLevel();
-                srvDesc.Texture2DArray.PlaneSlice = GetAspectIndex(GetAspects());
-                break;
-            case wgpu::TextureViewDimension::Cube:
-            case wgpu::TextureViewDimension::CubeArray:
-                DAWN_ASSERT(GetTexture()->GetDimension() == wgpu::TextureDimension::e2D);
-                DAWN_ASSERT(GetLayerCount() % 6 == 0);
-                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBEARRAY;
-                srvDesc.TextureCubeArray.First2DArrayFace = GetBaseArrayLayer();
-                srvDesc.TextureCubeArray.NumCubes = GetLayerCount() / 6;
-                srvDesc.TextureCubeArray.MipLevels = GetLevelCount();
-                srvDesc.TextureCubeArray.MostDetailedMip = GetBaseMipLevel();
-                break;
-            case wgpu::TextureViewDimension::e3D:
-                DAWN_ASSERT(GetTexture()->GetDimension() == wgpu::TextureDimension::e3D);
-                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
-                srvDesc.Texture3D.MostDetailedMip = GetBaseMipLevel();
-                srvDesc.Texture3D.MipLevels = GetLevelCount();
-                break;
-
-            case wgpu::TextureViewDimension::Undefined:
-                DAWN_UNREACHABLE();
-        }
-    }
-
-    ComPtr<ID3D11ShaderResourceView1> srv;
-    DAWN_TRY(CheckHRESULT(device->GetD3D11Device3()->CreateShaderResourceView1(
-                              ToBackend(GetTexture())->GetD3D11Resource(), &srvDesc, &srv),
-                          "CreateShaderResourceView1"));
-    mD3d11SharedResourceView = std::move(srv);
+    DAWN_TRY_ASSIGN(
+        mD3d11SharedResourceView,
+        ToBackend(GetTexture())
+            ->GetOrCreateSRV({GetDimension(), GetFormat().format, GetAspects(), GetBaseMipLevel(),
+                              GetLevelCount(), GetBaseArrayLayer(), GetLayerCount()}));
     return mD3d11SharedResourceView.Get();
 }
 
@@ -1257,11 +1305,12 @@
     // 2d RTVs, which value is set to 0. For 3d RTVs, the baseArrayLayer must be 0. So here we can
     // simply use baseArrayLayer + depthSlice to specify the slice in RTVs without checking the
     // view's dimension.
-    DAWN_TRY_ASSIGN(mD3d11RenderTargetViews[depthSlice],
-                    ToBackend(GetTexture())
-                        ->CreateD3D11RenderTargetView(
-                            GetFormat().format, GetBaseMipLevel(), GetBaseArrayLayer() + depthSlice,
-                            GetLayerCount(), GetAspectIndex(GetAspects())));
+    DAWN_TRY_ASSIGN(
+        mD3d11RenderTargetViews[depthSlice],
+        ToBackend(GetTexture())
+            ->GetOrCreateD3D11RenderTargetView({GetFormat().format, GetBaseMipLevel(),
+                                                GetBaseArrayLayer() + depthSlice, GetLayerCount(),
+                                                GetAspectIndex(GetAspects())}));
     return mD3d11RenderTargetViews[depthSlice].Get();
 }
 
diff --git a/src/dawn/native/d3d11/TextureD3D11.h b/src/dawn/native/d3d11/TextureD3D11.h
index 3f9b21f..770c676 100644
--- a/src/dawn/native/d3d11/TextureD3D11.h
+++ b/src/dawn/native/d3d11/TextureD3D11.h
@@ -28,8 +28,10 @@
 #ifndef SRC_DAWN_NATIVE_D3D11_TEXTURED3D11_H_
 #define SRC_DAWN_NATIVE_D3D11_TEXTURED3D11_H_
 
+#include <utility>
 #include <vector>
 
+#include "absl/container/inlined_vector.h"
 #include "dawn/native/DawnNative.h"
 #include "dawn/native/Error.h"
 #include "dawn/native/IntegerTypes.h"
@@ -65,12 +67,22 @@
         const UnpackedPtr<TextureDescriptor>& descriptor);
     ID3D11Resource* GetD3D11Resource() const;
 
-    ResultOrError<ComPtr<ID3D11RenderTargetView>> CreateD3D11RenderTargetView(
-        wgpu::TextureFormat format,
-        uint32_t mipLevel,
-        uint32_t baseSlice,
-        uint32_t sliceCount,
-        uint32_t planeSlice) const;
+    struct RTVKey {
+        bool operator==(const RTVKey& rhs) const {
+            return viewFormat == rhs.viewFormat && mipLevel == rhs.mipLevel &&
+                   baseSlice == rhs.baseSlice && sliceCount == rhs.sliceCount &&
+                   planeSlice == rhs.planeSlice;
+        }
+
+        wgpu::TextureFormat viewFormat;
+        uint32_t mipLevel;
+        uint32_t baseSlice;
+        uint32_t sliceCount;
+        uint32_t planeSlice;
+    };
+
+    ResultOrError<ComPtr<ID3D11RenderTargetView1>> GetOrCreateD3D11RenderTargetView(
+        const RTVKey& key);
     ResultOrError<ComPtr<ID3D11DepthStencilView>> CreateD3D11DepthStencilView(
         const SubresourceRange& singleLevelRange,
         bool depthReadOnly,
@@ -99,7 +111,6 @@
     static MaybeError Copy(const ScopedCommandRecordingContext* commandContext,
                            CopyTextureToTextureCmd* copy);
 
-
     // 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.
@@ -107,6 +118,24 @@
         const ScopedCommandRecordingContext* commandContext,
         const TextureView* view);
 
+    struct SRVKey {
+        bool operator==(const SRVKey& rhs) const {
+            return viewDimension == rhs.viewDimension && viewFormat == rhs.viewFormat &&
+                   aspects == rhs.aspects && mipLevel == rhs.mipLevel &&
+                   levelCount == rhs.levelCount && baseSlice == rhs.baseSlice &&
+                   sliceCount == rhs.sliceCount;
+        }
+
+        wgpu::TextureViewDimension viewDimension;
+        wgpu::TextureFormat viewFormat;
+        Aspect aspects;
+        uint32_t mipLevel;
+        uint32_t levelCount;
+        uint32_t baseSlice;
+        uint32_t sliceCount;
+    };
+    ResultOrError<ComPtr<ID3D11ShaderResourceView1>> GetOrCreateSRV(const SRVKey& key);
+
   private:
     using Base = TextureBase;
 
@@ -187,6 +216,28 @@
 
     // The internal 'R8Uint' texture for sampling stencil from depth-stencil textures.
     Ref<Texture> mTextureForStencilSampling;
+
+    // Simple view cache that store up to 3 recently created views.
+    template <typename K, typename T>
+    class ViewCache {
+      public:
+        ComPtr<T> Get(const K& key) const;
+        void Set(const K& key, ComPtr<T> view);
+        void Clear();
+
+      private:
+        static constexpr uint32_t kMaxCacheSize = 3;
+
+        struct Entry {
+            Entry(const K& keyIn, ComPtr<T> viewIn) : key(keyIn), view(std::move(viewIn)) {}
+
+            K key;
+            ComPtr<T> view;
+        };
+        absl::InlinedVector<Entry, kMaxCacheSize> mViews;
+    };
+    ViewCache<RTVKey, ID3D11RenderTargetView1> mCachedRTVs;
+    ViewCache<SRVKey, ID3D11ShaderResourceView1> mCachedSRVs;
 };
 
 class TextureView final : public TextureViewBase {