Refactor signal utils into Observer pattern.

These types were over-generalized. All use cases featured
arrays of resources attached to single parent resources. The
channel ID is sufficient to identify the child resource in the
parent, and having variadic template arguments wasn't necessary.

Futhermore we can rename these types to use the common Observer
pattern. This should make them more readable to new developers.

Also update some classes to inherit from Subject instead of
having a member Subject. This cleans up the code in a few places.

This should lead to a simpler refactor to allow dependent dirty
bits notifications in the Vulkan back-end.

In the following patch the signal_utils files will be renamed. They
are not renamed in this patch to ensure git history is preserved.

Bug: angleproject:2372
Change-Id: I17a3f2c8d92afd4bb3cba2d378c3a2e8a6d7fb11
Reviewed-on: https://chromium-review.googlesource.com/936690
Reviewed-by: Luc Ferron <lucferron@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 7a758ed..1f8c147 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -2625,10 +2625,10 @@
 
     // Invalidate all textures and framebuffer. Some extensions make new formats renderable or
     // sampleable.
-    mState.mTextures->signalAllTexturesDirty();
+    mState.mTextures->signalAllTexturesDirty(this);
     for (auto &zeroTexture : mZeroTextures)
     {
-        zeroTexture.second->signalDirty(InitState::Initialized);
+        zeroTexture.second->signalDirty(this, InitState::Initialized);
     }
 
     mState.mFramebuffers->invalidateFramebufferComplenessCache();
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 16d0c11..25538d3 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -35,11 +35,6 @@
 namespace
 {
 
-void BindResourceChannel(OnAttachmentDirtyBinding *binding, FramebufferAttachmentObject *resource)
-{
-    binding->bind(resource ? resource->getDirtyChannel() : nullptr);
-}
-
 bool CheckMultiviewStateMatchesForCompleteness(const FramebufferAttachment *firstAttachment,
                                                const FramebufferAttachment *secondAttachment)
 {
@@ -199,13 +194,13 @@
 
 // Needed to index into the attachment arrays/bitsets.
 static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) ==
-                  gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX,
+                  Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX,
               "Framebuffer Dirty bit mismatch");
 static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) ==
-                  gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT,
+                  Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT,
               "Framebuffer Dirty bit mismatch");
 static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS + 1) ==
-                  gl::Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT,
+                  Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT,
               "Framebuffer Dirty bit mismatch");
 
 Error InitAttachment(const Context *context, FramebufferAttachment *attachment)
@@ -497,7 +492,7 @@
     return !hasMismatchedSize(mStencilAttachment);
 }
 
-const gl::FramebufferAttachment *FramebufferState::getDrawBuffer(size_t drawBufferIdx) const
+const FramebufferAttachment *FramebufferState::getDrawBuffer(size_t drawBufferIdx) const
 {
     ASSERT(drawBufferIdx < mDrawBufferStates.size());
     if (mDrawBufferStates[drawBufferIdx] != GL_NONE)
@@ -532,7 +527,7 @@
     for (size_t firstAttachmentIdx = 0; firstAttachmentIdx < mColorAttachments.size();
          firstAttachmentIdx++)
     {
-        const gl::FramebufferAttachment &firstAttachment = mColorAttachments[firstAttachmentIdx];
+        const FramebufferAttachment &firstAttachment = mColorAttachments[firstAttachmentIdx];
         if (!firstAttachment.isAttached())
         {
             continue;
@@ -541,8 +536,7 @@
         for (size_t secondAttachmentIdx = firstAttachmentIdx + 1;
              secondAttachmentIdx < mColorAttachments.size(); secondAttachmentIdx++)
         {
-            const gl::FramebufferAttachment &secondAttachment =
-                mColorAttachments[secondAttachmentIdx];
+            const FramebufferAttachment &secondAttachment = mColorAttachments[secondAttachmentIdx];
             if (!secondAttachment.isAttached())
             {
                 continue;
@@ -648,7 +642,7 @@
 
     const Context *proxyContext = display->getProxyContext();
 
-    setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_BACK, gl::ImageIndex::MakeInvalid(),
+    setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex::MakeInvalid(),
                       surface, FramebufferAttachment::kDefaultNumViews,
                       FramebufferAttachment::kDefaultBaseViewIndex,
                       FramebufferAttachment::kDefaultMultiviewLayout,
@@ -656,21 +650,20 @@
 
     if (surface->getConfig()->depthSize > 0)
     {
-        setAttachmentImpl(
-            proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, gl::ImageIndex::MakeInvalid(), surface,
-            FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex,
-            FramebufferAttachment::kDefaultMultiviewLayout,
-            FramebufferAttachment::kDefaultViewportOffsets);
+        setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex::MakeInvalid(),
+                          surface, FramebufferAttachment::kDefaultNumViews,
+                          FramebufferAttachment::kDefaultBaseViewIndex,
+                          FramebufferAttachment::kDefaultMultiviewLayout,
+                          FramebufferAttachment::kDefaultViewportOffsets);
     }
 
     if (surface->getConfig()->stencilSize > 0)
     {
-        setAttachmentImpl(proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL,
-                          gl::ImageIndex::MakeInvalid(), surface,
-                          FramebufferAttachment::kDefaultNumViews,
-                          FramebufferAttachment::kDefaultBaseViewIndex,
-                          FramebufferAttachment::kDefaultMultiviewLayout,
-                          FramebufferAttachment::kDefaultViewportOffsets);
+        setAttachmentImpl(
+            proxyContext, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex::MakeInvalid(), surface,
+            FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex,
+            FramebufferAttachment::kDefaultMultiviewLayout,
+            FramebufferAttachment::kDefaultViewportOffsets);
     }
     mState.mDrawBufferTypeMask.setIndex(getDrawbufferWriteType(0), 0);
 }
@@ -1277,7 +1270,7 @@
 Error Framebuffer::invalidateSub(const Context *context,
                                  size_t count,
                                  const GLenum *attachments,
-                                 const gl::Rectangle &area)
+                                 const Rectangle &area)
 {
     // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations
     // can be no-ops, so we should probably do that to ensure consistency.
@@ -1286,7 +1279,7 @@
     return mImpl->invalidateSub(context, count, attachments, area);
 }
 
-Error Framebuffer::clear(const gl::Context *context, GLbitfield mask)
+Error Framebuffer::clear(const Context *context, GLbitfield mask)
 {
     const auto &glState = context->getGLState();
     if (glState.isRasterizerDiscardEnabled())
@@ -1316,7 +1309,7 @@
     return NoError();
 }
 
-Error Framebuffer::clearBufferfv(const gl::Context *context,
+Error Framebuffer::clearBufferfv(const Context *context,
                                  GLenum buffer,
                                  GLint drawbuffer,
                                  const GLfloat *values)
@@ -1341,7 +1334,7 @@
     return NoError();
 }
 
-Error Framebuffer::clearBufferuiv(const gl::Context *context,
+Error Framebuffer::clearBufferuiv(const Context *context,
                                   GLenum buffer,
                                   GLint drawbuffer,
                                   const GLuint *values)
@@ -1366,7 +1359,7 @@
     return NoError();
 }
 
-Error Framebuffer::clearBufferiv(const gl::Context *context,
+Error Framebuffer::clearBufferiv(const Context *context,
                                  GLenum buffer,
                                  GLint drawbuffer,
                                  const GLint *values)
@@ -1391,7 +1384,7 @@
     return NoError();
 }
 
-Error Framebuffer::clearBufferfi(const gl::Context *context,
+Error Framebuffer::clearBufferfi(const Context *context,
                                  GLenum buffer,
                                  GLint drawbuffer,
                                  GLfloat depth,
@@ -1427,7 +1420,7 @@
     return mImpl->getImplementationColorReadType(context);
 }
 
-Error Framebuffer::readPixels(const gl::Context *context,
+Error Framebuffer::readPixels(const Context *context,
                               const Rectangle &area,
                               GLenum format,
                               GLenum type,
@@ -1436,7 +1429,7 @@
     ANGLE_TRY(ensureReadAttachmentInitialized(context, GL_COLOR_BUFFER_BIT));
     ANGLE_TRY(mImpl->readPixels(context, area, format, type, pixels));
 
-    Buffer *unpackBuffer = context->getGLState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
+    Buffer *unpackBuffer = context->getGLState().getTargetBuffer(BufferBinding::PixelUnpack);
     if (unpackBuffer)
     {
         unpackBuffer->onPixelUnpack();
@@ -1445,7 +1438,7 @@
     return NoError();
 }
 
-Error Framebuffer::blit(const gl::Context *context,
+Error Framebuffer::blit(const Context *context,
                         const Rectangle &sourceArea,
                         const Rectangle &destArea,
                         GLbitfield mask,
@@ -1766,7 +1759,7 @@
 void Framebuffer::updateAttachment(const Context *context,
                                    FramebufferAttachment *attachment,
                                    size_t dirtyBit,
-                                   OnAttachmentDirtyBinding *onDirtyBinding,
+                                   angle::ObserverBinding *onDirtyBinding,
                                    GLenum type,
                                    GLenum binding,
                                    const ImageIndex &textureIndex,
@@ -1780,7 +1773,7 @@
                        multiviewLayout, viewportOffsets);
     mDirtyBits.set(dirtyBit);
     mState.mResourceNeedsInit.set(dirtyBit, attachment->initState() == InitState::MayNeedInit);
-    BindResourceChannel(onDirtyBinding, resource);
+    onDirtyBinding->bind(resource);
 }
 
 void Framebuffer::resetAttachment(const Context *context, GLenum binding)
@@ -1801,7 +1794,9 @@
     }
 }
 
-void Framebuffer::signal(size_t dirtyBit, InitState state)
+void Framebuffer::onSubjectStateChange(const Context *context,
+                                       angle::SubjectIndex index,
+                                       angle::SubjectMessage message)
 {
     // Only reset the cached status if this is not the default framebuffer.  The default framebuffer
     // will still use this channel to mark itself dirty.
@@ -1811,8 +1806,25 @@
         mCachedStatus.reset();
     }
 
+    FramebufferAttachment *attachment = getAttachmentFromSubjectIndex(index);
+
     // Mark the appropriate init flag.
-    mState.mResourceNeedsInit.set(dirtyBit, state == InitState::MayNeedInit);
+    mState.mResourceNeedsInit.set(index, attachment->initState() == InitState::MayNeedInit);
+}
+
+FramebufferAttachment *Framebuffer::getAttachmentFromSubjectIndex(angle::SubjectIndex index)
+{
+    switch (index)
+    {
+        case DIRTY_BIT_DEPTH_ATTACHMENT:
+            return &mState.mDepthAttachment;
+        case DIRTY_BIT_STENCIL_ATTACHMENT:
+            return &mState.mStencilAttachment;
+        default:
+            size_t colorIndex = (index - DIRTY_BIT_COLOR_ATTACHMENT_0);
+            ASSERT(colorIndex < mState.mColorAttachments.size());
+            return &mState.mColorAttachments[colorIndex];
+    }
 }
 
 bool Framebuffer::complete(const Context *context)
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index 0843871..e7fe890 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -133,7 +133,7 @@
     angle::BitSet<IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS + 2> mResourceNeedsInit;
 };
 
-class Framebuffer final : public LabeledObject, public OnAttachmentDirtyReceiver
+class Framebuffer final : public LabeledObject, public angle::ObserverInterface
 {
   public:
     // Constructor to build application-defined framebuffers
@@ -250,22 +250,22 @@
     Error invalidateSub(const Context *context,
                         size_t count,
                         const GLenum *attachments,
-                        const gl::Rectangle &area);
+                        const Rectangle &area);
 
-    Error clear(const gl::Context *context, GLbitfield mask);
-    Error clearBufferfv(const gl::Context *context,
+    Error clear(const Context *context, GLbitfield mask);
+    Error clearBufferfv(const Context *context,
                         GLenum buffer,
                         GLint drawbuffer,
                         const GLfloat *values);
-    Error clearBufferuiv(const gl::Context *context,
+    Error clearBufferuiv(const Context *context,
                          GLenum buffer,
                          GLint drawbuffer,
                          const GLuint *values);
-    Error clearBufferiv(const gl::Context *context,
+    Error clearBufferiv(const Context *context,
                         GLenum buffer,
                         GLint drawbuffer,
                         const GLint *values);
-    Error clearBufferfi(const gl::Context *context,
+    Error clearBufferfi(const Context *context,
                         GLenum buffer,
                         GLint drawbuffer,
                         GLfloat depth,
@@ -273,13 +273,13 @@
 
     GLenum getImplementationColorReadFormat(const Context *context) const;
     GLenum getImplementationColorReadType(const Context *context) const;
-    Error readPixels(const gl::Context *context,
-                     const gl::Rectangle &area,
+    Error readPixels(const Context *context,
+                     const Rectangle &area,
                      GLenum format,
                      GLenum type,
                      void *pixels);
 
-    Error blit(const gl::Context *context,
+    Error blit(const Context *context,
                const Rectangle &sourceArea,
                const Rectangle &destArea,
                GLbitfield mask,
@@ -289,7 +289,7 @@
     {
         DIRTY_BIT_COLOR_ATTACHMENT_0,
         DIRTY_BIT_COLOR_ATTACHMENT_MAX =
-            DIRTY_BIT_COLOR_ATTACHMENT_0 + gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS,
+            DIRTY_BIT_COLOR_ATTACHMENT_0 + IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS,
         DIRTY_BIT_DEPTH_ATTACHMENT = DIRTY_BIT_COLOR_ATTACHMENT_MAX,
         DIRTY_BIT_STENCIL_ATTACHMENT,
         DIRTY_BIT_DRAW_BUFFERS,
@@ -307,8 +307,10 @@
 
     void syncState(const Context *context);
 
-    // OnAttachmentChangedReceiver implementation
-    void signal(size_t dirtyBit, InitState state) override;
+    // Observer implementation
+    void onSubjectStateChange(const Context *context,
+                              angle::SubjectIndex index,
+                              angle::SubjectMessage message) override;
 
     bool formsRenderingFeedbackLoopWith(const State &state) const;
     bool formsCopyingFeedbackLoopWith(GLuint copyTextureID,
@@ -355,7 +357,7 @@
     void updateAttachment(const Context *context,
                           FramebufferAttachment *attachment,
                           size_t dirtyBit,
-                          OnAttachmentDirtyBinding *onDirtyBinding,
+                          angle::ObserverBinding *onDirtyBinding,
                           GLenum type,
                           GLenum binding,
                           const ImageIndex &textureIndex,
@@ -376,14 +378,16 @@
     bool partialClearNeedsInit(const Context *context, bool color, bool depth, bool stencil);
     bool partialBufferClearNeedsInit(const Context *context, GLenum bufferType);
 
+    FramebufferAttachment *getAttachmentFromSubjectIndex(angle::SubjectIndex index);
+
     FramebufferState mState;
     rx::FramebufferImpl *mImpl;
     GLuint mId;
 
     Optional<GLenum> mCachedStatus;
-    std::vector<OnAttachmentDirtyBinding> mDirtyColorAttachmentBindings;
-    OnAttachmentDirtyBinding mDirtyDepthAttachmentBinding;
-    OnAttachmentDirtyBinding mDirtyStencilAttachmentBinding;
+    std::vector<angle::ObserverBinding> mDirtyColorAttachmentBindings;
+    angle::ObserverBinding mDirtyDepthAttachmentBinding;
+    angle::ObserverBinding mDirtyStencilAttachmentBinding;
 
     DirtyBits mDirtyBits;
 
diff --git a/src/libANGLE/FramebufferAttachment.cpp b/src/libANGLE/FramebufferAttachment.cpp
index e2beffa..762f47b 100644
--- a/src/libANGLE/FramebufferAttachment.cpp
+++ b/src/libANGLE/FramebufferAttachment.cpp
@@ -353,11 +353,6 @@
     return getAttachmentImpl()->getAttachmentRenderTarget(context, binding, imageIndex, rtOut);
 }
 
-OnAttachmentDirtyChannel *FramebufferAttachmentObject::getDirtyChannel()
-{
-    return &mDirtyChannel;
-}
-
 Error FramebufferAttachmentObject::initializeContents(const Context *context,
                                                       const ImageIndex &imageIndex)
 {
diff --git a/src/libANGLE/FramebufferAttachment.h b/src/libANGLE/FramebufferAttachment.h
index 5c0553a..44f2fab 100644
--- a/src/libANGLE/FramebufferAttachment.h
+++ b/src/libANGLE/FramebufferAttachment.h
@@ -49,10 +49,6 @@
     Initialized,
 };
 
-using OnAttachmentDirtyBinding  = angle::ChannelBinding<size_t, InitState>;
-using OnAttachmentDirtyChannel  = angle::BroadcastChannel<size_t, InitState>;
-using OnAttachmentDirtyReceiver = angle::SignalReceiver<size_t, InitState>;
-
 // FramebufferAttachment implements a GL framebuffer attachment.
 // Attachments are "light" containers, which store pointers to ref-counted GL objects.
 // We support GL texture (2D/3D/Cube/2D array) and renderbuffer object attachments.
@@ -184,7 +180,7 @@
 };
 
 // A base class for objects that FBO Attachments may point to.
-class FramebufferAttachmentObject
+class FramebufferAttachmentObject : public angle::Subject
 {
   public:
     FramebufferAttachmentObject();
@@ -210,12 +206,8 @@
 
     Error initializeContents(const Context *context, const ImageIndex &imageIndex);
 
-    OnAttachmentDirtyChannel *getDirtyChannel();
-
   protected:
     virtual rx::FramebufferAttachmentObjectImpl *getAttachmentImpl() const = 0;
-
-    OnAttachmentDirtyChannel mDirtyChannel;
 };
 
 inline Extents FramebufferAttachment::getSize() const
diff --git a/src/libANGLE/Renderbuffer.cpp b/src/libANGLE/Renderbuffer.cpp
index 7358aa5..c797d15 100644
--- a/src/libANGLE/Renderbuffer.cpp
+++ b/src/libANGLE/Renderbuffer.cpp
@@ -109,7 +109,7 @@
 
     mState.update(static_cast<GLsizei>(width), static_cast<GLsizei>(height), Format(internalformat),
                   0, InitState::MayNeedInit);
-    mDirtyChannel.signal(mState.mInitState);
+    onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 
     return NoError();
 }
@@ -126,7 +126,7 @@
 
     mState.update(static_cast<GLsizei>(width), static_cast<GLsizei>(height), Format(internalformat),
                   static_cast<GLsizei>(samples), InitState::MayNeedInit);
-    mDirtyChannel.signal(mState.mInitState);
+    onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 
     return NoError();
 }
@@ -140,7 +140,7 @@
 
     mState.update(static_cast<GLsizei>(image->getWidth()), static_cast<GLsizei>(image->getHeight()),
                   Format(image->getFormat()), 0, image->sourceInitState());
-    mDirtyChannel.signal(mState.mInitState);
+    onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 
     return NoError();
 }
diff --git a/src/libANGLE/ResourceManager.cpp b/src/libANGLE/ResourceManager.cpp
index 9abb5b1..a7c0af4 100644
--- a/src/libANGLE/ResourceManager.cpp
+++ b/src/libANGLE/ResourceManager.cpp
@@ -247,7 +247,7 @@
     return mObjectMap.query(handle);
 }
 
-void TextureManager::signalAllTexturesDirty() const
+void TextureManager::signalAllTexturesDirty(const Context *context) const
 {
     for (const auto &texture : mObjectMap)
     {
@@ -255,7 +255,7 @@
         {
             // We don't know if the Texture needs init, but that's ok, since it will only force
             // a re-check, and will not initialize the pixels if it's not needed.
-            texture.second->signalDirty(InitState::MayNeedInit);
+            texture.second->signalDirty(context, InitState::MayNeedInit);
         }
     }
 }
diff --git a/src/libANGLE/ResourceManager.h b/src/libANGLE/ResourceManager.h
index 4dadca6..c5b0d37 100644
--- a/src/libANGLE/ResourceManager.h
+++ b/src/libANGLE/ResourceManager.h
@@ -157,7 +157,7 @@
     GLuint createTexture();
     Texture *getTexture(GLuint handle) const;
 
-    void signalAllTexturesDirty() const;
+    void signalAllTexturesDirty(const Context *context) const;
 
     Texture *checkTextureAllocation(rx::GLImplFactory *factory, GLuint handle, GLenum target)
     {
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index d837af8..719bfe8 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -180,7 +180,7 @@
     for (uint32_t textureIndex = 0; textureIndex < caps.maxCombinedTextureImageUnits;
          ++textureIndex)
     {
-        mCompleteTextureBindings.emplace_back(OnAttachmentDirtyBinding(this, textureIndex));
+        mCompleteTextureBindings.emplace_back(this, textureIndex);
     }
 
     mSamplers.resize(caps.maxCombinedTextureImageUnits);
@@ -2320,7 +2320,7 @@
             }
 
             // Bind the texture unconditionally, to recieve completeness change notifications.
-            mCompleteTextureBindings[textureUnitIndex].bind(texture->getDirtyChannel());
+            mCompleteTextureBindings[textureUnitIndex].bind(texture);
             mActiveTexturesMask.set(textureUnitIndex);
             newActiveTextures.set(textureUnitIndex);
 
@@ -2440,13 +2440,16 @@
 }
 
 // Handle a dirty texture event.
-void State::signal(size_t textureIndex, InitState initState)
+void State::onSubjectStateChange(const Context *context,
+                                 angle::SubjectIndex index,
+                                 angle::SubjectMessage message)
 {
     // Conservatively assume all textures are dirty.
     // TODO(jmadill): More fine-grained update.
     mDirtyObjects.set(DIRTY_OBJECT_PROGRAM_TEXTURES);
 
-    if (initState == InitState::MayNeedInit)
+    if (!mCompleteTextureCache[index] ||
+        mCompleteTextureCache[index]->initState() == InitState::MayNeedInit)
     {
         mCachedTexturesInitState = InitState::MayNeedInit;
     }
diff --git a/src/libANGLE/State.h b/src/libANGLE/State.h
index d160c03..af80fa1 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -34,7 +34,7 @@
 class Context;
 struct Caps;
 
-class State : public OnAttachmentDirtyReceiver, angle::NonCopyable
+class State : public angle::ObserverInterface, angle::NonCopyable
 {
   public:
     State();
@@ -462,8 +462,10 @@
     const std::vector<Texture *> &getCompleteTextureCache() const { return mCompleteTextureCache; }
     ComponentTypeMask getCurrentValuesTypeMask() const { return mCurrentValuesTypeMask; }
 
-    // Handle a dirty texture event.
-    void signal(size_t textureIndex, InitState initState) override;
+    // Observer implementation.
+    void onSubjectStateChange(const Context *context,
+                              angle::SubjectIndex index,
+                              angle::SubjectMessage message) override;
 
     Error clearUnclearedActiveTextures(const Context *context);
 
@@ -527,11 +529,10 @@
 
     // Texture Completeness Caching
     // ----------------------------
-    // The texture completeness cache uses dirty bits to avoid having to scan the list
-    // of textures each draw call. This gl::State class implements OnAttachmentDirtyReceiver,
-    // and keeps an array of bindings to the Texture class. When the Textures are marked dirty,
-    // they send messages to the State class (and any Framebuffers they're attached to) via the
-    // State::signal method (see above). Internally this then invalidates the completeness cache.
+    // The texture completeness cache uses dirty bits to avoid having to scan the list of textures
+    // each draw call. This gl::State class implements angle::Observer interface. When subject
+    // Textures have state changes, messages reach 'State' (also any observing Framebuffers) via the
+    // onSubjectStateChange method (above). This then invalidates the completeness cache.
     //
     // Note this requires that we also invalidate the completeness cache manually on events like
     // re-binding textures/samplers or a change in the program. For more information see the
@@ -541,7 +542,7 @@
     // Don't use BindingPointer because this cache is only valid within a draw call.
     // Also stores a notification channel to the texture itself to handle texture change events.
     std::vector<Texture *> mCompleteTextureCache;
-    std::vector<OnAttachmentDirtyBinding> mCompleteTextureBindings;
+    std::vector<angle::ObserverBinding> mCompleteTextureBindings;
     InitState mCachedTexturesInitState;
     using ActiveTextureMask = angle::BitSet<IMPLEMENTATION_MAX_ACTIVE_TEXTURES>;
     ActiveTextureMask mActiveTexturesMask;
diff --git a/src/libANGLE/Surface.cpp b/src/libANGLE/Surface.cpp
index d1a8242..aff7131 100644
--- a/src/libANGLE/Surface.cpp
+++ b/src/libANGLE/Surface.cpp
@@ -151,12 +151,12 @@
     return NoError();
 }
 
-void Surface::postSwap()
+void Surface::postSwap(const gl::Context *context)
 {
     if (mRobustResourceInitialization && mSwapBehavior != EGL_BUFFER_PRESERVED)
     {
         mInitState = gl::InitState::MayNeedInit;
-        mDirtyChannel.signal(mInitState);
+        onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
     }
 }
 
@@ -231,14 +231,14 @@
 Error Surface::swap(const gl::Context *context)
 {
     ANGLE_TRY(mImplementation->swap(context));
-    postSwap();
+    postSwap(context);
     return NoError();
 }
 
 Error Surface::swapWithDamage(const gl::Context *context, EGLint *rects, EGLint n_rects)
 {
     ANGLE_TRY(mImplementation->swapWithDamage(context, rects, n_rects));
-    postSwap();
+    postSwap(context);
     return NoError();
 }
 
diff --git a/src/libANGLE/Surface.h b/src/libANGLE/Surface.h
index 3a9266a..d66245d 100644
--- a/src/libANGLE/Surface.h
+++ b/src/libANGLE/Surface.h
@@ -187,7 +187,7 @@
   private:
     Error destroyImpl(const Display *display);
 
-    void postSwap();
+    void postSwap(const gl::Context *context);
 
     gl::InitState mInitState;
 };
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index f4646fc..a8200b3 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -913,9 +913,10 @@
     return mBoundStream;
 }
 
-void Texture::signalDirty(InitState initState) const
+void Texture::signalDirty(const Context *context, InitState initState)
 {
-    mDirtyChannel.signal(initState);
+    mState.mInitState = initState;
+    onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
     invalidateCompletenessCache();
 }
 
@@ -941,7 +942,7 @@
 
     InitState initState = DetermineInitState(context, pixels);
     mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormat, type), initState));
-    signalDirty(initState);
+    signalDirty(context, initState);
 
     return NoError();
 }
@@ -984,7 +985,7 @@
 
     InitState initState = DetermineInitState(context, pixels);
     mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormat), initState));
-    signalDirty(initState);
+    signalDirty(context, initState);
 
     return NoError();
 }
@@ -1038,7 +1039,7 @@
                                   Format(internalFormatInfo), InitState::Initialized));
 
     // We need to initialize this texture only if the source attachment is not initialized.
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
 
     return NoError();
 }
@@ -1094,7 +1095,7 @@
         target, level,
         ImageDesc(sourceDesc.size, Format(internalFormatInfo), InitState::Initialized));
 
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
 
     return NoError();
 }
@@ -1166,7 +1167,7 @@
     mDirtyBits.set(DIRTY_BIT_BASE_LEVEL);
     mDirtyBits.set(DIRTY_BIT_MAX_LEVEL);
 
-    signalDirty(InitState::MayNeedInit);
+    signalDirty(context, InitState::MayNeedInit);
 
     return NoError();
 }
@@ -1193,7 +1194,7 @@
     mState.setImageDescChainMultisample(size, Format(internalFormat), samples, fixedSampleLocations,
                                         InitState::MayNeedInit);
 
-    signalDirty(InitState::MayNeedInit);
+    signalDirty(context, InitState::MayNeedInit);
 
     return NoError();
 }
@@ -1233,7 +1234,7 @@
                                  InitState::Initialized);
     }
 
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
 
     return NoError();
 }
@@ -1255,7 +1256,7 @@
     Extents size(surface->getWidth(), surface->getHeight(), 1);
     ImageDesc desc(size, surface->getBindTexImageFormat(), InitState::Initialized);
     mState.setImageDesc(mState.mTarget, 0, desc);
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
     return NoError();
 }
 
@@ -1268,7 +1269,7 @@
     // Erase the image info for level 0
     ASSERT(mState.mTarget == GL_TEXTURE_2D || mState.mTarget == GL_TEXTURE_RECTANGLE_ANGLE);
     mState.clearImageDesc(mState.mTarget, 0);
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
     return NoError();
 }
 
@@ -1299,7 +1300,7 @@
     Extents size(desc.width, desc.height, 1);
     mState.setImageDesc(mState.mTarget, 0,
                         ImageDesc(size, Format(desc.internalFormat), InitState::Initialized));
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
     return NoError();
 }
 
@@ -1311,7 +1312,7 @@
 
     // Set to incomplete
     mState.clearImageDesc(mState.mTarget, 0);
-    signalDirty(InitState::Initialized);
+    signalDirty(context, InitState::Initialized);
     return NoError();
 }
 
@@ -1348,7 +1349,7 @@
 
     mState.clearImageDescs();
     mState.setImageDesc(target, 0, ImageDesc(size, imageTarget->getFormat(), initState));
-    signalDirty(initState);
+    signalDirty(context, initState);
 
     return NoError();
 }
@@ -1445,7 +1446,7 @@
     }
     if (anyDirty)
     {
-        signalDirty(InitState::Initialized);
+        signalDirty(context, InitState::Initialized);
     }
     mState.mInitState = InitState::Initialized;
 
diff --git a/src/libANGLE/Texture.h b/src/libANGLE/Texture.h
index 7a849d4..9293cd0 100644
--- a/src/libANGLE/Texture.h
+++ b/src/libANGLE/Texture.h
@@ -349,7 +349,7 @@
     egl::Surface *getBoundSurface() const;
     egl::Stream *getBoundStream() const;
 
-    void signalDirty(InitState initState) const;
+    void signalDirty(const Context *context, InitState initState);
 
     bool isSamplerComplete(const Context *context, const Sampler *optionalSampler);
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
index 33ce656..d71018f 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.cpp
@@ -144,9 +144,7 @@
 class Buffer11::NativeStorage : public Buffer11::BufferStorage
 {
   public:
-    NativeStorage(Renderer11 *renderer,
-                  BufferUsage usage,
-                  const OnBufferDataDirtyChannel *onStorageChanged);
+    NativeStorage(Renderer11 *renderer, BufferUsage usage, const angle::Subject *onStorageChanged);
     ~NativeStorage() override;
 
     bool isCPUAccessible(GLbitfield access) const override;
@@ -177,7 +175,7 @@
     void clearSRVs();
 
     d3d11::Buffer mBuffer;
-    const OnBufferDataDirtyChannel *mOnStorageChanged;
+    const angle::Subject *mOnStorageChanged;
     std::map<DXGI_FORMAT, d3d11::ShaderResourceView> mBufferResourceViews;
 };
 
@@ -403,7 +401,7 @@
 
         // Notify any vertex arrays that we have dirty data.
         // TODO(jmadill): Use a more fine grained notification for data updates.
-        mDirectBroadcastChannel.signal(context);
+        mDirectSubject.onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
     }
 
     mSize = std::max(mSize, requiredSize);
@@ -474,7 +472,7 @@
     invalidateStaticData(context);
 
     // Also notify that direct buffers are dirty.
-    mDirectBroadcastChannel.signal(context);
+    mDirectSubject.onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 
     return gl::NoError();
 }
@@ -760,7 +758,7 @@
             return new EmulatedIndexedStorage(mRenderer);
         case BUFFER_USAGE_INDEX:
         case BUFFER_USAGE_VERTEX_OR_TRANSFORM_FEEDBACK:
-            return new NativeStorage(mRenderer, usage, &mDirectBroadcastChannel);
+            return new NativeStorage(mRenderer, usage, &mDirectSubject);
         default:
             return new NativeStorage(mRenderer, usage, nullptr);
     }
@@ -923,7 +921,7 @@
     BufferD3D::initializeStaticData(context);
 
     // Notify when static data changes.
-    mStaticBroadcastChannel.signal(context);
+    mStaticSubject.onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 }
 
 void Buffer11::invalidateStaticData(const gl::Context *context)
@@ -931,17 +929,17 @@
     BufferD3D::invalidateStaticData(context);
 
     // Notify when static data changes.
-    mStaticBroadcastChannel.signal(context);
+    mStaticSubject.onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 }
 
-OnBufferDataDirtyChannel *Buffer11::getStaticBroadcastChannel()
+angle::Subject *Buffer11::getStaticSubject()
 {
-    return &mStaticBroadcastChannel;
+    return &mStaticSubject;
 }
 
-OnBufferDataDirtyChannel *Buffer11::getDirectBroadcastChannel()
+angle::Subject *Buffer11::getDirectSubject()
 {
-    return &mDirectBroadcastChannel;
+    return &mDirectSubject;
 }
 
 void Buffer11::onCopyStorage(BufferStorage *dest, BufferStorage *source)
@@ -991,7 +989,7 @@
 
 Buffer11::NativeStorage::NativeStorage(Renderer11 *renderer,
                                        BufferUsage usage,
-                                       const OnBufferDataDirtyChannel *onStorageChanged)
+                                       const angle::Subject *onStorageChanged)
     : BufferStorage(renderer, usage), mBuffer(), mOnStorageChanged(onStorageChanged)
 {
 }
@@ -1111,7 +1109,7 @@
     // Notify that the storage has changed.
     if (mOnStorageChanged)
     {
-        mOnStorageChanged->signal(context);
+        mOnStorageChanged->onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
     }
 
     return gl::NoError();
diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
index ddbeeb9..cbe0b62 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
@@ -107,8 +107,8 @@
     // We use two set of dirty events. Static buffers are marked dirty whenever
     // data changes, because they must be re-translated. Direct buffers only need to be
     // updated when the underlying ID3D11Buffer pointer changes - hopefully far less often.
-    OnBufferDataDirtyChannel *getStaticBroadcastChannel();
-    OnBufferDataDirtyChannel *getDirectBroadcastChannel();
+    angle::Subject *getStaticSubject();
+    angle::Subject *getDirectSubject();
 
   private:
     class BufferStorage;
@@ -180,8 +180,8 @@
     size_t mConstantBufferStorageAdditionalSize;
     unsigned int mMaxConstantBufferLruCount;
 
-    OnBufferDataDirtyChannel mStaticBroadcastChannel;
-    OnBufferDataDirtyChannel mDirectBroadcastChannel;
+    angle::Subject mStaticSubject;
+    angle::Subject mDirectSubject;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.cpp
index 02326d7..333ea3d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.cpp
@@ -57,7 +57,7 @@
 void UpdateCachedRenderTarget(const gl::Context *context,
                               const gl::FramebufferAttachment *attachment,
                               RenderTarget11 *&cachedRenderTarget,
-                              OnRenderTargetDirtyBinding *channelBinding)
+                              angle::ObserverBinding *channelBinding)
 {
     RenderTarget11 *newRenderTarget = nullptr;
     if (attachment)
@@ -71,9 +71,7 @@
     }
     if (newRenderTarget != cachedRenderTarget)
     {
-        OnRenderTargetDirtyChannel *channel =
-            (newRenderTarget ? newRenderTarget->getBroadcastChannel() : nullptr);
-        channelBinding->bind(channel);
+        channelBinding->bind(newRenderTarget);
         cachedRenderTarget = newRenderTarget;
     }
 }
@@ -451,9 +449,11 @@
     }
 }
 
-void Framebuffer11::signal(size_t channelID, const gl::Context *context)
+void Framebuffer11::onSubjectStateChange(const gl::Context *context,
+                                         angle::SubjectIndex index,
+                                         angle::SubjectMessage message)
 {
-    if (channelID == gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS)
+    if (index == gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS)
     {
         // Stencil is redundant in this case.
         mInternalDirtyBits.set(gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT);
@@ -461,8 +461,8 @@
     }
     else
     {
-        mInternalDirtyBits.set(gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 + channelID);
-        mCachedColorRenderTargets[channelID] = nullptr;
+        mInternalDirtyBits.set(gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 + index);
+        mCachedColorRenderTargets[index] = nullptr;
     }
 
     // Notify the context we need to re-validate the RenderTarget.
diff --git a/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.h b/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.h
index afdda29..ddb2add 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Framebuffer11.h
@@ -17,7 +17,7 @@
 {
 class Renderer11;
 
-class Framebuffer11 : public FramebufferD3D, public OnRenderTargetDirtyReceiver
+class Framebuffer11 : public FramebufferD3D, public angle::ObserverInterface
 {
   public:
     Framebuffer11(const gl::FramebufferState &data, Renderer11 *renderer);
@@ -52,7 +52,10 @@
     bool hasAnyInternalDirtyBit() const;
     void syncInternalState(const gl::Context *context);
 
-    void signal(size_t channelID, const gl::Context *context) override;
+    // Observer implementation.
+    void onSubjectStateChange(const gl::Context *context,
+                              angle::SubjectIndex index,
+                              angle::SubjectMessage message) override;
 
     gl::Error getSamplePosition(size_t index, GLfloat *xy) const override;
 
@@ -93,8 +96,8 @@
     RenderTargetArray mCachedColorRenderTargets;
     RenderTarget11 *mCachedDepthStencilRenderTarget;
 
-    std::vector<OnRenderTargetDirtyBinding> mColorRenderTargetsDirty;
-    OnRenderTargetDirtyBinding mDepthStencilRenderTargetDirty;
+    std::vector<angle::ObserverBinding> mColorRenderTargetsDirty;
+    angle::ObserverBinding mDepthStencilRenderTargetDirty;
 
     gl::Framebuffer::DirtyBits mInternalDirtyBits;
 };
diff --git a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
index 594a382..f16fc84 100644
--- a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.cpp
@@ -200,15 +200,15 @@
 
 RenderTarget11::~RenderTarget11()
 {
-    ASSERT(mBroadcastChannel.empty());
+    ASSERT(!hasObservers());
 }
 
 void RenderTarget11::signalDirty(const gl::Context *context)
 {
-    mBroadcastChannel.signal(context);
+    onStateChange(context, angle::SubjectMessage::STATE_CHANGE);
 
     // Clear the list. We can't do this in the receiver because it would mutate during iteration.
-    mBroadcastChannel.reset();
+    resetObservers();
 }
 
 TextureRenderTarget11::TextureRenderTarget11(d3d11::RenderTargetView &&rtv,
diff --git a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
index db49cac..66930c4 100644
--- a/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/RenderTarget11.h
@@ -20,7 +20,7 @@
 class SwapChain11;
 class Renderer11;
 
-class RenderTarget11 : public RenderTargetD3D
+class RenderTarget11 : public RenderTargetD3D, public angle::Subject
 {
   public:
     RenderTarget11(const d3d11::Format &formatSet);
@@ -35,12 +35,10 @@
     virtual unsigned int getSubresourceIndex() const = 0;
 
     void signalDirty(const gl::Context *context) override;
-    OnRenderTargetDirtyChannel *getBroadcastChannel() { return &mBroadcastChannel; }
 
     const d3d11::Format &getFormatSet() const { return mFormatSet; }
 
   protected:
-    OnRenderTargetDirtyChannel mBroadcastChannel;
     const d3d11::Format &mFormatSet;
 };
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index e9fe1cc..d325e59 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -3263,8 +3263,10 @@
 {
 }
 
-void StateManager11::OnConstantBufferDirtyReceiver::signal(size_t messageID,
-                                                           const gl::Context *context)
+void StateManager11::OnConstantBufferDirtyReceiver::onSubjectStateChange(
+    const gl::Context *context,
+    angle::SubjectIndex index,
+    angle::SubjectMessage message)
 {
     StateManager11 *stateManager = GetImplAs<Context11>(context)->getRenderer()->getStateManager();
     stateManager->invalidateProgramUniformBuffers();
@@ -3274,24 +3276,24 @@
 {
     ASSERT(buffer);
     ASSERT(index < mBindingsVS.size());
-    mBindingsVS[index].bind(buffer->getDirectBroadcastChannel());
+    mBindingsVS[index].bind(buffer->getDirectSubject());
 }
 
 void StateManager11::OnConstantBufferDirtyReceiver::bindPS(size_t index, Buffer11 *buffer)
 {
     ASSERT(buffer);
     ASSERT(index < mBindingsPS.size());
-    mBindingsPS[index].bind(buffer->getDirectBroadcastChannel());
+    mBindingsPS[index].bind(buffer->getDirectSubject());
 }
 
 void StateManager11::OnConstantBufferDirtyReceiver::reset()
 {
-    for (OnBufferDataDirtyBinding &vsBinding : mBindingsVS)
+    for (angle::ObserverBinding &vsBinding : mBindingsVS)
     {
         vsBinding.bind(nullptr);
     }
 
-    for (OnBufferDataDirtyBinding &psBinding : mBindingsPS)
+    for (angle::ObserverBinding &psBinding : mBindingsPS)
     {
         psBinding.bind(nullptr);
     }
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
index 02bf396..e016a14 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.h
@@ -566,21 +566,23 @@
     FragmentConstantBufferArray<GLintptr> mCurrentConstantBufferPSOffset;
     FragmentConstantBufferArray<GLsizeiptr> mCurrentConstantBufferPSSize;
 
-    class OnConstantBufferDirtyReceiver : public OnBufferDataDirtyReceiver
+    class OnConstantBufferDirtyReceiver : public angle::ObserverInterface
     {
       public:
         OnConstantBufferDirtyReceiver();
         ~OnConstantBufferDirtyReceiver() override;
 
-        void signal(size_t messageID, const gl::Context *context) override;
+        void onSubjectStateChange(const gl::Context *context,
+                                  angle::SubjectIndex index,
+                                  angle::SubjectMessage message) override;
 
         void reset();
         void bindVS(size_t index, Buffer11 *buffer);
         void bindPS(size_t index, Buffer11 *buffer);
 
       private:
-        std::vector<OnBufferDataDirtyBinding> mBindingsVS;
-        std::vector<OnBufferDataDirtyBinding> mBindingsPS;
+        std::vector<angle::ObserverBinding> mBindingsVS;
+        std::vector<angle::ObserverBinding> mBindingsPS;
     };
     OnConstantBufferDirtyReceiver mOnConstantBufferDirtyReceiver;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
index 97c2941..3c831b2 100644
--- a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
@@ -21,17 +21,16 @@
 
 namespace
 {
-OnBufferDataDirtyChannel *GetBufferBroadcastChannel(Buffer11 *buffer11,
-                                                    IndexStorageType storageType)
+angle::Subject *GetBufferSubject(Buffer11 *buffer11, IndexStorageType storageType)
 {
     switch (storageType)
     {
         case IndexStorageType::Direct:
-            return buffer11->getDirectBroadcastChannel();
+            return buffer11->getDirectSubject();
         case IndexStorageType::Static:
-            return buffer11->getStaticBroadcastChannel();
+            return buffer11->getStaticSubject();
         case IndexStorageType::Dynamic:
-            return buffer11 ? buffer11->getStaticBroadcastChannel() : nullptr;
+            return buffer11 ? buffer11->getStaticSubject() : nullptr;
         default:
             UNREACHABLE();
             return nullptr;
@@ -157,10 +156,10 @@
     {
         Buffer11 *newBuffer11 = SafeGetImplAs<Buffer11>(newBuffer);
 
-        auto *newChannel = GetBufferBroadcastChannel(newBuffer11, newStorageType);
+        angle::Subject *subject = GetBufferSubject(newBuffer11, newStorageType);
 
         mCurrentElementArrayStorage = newStorageType;
-        mOnElementArrayBufferDataDirty.bind(newChannel);
+        mOnElementArrayBufferDataDirty.bind(subject);
         needsTranslation = true;
     }
 
@@ -222,7 +221,7 @@
 
     if (oldBuffer11 != newBuffer11 || oldStorageType != newStorageType)
     {
-        OnBufferDataDirtyChannel *newChannel = nullptr;
+        angle::Subject *subject = nullptr;
 
         if (newStorageType == VertexStorageType::CURRENT_VALUE)
         {
@@ -235,11 +234,11 @@
             switch (newStorageType)
             {
                 case VertexStorageType::DIRECT:
-                    newChannel = newBuffer11->getDirectBroadcastChannel();
+                    subject = newBuffer11->getDirectSubject();
                     break;
                 case VertexStorageType::STATIC:
                 case VertexStorageType::DYNAMIC:
-                    newChannel = newBuffer11->getStaticBroadcastChannel();
+                    subject = newBuffer11->getStaticSubject();
                     break;
                 default:
                     UNREACHABLE();
@@ -247,7 +246,7 @@
             }
         }
 
-        mOnArrayBufferDataDirty[attribIndex].bind(newChannel);
+        mOnArrayBufferDataDirty[attribIndex].bind(subject);
         mCurrentArrayBuffers[attribIndex].set(context, binding.getBuffer().get());
     }
 }
@@ -348,9 +347,11 @@
     return mTranslatedAttribs;
 }
 
-void VertexArray11::signal(size_t channelID, const gl::Context *context)
+void VertexArray11::onSubjectStateChange(const gl::Context *context,
+                                         angle::SubjectIndex index,
+                                         angle::SubjectMessage message)
 {
-    if (channelID == mAttributeStorageTypes.size())
+    if (index == mAttributeStorageTypes.size())
     {
         mCachedIndexInfoValid   = false;
         mLastElementType        = GL_NONE;
@@ -358,10 +359,10 @@
     }
     else
     {
-        ASSERT(mAttributeStorageTypes[channelID] != VertexStorageType::CURRENT_VALUE);
+        ASSERT(mAttributeStorageTypes[index] != VertexStorageType::CURRENT_VALUE);
 
         // This can change a buffer's storage, we'll need to re-check.
-        mAttribsToUpdate.set(channelID);
+        mAttribsToUpdate.set(index);
 
         // Changing the vertex attribute state can affect the vertex shader.
         Renderer11 *renderer = GetImplAs<Context11>(context)->getRenderer();
diff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
index 4cdc925..ee4edbf 100644
--- a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.h
@@ -19,7 +19,7 @@
 {
 class Renderer11;
 
-class VertexArray11 : public VertexArrayImpl, public OnBufferDataDirtyReceiver
+class VertexArray11 : public VertexArrayImpl, public angle::ObserverInterface
 {
   public:
     VertexArray11(const gl::VertexArrayState &data);
@@ -38,8 +38,10 @@
 
     const std::vector<TranslatedAttribute> &getTranslatedAttribs() const;
 
-    // SignalReceiver implementation
-    void signal(size_t channelID, const gl::Context *context) override;
+    // Observer implementation
+    void onSubjectStateChange(const gl::Context *context,
+                              angle::SubjectIndex index,
+                              angle::SubjectMessage message) override;
 
     Serial getCurrentStateSerial() const { return mCurrentStateSerial; }
 
@@ -78,8 +80,8 @@
     std::vector<gl::BindingPointer<gl::Buffer>> mCurrentArrayBuffers;
     gl::BindingPointer<gl::Buffer> mCurrentElementArrayBuffer;
 
-    std::vector<OnBufferDataDirtyBinding> mOnArrayBufferDataDirty;
-    OnBufferDataDirtyBinding mOnElementArrayBufferDataDirty;
+    std::vector<angle::ObserverBinding> mOnArrayBufferDataDirty;
+    angle::ObserverBinding mOnElementArrayBufferDataDirty;
 
     Serial mCurrentStateSerial;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
index bc7a616..35dff28 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.h
@@ -424,16 +424,6 @@
                                       unsigned int offset,
                                       bool *needsTranslation);
 
-// Used for state change notifications between buffers and vertex arrays.
-using OnBufferDataDirtyBinding  = angle::ChannelBinding<size_t, const gl::Context *>;
-using OnBufferDataDirtyChannel  = angle::BroadcastChannel<size_t, const gl::Context *>;
-using OnBufferDataDirtyReceiver = angle::SignalReceiver<size_t, const gl::Context *>;
-
-// Used for state change notifications between RenderTarget11 and Framebuffer11.
-using OnRenderTargetDirtyBinding  = angle::ChannelBinding<size_t, const gl::Context *>;
-using OnRenderTargetDirtyChannel  = angle::BroadcastChannel<size_t, const gl::Context *>;
-using OnRenderTargetDirtyReceiver = angle::SignalReceiver<size_t, const gl::Context *>;
-
 }  // namespace rx
 
 #endif // LIBANGLE_RENDERER_D3D_D3D11_RENDERER11_UTILS_H_
diff --git a/src/libANGLE/signal_utils.cpp b/src/libANGLE/signal_utils.cpp
new file mode 100644
index 0000000..3f327d7
--- /dev/null
+++ b/src/libANGLE/signal_utils.cpp
@@ -0,0 +1,116 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// signal_utils:
+//   Implements the Observer pattern for sending state change notifications
+//   from Subject objects to dependent Observer objects.
+//
+//   See design document:
+//   https://docs.google.com/document/d/15Edfotqg6_l1skTEL8ADQudF_oIdNa7i8Po43k6jMd4/
+
+#include "libANGLE/signal_utils.h"
+
+#include "common/debug.h"
+
+namespace angle
+{
+// Observer implementation.
+ObserverInterface::~ObserverInterface() = default;
+
+// Subject implementation.
+Subject::Subject()
+{
+}
+
+Subject::~Subject()
+{
+    resetObservers();
+}
+
+bool Subject::hasObservers() const
+{
+    return !mObservers.empty();
+}
+
+void Subject::addObserver(ObserverBinding *observer)
+{
+    ASSERT(std::find(mObservers.begin(), mObservers.end(), observer) == mObservers.end());
+    mObservers.push_back(observer);
+}
+
+void Subject::removeObserver(ObserverBinding *observer)
+{
+    auto iter = std::find(mObservers.begin(), mObservers.end(), observer);
+    ASSERT(iter != mObservers.end());
+    mObservers.erase(iter);
+}
+
+void Subject::onStateChange(const gl::Context *context, SubjectMessage message) const
+{
+    if (mObservers.empty())
+        return;
+
+    for (const angle::ObserverBinding *receiver : mObservers)
+    {
+        receiver->onStateChange(context, message);
+    }
+}
+
+void Subject::resetObservers()
+{
+    for (angle::ObserverBinding *observer : mObservers)
+    {
+        observer->onSubjectReset();
+    }
+    mObservers.clear();
+}
+
+// ObserverBinding implementation.
+ObserverBinding::ObserverBinding(ObserverInterface *observer, SubjectIndex index)
+    : mSubject(nullptr), mObserver(observer), mIndex(index)
+{
+    ASSERT(observer);
+}
+
+ObserverBinding::~ObserverBinding()
+{
+    reset();
+}
+
+ObserverBinding::ObserverBinding(const ObserverBinding &other) = default;
+
+ObserverBinding &ObserverBinding::operator=(const ObserverBinding &other) = default;
+
+void ObserverBinding::bind(Subject *subject)
+{
+    ASSERT(mObserver);
+    if (mSubject)
+    {
+        mSubject->removeObserver(this);
+    }
+
+    mSubject = subject;
+
+    if (mSubject)
+    {
+        mSubject->addObserver(this);
+    }
+}
+
+void ObserverBinding::reset()
+{
+    bind(nullptr);
+}
+
+void ObserverBinding::onStateChange(const gl::Context *context, SubjectMessage message) const
+{
+    mObserver->onSubjectStateChange(context, mIndex, message);
+}
+
+void ObserverBinding::onSubjectReset()
+{
+    mSubject = nullptr;
+}
+}  // namespace angle
diff --git a/src/libANGLE/signal_utils.h b/src/libANGLE/signal_utils.h
index 3dd1332..d81d742 100644
--- a/src/libANGLE/signal_utils.h
+++ b/src/libANGLE/signal_utils.h
@@ -4,184 +4,84 @@
 // found in the LICENSE file.
 //
 // signal_utils:
-//   Helper classes for tracking dependent state changes between objects.
-//   These changes are signaled to the dependent class via channels.
+//   Implements the Observer pattern for sending state change notifications
+//   from Subject objects to dependent Observer objects.
+//
 //   See design document:
 //   https://docs.google.com/document/d/15Edfotqg6_l1skTEL8ADQudF_oIdNa7i8Po43k6jMd4/
 
 #ifndef LIBANGLE_SIGNAL_UTILS_H_
 #define LIBANGLE_SIGNAL_UTILS_H_
 
-#include <set>
-
 #include "common/angleutils.h"
-#include "common/debug.h"
+
+namespace gl
+{
+class Context;
+}  // namespace gl
 
 namespace angle
 {
 
-// Interface that the depending class inherits from.
-template <typename ChannelID = uint32_t, typename... MessageT>
-class SignalReceiver
+using SubjectIndex = size_t;
+
+enum class SubjectMessage
 {
-  public:
-    virtual ~SignalReceiver() = default;
-    virtual void signal(ChannelID channelID, MessageT... message) = 0;
+    STATE_CHANGE,
 };
 
-template <typename ChannelID, typename... MessageT>
-class ChannelBinding;
-
-// The host class owns the channel. It uses the channel to fire signals to the receiver.
-template <typename ChannelID = uint32_t, typename... MessageT>
-class BroadcastChannel final : NonCopyable
+// The observing class inherits from this interface class.
+class ObserverInterface
 {
   public:
-    BroadcastChannel();
-    ~BroadcastChannel();
+    virtual ~ObserverInterface();
+    virtual void onSubjectStateChange(const gl::Context *context,
+                                      SubjectIndex index,
+                                      SubjectMessage message) = 0;
+};
 
-    void signal(MessageT... message) const;
+class ObserverBinding;
 
-    void reset();
+// Maintains a list of observer bindings. Sends update messages to the observer.
+class Subject : NonCopyable
+{
+  public:
+    Subject();
+    virtual ~Subject();
 
-    bool empty() const;
+    void onStateChange(const gl::Context *context, SubjectMessage message) const;
+    bool hasObservers() const;
+    void resetObservers();
 
   private:
-    // Only the ChannelBinding class should add or remove receivers.
-    friend class ChannelBinding<ChannelID, MessageT...>;
-    void addReceiver(ChannelBinding<ChannelID, MessageT...> *receiver);
-    void removeReceiver(ChannelBinding<ChannelID, MessageT...> *receiver);
+    // Only the ObserverBinding class should add or remove observers.
+    friend class ObserverBinding;
+    void addObserver(ObserverBinding *observer);
+    void removeObserver(ObserverBinding *observer);
 
-    std::vector<ChannelBinding<ChannelID, MessageT...> *> mReceivers;
+    std::vector<ObserverBinding *> mObservers;
 };
 
-template <typename ChannelID, typename... MessageT>
-BroadcastChannel<ChannelID, MessageT...>::BroadcastChannel()
-{
-}
-
-template <typename ChannelID, typename... MessageT>
-BroadcastChannel<ChannelID, MessageT...>::~BroadcastChannel()
-{
-    reset();
-}
-
-template <typename ChannelID, typename... MessageT>
-void BroadcastChannel<ChannelID, MessageT...>::addReceiver(
-    ChannelBinding<ChannelID, MessageT...> *receiver)
-{
-    ASSERT(std::find(mReceivers.begin(), mReceivers.end(), receiver) == mReceivers.end());
-    mReceivers.push_back(receiver);
-}
-
-template <typename ChannelID, typename... MessageT>
-void BroadcastChannel<ChannelID, MessageT...>::removeReceiver(
-    ChannelBinding<ChannelID, MessageT...> *receiver)
-{
-    auto iter = std::find(mReceivers.begin(), mReceivers.end(), receiver);
-    ASSERT(iter != mReceivers.end());
-    mReceivers.erase(iter);
-}
-
-template <typename ChannelID, typename... MessageT>
-void BroadcastChannel<ChannelID, MessageT...>::signal(MessageT... message) const
-{
-    if (mReceivers.empty())
-        return;
-
-    for (const auto *receiver : mReceivers)
-    {
-        receiver->signal(message...);
-    }
-}
-
-template <typename ChannelID, typename... MessageT>
-void BroadcastChannel<ChannelID, MessageT...>::reset()
-{
-    for (auto receiver : mReceivers)
-    {
-        receiver->onChannelClosed();
-    }
-    mReceivers.clear();
-}
-
-template <typename ChannelID, typename... MessageT>
-bool BroadcastChannel<ChannelID, MessageT...>::empty() const
-{
-    return mReceivers.empty();
-}
-
-// The dependent class keeps bindings to the host's BroadcastChannel.
-template <typename ChannelID = uint32_t, typename... MessageT>
-class ChannelBinding final
+// Keeps a binding between a Subject and Observer, with a specific subject index.
+class ObserverBinding final
 {
   public:
-    ChannelBinding(SignalReceiver<ChannelID, MessageT...> *receiver, ChannelID channelID);
-    ~ChannelBinding();
-    ChannelBinding(const ChannelBinding &other) = default;
-    ChannelBinding &operator=(const ChannelBinding &other) = default;
+    ObserverBinding(ObserverInterface *observer, SubjectIndex index);
+    ~ObserverBinding();
+    ObserverBinding(const ObserverBinding &other);
+    ObserverBinding &operator=(const ObserverBinding &other);
 
-    void bind(BroadcastChannel<ChannelID, MessageT...> *channel);
+    void bind(Subject *subject);
     void reset();
-    void signal(MessageT... message) const;
-    void onChannelClosed();
+    void onStateChange(const gl::Context *context, SubjectMessage message) const;
+    void onSubjectReset();
 
   private:
-    BroadcastChannel<ChannelID, MessageT...> *mChannel;
-    SignalReceiver<ChannelID, MessageT...> *mReceiver;
-    ChannelID mChannelID;
+    Subject *mSubject;
+    ObserverInterface *mObserver;
+    SubjectIndex mIndex;
 };
 
-template <typename ChannelID, typename... MessageT>
-ChannelBinding<ChannelID, MessageT...>::ChannelBinding(
-    SignalReceiver<ChannelID, MessageT...> *receiver,
-    ChannelID channelID)
-    : mChannel(nullptr), mReceiver(receiver), mChannelID(channelID)
-{
-    ASSERT(receiver);
-}
-
-template <typename ChannelID, typename... MessageT>
-ChannelBinding<ChannelID, MessageT...>::~ChannelBinding()
-{
-    reset();
-}
-
-template <typename ChannelID, typename... MessageT>
-void ChannelBinding<ChannelID, MessageT...>::bind(BroadcastChannel<ChannelID, MessageT...> *channel)
-{
-    ASSERT(mReceiver);
-    if (mChannel)
-    {
-        mChannel->removeReceiver(this);
-    }
-
-    mChannel = channel;
-
-    if (mChannel)
-    {
-        mChannel->addReceiver(this);
-    }
-}
-
-template <typename ChannelID, typename... MessageT>
-void ChannelBinding<ChannelID, MessageT...>::reset()
-{
-    bind(nullptr);
-}
-
-template <typename ChannelID, typename... MessageT>
-void ChannelBinding<ChannelID, MessageT...>::signal(MessageT... message) const
-{
-    mReceiver->signal(mChannelID, message...);
-}
-
-template <typename ChannelID, typename... MessageT>
-void ChannelBinding<ChannelID, MessageT...>::onChannelClosed()
-{
-    mChannel = nullptr;
-}
-
 }  // namespace angle
 
 #endif  // LIBANGLE_SIGNAL_UTILS_H_
diff --git a/src/libANGLE/signal_utils_unittest.cpp b/src/libANGLE/signal_utils_unittest.cpp
index 06cb392..69b545d 100644
--- a/src/libANGLE/signal_utils_unittest.cpp
+++ b/src/libANGLE/signal_utils_unittest.cpp
@@ -4,7 +4,7 @@
 // found in the LICENSE file.
 //
 // signal_utils_unittest:
-//   Unit tests for signals and related utils.
+//   Unit tests for Observers and related classes.
 
 #include <gtest/gtest.h>
 
@@ -16,23 +16,28 @@
 namespace
 {
 
-struct SignalThing : public SignalReceiver<>
+struct ObserverClass : public ObserverInterface
 {
-    void signal(uint32_t channelID) override { wasSignaled = true; }
-    bool wasSignaled = false;
+    void onSubjectStateChange(const gl::Context *context,
+                              SubjectIndex index,
+                              SubjectMessage message) override
+    {
+        wasNotified = true;
+    }
+    bool wasNotified = false;
 };
 
-// Test that broadcast signals work.
-TEST(SignalTest, BroadcastSignals)
+// Test that Observer/Subject state change notifications work.
+TEST(ObserverTest, BasicUsage)
 {
-    BroadcastChannel<> channel;
-    SignalThing thing;
-    ChannelBinding<> binding(&thing, 0u);
+    Subject subject;
+    ObserverClass observer;
+    ObserverBinding binding(&observer, 0u);
 
-    binding.bind(&channel);
-    ASSERT_FALSE(thing.wasSignaled);
-    channel.signal();
-    ASSERT_TRUE(thing.wasSignaled);
+    binding.bind(&subject);
+    ASSERT_FALSE(observer.wasNotified);
+    subject.onStateChange(nullptr, SubjectMessage::STATE_CHANGE);
+    ASSERT_TRUE(observer.wasNotified);
 }
 
 }  // anonymous namespace
diff --git a/src/libGLESv2.gypi b/src/libGLESv2.gypi
index a49df48..cac49e0 100644
--- a/src/libGLESv2.gypi
+++ b/src/libGLESv2.gypi
@@ -284,6 +284,7 @@
             'libANGLE/renderer/load_functions_table_autogen.cpp',
             'libANGLE/renderer/renderer_utils.cpp',
             'libANGLE/renderer/renderer_utils.h',
+            'libANGLE/signal_utils.cpp',
             'libANGLE/signal_utils.h',
             'libANGLE/validationEGL.cpp',
             'libANGLE/validationEGL.h',