Support SharedFenceSyncFD in the OpenGL backend.

Add a wrapper around EGLSync objects so that they can be ref counted
and shared between SharedFence objects and the GL queue.

Add synchronization points in GL command submission for waiting on
SharedFences. These locations mirror the Metal backend.

Bug: 42241435
Change-Id: I5450a899e9733c590d9e1deb409690d95648bbe8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/213934
Commit-Queue: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index 8089e30..4389544 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -757,6 +757,10 @@
       "opengl/SamplerGL.h",
       "opengl/ShaderModuleGL.cpp",
       "opengl/ShaderModuleGL.h",
+      "opengl/SharedFenceEGL.cpp",
+      "opengl/SharedFenceEGL.h",
+      "opengl/SharedFenceGL.cpp",
+      "opengl/SharedFenceGL.h",
       "opengl/SharedTextureMemoryEGL.cpp",
       "opengl/SharedTextureMemoryEGL.h",
       "opengl/SharedTextureMemoryGL.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 35dffb2..85169c0 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -611,6 +611,8 @@
         "opengl/RenderPipelineGL.h"
         "opengl/SamplerGL.h"
         "opengl/ShaderModuleGL.h"
+        "opengl/SharedFenceEGL.h"
+        "opengl/SharedFenceGL.h"
         "opengl/SharedTextureMemoryEGL.h"
         "opengl/SharedTextureMemoryGL.h"
         "opengl/SwapChainEGL.h"
@@ -642,6 +644,8 @@
         "opengl/RenderPipelineGL.cpp"
         "opengl/SamplerGL.cpp"
         "opengl/ShaderModuleGL.cpp"
+        "opengl/SharedFenceEGL.cpp"
+        "opengl/SharedFenceGL.cpp"
         "opengl/SharedTextureMemoryEGL.cpp"
         "opengl/SharedTextureMemoryGL.cpp"
         "opengl/SwapChainEGL.cpp"
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 3f0d3ea..91dc7b4 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -605,6 +605,10 @@
         switch (type) {
             case Command::BeginComputePass: {
                 mCommands.NextCommand<BeginComputePassCmd>();
+                for (TextureBase* texture :
+                     GetResourceUsages().computePasses[nextComputePassNumber].referencedTextures) {
+                    DAWN_TRY(ToBackend(texture)->SynchronizeTextureBeforeUse());
+                }
                 for (const SyncScopeResourceUsage& scope :
                      GetResourceUsages().computePasses[nextComputePassNumber].dispatchUsages) {
                     DAWN_TRY(LazyClearSyncScope(scope));
@@ -617,6 +621,10 @@
 
             case Command::BeginRenderPass: {
                 auto* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
+                for (TextureBase* texture :
+                     this->GetResourceUsages().renderPasses[nextRenderPassNumber].textures) {
+                    DAWN_TRY(ToBackend(texture)->SynchronizeTextureBeforeUse());
+                }
                 DAWN_TRY(
                     LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber]));
                 LazyClearRenderPassAttachments(cmd);
@@ -660,14 +668,17 @@
                 auto& src = copy->source;
                 auto& dst = copy->destination;
                 Buffer* buffer = ToBackend(src.buffer.Get());
+                Texture* texture = ToBackend(dst.texture.Get());
+
+                DAWN_TRY(texture->SynchronizeTextureBeforeUse());
 
                 buffer->EnsureDataInitialized();
                 SubresourceRange range = GetSubresourcesAffectedByCopy(dst, copy->copySize);
-                if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize, dst.mipLevel,
+                if (IsCompleteSubresourceCopiedTo(texture, copy->copySize, dst.mipLevel,
                                                   dst.aspect)) {
-                    dst.texture->SetIsSubresourceContentInitialized(true, range);
+                    texture->SetIsSubresourceContentInitialized(true, range);
                 } else {
-                    DAWN_TRY(ToBackend(dst.texture)->EnsureSubresourceContentInitialized(range));
+                    DAWN_TRY(texture->EnsureSubresourceContentInitialized(range));
                 }
 
                 gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
@@ -706,6 +717,7 @@
                 }
 
                 buffer->EnsureDataInitializedAsDestination(copy);
+                DAWN_TRY(texture->SynchronizeTextureBeforeUse());
 
                 SubresourceRange subresources = GetSubresourcesAffectedByCopy(src, copy->copySize);
                 DAWN_TRY(texture->EnsureSubresourceContentInitialized(subresources));
@@ -825,6 +837,9 @@
                 Texture* srcTexture = ToBackend(src.texture.Get());
                 Texture* dstTexture = ToBackend(dst.texture.Get());
 
+                DAWN_TRY(srcTexture->SynchronizeTextureBeforeUse());
+                DAWN_TRY(dstTexture->SynchronizeTextureBeforeUse());
+
                 SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
                 SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index dd9568f..bc6df99 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -48,6 +48,7 @@
 #include "dawn/native/opengl/RenderPipelineGL.h"
 #include "dawn/native/opengl/SamplerGL.h"
 #include "dawn/native/opengl/ShaderModuleGL.h"
+#include "dawn/native/opengl/SharedFenceEGL.h"
 #include "dawn/native/opengl/SharedTextureMemoryEGL.h"
 #include "dawn/native/opengl/SwapChainEGL.h"
 #include "dawn/native/opengl/TextureGL.h"
@@ -308,6 +309,25 @@
     }
 }
 
+ResultOrError<Ref<SharedFenceBase>> Device::ImportSharedFenceImpl(
+    const SharedFenceDescriptor* descriptor) {
+    UnpackedPtr<SharedFenceDescriptor> unpacked;
+    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
+
+    wgpu::SType type;
+    DAWN_TRY_ASSIGN(type, (unpacked.ValidateBranches<Branch<SharedFenceSyncFDDescriptor>>()));
+
+    switch (type) {
+        case wgpu::SType::SharedFenceSyncFDDescriptor:
+            DAWN_INVALID_IF(!HasFeature(Feature::SharedFenceSyncFD), "%s is not enabled.",
+                            wgpu::FeatureName::SharedFenceSyncFD);
+            return SharedFenceEGL::Create(this, descriptor->label,
+                                          unpacked.Get<SharedFenceSyncFDDescriptor>());
+        default:
+            DAWN_UNREACHABLE();
+    }
+}
+
 MaybeError Device::ValidateTextureCanBeWrapped(const UnpackedPtr<TextureDescriptor>& descriptor) {
     DAWN_INVALID_IF(descriptor->dimension != wgpu::TextureDimension::e2D,
                     "Texture dimension (%s) is not %s.", descriptor->dimension,
@@ -415,7 +435,7 @@
 }
 
 MaybeError Device::TickImpl() {
-    ToBackend(GetQueue())->SubmitFenceSync();
+    DAWN_TRY(ToBackend(GetQueue())->SubmitFenceSync());
     return {};
 }
 
@@ -431,6 +451,7 @@
                                                 const TextureDataLayout& src,
                                                 const TextureCopy& dst,
                                                 const Extent3D& copySizePixels) {
+    // If implemented, be sure to call SynchronizeTextureBeforeUse on the destination texture.
     return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer to texture.");
 }
 
diff --git a/src/dawn/native/opengl/DeviceGL.h b/src/dawn/native/opengl/DeviceGL.h
index 13a551c..0b75b37 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -146,6 +146,8 @@
         const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     ResultOrError<Ref<SharedTextureMemoryBase>> ImportSharedTextureMemoryImpl(
         const SharedTextureMemoryDescriptor* descriptor) override;
+    ResultOrError<Ref<SharedFenceBase>> ImportSharedFenceImpl(
+        const SharedFenceDescriptor* descriptor) override;
 
     GLenum GetBGRAInternalFormat(const OpenGLFunctions& gl) const;
     void DestroyImpl() override;
diff --git a/src/dawn/native/opengl/EGLFunctions.cpp b/src/dawn/native/opengl/EGLFunctions.cpp
index 4c0d4bd..128c9f1 100644
--- a/src/dawn/native/opengl/EGLFunctions.cpp
+++ b/src/dawn/native/opengl/EGLFunctions.cpp
@@ -62,7 +62,7 @@
     //
     {EGLExt::ClientExtensions, "EGL_EXT_client_extensions", EGL1_5, ExtType::Client},
     {EGLExt::PlatformBase, "EGL_EXT_platform_base", EGL1_5, ExtType::Client},
-    // TODO(344606402): Mark as promoted again when ANGLE supports EGL_SYNC_FENCE in EGL 1.5
+    // EGL_KHR_fence_sync cannot be marked as promoted to core due to different function prototypes.
     {EGLExt::FenceSync, "EGL_KHR_fence_sync", NeverPromoted, ExtType::Display},
     {EGLExt::CLEvent2, "EGL_KHR_cl_event2", EGL1_5, ExtType::Display},
     {EGLExt::WaitSync, "EGL_KHR_wait_sync", EGL1_5, ExtType::Display},
@@ -79,6 +79,7 @@
      ExtType::Client},
     {EGLExt::GLColorSpace, "EGL_KHR_gl_colorspace", EGL1_5, ExtType::Display},
     {EGLExt::SurfacelessContext, "EGL_KHR_surfaceless_context", EGL1_5, ExtType::Display},
+    {EGLExt::NativeFenceSync, "EGL_ANDROID_native_fence_sync", NeverPromoted, ExtType::Display},
 
     {EGLExt::DisplayTextureShareGroup, "EGL_ANGLE_display_texture_share_group", NeverPromoted,
      ExtType::Display},
@@ -210,13 +211,6 @@
     } else {
         // Load display extensions that would otherwise be promoted to EGL 1.5.
 
-        if (HasExt(EGLExt::FenceSync)) {
-            GET_PROC_WITH_NAME(ClientWaitSync, "eglClientWaitSyncKHR");
-            GET_PROC_WITH_NAME(CreateSync, "eglCreateSyncKHR");
-            GET_PROC_WITH_NAME(DestroySync, "eglDestroySyncKHR");
-            GET_PROC_WITH_NAME(ClientWaitSync, "eglClientWaitSyncKHR");
-        }
-
         if (HasExt(EGLExt::ImageBase)) {
             GET_PROC_WITH_NAME(CreateImage, "eglCreateImageKHR");
             GET_PROC_WITH_NAME(DestroyImage, "eglDestroyImageKHR");
@@ -234,6 +228,13 @@
 
     // Other extensions
 
+    if (HasExt(EGLExt::FenceSync)) {
+        GET_PROC_WITH_NAME(ClientWaitSyncKHR, "eglClientWaitSyncKHR");
+        GET_PROC_WITH_NAME(CreateSyncKHR, "eglCreateSyncKHR");
+        GET_PROC_WITH_NAME(DestroySyncKHR, "eglDestroySyncKHR");
+        GET_PROC_WITH_NAME(ClientWaitSyncKHR, "eglClientWaitSyncKHR");
+    }
+
     if (HasExt(EGLExt::ReusableSync)) {
         GET_PROC_WITH_NAME(SignalSync, "eglSignalSyncKHR");
     }
@@ -242,6 +243,10 @@
         GET_PROC_WITH_NAME(GetNativeClientBuffer, "eglGetNativeClientBufferANDROID");
     }
 
+    if (HasExt(EGLExt::NativeFenceSync)) {
+        GET_PROC_WITH_NAME(DupNativeFenceFD, "eglDupNativeFenceFDANDROID");
+    }
+
     return {};
 }
 
@@ -253,6 +258,10 @@
     return mMinorVersion;
 }
 
+bool EGLFunctions::IsAtLeastVersion(uint32_t major, uint32_t minor) const {
+    return std::tie(major, minor) >= std::tie(mMajorVersion, mMinorVersion);
+}
+
 bool EGLFunctions::HasExt(EGLExt extension) const {
     return mExtensions[extension];
 }
diff --git a/src/dawn/native/opengl/EGLFunctions.h b/src/dawn/native/opengl/EGLFunctions.h
index 68ef1d1..eb1538e 100644
--- a/src/dawn/native/opengl/EGLFunctions.h
+++ b/src/dawn/native/opengl/EGLFunctions.h
@@ -39,7 +39,6 @@
     // Promoted to EGL 1.5
     ClientExtensions,
     PlatformBase,
-    FenceSync,
     CLEvent2,
     WaitSync,
     ImageBase,
@@ -55,11 +54,13 @@
     SurfacelessContext,
 
     // Other extensions,
+    FenceSync,  // Not marked as promoted due to different function prototypes
     DisplayTextureShareGroup,
     ReusableSync,
     NoConfigContext,
     PixelFormatFloat,
     GLColorspace,
+    NativeFenceSync,  // EGL_ANDROID_native_fence_sync
 
     // EGL image creation extensions
     ImageNativeBuffer,      // EGL_ANDROID_image_native_buffer
@@ -77,6 +78,7 @@
 
     uint32_t GetMajorVersion() const;
     uint32_t GetMinorVersion() const;
+    bool IsAtLeastVersion(uint32_t major, uint32_t minor) const;
     bool HasExt(EGLExt extension) const;
 
     // EGL 1.0
@@ -136,12 +138,23 @@
     PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC CreatePlatformPixmapSurface;
     PFNEGLWAITSYNCPROC WaitSync;
 
+    // EGL_KHR_fence_sync
+    // NOTE: These functions use attribute lists with EGLint but the core versions use EGLattrib.
+    // They are not compatible.
+    PFNEGLCREATESYNCKHRPROC CreateSyncKHR;
+    PFNEGLDESTROYSYNCKHRPROC DestroySyncKHR;
+    PFNEGLCLIENTWAITSYNCKHRPROC ClientWaitSyncKHR;
+    PFNEGLGETSYNCATTRIBKHRPROC GetSyncAttribKHR;
+
     // EGL_KHR_reusable_sync
     PFNEGLSIGNALSYNCKHRPROC SignalSync;
 
     // EGL_ANDROID_get_native_client_buffer
     PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC GetNativeClientBuffer;
 
+    // EGL_ANDROID_native_fence_sync
+    PFNEGLDUPNATIVEFENCEFDANDROIDPROC DupNativeFenceFD;
+
   private:
     MaybeError LoadClientExtensions();
 
diff --git a/src/dawn/native/opengl/Forward.h b/src/dawn/native/opengl/Forward.h
index e284c96..4b82bf3 100644
--- a/src/dawn/native/opengl/Forward.h
+++ b/src/dawn/native/opengl/Forward.h
@@ -46,6 +46,8 @@
 class RenderPipeline;
 class Sampler;
 class ShaderModule;
+class SharedFence;
+class SharedTextureMemory;
 class SwapChain;
 class Texture;
 class TextureView;
@@ -64,6 +66,8 @@
     using RenderPipelineType = RenderPipeline;
     using SamplerType = Sampler;
     using ShaderModuleType = ShaderModule;
+    using SharedFenceType = SharedFence;
+    using SharedTextureMemoryType = SharedTextureMemory;
     using SwapChainType = SwapChain;
     using TextureType = Texture;
     using TextureViewType = TextureView;
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index ec61445..2793016 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -257,6 +257,11 @@
         EnableFeature(dawn::native::Feature::SharedTextureMemoryAHardwareBuffer);
     }
 
+    if (mDisplay->egl.HasExt(EGLExt::NativeFenceSync) && mDisplay->egl.HasExt(EGLExt::WaitSync) &&
+        mFunctions.IsGLExtensionSupported("GL_OES_EGL_sync")) {
+        EnableFeature(dawn::native::Feature::SharedFenceSyncFD);
+    }
+
     // Non-zero baseInstance requires at least desktop OpenGL 4.2, and it is not supported in
     // OpenGL ES OpenGL:
     // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawElementsIndirect.xhtml
diff --git a/src/dawn/native/opengl/QueueGL.cpp b/src/dawn/native/opengl/QueueGL.cpp
index e23aa73..06c6a0f 100644
--- a/src/dawn/native/opengl/QueueGL.cpp
+++ b/src/dawn/native/opengl/QueueGL.cpp
@@ -34,6 +34,8 @@
 #include "dawn/native/opengl/CommandBufferGL.h"
 #include "dawn/native/opengl/DeviceGL.h"
 #include "dawn/native/opengl/EGLFunctions.h"
+#include "dawn/native/opengl/PhysicalDeviceGL.h"
+#include "dawn/native/opengl/SharedFenceEGL.h"
 #include "dawn/native/opengl/TextureGL.h"
 #include "dawn/platform/DawnPlatform.h"
 #include "dawn/platform/tracing/TraceEvent.h"
@@ -47,8 +49,15 @@
 Queue::Queue(Device* device, const QueueDescriptor* descriptor) : QueueBase(device, descriptor) {
     const auto& egl = device->GetEGL(false);
 
-    DAWN_ASSERT(egl.HasExt(EGLExt::FenceSync) || egl.HasExt(EGLExt::ReusableSync));
-    mEGLSyncType = egl.HasExt(EGLExt::FenceSync) ? EGL_SYNC_FENCE : EGL_SYNC_REUSABLE_KHR;
+    if (egl.HasExt(EGLExt::NativeFenceSync)) {
+        mEGLSyncType = EGL_SYNC_NATIVE_FENCE_ANDROID;
+    } else if (egl.HasExt(EGLExt::FenceSync)) {
+        mEGLSyncType = EGL_SYNC_FENCE;
+    } else if (egl.HasExt(EGLExt::ReusableSync)) {
+        mEGLSyncType = EGL_SYNC_REUSABLE_KHR;
+    } else {
+        DAWN_UNREACHABLE();
+    }
 }
 
 MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
@@ -146,99 +155,120 @@
     mHasPendingCommands = true;
 }
 
-GLenum Queue::ClientWaitSync(EGLSync sync, Nanoseconds timeout) {
-    const Device* device = ToBackend(GetDevice());
-    const EGLFunctions& egl = device->GetEGL(/*makeCurrent=*/false);
-
-    return egl.ClientWaitSync(device->GetEGLDisplay(), sync, EGL_SYNC_FLUSH_COMMANDS_BIT,
-                              uint64_t(timeout));
-}
-
 ResultOrError<bool> Queue::WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) {
     // Search for the first fence >= serial.
     return mFencesInFlight.Use([&](auto fencesInFlight) -> ResultOrError<bool> {
-        EGLSync waitSync = nullptr;
+        Ref<WrappedEGLSync> sync;
         for (auto it = fencesInFlight->begin(); it != fencesInFlight->end(); ++it) {
             if (it->second >= serial) {
-                waitSync = it->first;
+                sync = it->first;
                 break;
             }
         }
-        if (waitSync == nullptr) {
+        if (sync == nullptr) {
             // Fence sync not found. This serial must have already completed.
             // Return a success status.
             return true;
         }
 
         // Wait for the fence sync.
-        GLenum result = ClientWaitSync(waitSync, timeout);
+        GLenum result;
+        DAWN_TRY_ASSIGN(result, sync->ClientWait(EGL_SYNC_FLUSH_COMMANDS_BIT, timeout));
+
         switch (result) {
             case EGL_TIMEOUT_EXPIRED:
                 return false;
             case EGL_CONDITION_SATISFIED:
                 return true;
-            case EGL_FALSE:
-                return DAWN_INTERNAL_ERROR("glClientWaitSync failed");
             default:
                 DAWN_UNREACHABLE();
         }
     });
 }
 
-void Queue::SubmitFenceSync() {
-    mFencesInFlight.Use([&](auto fencesInFlight) {
+MaybeError Queue::SubmitFenceSync() {
+    return mFencesInFlight.Use([&](auto fencesInFlight) -> MaybeError {
         if (!mHasPendingCommands) {
-            return;
+            return {};
         }
-        const Device* device = ToBackend(GetDevice());
-        const EGLFunctions& egl = device->GetEGL(/*makeCurrent=*/true);
+        DisplayEGL* display = ToBackend(GetDevice()->GetPhysicalDevice())->GetDisplay();
 
-        EGLSync sync = egl.CreateSync(device->GetEGLDisplay(), mEGLSyncType, nullptr);
-        DAWN_ASSERT(sync != EGL_NO_SYNC);
+        Ref<WrappedEGLSync> sync;
+        DAWN_TRY_ASSIGN(sync, WrappedEGLSync::Create(display, mEGLSyncType, nullptr));
 
         // Signal the sync if it is EGL_SYNC_REUSABLE_KHR. On the other hand,
         // EGL_SYNC_FENCE_KHR has its signal scheduled on creation.
         if (mEGLSyncType == EGL_SYNC_REUSABLE_KHR) {
-            EGLBoolean status = egl.SignalSync(device->GetEGLDisplay(), sync, EGL_SIGNALED);
-            DAWN_ASSERT(status == EGL_TRUE);
+            DAWN_TRY(sync->Signal(EGL_SIGNALED));
         }
 
         IncrementLastSubmittedCommandSerial();
         fencesInFlight->emplace_back(sync, GetLastSubmittedCommandSerial());
         mHasPendingCommands = false;
+        return {};
     });
 }
 
+ResultOrError<Ref<SharedFence>> Queue::GetOrCreateSharedFence(ExecutionSerial lastUsageSerial) {
+    DAWN_ASSERT(mEGLSyncType == EGL_SYNC_NATIVE_FENCE_ANDROID);
+
+    // Look for an existing sync that can represent this serial.
+    Ref<WrappedEGLSync> sync = mFencesInFlight.Use([&](auto fencesInFlight) -> Ref<WrappedEGLSync> {
+        for (auto it = fencesInFlight->begin(); it != fencesInFlight->end(); ++it) {
+            if (it->second >= lastUsageSerial) {
+                return it->first;
+            }
+        }
+        return {};
+    });
+
+    Device* device = ToBackend(GetDevice());
+
+    if (sync == nullptr) {
+        DisplayEGL* display = ToBackend(device->GetPhysicalDevice())->GetDisplay();
+        DAWN_TRY_ASSIGN(sync, WrappedEGLSync::Create(display, mEGLSyncType, nullptr));
+    }
+
+    // If we are sharing this sync externally, make sure to flush all commands.
+    // The FD cannot be queried before the flush and clients may hang if they try to wait on the
+    // sync.
+    const OpenGLFunctions& gl = device->GetGL();
+    gl.Flush();
+
+    EGLint fd;
+    DAWN_TRY_ASSIGN(fd, sync->DupFD());
+
+    DAWN_ASSERT(sync != nullptr);
+    return AcquireRef(new SharedFenceEGL(ToBackend(GetDevice()), "Internal EGLSync",
+                                         wgpu::SharedFenceType::SyncFD, SystemHandle::Acquire(fd),
+                                         sync));
+}
+
 bool Queue::HasPendingCommands() const {
     return mHasPendingCommands;
 }
 
 MaybeError Queue::SubmitPendingCommands() {
-    SubmitFenceSync();
+    DAWN_TRY(SubmitFenceSync());
     return {};
 }
 
 ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() {
-    const Device* device = ToBackend(GetDevice());
-    const EGLFunctions& egl = device->GetEGL(/*makeCurrent=*/false);
-    EGLDisplay display = device->GetEGLDisplay();
-
-    return mFencesInFlight.Use([&](auto fencesInFlight) {
+    return mFencesInFlight.Use([&](auto fencesInFlight) -> ResultOrError<ExecutionSerial> {
         ExecutionSerial fenceSerial{0};
         while (!fencesInFlight->empty()) {
             auto [sync, tentativeSerial] = fencesInFlight->front();
 
             // Fence are added in order, so we can stop searching as soon
             // as we see one that's not ready.
-            GLenum result = ClientWaitSync(sync, Nanoseconds(0));
+            GLenum result;
+            DAWN_TRY_ASSIGN(result, sync->ClientWait(EGL_SYNC_FLUSH_COMMANDS_BIT, Nanoseconds(0)));
             if (result == EGL_TIMEOUT_EXPIRED) {
                 return fenceSerial;
             }
             // Update fenceSerial since fence is ready.
             fenceSerial = tentativeSerial;
 
-            egl.DestroySync(display, sync);
-
             fencesInFlight->pop_front();
 
             DAWN_ASSERT(fenceSerial > GetCompletedCommandSerial());
diff --git a/src/dawn/native/opengl/QueueGL.h b/src/dawn/native/opengl/QueueGL.h
index 6c9787b..f57080c 100644
--- a/src/dawn/native/opengl/QueueGL.h
+++ b/src/dawn/native/opengl/QueueGL.h
@@ -32,18 +32,24 @@
 #include <utility>
 
 #include "dawn/native/Queue.h"
+#include "dawn/native/opengl/UtilsEGL.h"
 #include "dawn/native/opengl/opengl_platform.h"
 
 namespace dawn::native::opengl {
 
 class Device;
+class SharedFence;
 
 class Queue final : public QueueBase {
   public:
     static ResultOrError<Ref<Queue>> Create(Device* device, const QueueDescriptor* descriptor);
 
     void OnGLUsed();
-    void SubmitFenceSync();
+    MaybeError SubmitFenceSync();
+
+    // Returns a shared fence which represents work done up to lastUsageSerial. It may be a cached
+    // fence or newly created.
+    ResultOrError<Ref<SharedFence>> GetOrCreateSharedFence(ExecutionSerial lastUsageSerial);
 
   private:
     Queue(Device* device, const QueueDescriptor* descriptor);
@@ -59,8 +65,6 @@
                                 const TextureDataLayout& dataLayout,
                                 const Extent3D& writeSizePixel) override;
 
-    GLenum ClientWaitSync(EGLSyncKHR sync, Nanoseconds timeout);
-
     ResultOrError<bool> WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) override;
 
     bool HasPendingCommands() const override;
@@ -70,7 +74,7 @@
     MaybeError WaitForIdleForDestruction() override;
 
     uint32_t mEGLSyncType;
-    MutexProtected<std::deque<std::pair<EGLSyncKHR, ExecutionSerial>>> mFencesInFlight;
+    MutexProtected<std::deque<std::pair<Ref<WrappedEGLSync>, ExecutionSerial>>> mFencesInFlight;
 
     // Has pending GL commands which are not associated with a fence.
     bool mHasPendingCommands = false;
diff --git a/src/dawn/native/opengl/SharedFenceEGL.cpp b/src/dawn/native/opengl/SharedFenceEGL.cpp
new file mode 100644
index 0000000..26d18ec
--- /dev/null
+++ b/src/dawn/native/opengl/SharedFenceEGL.cpp
@@ -0,0 +1,113 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/opengl/SharedFenceEGL.h"
+
+#include <utility>
+
+#include "dawn/native/ChainUtils.h"
+#include "dawn/native/SystemHandle.h"
+#include "dawn/native/opengl/DeviceGL.h"
+#include "dawn/native/opengl/EGLFunctions.h"
+#include "dawn/native/opengl/PhysicalDeviceGL.h"
+
+namespace dawn::native::opengl {
+ResultOrError<Ref<SharedFence>> SharedFenceEGL::Create(
+    Device* device,
+    StringView label,
+    const SharedFenceSyncFDDescriptor* descriptor) {
+#if DAWN_PLATFORM_IS(POSIX)
+    DAWN_INVALID_IF(descriptor->handle < 0, "File descriptor (%d) was invalid.",
+                    descriptor->handle);
+
+    SystemHandle handleForSyncCreation;
+    DAWN_TRY_ASSIGN(handleForSyncCreation, SystemHandle::Duplicate(descriptor->handle));
+
+    const EGLint attribs[] = {
+        EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
+        handleForSyncCreation.Get(),
+        EGL_NONE,
+    };
+
+    DisplayEGL* display = ToBackend(device->GetPhysicalDevice())->GetDisplay();
+
+    Ref<WrappedEGLSync> sync;
+    DAWN_TRY_ASSIGN(sync, WrappedEGLSync::Create(display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs));
+
+    // If EGLSync creation succeeded, the sync now owns the handle.
+    handleForSyncCreation.Detach();
+
+    EGLint fdForSharedFence;
+    DAWN_TRY_ASSIGN(fdForSharedFence, sync->DupFD());
+
+    auto fence = AcquireRef(new SharedFenceEGL(device, label, wgpu::SharedFenceType::SyncFD,
+                                               SystemHandle::Acquire(fdForSharedFence), sync));
+    return fence;
+#else
+    DAWN_UNREACHABLE();
+#endif
+}
+
+SharedFenceEGL::SharedFenceEGL(Device* device,
+                               StringView label,
+                               wgpu::SharedFenceType type,
+                               SystemHandle&& handle,
+                               Ref<WrappedEGLSync> sync)
+    : SharedFence(device, label), mType(type), mHandle(std::move(handle)), mSync(sync) {}
+
+MaybeError SharedFenceEGL::ServerWait(uint64_t signaledValue) {
+    // All GL sync objects are binary, this should be validated at SharedTextureMemory::BeginAccess.
+    DAWN_ASSERT(signaledValue == 1);
+
+    DAWN_TRY(mSync->Wait());
+    return {};
+}
+
+MaybeError SharedFenceEGL::ExportInfoImpl(UnpackedPtr<SharedFenceExportInfo>& info) const {
+    info->type = mType;
+
+    switch (mType) {
+#if DAWN_PLATFORM_IS(POSIX)
+        case wgpu::SharedFenceType::SyncFD:
+            DAWN_TRY(info.ValidateSubset<SharedFenceSyncFDExportInfo>());
+            {
+                SharedFenceSyncFDExportInfo* exportInfo = info.Get<SharedFenceSyncFDExportInfo>();
+                if (exportInfo != nullptr) {
+                    DAWN_ASSERT(mHandle.IsValid());
+                    exportInfo->handle = mHandle.Get();
+                }
+            }
+            break;
+#endif
+        default:
+            DAWN_UNREACHABLE();
+    }
+
+    return {};
+}
+
+}  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/SharedFenceEGL.h b/src/dawn/native/opengl/SharedFenceEGL.h
new file mode 100644
index 0000000..ce83afe
--- /dev/null
+++ b/src/dawn/native/opengl/SharedFenceEGL.h
@@ -0,0 +1,63 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_EGL_H_
+#define SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_EGL_H_
+
+#include "dawn/native/SystemHandle.h"
+#include "dawn/native/opengl/SharedFenceGL.h"
+#include "dawn/native/opengl/UtilsEGL.h"
+
+namespace dawn::native::opengl {
+
+class Device;
+
+class SharedFenceEGL : public SharedFence {
+  public:
+    static ResultOrError<Ref<SharedFence>> Create(Device* device,
+                                                  StringView label,
+                                                  const SharedFenceSyncFDDescriptor* descriptor);
+
+    SharedFenceEGL(Device* device,
+                   StringView label,
+                   wgpu::SharedFenceType type,
+                   SystemHandle&& handle,
+                   Ref<WrappedEGLSync> sync);
+
+    MaybeError ServerWait(uint64_t signaledValue) override;
+
+  private:
+    MaybeError ExportInfoImpl(UnpackedPtr<SharedFenceExportInfo>& info) const override;
+
+    wgpu::SharedFenceType mType;
+    SystemHandle mHandle;
+    Ref<WrappedEGLSync> mSync;
+};
+
+}  // namespace dawn::native::opengl
+
+#endif  // SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_EGL_H_
diff --git a/src/dawn/native/opengl/SharedFenceGL.cpp b/src/dawn/native/opengl/SharedFenceGL.cpp
new file mode 100644
index 0000000..f841f72
--- /dev/null
+++ b/src/dawn/native/opengl/SharedFenceGL.cpp
@@ -0,0 +1,36 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/opengl/SharedFenceGL.h"
+
+#include "dawn/native/opengl/DeviceGL.h"
+
+namespace dawn::native::opengl {
+
+SharedFence::SharedFence(Device* device, StringView label) : SharedFenceBase(device, label) {}
+
+}  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/SharedFenceGL.h b/src/dawn/native/opengl/SharedFenceGL.h
new file mode 100644
index 0000000..ccb33c3
--- /dev/null
+++ b/src/dawn/native/opengl/SharedFenceGL.h
@@ -0,0 +1,48 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_GL_H_
+#define SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_GL_H_
+
+#include "dawn/native/SharedFence.h"
+#include "dawn/native/opengl/opengl_platform.h"
+
+namespace dawn::native::opengl {
+
+class Device;
+
+class SharedFence : public SharedFenceBase {
+  public:
+    virtual MaybeError ServerWait(uint64_t signaledValue) = 0;
+
+  protected:
+    SharedFence(Device* device, StringView label);
+};
+
+}  // namespace dawn::native::opengl
+
+#endif  // SRC_DAWN_NATIVE_OPENGL_SHARED_FENCE_GL_H_
diff --git a/src/dawn/native/opengl/SharedTextureMemoryGL.cpp b/src/dawn/native/opengl/SharedTextureMemoryGL.cpp
index 0b8f21c..0ee5a8e 100644
--- a/src/dawn/native/opengl/SharedTextureMemoryGL.cpp
+++ b/src/dawn/native/opengl/SharedTextureMemoryGL.cpp
@@ -29,7 +29,10 @@
 
 #include <utility>
 
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/opengl/DeviceGL.h"
+#include "dawn/native/opengl/QueueGL.h"
+#include "dawn/native/opengl/SharedFenceGL.h"
 #include "dawn/native/opengl/TextureGL.h"
 
 namespace dawn::native::opengl {
@@ -47,6 +50,28 @@
 MaybeError SharedTextureMemory::BeginAccessImpl(
     TextureBase* texture,
     const UnpackedPtr<BeginAccessDescriptor>& descriptor) {
+    DAWN_TRY(descriptor.ValidateSubset<>());
+    for (size_t i = 0; i < descriptor->fenceCount; ++i) {
+        SharedFenceBase* fence = descriptor->fences[i];
+
+        SharedFenceExportInfo exportInfo;
+        DAWN_TRY(fence->ExportInfo(&exportInfo));
+        switch (exportInfo.type) {
+            case wgpu::SharedFenceType::SyncFD:
+                DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceSyncFD),
+                                "Required feature (%s) for %s is missing.",
+                                wgpu::FeatureName::SharedFenceSyncFD,
+                                wgpu::SharedFenceType::SyncFD);
+                break;
+            default:
+                return DAWN_VALIDATION_ERROR("Unsupported fence type %s.", exportInfo.type);
+        }
+
+        // All GL sync objects are binary.
+        DAWN_INVALID_IF(descriptor->signaledValues[i] != 1, "%s signaled value (%u) was not 1.",
+                        fence, descriptor->signaledValues[i]);
+    }
+
     return {};
 }
 
@@ -54,7 +79,13 @@
     TextureBase* texture,
     ExecutionSerial lastUsageSerial,
     UnpackedPtr<EndAccessState>& state) {
-    return DAWN_VALIDATION_ERROR("No shared fence features supported.");
+    DAWN_TRY(state.ValidateSubset<>());
+    DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceSyncFD),
+                    "Required feature (%s) is missing.", wgpu::FeatureName::SharedFenceSyncFD);
+    Ref<SharedFence> fence;
+    DAWN_TRY_ASSIGN(fence,
+                    ToBackend(GetDevice()->GetQueue())->GetOrCreateSharedFence(lastUsageSerial));
+    return FenceAndSignalValue{std::move(fence), 1};
 }
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/TextureGL.cpp b/src/dawn/native/opengl/TextureGL.cpp
index 1fd249d..7e91ab4 100644
--- a/src/dawn/native/opengl/TextureGL.cpp
+++ b/src/dawn/native/opengl/TextureGL.cpp
@@ -34,10 +34,13 @@
 #include "dawn/common/Constants.h"
 #include "dawn/common/Math.h"
 #include "dawn/native/ChainUtils.h"
+#include "dawn/native/Device.h"
 #include "dawn/native/EnumMaskIterator.h"
+#include "dawn/native/Queue.h"
 #include "dawn/native/opengl/BufferGL.h"
 #include "dawn/native/opengl/CommandBufferGL.h"
 #include "dawn/native/opengl/DeviceGL.h"
+#include "dawn/native/opengl/SharedFenceGL.h"
 #include "dawn/native/opengl/SharedTextureMemoryGL.h"
 #include "dawn/native/opengl/UtilsGL.h"
 
@@ -525,6 +528,21 @@
     return {};
 }
 
+MaybeError Texture::SynchronizeTextureBeforeUse() {
+    SharedTextureMemoryBase::PendingFenceList fences;
+    SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents();
+    if (contents != nullptr) {
+        contents->AcquirePendingFences(&fences);
+    }
+    for (const auto& fenceAndSignaledValue : fences) {
+        SharedFence* fence = ToBackend(fenceAndSignaledValue.object).Get();
+        DAWN_TRY(fence->ServerWait(fenceAndSignaledValue.signaledValue));
+    }
+
+    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();
+    return {};
+}
+
 // TextureView
 
 TextureView::TextureView(TextureBase* texture, const UnpackedPtr<TextureViewDescriptor>& descriptor)
diff --git a/src/dawn/native/opengl/TextureGL.h b/src/dawn/native/opengl/TextureGL.h
index d63a4f8..c1e247c 100644
--- a/src/dawn/native/opengl/TextureGL.h
+++ b/src/dawn/native/opengl/TextureGL.h
@@ -62,6 +62,8 @@
 
     MaybeError EnsureSubresourceContentInitialized(const SubresourceRange& range);
 
+    MaybeError SynchronizeTextureBeforeUse();
+
   private:
     Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor);
     ~Texture() override;
diff --git a/src/dawn/native/opengl/UtilsEGL.cpp b/src/dawn/native/opengl/UtilsEGL.cpp
index fd4b696..06a334e 100644
--- a/src/dawn/native/opengl/UtilsEGL.cpp
+++ b/src/dawn/native/opengl/UtilsEGL.cpp
@@ -28,11 +28,25 @@
 #include "dawn/native/opengl/UtilsEGL.h"
 
 #include <string>
+#include <vector>
 
 #include "dawn/native/opengl/EGLFunctions.h"
 
 namespace dawn::native::opengl {
 
+namespace {
+std::vector<EGLAttrib> ConvertEGLIntParameterListToEGLAttrib(const EGLint* intAttribs) {
+    std::vector<EGLAttrib> attribs;
+    if (intAttribs) {
+        for (const EGLint* curAttrib = intAttribs; *curAttrib != EGL_NONE; curAttrib++) {
+            attribs.push_back(static_cast<EGLAttrib>(*curAttrib));
+        }
+    }
+    attribs.push_back(EGL_NONE);
+    return attribs;
+}
+}  // namespace
+
 const char* EGLErrorAsString(EGLint error) {
     switch (error) {
         case EGL_SUCCESS:
@@ -71,7 +85,7 @@
 }
 
 MaybeError CheckEGL(const EGLFunctions& egl, EGLBoolean result, const char* context) {
-    if (DAWN_LIKELY(result == EGL_TRUE)) {
+    if (DAWN_LIKELY(result != EGL_FALSE)) {
         return {};
     }
     EGLint error = egl.GetError();
@@ -85,4 +99,83 @@
     }
 }
 
+ResultOrError<Ref<WrappedEGLSync>> WrappedEGLSync::Create(DisplayEGL* display,
+                                                          EGLenum type,
+                                                          const EGLint* attribs) {
+    const EGLFunctions& egl = display->egl;
+
+    EGLSyncKHR sync = EGL_NO_SYNC;
+    if (egl.HasExt(EGLExt::FenceSync)) {
+        sync = egl.CreateSyncKHR(display->GetDisplay(), type, attribs);
+    } else {
+        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
+        std::vector<EGLAttrib> convertedAttribs = ConvertEGLIntParameterListToEGLAttrib(attribs);
+        sync = egl.CreateSync(display->GetDisplay(), type, convertedAttribs.data());
+    }
+
+    DAWN_TRY(CheckEGL(egl, sync != EGL_NO_SYNC, "eglCreateSync"));
+    return AcquireRef(new WrappedEGLSync(display, sync));
+}
+
+WrappedEGLSync::WrappedEGLSync(DisplayEGL* display, EGLSync sync) : mDisplay(display), mSync(sync) {
+    DAWN_ASSERT(mDisplay != nullptr);
+    DAWN_ASSERT(mSync != EGL_NO_SYNC);
+}
+
+WrappedEGLSync::~WrappedEGLSync() {
+    const EGLFunctions& egl = mDisplay->egl;
+    if (egl.HasExt(EGLExt::FenceSync)) {
+        egl.DestroySyncKHR(mDisplay->GetDisplay(), mSync);
+    } else {
+        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
+        egl.DestroySync(mDisplay->GetDisplay(), mSync);
+    }
+}
+
+EGLSync WrappedEGLSync::Get() const {
+    return mSync;
+}
+
+MaybeError WrappedEGLSync::Signal(EGLenum mode) {
+    const EGLFunctions& egl = mDisplay->egl;
+    DAWN_ASSERT(egl.HasExt(EGLExt::ReusableSync));
+
+    DAWN_TRY(CheckEGL(egl, egl.SignalSync(mDisplay->GetDisplay(), mSync, mode), "eglSignalSync"));
+    return {};
+}
+
+ResultOrError<EGLenum> WrappedEGLSync::ClientWait(EGLint flags, Nanoseconds timeout) {
+    const EGLFunctions& egl = mDisplay->egl;
+
+    EGLenum result = EGL_FALSE;
+    if (egl.HasExt(EGLExt::FenceSync)) {
+        result = egl.ClientWaitSyncKHR(mDisplay->GetDisplay(), mSync, flags, uint64_t(timeout));
+    } else {
+        DAWN_ASSERT(egl.IsAtLeastVersion(1, 5));
+        result = egl.ClientWaitSync(mDisplay->GetDisplay(), mSync, flags, uint64_t(timeout));
+    }
+
+    DAWN_TRY(CheckEGL(egl, result != EGL_FALSE, "eglClientWaitSync"));
+    return result;
+}
+
+MaybeError WrappedEGLSync::Wait() {
+    const EGLFunctions& egl = mDisplay->egl;
+    DAWN_ASSERT(egl.HasExt(EGLExt::WaitSync));
+
+    constexpr EGLint flags = 0;
+    DAWN_TRY(CheckEGL(egl, egl.WaitSync(mDisplay->GetDisplay(), mSync, flags), "eglWaitSync"));
+    return {};
+}
+
+ResultOrError<EGLint> WrappedEGLSync::DupFD() {
+    const EGLFunctions& egl = mDisplay->egl;
+    DAWN_ASSERT(egl.HasExt(EGLExt::NativeFenceSync));
+
+    EGLint fd = egl.DupNativeFenceFD(mDisplay->GetDisplay(), mSync);
+    DAWN_TRY(CheckEGL(egl, fd != EGL_NO_NATIVE_FENCE_FD_ANDROID, "eglDupNativeFenceFDANDROID"));
+
+    return fd;
+}
+
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/UtilsEGL.h b/src/dawn/native/opengl/UtilsEGL.h
index 43c8a51..6494322 100644
--- a/src/dawn/native/opengl/UtilsEGL.h
+++ b/src/dawn/native/opengl/UtilsEGL.h
@@ -28,8 +28,12 @@
 #ifndef SRC_DAWN_NATIVE_OPENGL_UTILSEGL_H_
 #define SRC_DAWN_NATIVE_OPENGL_UTILSEGL_H_
 
+#include "dawn/common/NonMovable.h"
+#include "dawn/common/RefCounted.h"
 #include "dawn/common/egl_platform.h"
 #include "dawn/native/Error.h"
+#include "dawn/native/IntegerTypes.h"
+#include "dawn/native/opengl/DisplayEGL.h"
 
 namespace dawn::native::opengl {
 
@@ -38,6 +42,35 @@
 const char* EGLErrorAsString(EGLint error);
 MaybeError CheckEGL(const EGLFunctions& egl, EGLBoolean result, const char* context);
 
+class WrappedEGLSync : public RefCounted, NonMovable {
+  public:
+    static ResultOrError<Ref<WrappedEGLSync>> Create(DisplayEGL* display,
+                                                     EGLenum type,
+                                                     const EGLint* attribs);
+
+    EGLSync Get() const;
+
+    // Call eglSignalSync with given mode. Requires sync type of EGL_SYNC_REUSABLE_KHR.
+    MaybeError Signal(EGLenum mode);
+
+    // Call eglClientWaitSync
+    ResultOrError<EGLenum> ClientWait(EGLint flags, Nanoseconds timeout);
+
+    // Call eglWaitSync (server wait)
+    MaybeError Wait();
+
+    // Call eglDupNativeFenceFDANDROID
+    ResultOrError<EGLint> DupFD();
+
+  protected:
+    WrappedEGLSync(DisplayEGL* display, EGLSync sync);
+    ~WrappedEGLSync() override;
+
+  private:
+    Ref<DisplayEGL> mDisplay;
+    EGLSync mSync = nullptr;
+};
+
 }  // namespace dawn::native::opengl
 
 #endif  // SRC_DAWN_NATIVE_OPENGL_UTILSEGL_H_
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
index 8d84151..1268a20 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
@@ -829,9 +829,6 @@
 // Test that it is an error to import a shared fence with no chained struct.
 // Also test that ExportInfo reports an Undefined type for the error fence.
 TEST_P(SharedTextureMemoryTests, ImportSharedFenceNoChain) {
-    // TODO(dawn/42241435): No shared texture memory extensions are supported yet.
-    DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
-
     wgpu::SharedFenceDescriptor desc;
     ASSERT_DEVICE_ERROR_MSG(wgpu::SharedFence fence = device.ImportSharedFence(&desc),
                             HasSubstr("chain"));
@@ -2245,9 +2242,6 @@
     // Not supported if using the same device. Not possible to lose one without losing the other.
     DAWN_TEST_UNSUPPORTED_IF(GetParam().mBackend->UseSameDevice());
 
-    // TODO(dawn/42241435): No shared texture memory extensions are supported yet.
-    DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
-
     // crbug.com/358166479
     DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan());
 
@@ -2315,9 +2309,6 @@
 TEST_P(SharedTextureMemoryTests, SeparateDevicesWriteThenConcurrentReadThenWrite) {
     DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead());
 
-    // TODO(dawn/42241435): No shared texture memory extensions are supported yet.
-    DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
-
     // crbug.com/358166479
     DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan());
 
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
index 257101d..54f147a 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests_android.cpp
@@ -194,7 +194,8 @@
     }
 
     std::vector<wgpu::FeatureName> RequiredFeatures(const wgpu::Adapter&) const override {
-        return {wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer};
+        return {wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
+                wgpu::FeatureName::SharedFenceSyncFD};
     }
 };
 
@@ -235,16 +236,20 @@
 
     wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
     wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
-    beginLayout.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-    beginLayout.newLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-    beginDesc.nextInChain = &beginLayout;
+    if (IsVulkan()) {
+        beginLayout.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        beginLayout.newLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        beginDesc.nextInChain = &beginLayout;
+    }
     memory.BeginAccess(texture, &beginDesc);
 
     device.GetQueue().Submit(1, &commandBuffer);
 
     wgpu::SharedTextureMemoryEndAccessState endState = {};
     wgpu::SharedTextureMemoryVkImageLayoutEndState endLayout{};
-    endState.nextInChain = &endLayout;
+    if (IsVulkan()) {
+        endState.nextInChain = &endLayout;
+    }
     memory.EndAccess(texture, &endState);
 
     wgpu::SharedFenceExportInfo exportInfo;
@@ -332,8 +337,11 @@
 
     wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
     beginDesc.initialized = true;
+
     wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
-    beginDesc.nextInChain = &beginLayout;
+    if (IsVulkan()) {
+        beginDesc.nextInChain = &beginLayout;
+    }
 
     memory.BeginAccess(texture, &beginDesc);
     EXPECT_TEXTURE_EQ(expected.data(), texture, {0, 0},