Fix SharedResourceMemory begin/end access without using texture

 - Change SetHasAccess to Resume/PauseAccess. PauseAccess returns
   the last usage serial for exporting. If the resource is not used
   it returns serial 0 (beginning of time).

 - When there are multiple concurrent readers, only acquire the
   unacquired pending fences when the last read ends.

 - Update a few tests since now, EndAccess may yield zero fences.
   This happens if the buffer is not used, and it is not the last
   concurrent read, or if there were no begin fences to forward.

Bug: 42241403
Change-Id: If43543f90a896d3c5aabaf21e788276545f67757
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/186846
Reviewed-by: Sunny Sachanandani <sunnyps@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 26440c1..974ac9b 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -888,8 +888,15 @@
                                            this, bufferOffset, size);
 }
 
-void BufferBase::SetHasAccess(bool hasAccess) {
-    mState = hasAccess ? BufferState::Unmapped : BufferState::SharedMemoryNoAccess;
+ExecutionSerial BufferBase::OnEndAccess() {
+    mState = BufferState::SharedMemoryNoAccess;
+    ExecutionSerial lastUsageSerial = mLastUsageSerial;
+    mLastUsageSerial = kBeginningOfGPUTime;
+    return lastUsageSerial;
+}
+
+void BufferBase::OnBeginAccess() {
+    mState = BufferState::Unmapped;
 }
 
 bool BufferBase::HasAccess() const {
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index 5ac26c6..e1378ce 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -105,7 +105,8 @@
     virtual MaybeError UploadData(uint64_t bufferOffset, const void* data, size_t size);
 
     // SharedResource impl.
-    void SetHasAccess(bool hasAccess) override;
+    ExecutionSerial OnEndAccess() override;
+    void OnBeginAccess() override;
     bool HasAccess() const override;
     bool IsDestroyed() const override;
     void SetInitialized(bool initialized) override;
diff --git a/src/dawn/native/SharedBufferMemory.cpp b/src/dawn/native/SharedBufferMemory.cpp
index 21a5697..510993a 100644
--- a/src/dawn/native/SharedBufferMemory.cpp
+++ b/src/dawn/native/SharedBufferMemory.cpp
@@ -53,6 +53,7 @@
         DAWN_UNREACHABLE();
     }
     ResultOrError<FenceAndSignalValue> EndAccessImpl(BufferBase* buffer,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override {
         DAWN_UNREACHABLE();
     }
@@ -143,7 +144,7 @@
     Ref<BufferBase> buffer;
     DAWN_TRY_ASSIGN(buffer, CreateBufferImpl(descriptor));
     // Access is not allowed until BeginAccess has been called.
-    buffer->SetHasAccess(false);
+    buffer->OnEndAccess();
     return buffer;
 }
 
diff --git a/src/dawn/native/SharedResourceMemory.cpp b/src/dawn/native/SharedResourceMemory.cpp
index f33640e..d644fe6 100644
--- a/src/dawn/native/SharedResourceMemory.cpp
+++ b/src/dawn/native/SharedResourceMemory.cpp
@@ -27,6 +27,7 @@
 
 #include "dawn/native/SharedResourceMemory.h"
 
+#include <algorithm>
 #include <utility>
 
 #include "dawn/native/Buffer.h"
@@ -165,11 +166,21 @@
     DAWN_TRY(BeginAccessImpl(resource, descriptor));
 
     for (size_t i = 0; i < descriptor->fenceCount; ++i) {
+        // Add the fences to mPendingFences if they are not already contained in the list.
+        // This loop is O(n*m), but there shouldn't be very many fences.
+        auto it = std::find_if(mContents->mPendingFences.begin(), mContents->mPendingFences.end(),
+                               [&](const auto& fence) {
+                                   return fence.object.Get() == descriptor->fences[i] &&
+                                          fence.signaledValue == descriptor->signaledValues[i];
+                               });
+        if (it != mContents->mPendingFences.end()) {
+            continue;
+        }
         mContents->mPendingFences.push_back({descriptor->fences[i], descriptor->signaledValues[i]});
     }
 
     DAWN_ASSERT(!resource->IsError());
-    resource->SetHasAccess(true);
+    resource->OnBeginAccess();
     resource->SetInitialized(descriptor->initialized);
     return {};
 }
@@ -204,12 +215,14 @@
 
 ResultOrError<FenceAndSignalValue> SharedResourceMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<SharedTextureMemoryEndAccessState>& state) {
     DAWN_UNREACHABLE();
 }
 
 ResultOrError<FenceAndSignalValue> SharedResourceMemory::EndAccessImpl(
     BufferBase* buffer,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<SharedBufferMemoryEndAccessState>& state) {
     DAWN_UNREACHABLE();
 }
@@ -260,19 +273,26 @@
     }
 
     PendingFenceList fenceList;
-    mContents->AcquirePendingFences(&fenceList);
+    // The state transitions to NotAccessed if the exclusive access ends, or the last concurrent
+    // read ends. When this occurs, acquire any pending fences that may remain. This occurs if
+    // the accesses never acquired them.
+    if (mContents->mSharedResourceAccessState == SharedResourceAccessState::NotAccessed) {
+        mContents->AcquirePendingFences(&fenceList);
+    }
 
     DAWN_ASSERT(!resource->IsError());
-    resource->SetHasAccess(false);
+    ExecutionSerial lastUsageSerial = resource->OnEndAccess();
 
     *didEnd = true;
 
-    // Call the error-generating part of the EndAccess implementation. This is separated out because
-    // writing the output state must happen regardless of whether or not EndAccessInternal
-    // succeeds.
+    // If the last usage serial is non-zero, the texture was used.
+    // Call the error-generating part of the EndAccess implementation to export a fence.
+    // This is separated out because writing the output state must happen regardless of whether
+    // or not EndAccessInternal succeeds.
     MaybeError err;
-    {
-        ResultOrError<FenceAndSignalValue> result = EndAccessInternal(resource, state);
+    if (lastUsageSerial != kBeginningOfGPUTime) {
+        ResultOrError<FenceAndSignalValue> result =
+            EndAccessInternal(lastUsageSerial, resource, state);
         if (result.IsSuccess()) {
             fenceList.push_back(result.AcquireSuccess());
         } else {
@@ -303,13 +323,14 @@
 
 template <typename Resource, typename EndAccessState>
 ResultOrError<FenceAndSignalValue> SharedResourceMemory::EndAccessInternal(
+    ExecutionSerial lastUsageSerial,
     Resource* resource,
     EndAccessState* rawState) {
     UnpackedPtr<EndAccessState> state;
     DAWN_TRY_ASSIGN(state, ValidateAndUnpack(rawState));
     // Ensure that commands are submitted before exporting fences with the last usage serial.
-    DAWN_TRY(GetDevice()->GetQueue()->EnsureCommandsFlushed(mContents->GetLastUsageSerial()));
-    return EndAccessImpl(resource, state);
+    DAWN_TRY(GetDevice()->GetQueue()->EnsureCommandsFlushed(lastUsageSerial));
+    return EndAccessImpl(resource, lastUsageSerial, state);
 }
 
 // SharedResourceMemoryContents
@@ -327,12 +348,4 @@
     mPendingFences.clear();
 }
 
-void SharedResourceMemoryContents::SetLastUsageSerial(ExecutionSerial lastUsageSerial) {
-    mLastUsageSerial = lastUsageSerial;
-}
-
-ExecutionSerial SharedResourceMemoryContents::GetLastUsageSerial() const {
-    return mLastUsageSerial;
-}
-
 }  // namespace dawn::native
diff --git a/src/dawn/native/SharedResourceMemory.h b/src/dawn/native/SharedResourceMemory.h
index cfe3abf..a5a9956 100644
--- a/src/dawn/native/SharedResourceMemory.h
+++ b/src/dawn/native/SharedResourceMemory.h
@@ -51,7 +51,11 @@
 
     SharedResourceMemoryContents* GetSharedResourceMemoryContents() const;
 
-    virtual void SetHasAccess(bool hasAccess) = 0;
+    // Set the resource state to allow access.
+    virtual void OnBeginAccess() = 0;
+    // Set the resource state to disallow access, and return the last usage serial.
+    virtual ExecutionSerial OnEndAccess() = 0;
+    // Check whether the resource may be accessed.
     virtual bool HasAccess() const = 0;
     virtual bool IsDestroyed() const = 0;
     virtual void SetInitialized(bool initialized) = 0;
@@ -110,7 +114,8 @@
     MaybeError EndAccess(Resource* resource, EndAccessState* state, bool* didEnd);
 
     template <typename Resource, typename EndAccessState>
-    ResultOrError<FenceAndSignalValue> EndAccessInternal(Resource* resource,
+    ResultOrError<FenceAndSignalValue> EndAccessInternal(ExecutionSerial lastUsageSerial,
+                                                         Resource* resource,
                                                          EndAccessState* rawState);
 
     // BeginAccessImpl validates the operation is valid on the backend, and performs any
@@ -126,9 +131,11 @@
     // It should also write out any backend specific state in chained out structs of EndAccessState.
     virtual ResultOrError<FenceAndSignalValue> EndAccessImpl(
         TextureBase* texture,
+        ExecutionSerial lastUsageSerial,
         UnpackedPtr<SharedTextureMemoryEndAccessState>& state);
     virtual ResultOrError<FenceAndSignalValue> EndAccessImpl(
         BufferBase* buffer,
+        ExecutionSerial lastUsageSerial,
         UnpackedPtr<SharedBufferMemoryEndAccessState>& state);
 
     Ref<SharedResource> mExclusiveAccess;
@@ -147,11 +154,6 @@
 
     void AcquirePendingFences(PendingFenceList* fences);
 
-    // Set the last usage serial. This indicates when the SharedFence exported
-    // from APIEndAccess will complete.
-    void SetLastUsageSerial(ExecutionSerial lastUsageSerial);
-    ExecutionSerial GetLastUsageSerial() const;
-
     const WeakRef<SharedResourceMemory>& GetSharedResourceMemory() const;
 
     bool HasWriteAccess() const;
@@ -162,7 +164,6 @@
     friend class SharedResourceMemory;
 
     PendingFenceList mPendingFences;
-    ExecutionSerial mLastUsageSerial{0};
 
     SharedResourceAccessState mSharedResourceAccessState = SharedResourceAccessState::NotAccessed;
     int mReadAccessCount = 0;
diff --git a/src/dawn/native/SharedTextureMemory.cpp b/src/dawn/native/SharedTextureMemory.cpp
index 01f84e1..3e9f2ba 100644
--- a/src/dawn/native/SharedTextureMemory.cpp
+++ b/src/dawn/native/SharedTextureMemory.cpp
@@ -54,6 +54,7 @@
         DAWN_UNREACHABLE();
     }
     ResultOrError<FenceAndSignalValue> EndAccessImpl(TextureBase* texture,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override {
         DAWN_UNREACHABLE();
     }
@@ -178,7 +179,7 @@
     Ref<TextureBase> texture;
     DAWN_TRY_ASSIGN(texture, CreateTextureImpl(descriptor));
     // Access is started on memory.BeginAccess.
-    texture->SetHasAccess(false);
+    texture->OnEndAccess();
     return texture;
 }
 
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 07e1d3c..adf6910 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -996,9 +996,15 @@
     SetIsSubresourceContentInitialized(initialized, GetAllSubresources());
 }
 
-void TextureBase::SetHasAccess(bool hasAccess) {
-    DAWN_ASSERT(!IsError());
-    mState.hasAccess = hasAccess;
+ExecutionSerial TextureBase::OnEndAccess() {
+    mState.hasAccess = false;
+    ExecutionSerial lastUsageSerial = mLastSharedTextureMemoryUsageSerial;
+    mLastSharedTextureMemoryUsageSerial = kBeginningOfGPUTime;
+    return lastUsageSerial;
+}
+
+void TextureBase::OnBeginAccess() {
+    mState.hasAccess = true;
 }
 
 bool TextureBase::HasAccess() const {
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 76f41e9..d14b682 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -113,7 +113,8 @@
     wgpu::TextureUsage GetInternalUsage() const;
 
     // SharedResource implementation
-    void SetHasAccess(bool hasAccess) override;
+    ExecutionSerial OnEndAccess() override;
+    void OnBeginAccess() override;
     bool HasAccess() const override;
     bool IsDestroyed() const override;
     bool IsInitialized() const override;
@@ -176,6 +177,8 @@
     void AddInternalUsage(wgpu::TextureUsage usage);
     void SetSharedResourceMemoryContentsForTesting(Ref<SharedResourceMemoryContents> contents);
 
+    ExecutionSerial mLastSharedTextureMemoryUsageSerial{kBeginningOfGPUTime};
+
   private:
     struct TextureState {
         TextureState();
diff --git a/src/dawn/native/d3d/SharedTextureMemoryD3D.cpp b/src/dawn/native/d3d/SharedTextureMemoryD3D.cpp
index 88dc163..0211719 100644
--- a/src/dawn/native/d3d/SharedTextureMemoryD3D.cpp
+++ b/src/dawn/native/d3d/SharedTextureMemoryD3D.cpp
@@ -68,6 +68,7 @@
 
 ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     DAWN_TRY(state.ValidateSubset<>());
     DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceDXGISharedHandle),
@@ -77,9 +78,7 @@
     Ref<SharedFence> sharedFence;
     DAWN_TRY_ASSIGN(sharedFence, ToBackend(GetDevice()->GetQueue())->GetOrCreateSharedFence());
 
-    return FenceAndSignalValue{
-        std::move(sharedFence),
-        static_cast<uint64_t>(texture->GetSharedResourceMemoryContents()->GetLastUsageSerial())};
+    return FenceAndSignalValue{std::move(sharedFence), static_cast<uint64_t>(lastUsageSerial)};
 }
 
 }  // namespace dawn::native::d3d
diff --git a/src/dawn/native/d3d/SharedTextureMemoryD3D.h b/src/dawn/native/d3d/SharedTextureMemoryD3D.h
index cfdf697..ebfd138 100644
--- a/src/dawn/native/d3d/SharedTextureMemoryD3D.h
+++ b/src/dawn/native/d3d/SharedTextureMemoryD3D.h
@@ -44,6 +44,7 @@
     MaybeError BeginAccessImpl(TextureBase* texture,
                                const UnpackedPtr<BeginAccessDescriptor>& descriptor) override;
     ResultOrError<FenceAndSignalValue> EndAccessImpl(TextureBase* texture,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
 };
 
diff --git a/src/dawn/native/d3d11/TextureD3D11.cpp b/src/dawn/native/d3d11/TextureD3D11.cpp
index e050471..5ef307f 100644
--- a/src/dawn/native/d3d11/TextureD3D11.cpp
+++ b/src/dawn/native/d3d11/TextureD3D11.cpp
@@ -505,7 +505,6 @@
     if (auto* contents = GetSharedResourceMemoryContents()) {
         SharedTextureMemoryBase::PendingFenceList fences;
         contents->AcquirePendingFences(&fences);
-        contents->SetLastUsageSerial(GetDevice()->GetQueue()->GetPendingCommandSerial());
         for (const auto& fence : fences) {
             DAWN_TRY(CheckHRESULT(
                 commandContext->Wait(ToBackend(fence.object)->GetD3DFence(), fence.signaledValue),
@@ -516,7 +515,7 @@
     if (mKeyedMutex != nullptr) {
         DAWN_TRY(commandContext->AcquireKeyedMutex(mKeyedMutex));
     }
-    mLastUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
+    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
     return {};
 }
 
@@ -1144,13 +1143,14 @@
 
 ResultOrError<ExecutionSerial> Texture::EndAccess() {
     // TODO(dawn:1705): submit pending commands if deferred context is used.
-    if (mLastUsageSerial) {
+    if (mLastSharedTextureMemoryUsageSerial != kBeginningOfGPUTime) {
         // Make the queue signal the fence in finite time.
-        DAWN_TRY(GetDevice()->GetQueue()->EnsureCommandsFlushed(*mLastUsageSerial));
+        DAWN_TRY(
+            GetDevice()->GetQueue()->EnsureCommandsFlushed(mLastSharedTextureMemoryUsageSerial));
     }
-    // Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
-    mLastUsageSerial.reset();
-    return GetDevice()->GetQueue()->GetLastSubmittedCommandSerial();
+    ExecutionSerial ret = mLastSharedTextureMemoryUsageSerial;
+    mLastSharedTextureMemoryUsageSerial = kBeginningOfGPUTime;
+    return ret;
 }
 
 ResultOrError<ComPtr<ID3D11ShaderResourceView>> Texture::GetStencilSRV(
diff --git a/src/dawn/native/d3d11/TextureD3D11.h b/src/dawn/native/d3d11/TextureD3D11.h
index 3c0d16f..7afca35 100644
--- a/src/dawn/native/d3d11/TextureD3D11.h
+++ b/src/dawn/native/d3d11/TextureD3D11.h
@@ -198,9 +198,6 @@
     ComPtr<ID3D11Resource> mD3d11Resource;
     Ref<d3d::KeyedMutex> mKeyedMutex;
 
-    // TODO(crbug.com/1515640): Remove this once Chromium has migrated to SharedTextureMemory.
-    std::optional<ExecutionSerial> mLastUsageSerial;
-
     // The internal 'R8Uint' texture for sampling stencil from depth-stencil textures.
     Ref<Texture> mTextureForStencilSampling;
 };
diff --git a/src/dawn/native/d3d12/SharedBufferMemoryD3D12.cpp b/src/dawn/native/d3d12/SharedBufferMemoryD3D12.cpp
index 005a5d1..2c4266b 100644
--- a/src/dawn/native/d3d12/SharedBufferMemoryD3D12.cpp
+++ b/src/dawn/native/d3d12/SharedBufferMemoryD3D12.cpp
@@ -144,6 +144,7 @@
 
 ResultOrError<FenceAndSignalValue> SharedBufferMemory::EndAccessImpl(
     BufferBase* buffer,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     DAWN_TRY(state.ValidateSubset<>());
     DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceDXGISharedHandle),
@@ -153,8 +154,7 @@
     Ref<d3d::SharedFence> sharedFence;
     DAWN_TRY_ASSIGN(sharedFence, ToBackend(GetDevice()->GetQueue())->GetOrCreateSharedFence());
 
-    return FenceAndSignalValue{std::move(sharedFence),
-                               static_cast<uint64_t>(buffer->GetLastUsageSerial())};
+    return FenceAndSignalValue{std::move(sharedFence), static_cast<uint64_t>(lastUsageSerial)};
 }
 
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/SharedBufferMemoryD3D12.h b/src/dawn/native/d3d12/SharedBufferMemoryD3D12.h
index 53ccae8..894cf19 100644
--- a/src/dawn/native/d3d12/SharedBufferMemoryD3D12.h
+++ b/src/dawn/native/d3d12/SharedBufferMemoryD3D12.h
@@ -58,6 +58,7 @@
     MaybeError BeginAccessImpl(BufferBase* buffer,
                                const UnpackedPtr<BeginAccessDescriptor>& descriptor) override;
     ResultOrError<FenceAndSignalValue> EndAccessImpl(BufferBase* buffer,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
 
     ComPtr<ID3D12Resource> mResource;
diff --git a/src/dawn/native/d3d12/SharedTextureMemoryD3D12.cpp b/src/dawn/native/d3d12/SharedTextureMemoryD3D12.cpp
index 4e16917..248e083 100644
--- a/src/dawn/native/d3d12/SharedTextureMemoryD3D12.cpp
+++ b/src/dawn/native/d3d12/SharedTextureMemoryD3D12.cpp
@@ -146,9 +146,10 @@
 
 ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     ToBackend(texture)->NotifySwapChainPresentToPIX();
-    return d3d::SharedTextureMemory::EndAccessImpl(texture, state);
+    return d3d::SharedTextureMemory::EndAccessImpl(texture, lastUsageSerial, state);
 }
 
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/SharedTextureMemoryD3D12.h b/src/dawn/native/d3d12/SharedTextureMemoryD3D12.h
index 440c1ba..b139df7 100644
--- a/src/dawn/native/d3d12/SharedTextureMemoryD3D12.h
+++ b/src/dawn/native/d3d12/SharedTextureMemoryD3D12.h
@@ -67,6 +67,7 @@
                                const UnpackedPtr<BeginAccessDescriptor>& descriptor) override;
 
     ResultOrError<FenceAndSignalValue> EndAccessImpl(TextureBase* texture,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
 
     ComPtr<ID3D12Resource> mResource;
diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp
index b47b0f9..9a6fd16 100644
--- a/src/dawn/native/d3d12/TextureD3D12.cpp
+++ b/src/dawn/native/d3d12/TextureD3D12.cpp
@@ -377,24 +377,23 @@
     NotifySwapChainPresentToPIX();
 
     // Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists. If
-    // there were pending commands that used this texture mSignalFenceValue will be set, but if it's
-    // still not set, generate a signal fence after waiting on wait fences.
+    // there were pending commands that used this texture mLastSharedTextureMemoryUsageSerial will
+    // be set, but if it's still not set, generate a signal fence after waiting on wait fences.
     Queue* queue = ToBackend(GetDevice()->GetQueue());
-    if (!mSignalFenceValue) {
+    if (mLastSharedTextureMemoryUsageSerial == kBeginningOfGPUTime) {
         // Even though we aren't recording any commands here, asking for a command context ensures
         // that the device fence is signaled eventually even if no commands were recorded before
         // EndAccess. This is a little sub-optimal, but shouldn't occur often in practice.
         CommandRecordingContext* context =
             queue->GetPendingCommandContext(ExecutionQueueBase::SubmitMode::Passive);
         DAWN_TRY(SynchronizeTextureBeforeUse(context));
-        DAWN_ASSERT(mSignalFenceValue);
+        DAWN_ASSERT(mLastSharedTextureMemoryUsageSerial != kBeginningOfGPUTime);
     }
     // Make the queue signal the fence in finite time.
-    DAWN_TRY(queue->EnsureCommandsFlushed(*mSignalFenceValue));
+    DAWN_TRY(queue->EnsureCommandsFlushed(mLastSharedTextureMemoryUsageSerial));
 
-    ExecutionSerial ret = mSignalFenceValue.value();
-    // Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
-    mSignalFenceValue.reset();
+    ExecutionSerial ret = mLastSharedTextureMemoryUsageSerial;
+    mLastSharedTextureMemoryUsageSerial = kBeginningOfGPUTime;
     return ret;
 }
 
@@ -443,7 +442,6 @@
         contents->AcquirePendingFences(&fences);
         waitFences.insert(waitFences.end(), std::make_move_iterator(fences.begin()),
                           std::make_move_iterator(fences.end()));
-        contents->SetLastUsageSerial(queue->GetPendingCommandSerial());
     }
 
     ID3D12CommandQueue* commandQueue = queue->GetCommandQueue();
@@ -457,7 +455,7 @@
     if (mKeyedMutex != nullptr) {
         DAWN_TRY(commandContext->AcquireKeyedMutex(mKeyedMutex));
     }
-    mSignalFenceValue = queue->GetPendingCommandSerial();
+    mLastSharedTextureMemoryUsageSerial = queue->GetPendingCommandSerial();
     return {};
 }
 
diff --git a/src/dawn/native/d3d12/TextureD3D12.h b/src/dawn/native/d3d12/TextureD3D12.h
index 2f701f6..4dc1dce 100644
--- a/src/dawn/native/d3d12/TextureD3D12.h
+++ b/src/dawn/native/d3d12/TextureD3D12.h
@@ -171,9 +171,9 @@
 
     Ref<d3d::KeyedMutex> mKeyedMutex;
 
-    // TODO(crbug.com/1515640): Remove these once Chromium has migrated to SharedTextureMemory.
+    // TODO(crbug.com/1515640): Remove wait fences once Chromium has migrated to
+    // SharedTextureMemory.
     std::vector<FenceAndSignalValue> mWaitFences;
-    std::optional<ExecutionSerial> mSignalFenceValue;
 
     bool mSwapChainTexture = false;
 
diff --git a/src/dawn/native/metal/SharedTextureMemoryMTL.h b/src/dawn/native/metal/SharedTextureMemoryMTL.h
index 7caf798..b77f887 100644
--- a/src/dawn/native/metal/SharedTextureMemoryMTL.h
+++ b/src/dawn/native/metal/SharedTextureMemoryMTL.h
@@ -73,6 +73,7 @@
     MaybeError BeginAccessImpl(TextureBase* texture,
                                const UnpackedPtr<BeginAccessDescriptor>& descriptor) override;
     ResultOrError<FenceAndSignalValue> EndAccessImpl(TextureBase* texture,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
     MaybeError CreateMtlTextures();
 
diff --git a/src/dawn/native/metal/SharedTextureMemoryMTL.mm b/src/dawn/native/metal/SharedTextureMemoryMTL.mm
index 2dc9ead..848889d 100644
--- a/src/dawn/native/metal/SharedTextureMemoryMTL.mm
+++ b/src/dawn/native/metal/SharedTextureMemoryMTL.mm
@@ -198,6 +198,7 @@
 
 ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     DAWN_TRY(state.ValidateSubset<>());
     DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceMTLSharedEvent),
@@ -205,9 +206,7 @@
                     wgpu::FeatureName::SharedFenceMTLSharedEvent);
     Ref<SharedFence> fence;
     DAWN_TRY_ASSIGN(fence, ToBackend(GetDevice()->GetQueue())->GetOrCreateSharedFence());
-    return FenceAndSignalValue{
-        std::move(fence),
-        static_cast<uint64_t>(texture->GetSharedResourceMemoryContents()->GetLastUsageSerial())};
+    return FenceAndSignalValue{std::move(fence), static_cast<uint64_t>(lastUsageSerial)};
 }
 
 MaybeError SharedTextureMemory::CreateMtlTextures() {
diff --git a/src/dawn/native/metal/TextureMTL.mm b/src/dawn/native/metal/TextureMTL.mm
index cb2187e..5f3e5d6 100644
--- a/src/dawn/native/metal/TextureMTL.mm
+++ b/src/dawn/native/metal/TextureMTL.mm
@@ -418,7 +418,6 @@
         SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents();
         if (contents != nullptr) {
             contents->AcquirePendingFences(&fences);
-            contents->SetLastUsageSerial(GetDevice()->GetQueue()->GetPendingCommandSerial());
         }
 
         if (!mWaitEvents.empty() || !fences.empty()) {
@@ -439,6 +438,7 @@
                                         value:fence.signaledValue];
         }
     }
+    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
 }
 
 Texture::Texture(DeviceBase* dev, const UnpackedPtr<TextureDescriptor>& desc)
diff --git a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
index cd2f959..d6b5d2a 100644
--- a/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
+++ b/src/dawn/native/vulkan/SharedTextureMemoryVk.cpp
@@ -971,6 +971,7 @@
 #if DAWN_PLATFORM_IS(FUCHSIA) || DAWN_PLATFORM_IS(LINUX)
 ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     wgpu::SType type;
     DAWN_TRY_ASSIGN(type,
@@ -1042,6 +1043,7 @@
 
 ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl(
     TextureBase* texture,
+    ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
     return DAWN_VALIDATION_ERROR("No shared fence features supported.");
 }
diff --git a/src/dawn/native/vulkan/SharedTextureMemoryVk.h b/src/dawn/native/vulkan/SharedTextureMemoryVk.h
index eb1206d..5912f6e 100644
--- a/src/dawn/native/vulkan/SharedTextureMemoryVk.h
+++ b/src/dawn/native/vulkan/SharedTextureMemoryVk.h
@@ -75,6 +75,7 @@
     MaybeError BeginAccessImpl(TextureBase* texture,
                                const UnpackedPtr<BeginAccessDescriptor>& descriptor) override;
     ResultOrError<FenceAndSignalValue> EndAccessImpl(TextureBase* texture,
+                                                     ExecutionSerial lastUsageSerial,
                                                      UnpackedPtr<EndAccessState>& state) override;
 
     Ref<RefCountedVkHandle<VkImage>> mVkImage;
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 42d2f1c..29958df 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -1213,6 +1213,8 @@
                                               size_t transitionBarrierStart) {
     DAWN_ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
 
+    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
+
     // transitionBarrierStart specify the index where barriers for current transition start in
     // the vector. barriers->size() - transitionBarrierStart is the number of barriers that we
     // have already added into the vector during current transition.
diff --git a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
index a7de84d..b580837 100644
--- a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
@@ -318,6 +318,17 @@
     wgpu::SharedBufferMemoryEndAccessState state;
     ASSERT_DEVICE_ERROR(memory.EndAccess(buffer2, &state));
 
+    wgpu::BufferDescriptor descriptor;
+    descriptor.size = kBufferSize;
+    descriptor.usage = wgpu::BufferUsage::CopyDst;
+    wgpu::Buffer dst = device.CreateBuffer(&descriptor);
+
+    // Use the buffer in a submit.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyBufferToBuffer(buffer, 0, dst, 0, kBufferSize);
+    wgpu::CommandBuffer commandBuffer = encoder.Finish();
+    queue.Submit(1, &commandBuffer);
+
     // Ensure that calling EndAccess on the correct buffer still returns a fence.
     memory.EndAccess(buffer, &state);
     ASSERT_EQ(state.fenceCount, static_cast<size_t>(1));
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
index 7a9621a..7b7bddc 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
@@ -27,6 +27,7 @@
 
 #include "dawn/tests/white_box/SharedTextureMemoryTests.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 #include <vector>
@@ -1625,6 +1626,116 @@
     }
 }
 
+// Test that BeginAccess without waiting on anything, followed by EndAccess
+// without using the texture, does not export any fences.
+TEST_P(SharedTextureMemoryTests, EndWithoutUse) {
+    for (const auto& memory : GetParam().mBackend->CreateSharedTextureMemories(device)) {
+        wgpu::Texture texture = memory.CreateTexture();
+
+        wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
+        beginDesc.initialized = true;
+        auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
+        memory.BeginAccess(texture, &beginDesc);
+
+        wgpu::SharedTextureMemoryEndAccessState endState = {};
+        auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
+        memory.EndAccess(texture, &endState);
+
+        EXPECT_EQ(endState.fenceCount, 0u);
+    }
+}
+
+// Test that BeginAccess, waiting on previous work, followed by EndAccess when the
+// texture isn't used at all, doesn't export any new fences from Dawn. It exports the
+// old fences.
+// If concurrent read is supported, use two read textures. The first EndAccess should
+// see no fences. The second should then export all the unacquired fences.
+TEST_P(SharedTextureMemoryTests, BeginEndWithoutUse) {
+    std::vector<wgpu::Device> devices = {device, CreateDevice()};
+
+    for (const auto& memories :
+         GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
+             devices, wgpu::TextureUsage::TextureBinding)) {
+        wgpu::Texture texture = memories[0].CreateTexture();
+
+        wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
+        beginDesc.concurrentRead = false;
+        beginDesc.initialized = false;
+        auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
+        memories[0].BeginAccess(texture, &beginDesc);
+
+        // Create a texture of the same size to use as the source content.
+        wgpu::TextureDescriptor texDesc;
+        texDesc.format = texture.GetFormat();
+        texDesc.size = {texture.GetWidth(), texture.GetHeight()};
+        texDesc.usage =
+            texture.GetUsage() | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
+        wgpu::Texture srcTex = devices[0].CreateTexture(&texDesc);
+
+        // Populate the source texture.
+        wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], srcTex);
+        devices[0].GetQueue().Submit(1, &commandBuffer);
+
+        // Copy from the source texture into `texture`.
+        {
+            wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder();
+            auto src = utils::CreateImageCopyTexture(srcTex);
+            auto dst = utils::CreateImageCopyTexture(texture);
+            encoder.CopyTextureToTexture(&src, &dst, &texDesc.size);
+            commandBuffer = encoder.Finish();
+        }
+        devices[0].GetQueue().Submit(1, &commandBuffer);
+
+        wgpu::SharedTextureMemoryEndAccessState endState = {};
+        auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
+        memories[0].EndAccess(texture, &endState);
+
+        // Import fences and texture to the the other device.
+        std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
+        for (size_t i = 0; i < endState.fenceCount; ++i) {
+            sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
+        }
+        beginDesc.fenceCount = endState.fenceCount;
+        beginDesc.fences = sharedFences.data();
+        beginDesc.signaledValues = endState.signaledValues;
+        // Do concurrent read if the backend supports it, and not Vulkan.
+        // Note that here, the "backend" means the handle type. So on Vulkan, sync fds do support
+        // concurrent reads on different devices. But, in Dawn's Vulkan backend within a single
+        // device, support is not implemented yet.
+        beginDesc.concurrentRead = GetParam().mBackend->SupportsConcurrentRead() && !IsVulkan();
+        beginDesc.initialized = endState.initialized;
+        backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
+
+        texDesc.usage = wgpu::TextureUsage::TextureBinding;
+        texture = memories[1].CreateTexture(&texDesc);
+
+        memories[1].BeginAccess(texture, &beginDesc);
+
+        // Prepare to sample the texture without submit, and end access.
+        wgpu::Texture colorTarget;
+        std::tie(commandBuffer, colorTarget) =
+            MakeCheckBySamplingCommandBuffer(devices[1], texture);
+        if (beginDesc.concurrentRead) {
+            // If concurrent read, make another texture, and begin+end access without using it.
+            wgpu::Texture noopTexture = memories[1].CreateTexture(&texDesc);
+            memories[1].BeginAccess(noopTexture, &beginDesc);
+            memories[1].EndAccess(noopTexture, &endState);
+            EXPECT_EQ(endState.fenceCount, 0u);
+        }
+        memories[1].EndAccess(texture, &endState);
+
+        // All of the fences should be identical.
+        EXPECT_EQ(endState.fenceCount, sharedFences.size());
+        for (size_t i = 0; i < endState.fenceCount; ++i) {
+            EXPECT_NE(std::find_if(sharedFences.begin(), sharedFences.end(),
+                                   [&](const auto& fence) {
+                                       return fence.Get() == endState.fences[i].Get();
+                                   }),
+                      sharedFences.end());
+        }
+    }
+}
+
 // Test rendering to a texture memory on one device, then sampling it using another device.
 // Encode the commands after performing BeginAccess.
 TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeAfterBeginAccess) {