Add full mix-blend mode support to the OpenGL compositor. (bug 1235995 part 1, r=mstange)
authorDavid Anderson <danderson@mozilla.com>
Tue, 19 Jan 2016 13:24:19 +0700
changeset 280430 5b8303030d9ce36b350e170c3787defc0ebc0c5d
parent 280429 ffac58123eb089dbc94bed648df72136b1b4dc6d
child 280431 77ce3012d481b480f87ff2a0063e1b11d9999f68
push id70451
push userdanderson@mozilla.com
push dateTue, 19 Jan 2016 06:36:06 +0000
treeherdermozilla-inbound@77ce3012d481 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1235995
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Add full mix-blend mode support to the OpenGL compositor. (bug 1235995 part 1, r=mstange)
gfx/layers/Compositor.h
gfx/layers/opengl/CompositorOGL.cpp
gfx/layers/opengl/CompositorOGL.h
gfx/layers/opengl/OGLShaderProgram.cpp
gfx/layers/opengl/OGLShaderProgram.h
layout/reftests/css-blending/mix-blend-mode-soft-light-ref.html
layout/reftests/css-blending/mix-blend-mode-soft-light.html
layout/reftests/css-blending/reftest.list
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -553,12 +553,37 @@ private:
 
 // Returns the number of rects. (Up to 4)
 typedef gfx::Rect decomposedRectArrayT[4];
 size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
                                   const gfx::Rect& aTexCoordRect,
                                   decomposedRectArrayT* aLayerRects,
                                   decomposedRectArrayT* aTextureRects);
 
+static inline bool
+BlendOpIsMixBlendMode(gfx::CompositionOp aOp)
+{
+  switch (aOp) {
+  case gfx::CompositionOp::OP_MULTIPLY:
+  case gfx::CompositionOp::OP_SCREEN:
+  case gfx::CompositionOp::OP_OVERLAY:
+  case gfx::CompositionOp::OP_DARKEN:
+  case gfx::CompositionOp::OP_LIGHTEN:
+  case gfx::CompositionOp::OP_COLOR_DODGE:
+  case gfx::CompositionOp::OP_COLOR_BURN:
+  case gfx::CompositionOp::OP_HARD_LIGHT:
+  case gfx::CompositionOp::OP_SOFT_LIGHT:
+  case gfx::CompositionOp::OP_DIFFERENCE:
+  case gfx::CompositionOp::OP_EXCLUSION:
+  case gfx::CompositionOp::OP_HUE:
+  case gfx::CompositionOp::OP_SATURATION:
+  case gfx::CompositionOp::OP_COLOR:
+  case gfx::CompositionOp::OP_LUMINOSITY:
+    return true;
+  default:
+    return false;
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_COMPOSITOR_H */
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -66,16 +66,28 @@ BindMaskForProgram(ShaderProgramOGL* aPr
                    GLenum aTexUnit, const gfx::Matrix4x4& aTransform)
 {
   MOZ_ASSERT(LOCAL_GL_TEXTURE0 <= aTexUnit && aTexUnit <= LOCAL_GL_TEXTURE31);
   aSourceMask->BindTexture(aTexUnit, gfx::Filter::LINEAR);
   aProgram->SetMaskTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0);
   aProgram->SetMaskLayerTransform(aTransform);
 }
 
+void
+CompositorOGL::BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, GLenum aTexUnit)
+{
+  MOZ_ASSERT(aBackdrop);
+
+  mGLContext->fActiveTexture(aTexUnit);
+  mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, aBackdrop);
+  mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+  mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
+  aProgram->SetBackdropTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0);
+}
+
 CompositorOGL::CompositorOGL(nsIWidget *aWidget, int aSurfaceWidth,
                              int aSurfaceHeight, bool aUseExternalSurfaceSize)
   : mWidget(aWidget)
   , mWidgetSize(-1, -1)
   , mSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mHasBGRA(0)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mFrameInProgress(false)
@@ -702,32 +714,39 @@ CompositorOGL::BeginFrame(const nsIntReg
   // in mobile/android/base/gfx/LayerRenderer.java
 #ifndef MOZ_WIDGET_ANDROID
   mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
   mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
 #endif
 }
 
 void
-CompositorOGL::CreateFBOWithTexture(const IntRect& aRect, bool aCopyFromSource,
+CompositorOGL::CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource,
                                     GLuint aSourceFrameBuffer,
                                     GLuint *aFBO, GLuint *aTexture)
 {
+  *aTexture = CreateTexture(aRect, aCopyFromSource, aSourceFrameBuffer);
+  mGLContext->fGenFramebuffers(1, aFBO);
+}
+
+GLuint
+CompositorOGL::CreateTexture(const IntRect& aRect, bool aCopyFromSource, GLuint aSourceFrameBuffer)
+{
   // we're about to create a framebuffer backed by textures to use as an intermediate
   // surface. What to do if its size (as given by aRect) would exceed the
   // maximum texture size supported by the GL? The present code chooses the compromise
   // of just clamping the framebuffer's size to the max supported size.
   // This gives us a lower resolution rendering of the intermediate surface (children layers).
   // See bug 827170 for a discussion.
   IntRect clampedRect = aRect;
   int32_t maxTexSize = GetMaxTextureSize();
   clampedRect.width = std::min(clampedRect.width, maxTexSize);
   clampedRect.height = std::min(clampedRect.height, maxTexSize);
 
-  GLuint tex, fbo;
+  GLuint tex;
 
   mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
   mGLContext->fGenTextures(1, &tex);
   mGLContext->fBindTexture(mFBOTextureTarget, tex);
 
   if (aCopyFromSource) {
     GLuint curFBO = mCurrentRenderTarget->GetFBO();
     if (curFBO != aSourceFrameBuffer) {
@@ -769,16 +788,17 @@ CompositorOGL::CreateFBOWithTexture(cons
                               0,
                               LOCAL_GL_RGBA,
                               clampedRect.width, clampedRect.height,
                               0,
                               LOCAL_GL_RGBA,
                               LOCAL_GL_UNSIGNED_BYTE,
                               buf.get());
     }
+
     GLenum error = mGLContext->fGetError();
     if (error != LOCAL_GL_NO_ERROR) {
       nsAutoCString msg;
       msg.AppendPrintf("Texture initialization failed! -- error 0x%x, Source %d, Source format %d,  RGBA Compat %d",
                        error, aSourceFrameBuffer, format, isFormatCompatibleWithRGBA);
       NS_ERROR(msg.get());
     }
   } else {
@@ -796,20 +816,17 @@ CompositorOGL::CreateFBOWithTexture(cons
   mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MAG_FILTER,
                              LOCAL_GL_LINEAR);
   mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_S,
                              LOCAL_GL_CLAMP_TO_EDGE);
   mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_T,
                              LOCAL_GL_CLAMP_TO_EDGE);
   mGLContext->fBindTexture(mFBOTextureTarget, 0);
 
-  mGLContext->fGenFramebuffers(1, &fbo);
-
-  *aFBO = fbo;
-  *aTexture = tex;
+  return tex;
 }
 
 ShaderConfigOGL
 CompositorOGL::GetShaderConfigFor(Effect *aEffect,
                                   MaskType aMask,
                                   gfx::CompositionOp aOp,
                                   bool aColorMatrix,
                                   bool aDEAAEnabled) const
@@ -850,23 +867,27 @@ CompositorOGL::GetShaderConfigFor(Effect
                   source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 ||
                   source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8);
     MOZ_ASSERT_IF(source->GetTextureTarget() == LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                   source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 ||
                   source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 ||
                   source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16);
     config = ShaderConfigFromTargetAndFormat(source->GetTextureTarget(),
                                              source->GetFormat());
+    if (!texturedEffect->mPremultiplied) {
+      config.SetNoPremultipliedAlpha();
+    }
     break;
   }
   }
   config.SetColorMatrix(aColorMatrix);
   config.SetMask2D(aMask == MaskType::Mask2d);
   config.SetMask3D(aMask == MaskType::Mask3d);
   config.SetDEAA(aDEAAEnabled);
+  config.SetCompositionOp(aOp);
   return config;
 }
 
 ShaderProgramOGL*
 CompositorOGL::GetShaderProgramFor(const ShaderConfigOGL &aConfig)
 {
   std::map<ShaderConfigOGL, ShaderProgramOGL *>::iterator iter = mPrograms.find(aConfig);
   if (iter != mPrograms.end())
@@ -893,20 +914,25 @@ CompositorOGL::ActivateProgram(ShaderPro
 }
 
 void
 CompositorOGL::ResetProgram()
 {
   mCurrentProgram = nullptr;
 }
 
-
-
 static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, bool aIsPremultiplied = true)
 {
+  if (BlendOpIsMixBlendMode(aBlendMode)) {
+    // Mix-blend modes require an extra step (or more) that cannot be expressed
+    // in the fixed-function blending capabilities of opengl. We handle them
+    // separately in shaders, and the shaders assume we will use our default
+    // blend function for compositing (premultiplied OP_OVER).
+    return false;
+  }
   if (aBlendMode == gfx::CompositionOp::OP_OVER && aIsPremultiplied) {
     return false;
   }
 
   GLenum srcBlend;
   GLenum dstBlend;
   GLenum srcAlphaBlend = LOCAL_GL_ONE;
   GLenum dstAlphaBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA;
@@ -1061,21 +1087,28 @@ CompositorOGL::DrawQuad(const Rect& aRec
     color.g *= opacity;
     color.b *= opacity;
     color.a = opacity;
 
     // We can fold opacity into the color, so no need to consider it further.
     aOpacity = 1.f;
   }
 
+  GLuint mixBlendBackdrop = 0;
   gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER;
+
   if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) {
     EffectBlendMode *blendEffect =
       static_cast<EffectBlendMode*>(aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get());
     blendMode = blendEffect->mBlendMode;
+    if (BlendOpIsMixBlendMode(blendMode)) {
+      gfx::IntRect rect(gfx::IntPoint(0, 0), mCurrentRenderTarget->GetSize());
+
+      mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO());
+    }
   }
 
   // Only apply DEAA to quads that have been transformed such that aliasing
   // could be visible
   bool bEnableAA = gfxPrefs::LayersDEAAEnabled() &&
                    !aTransform.Is2DIntegerTranslation();
 
   bool colorMatrix = aEffectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX];
@@ -1179,16 +1212,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
 
   switch (aEffectChain.mPrimaryEffect->mType) {
     case EffectTypes::SOLID_COLOR: {
       program->SetRenderColor(color);
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE0, maskQuadTransform);
       }
+      if (mixBlendBackdrop) {
+        BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE1);
+      }
 
       didSetBlendMode = SetBlendMode(gl(), blendMode);
 
       BindAndDrawQuad(program, aRect);
     }
     break;
 
   case EffectTypes::RGB: {
@@ -1216,16 +1252,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
       source->AsSourceOGL()->BindTexture(LOCAL_GL_TEXTURE0, filter);
 
       program->SetTextureUnit(0);
       program->SetTextureTransform(textureTransform);
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform);
       }
+      if (mixBlendBackdrop) {
+        BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2);
+      }
 
       BindAndDrawQuadWithTextureRect(program, aRect, texturedEffect->mTextureCoords, source);
     }
     break;
   case EffectTypes::YCBCR: {
       EffectYCbCr* effectYCbCr =
         static_cast<EffectYCbCr*>(aEffectChain.mPrimaryEffect.get());
       TextureSource* sourceYCbCr = effectYCbCr->mTexture;
@@ -1244,16 +1283,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
       sourceCr->BindTexture(LOCAL_GL_TEXTURE2, effectYCbCr->mFilter);
 
       program->SetYCbCrTextureUnits(Y, Cb, Cr);
       program->SetTextureTransform(Matrix4x4());
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE3, maskQuadTransform);
       }
+      if (mixBlendBackdrop) {
+        BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE4);
+      }
       didSetBlendMode = SetBlendMode(gl(), blendMode);
       BindAndDrawQuadWithTextureRect(program,
                                      aRect,
                                      effectYCbCr->mTextureCoords,
                                      sourceYCbCr->GetSubSource(Y));
     }
     break;
   case EffectTypes::NV12: {
@@ -1278,16 +1320,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
       }
 
       program->SetNV12TextureUnits(Y, CbCr);
       program->SetTextureTransform(Matrix4x4());
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE2, maskQuadTransform);
       }
+      if (mixBlendBackdrop) {
+        BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE3);
+      }
       didSetBlendMode = SetBlendMode(gl(), blendMode);
       BindAndDrawQuadWithTextureRect(program,
                                      aRect,
                                      effectNV12->mTextureCoords,
                                      sourceNV12->GetSubSource(Y));
     }
     break;
   case EffectTypes::RENDER_TARGET: {
@@ -1304,16 +1349,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
       transform.PreTranslate(0.0, 1.0);
       transform.PreScale(1.0f, -1.0f);
       program->SetTextureTransform(Matrix4x4::From2D(transform));
       program->SetTextureUnit(0);
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform);
       }
+      if (mixBlendBackdrop) {
+        BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2);
+      }
 
       if (config.mFeatures & ENABLE_TEXTURE_RECT) {
         // 2DRect case, get the multiplier right for a sampler2DRect
         program->SetTexCoordMultiplier(aRect.width, aRect.height);
       }
 
       // Drawing is always flipped, but when copying between surfaces we want to avoid
       // this. Pass true for the flip parameter to introduce a second flip
@@ -1387,16 +1435,19 @@ CompositorOGL::DrawQuad(const Rect& aRec
     MOZ_ASSERT(false, "Unhandled effect type");
     break;
   }
 
   if (didSetBlendMode) {
     gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
                              LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA);
   }
+  if (mixBlendBackdrop) {
+    gl()->fDeleteTextures(1, &mixBlendBackdrop);
+  }
 
   // in case rendering has used some other GL context
   MakeCurrent();
   LayerScope::DrawEnd(mGLContext, aEffectChain, aRect.width, aRect.height);
 }
 
 void
 CompositorOGL::EndFrame()
@@ -1446,32 +1497,22 @@ CompositorOGL::EndFrame()
     mGLContext->SwapBuffers();
   }
 
   mCurrentRenderTarget = nullptr;
 
   mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
 
   // Unbind all textures
-  mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0);
-  mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
-  if (!mGLContext->IsGLES()) {
-    mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
-  }
-
-  mGLContext->fActiveTexture(LOCAL_GL_TEXTURE1);
-  mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
-  if (!mGLContext->IsGLES()) {
-    mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
-  }
-
-  mGLContext->fActiveTexture(LOCAL_GL_TEXTURE2);
-  mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
-  if (!mGLContext->IsGLES()) {
-    mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
+  for (GLuint i = 0; i <= 4; i++) {
+    mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+    mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0);
+    if (!mGLContext->IsGLES()) {
+      mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0);
+    }
   }
 }
 
 void
 CompositorOGL::EndFrameForExternalComposition(const gfx::Matrix& aTransform)
 {
   MOZ_ASSERT(!mTarget);
   if (mTexturePool) {
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -211,16 +211,21 @@ public:
   {
     TextureFactoryIdentifier result =
       TextureFactoryIdentifier(LayersBackend::LAYERS_OPENGL,
                                XRE_GetProcessType(),
                                GetMaxTextureSize(),
                                mFBOTextureTarget == LOCAL_GL_TEXTURE_2D,
                                SupportsPartialTextureUpdate());
     result.mSupportedBlendModes += gfx::CompositionOp::OP_SOURCE;
+    for (uint8_t op = 0; op < uint8_t(gfx::CompositionOp::OP_COUNT); op++) {
+      if (BlendOpIsMixBlendMode(gfx::CompositionOp(op))) {
+        result.mSupportedBlendModes += gfx::CompositionOp(op);
+      }
+    }
     return result;
   }
 
   virtual already_AddRefed<CompositingRenderTarget>
   CreateRenderTarget(const gfx::IntRect &aRect, SurfaceInitMode aInit) override;
 
   virtual already_AddRefed<CompositingRenderTarget>
   CreateRenderTargetFromSource(const gfx::IntRect &aRect,
@@ -417,16 +422,17 @@ private:
    * Note that the texture target type will be
    * of the type returned by FBOTextureTarget; different
    * shaders are required to sample from the different
    * texture types.
    */
   void CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource,
                             GLuint aSourceFrameBuffer,
                             GLuint *aFBO, GLuint *aTexture);
+  GLuint CreateTexture(const gfx::IntRect& aRect, bool aCopyFromSource, GLuint aSourceFrameBuffer);
 
   void BindAndDrawQuads(ShaderProgramOGL *aProg,
                         int aQuads,
                         const gfx::Rect* aLayerRect,
                         const gfx::Rect* aTextureRect);
   void BindAndDrawQuad(ShaderProgramOGL *aProg,
                        const gfx::Rect& aLayerRect,
                        const gfx::Rect& aTextureRect = gfx::Rect(0.0f, 0.0f, 1.0f, 1.0f)) {
@@ -441,16 +447,22 @@ private:
                                       const gfx::Rect& aTexCoordRect,
                                       TextureSource *aTexture);
   gfx::Point3D GetLineCoefficients(const gfx::Point& aPoint1,
                                    const gfx::Point& aPoint2);
   void ActivateProgram(ShaderProgramOGL *aProg);
   void CleanupResources();
 
   /**
+   * Bind the texture behind the current render target as the backdrop for a
+   * mix-blend shader.
+   */
+  void BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, GLenum aTexUnit);
+
+  /**
    * Copies the content of our backbuffer to the set transaction target.
    * Does not restore the target FBO, so only call from EndFrame.
    */
   void CopyToTarget(gfx::DrawTarget* aTarget, const nsIntPoint& aTopLeft, const gfx::Matrix& aWorldMatrix);
 
   /**
    * Implements the flipping of the y-axis to convert from layers/compositor
    * coordinates to OpenGL coordinates.
--- a/gfx/layers/opengl/OGLShaderProgram.cpp
+++ b/gfx/layers/opengl/OGLShaderProgram.cpp
@@ -38,16 +38,17 @@ AddUniforms(ProgramProfileOGL& aProfile)
         "uLayerOpacity",
         "uTexture",
         "uYTexture",
         "uCbTexture",
         "uCrTexture",
         "uBlackTexture",
         "uWhiteTexture",
         "uMaskTexture",
+        "uBackdropTexture",
         "uRenderColor",
         "uTexCoordMultiplier",
         "uCbCrTexCoordMultiplier",
         "uTexturePass2",
         "uColorMatrix",
         "uColorMatrixVector",
         "uBlurRadius",
         "uBlurOffset",
@@ -143,35 +144,43 @@ ShaderConfigOGL::SetMask2D(bool aEnabled
 
 void
 ShaderConfigOGL::SetMask3D(bool aEnabled)
 {
   SetFeature(ENABLE_MASK_3D, aEnabled);
 }
 
 void
-ShaderConfigOGL::SetPremultiply(bool aEnabled)
+ShaderConfigOGL::SetNoPremultipliedAlpha()
 {
-  SetFeature(ENABLE_PREMULTIPLY, aEnabled);
+  SetFeature(ENABLE_NO_PREMUL_ALPHA, true);
 }
 
 void
 ShaderConfigOGL::SetDEAA(bool aEnabled)
 {
   SetFeature(ENABLE_DEAA, aEnabled);
 }
 
+void
+ShaderConfigOGL::SetCompositionOp(CompositionOp aOp)
+{
+  mCompositionOp = aOp;
+}
+
 /* static */ ProgramProfileOGL
 ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
 {
   ProgramProfileOGL result;
   ostringstream fs, vs;
 
   AddUniforms(result);
 
+  CompositionOp blendOp = aConfig.mCompositionOp;
+
   vs << "#ifdef GL_ES" << endl;
   vs << "#define EDGE_PRECISION mediump" << endl;
   vs << "#else" << endl;
   vs << "#define EDGE_PRECISION" << endl;
   vs << "#endif" << endl;
   vs << "uniform mat4 uMatrixProj;" << endl;
   vs << "uniform vec4 uLayerRects[4];" << endl;
   vs << "uniform mat4 uLayerTransform;" << endl;
@@ -185,16 +194,20 @@ ProgramProfileOGL::GetProfileFor(ShaderC
   vs << "attribute vec4 aCoord;" << endl;
 
   if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
     vs << "uniform mat4 uTextureTransform;" << endl;
     vs << "uniform vec4 uTextureRects[4];" << endl;
     vs << "varying vec2 vTexCoord;" << endl;
   }
 
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    vs << "varying vec2 vBackdropCoord;" << endl;
+  }
+
   if (aConfig.mFeatures & ENABLE_MASK_2D ||
       aConfig.mFeatures & ENABLE_MASK_3D) {
     vs << "uniform mat4 uMaskTransform;" << endl;
     vs << "varying vec3 vMaskCoord;" << endl;
   }
 
   vs << "void main() {" << endl;
   vs << "  int vertexID = int(aCoord.w);" << endl;
@@ -268,16 +281,20 @@ ProgramProfileOGL::GetProfileFor(ShaderC
     if (aConfig.mFeatures & ENABLE_MASK_3D) {
       // correct for perspective correct interpolation, see comment in D3D10 shader
       vs << "  vMaskCoord.z = 1.0;" << endl;
       vs << "  vMaskCoord *= finalPosition.w;" << endl;
     }
   }
   vs << "  finalPosition.xy -= uRenderTargetOffset * finalPosition.w;" << endl;
   vs << "  finalPosition = uMatrixProj * finalPosition;" << endl;
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    // Move from clip space coordinates into texture/uv-coordinates.
+    vs << "  vBackdropCoord = (finalPosition.xy + vec2(1.0, 1.0)) / 2.0;" << endl;
+  }
   vs << "  gl_Position = finalPosition;" << endl;
   vs << "}" << endl;
 
   if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) {
     fs << "#extension GL_ARB_texture_rectangle : require" << endl;
   }
   if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) {
     fs << "#extension GL_OES_EGL_image_external : require" << endl;
@@ -304,16 +321,19 @@ ProgramProfileOGL::GetProfileFor(ShaderC
     if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) {
       fs << "uniform mat4 uColorMatrix;" << endl;
       fs << "uniform vec4 uColorMatrixVector;" << endl;
     }
     if (aConfig.mFeatures & ENABLE_OPACITY) {
       fs << "uniform COLOR_PRECISION float uLayerOpacity;" << endl;
     }
   }
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    fs << "varying vec2 vBackdropCoord;" << endl;
+  }
 
   const char *sampler2D = "sampler2D";
   const char *texture2D = "texture2D";
 
   if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) {
     fs << "uniform vec2 uTexCoordMultiplier;" << endl;
     if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR ||
         aConfig.mFeatures & ENABLE_TEXTURE_NV12) {
@@ -337,26 +357,37 @@ ProgramProfileOGL::GetProfileFor(ShaderC
   } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) {
     fs << "uniform sampler2D uBlackTexture;" << endl;
     fs << "uniform sampler2D uWhiteTexture;" << endl;
     fs << "uniform bool uTexturePass2;" << endl;
   } else {
     fs << "uniform " << sampler2D << " uTexture;" << endl;
   }
 
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    // Component alpha should be flattened away inside blend containers.
+    MOZ_ASSERT(!(aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA));
+
+    fs << "uniform sampler2D uBackdropTexture;" << endl;
+  }
+
   if (aConfig.mFeatures & ENABLE_MASK_2D ||
       aConfig.mFeatures & ENABLE_MASK_3D) {
     fs << "varying vec3 vMaskCoord;" << endl;
     fs << "uniform sampler2D uMaskTexture;" << endl;
   }
 
   if (aConfig.mFeatures & ENABLE_DEAA) {
     fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl;
   }
 
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    BuildMixBlender(aConfig, fs);
+  }
+
   if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
     fs << "vec4 sample(vec2 coord) {" << endl;
     fs << "  vec4 color;" << endl;
     if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR ||
         aConfig.mFeatures & ENABLE_TEXTURE_NV12) {
       if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) {
         if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) {
           fs << "  COLOR_PRECISION float y = texture2D(uYTexture, coord * uTexCoordMultiplier).r;" << endl;
@@ -454,30 +485,31 @@ For [0,1] instead of [0,255], and to 5 p
     }
     if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) {
       fs << "  color = uColorMatrix * vec4(color.rgb / color.a, color.a) + uColorMatrixVector;" << endl;
       fs << "  color.rgb *= color.a;" << endl;
     }
     if (aConfig.mFeatures & ENABLE_OPACITY) {
       fs << "  color *= uLayerOpacity;" << endl;
     }
-    if (aConfig.mFeatures & ENABLE_PREMULTIPLY) {
-      fs << " color.rgb *= color.a;" << endl;
-    }
   }
   if (aConfig.mFeatures & ENABLE_DEAA) {
     // Calculate the sub-pixel coverage of the pixel and modulate its opacity
     // by that amount to perform DEAA.
     fs << "  vec3 ssPos = vec3(gl_FragCoord.xy, 1.0);" << endl;
     fs << "  float deaaCoverage = clamp(dot(uSSEdges[0], ssPos), 0.0, 1.0);" << endl;
     fs << "  deaaCoverage *= clamp(dot(uSSEdges[1], ssPos), 0.0, 1.0);" << endl;
     fs << "  deaaCoverage *= clamp(dot(uSSEdges[2], ssPos), 0.0, 1.0);" << endl;
     fs << "  deaaCoverage *= clamp(dot(uSSEdges[3], ssPos), 0.0, 1.0);" << endl;
     fs << "  color *= deaaCoverage;" << endl;
   }
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    fs << "  vec4 backdrop = texture2D(uBackdropTexture, vBackdropCoord);" << endl;
+    fs << "  color = mixAndBlend(backdrop, color);" << endl;
+  }
   if (aConfig.mFeatures & ENABLE_MASK_3D) {
     fs << "  vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;" << endl;
     fs << "  COLOR_PRECISION float mask = texture2D(uMaskTexture, maskCoords).r;" << endl;
     fs << "  color *= mask;" << endl;
   } else if (aConfig.mFeatures & ENABLE_MASK_2D) {
     fs << "  COLOR_PRECISION float mask = texture2D(uMaskTexture, vMaskCoord.xy).r;" << endl;
     fs << "  color *= mask;" << endl;
   } else {
@@ -502,20 +534,236 @@ For [0,1] instead of [0,255], and to 5 p
     } else {
       result.mTextureCount = 1;
     }
   }
   if (aConfig.mFeatures & ENABLE_MASK_2D ||
       aConfig.mFeatures & ENABLE_MASK_3D) {
     result.mTextureCount = 1;
   }
+  if (BlendOpIsMixBlendMode(blendOp)) {
+    result.mTextureCount += 1;
+  }
 
   return result;
 }
 
+void
+ProgramProfileOGL::BuildMixBlender(const ShaderConfigOGL& aConfig, std::ostringstream& fs)
+{
+  // From the "Compositing and Blending Level 1" spec.
+  // Generate helper functions first.
+  switch (aConfig.mCompositionOp) {
+  case gfx::CompositionOp::OP_OVERLAY:
+  case gfx::CompositionOp::OP_HARD_LIGHT:
+    // Note: we substitute (2*src-1) into the screen formula below.
+    fs << "float hardlight(float dest, float src) {" << endl;
+    fs << "  if (src <= 0.5) {" << endl;
+    fs << "    return dest * (2.0 * src);" << endl;
+    fs << "  } else {" << endl;
+    fs << "    return 2.0*dest + 2.0*src - 1.0 - 2.0*dest*src;" << endl;
+    fs << "  }" << endl;
+    fs << "}" << endl;
+    break;
+  case gfx::CompositionOp::OP_COLOR_DODGE:
+    fs << "float dodge(float dest, float src) {" << endl;
+    fs << "  if (dest == 0.0) {" << endl;
+    fs << "    return 0.0;" << endl;
+    fs << "  } else if (src == 1.0) {" << endl;
+    fs << "    return 1.0;" << endl;
+    fs << "  } else {" << endl;
+    fs << "    return min(1.0, dest / (1.0 - src));" << endl;
+    fs << "  }" << endl;
+    fs << "}" << endl;
+    break;
+  case gfx::CompositionOp::OP_COLOR_BURN:
+    fs << "float burn(float dest, float src) {" << endl;
+    fs << "  if (dest == 1.0) {" << endl;
+    fs << "    return 1.0;" << endl;
+    fs << "  } else if (src == 0.0) {" << endl;
+    fs << "    return 0.0;" << endl;
+    fs << "  } else {" << endl;
+    fs << "    return 1.0 - min(1.0, (1.0 - dest) / src);" << endl;
+    fs << "  }" << endl;
+    fs << "}" << endl;
+    break;
+  case gfx::CompositionOp::OP_SOFT_LIGHT:
+    fs << "float darken(float dest) {" << endl;
+    fs << "  if (dest <= 0.25) {" << endl;
+    fs << "    return ((16.0 * dest - 12.0) * dest + 4.0) * dest;" << endl;
+    fs << "  } else {" << endl;
+    fs << "    return sqrt(dest);" << endl;
+    fs << "  }" << endl;
+    fs << "}" << endl;
+    fs << "float softlight(float dest, float src) {" << endl;
+    fs << "  if (src <= 0.5) {" << endl;
+    fs << "    return dest - (1.0 - 2.0 * src) * dest * (1.0 - dest);" << endl;
+    fs << "  } else {" << endl;
+    fs << "    return dest + (2.0 * src - 1.0) * (darken(dest) - dest);" << endl;
+    fs << "  }" << endl;
+    fs << "}" << endl;
+    break;
+  case gfx::CompositionOp::OP_HUE:
+  case gfx::CompositionOp::OP_SATURATION:
+  case gfx::CompositionOp::OP_COLOR:
+  case gfx::CompositionOp::OP_LUMINOSITY:
+    fs << "float Lum(vec3 c) {" << endl;
+    fs << "  return dot(vec3(0.3, 0.59, 0.11), c);" << endl;
+    fs << "}" << endl;
+    fs << "vec3 ClipColor(vec3 c) {" << endl;
+    fs << "  float L = Lum(c);" << endl;
+    fs << "  float n = min(min(c.r, c.g), c.b);" << endl;
+    fs << "  float x = max(max(c.r, c.g), c.b);" << endl;
+    fs << "  if (n < 0.0) {" << endl;
+    fs << "    c = L + (((c - L) * L) / (L - n));" << endl;
+    fs << "  }" << endl;
+    fs << "  if (x > 1.0) {" << endl;
+    fs << "    c = L + (((c - L) * (1.0 - L)) / (x - L));" << endl;
+    fs << "  }" << endl;
+    fs << "  return c;" << endl;
+    fs << "}" << endl;
+    fs << "vec3 SetLum(vec3 c, float L) {" << endl;
+    fs << "  float d = L - Lum(c);" << endl;
+    fs << "  return ClipColor(vec3(" << endl;
+    fs << "    c.r + d," << endl;
+    fs << "    c.g + d," << endl;
+    fs << "    c.b + d));" << endl;
+    fs << "}" << endl;
+    fs << "float Sat(vec3 c) {" << endl;
+    fs << "  return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);" << endl;
+    fs << "}" << endl;
+
+    // To use this helper, re-arrange rgb such that r=min, g=mid, and b=max.
+    fs << "vec3 SetSatInner(vec3 c, float s) {" << endl;
+    fs << "  if (c.b > c.r) {" << endl;
+    fs << "    c.g = (((c.g - c.r) * s) / (c.b - c.r));" << endl;
+    fs << "    c.b = s;" << endl;
+    fs << "  } else {" << endl;
+    fs << "    c.gb = vec2(0.0, 0.0);" << endl;
+    fs << "  }" << endl;
+    fs << "  return vec3(0.0, c.gb);" << endl;
+    fs << "}" << endl;
+
+    fs << "vec3 SetSat(vec3 c, float s) {" << endl;
+    fs << "  if (c.r <= c.g) {" << endl;
+    fs << "    if (c.g <= c.b) {" << endl;
+    fs << "      c.rgb = SetSatInner(c.rgb, s);" << endl;
+    fs << "    } else if (c.r <= c.b) {" << endl;
+    fs << "      c.rbg = SetSatInner(c.rbg, s);" << endl;
+    fs << "    } else {" << endl;
+    fs << "      c.brg = SetSatInner(c.brg, s);" << endl;
+    fs << "    }" << endl;
+    fs << "  } else if (c.r <= c.b) {" << endl;
+    fs << "    c.grb = SetSatInner(c.grb, s);" << endl;
+    fs << "  } else if (c.g <= c.b) {" << endl;
+    fs << "    c.gbr = SetSatInner(c.gbr, s);" << endl;
+    fs << "  } else {" << endl;
+    fs << "    c.bgr = SetSatInner(c.bgr, s);" << endl;
+    fs << "  }" << endl;
+    fs << "  return c;" << endl;
+    fs << "}" << endl;
+    break;
+  default:
+    break;
+  }
+
+  // Generate the main blending helper.
+  fs << "vec3 blend(vec3 dest, vec3 src) {" << endl;
+  switch (aConfig.mCompositionOp) {
+  case gfx::CompositionOp::OP_MULTIPLY:
+    fs << "  return dest * src;" << endl;
+    break;
+  case gfx::CompositionOp::OP_SCREEN:
+    fs << "  return dest + src - (dest * src);" << endl;
+    break;
+  case gfx::CompositionOp::OP_OVERLAY:
+    fs << "  return vec3(" << endl;
+    fs << "    hardlight(src.r, dest.r)," << endl;
+    fs << "    hardlight(src.g, dest.g)," << endl;
+    fs << "    hardlight(src.b, dest.b));" << endl;
+    break;
+  case gfx::CompositionOp::OP_DARKEN:
+    fs << "  return min(dest, src);" << endl;
+    break;
+  case gfx::CompositionOp::OP_LIGHTEN:
+    fs << "  return max(dest, src);" << endl;
+    break;
+  case gfx::CompositionOp::OP_COLOR_DODGE:
+    fs << "  return vec3(" << endl;
+    fs << "    dodge(dest.r, src.r)," << endl;
+    fs << "    dodge(dest.g, src.g)," << endl;
+    fs << "    dodge(dest.b, src.b));" << endl;
+    break;
+  case gfx::CompositionOp::OP_COLOR_BURN:
+    fs << "  return vec3(" << endl;
+    fs << "    burn(dest.r, src.r)," << endl;
+    fs << "    burn(dest.g, src.g)," << endl;
+    fs << "    burn(dest.b, src.b));" << endl;
+    break;
+  case gfx::CompositionOp::OP_HARD_LIGHT:
+    fs << "  return vec3(" << endl;
+    fs << "    hardlight(dest.r, src.r)," << endl;
+    fs << "    hardlight(dest.g, src.g)," << endl;
+    fs << "    hardlight(dest.b, src.b));" << endl;
+    break;
+  case gfx::CompositionOp::OP_SOFT_LIGHT:
+    fs << "  return vec3(" << endl;
+    fs << "    softlight(dest.r, src.r)," << endl;
+    fs << "    softlight(dest.g, src.g)," << endl;
+    fs << "    softlight(dest.b, src.b));" << endl;
+    break;
+  case gfx::CompositionOp::OP_DIFFERENCE:
+    fs << "  return abs(dest - src);" << endl;
+    break;
+  case gfx::CompositionOp::OP_EXCLUSION:
+    fs << "  return dest + src - 2.0*dest*src;" << endl;
+    break;
+  case gfx::CompositionOp::OP_HUE:
+    fs << "  return SetLum(SetSat(src, Sat(dest)), Lum(dest));" << endl;
+    break;
+  case gfx::CompositionOp::OP_SATURATION:
+    fs << "  return SetLum(SetSat(dest, Sat(src)), Lum(dest));" << endl;
+    break;
+  case gfx::CompositionOp::OP_COLOR:
+    fs << "  return SetLum(src, Lum(dest));" << endl;
+    break;
+  case gfx::CompositionOp::OP_LUMINOSITY:
+    fs << "  return SetLum(dest, Lum(src));" << endl;
+    break;
+  default:
+    MOZ_ASSERT_UNREACHABLE("unknown blend mode");
+  }
+  fs << "}" << endl;
+
+  // Generate the mix-blend function the fragment shader will call.
+  fs << "vec4 mixAndBlend(vec4 backdrop, vec4 color) {" << endl;
+
+  // Shortcut when the backdrop or source alpha is 0, otherwise we may leak
+  // Infinity into the blend function and return incorrect results.
+  fs << "  if (backdrop.a == 0.0) {" << endl;
+  fs << "    return color;" << endl;
+  fs << "  }" << endl;
+  fs << "  if (color.a == 0.0) {" << endl;
+  fs << "    return backdrop;" << endl;
+  fs << "  }" << endl;
+
+  // The spec assumes there is no premultiplied alpha. The backdrop is always
+  // premultiplied, so undo the premultiply. If the source is premultiplied we
+  // must fix that as well.
+  fs << "  backdrop.rgb /= backdrop.a;" << endl;
+  if (!(aConfig.mFeatures & ENABLE_NO_PREMUL_ALPHA)) {
+    fs << "  color.rgb /= color.a;" << endl;
+  }
+  fs << "  vec3 blended = blend(backdrop.rgb, color.rgb);" << endl;
+  fs << "  color.rgb = (1.0 - backdrop.a) * color.rgb + backdrop.a * blended.rgb;" << endl;
+  fs << "  color.rgb *= color.a;" << endl;
+  fs << "  return color;" << endl;
+  fs << "}" << endl;
+}
+
 ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile)
   : mGL(aGL)
   , mProgram(0)
   , mProfile(aProfile)
   , mProgramState(STATE_NEW)
 {
 }
 
--- a/gfx/layers/opengl/OGLShaderProgram.h
+++ b/gfx/layers/opengl/OGLShaderProgram.h
@@ -34,17 +34,17 @@ enum ShaderFeatures {
   ENABLE_TEXTURE_COMPONENT_ALPHA=0x20,
   ENABLE_TEXTURE_NO_ALPHA=0x40,
   ENABLE_TEXTURE_RB_SWAP=0x80,
   ENABLE_OPACITY=0x100,
   ENABLE_BLUR=0x200,
   ENABLE_COLOR_MATRIX=0x400,
   ENABLE_MASK_2D=0x800,
   ENABLE_MASK_3D=0x1000,
-  ENABLE_PREMULTIPLY=0x2000,
+  ENABLE_NO_PREMUL_ALPHA=0x2000,
   ENABLE_DEAA=0x4000
 };
 
 class KnownUniform {
 public:
   // this needs to be kept in sync with strings in 'AddUniforms'
   enum KnownUniformName {
     NotAKnownUniform = -1,
@@ -60,16 +60,17 @@ public:
     LayerOpacity,
     Texture,
     YTexture,
     CbTexture,
     CrTexture,
     BlackTexture,
     WhiteTexture,
     MaskTexture,
+    BackdropTexture,
     RenderColor,
     TexCoordMultiplier,
     CbCrTexCoordMultiplier,
     TexturePass2,
     ColorMatrix,
     ColorMatrixVector,
     BlurRadius,
     BlurOffset,
@@ -202,46 +203,52 @@ public:
     float f16v[16];
   } mValue;
 };
 
 class ShaderConfigOGL
 {
 public:
   ShaderConfigOGL() :
-    mFeatures(0) {}
+    mFeatures(0),
+    mCompositionOp(gfx::CompositionOp::OP_OVER)
+  {}
 
   void SetRenderColor(bool aEnabled);
   void SetTextureTarget(GLenum aTarget);
   void SetRBSwap(bool aEnabled);
   void SetNoAlpha(bool aEnabled);
   void SetOpacity(bool aEnabled);
   void SetYCbCr(bool aEnabled);
   void SetNV12(bool aEnabled);
   void SetComponentAlpha(bool aEnabled);
   void SetColorMatrix(bool aEnabled);
   void SetBlur(bool aEnabled);
   void SetMask2D(bool aEnabled);
   void SetMask3D(bool aEnabled);
-  void SetPremultiply(bool aEnabled);
   void SetDEAA(bool aEnabled);
+  void SetCompositionOp(gfx::CompositionOp aOp);
+  void SetNoPremultipliedAlpha();
 
   bool operator< (const ShaderConfigOGL& other) const {
-    return mFeatures < other.mFeatures;
+    return mFeatures < other.mFeatures ||
+           (mFeatures == other.mFeatures &&
+            (int)mCompositionOp < (int)other.mCompositionOp);
   }
 
 public:
   void SetFeature(int aBitmask, bool aState) {
     if (aState)
       mFeatures |= aBitmask;
     else
       mFeatures &= (~aBitmask);
   }
 
   int mFeatures;
+  gfx::CompositionOp mCompositionOp;
 };
 
 static inline ShaderConfigOGL
 ShaderConfigFromTargetAndFormat(GLenum aTarget,
                                 gfx::SurfaceFormat aFormat)
 {
   ShaderConfigOGL config;
   config.SetTextureTarget(aTarget);
@@ -273,16 +280,19 @@ struct ProgramProfileOGL
 
   KnownUniform mUniforms[KnownUniform::KnownUniformCount];
   nsTArray<const char *> mDefines;
   size_t mTextureCount;
 
   ProgramProfileOGL() :
     mTextureCount(0)
   {}
+
+ private:
+  static void BuildMixBlender(const ShaderConfigOGL& aConfig, std::ostringstream& fs);
 };
 
 
 #if defined(DEBUG)
 #define CHECK_CURRENT_PROGRAM 1
 #define ASSERT_THIS_PROGRAM                                             \
   do {                                                                  \
     GLuint currentProgram;                                              \
@@ -428,16 +438,20 @@ public:
   void SetWhiteTextureUnit(GLint aUnit) {
     SetUniform(KnownUniform::WhiteTexture, aUnit);
   }
 
   void SetMaskTextureUnit(GLint aUnit) {
     SetUniform(KnownUniform::MaskTexture, aUnit);
   }
 
+  void SetBackdropTextureUnit(GLint aUnit) {
+    SetUniform(KnownUniform::BackdropTexture, aUnit);
+  }
+
   void SetRenderColor(const gfx::Color& aColor) {
     SetUniform(KnownUniform::RenderColor, aColor);
   }
 
   void SetColorMatrix(const gfx::Matrix5x4& aColorMatrix)
   {
     SetMatrixUniform(KnownUniform::ColorMatrix, &aColorMatrix._11);
     SetUniform(KnownUniform::ColorMatrixVector, 4, &aColorMatrix._51);
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/mix-blend-mode-soft-light-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<head>
+  <style>
+    .parent {
+       width: 200px;
+       height: 200px;
+       isolation: isolate;
+    }
+    .child {
+      width: 200px;
+      height: 200px;
+      background: #7775b6;
+    }
+  </style>
+</head>
+<body>
+ <div class="parent">
+  <div class="child">
+  </div>
+ </div>
+</body>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-blending/mix-blend-mode-soft-light.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<head>
+  <style>
+    .parent {
+       width: 200px;
+       height: 200px;
+       isolation: isolate;
+       background: #5856a2;
+    }
+    .child {
+      width: 200px;
+      height: 200px;
+      mix-blend-mode: soft-light;
+      opacity: 0.5;
+      background: white;
+      will-change: opacity;
+    }
+  </style>
+</head>
+<body>
+ <div class="parent">
+  <div class="child">
+  </div>
+ </div>
+</body>
--- a/layout/reftests/css-blending/reftest.list
+++ b/layout/reftests/css-blending/reftest.list
@@ -38,17 +38,17 @@ fuzzy-if(azureQuartz,1,1600) fuzzy-if(d2
 fuzzy-if(azureQuartz,2,40000) fuzzy-if(azureSkia||d2d||gtkWidget,1,40000) pref(layout.css.background-blend-mode.enabled,true) == background-blending-image-color-959674.html background-blending-image-color-959674-ref.html
 
 #fuzzy due to inconsistencies in rounded rect cliping between parent and child; may be related to antialiasing. Between platforms, the max difference is the same, and the number of different pixels is either 36 or 37. (Win, Mac and Lin)
 fuzzy(64,37) pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-952051.html mix-blend-mode-952051-ref.html
 
 pref(layout.css.mix-blend-mode.enabled,true) pref(layout.css.filters.enabled,true) == mix-blend-mode-and-filter.html mix-blend-mode-and-filter-ref.html
 pref(layout.css.mix-blend-mode.enabled,true) pref(layout.css.filters.enabled,true) == mix-blend-mode-and-filter.svg mix-blend-mode-and-filter-ref.svg
 
-pref(layout.css.mix-blend-mode.enabled,true) fuzzy-if(d2d,1,14400) == mix-blend-mode-child-of-blended-has-opacity.html mix-blend-mode-child-of-blended-has-opacity-ref.html
+fuzzy(1,14400) pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-child-of-blended-has-opacity.html mix-blend-mode-child-of-blended-has-opacity-ref.html
 
 pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-nested-976533.html mix-blend-mode-nested-976533-ref.html
 pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-culling-1207041.html mix-blend-mode-culling-1207041-ref.html
 pref(layout.css.mix-blend-mode.enabled,true) == mix-blend-mode-dest-alpha-1135271.html mix-blend-mode-dest-alpha-1135271-ref.html
 
 # Test plan 5.3.1 Blending between the background layers and the background color for an element with background-blend-mode
 # Test 9
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-image-color-svg-as-data-uri.html background-blending-image-color-ref.html
@@ -81,14 +81,16 @@ pref(layout.css.background-blend-mode.en
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-background-origin-border-box.html background-blending-background-origin-ref.html
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-background-origin-content-box.html background-blending-background-origin-ref.html
 
 # Test plan 5.3.11 background-blend-mode for an element with background-attachement
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-background-attachement-fixed.html background-blending-background-attachement-fixed-ref.html
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-background-attachement-fixed-scroll.html background-blending-background-attachement-fixed-scroll-ref.html
 
 pref(layout.css.background-blend-mode.enabled,true) == background-blend-mode-body-image.html background-blend-mode-body-image-ref.html
-fuzzy-if(Android,4,768) pref(layout.css.background-blend-mode.enabled,true) == background-blend-mode-body-transparent-image.html background-blend-mode-body-transparent-image-ref.html
+fuzzy-if(Android,4,768) fuzzy-if(gtkWidget,1,132) pref(layout.css.background-blend-mode.enabled,true) == background-blend-mode-body-transparent-image.html background-blend-mode-body-transparent-image-ref.html
 
 pref(layout.css.background-blend-mode.enabled,true) == background-blending-moz-element.html background-blending-moz-element-ref.html
 
+fuzzy(1,40000) pref(layout.css.background-blend-mode.enabled,true) == mix-blend-mode-soft-light.html mix-blend-mode-soft-light-ref.html
+
 # Test plan 4.4.2 element with isolation:isolate creates an isolated group for blended children
 pref(layout.css.isolation.enabled,true) == blend-isolation.html blend-isolation-ref.html