Bug 1628175 - WebGL is drawn into the picture cache and then onto the screen r=gw
☠☠ backed out by e2443fd2a90e ☠ ☠
authorBert Peers <bpeers@mozilla.com>
Mon, 27 Apr 2020 19:38:02 +0000
changeset 526523 8433832c8f09d6787fe9157fad5a08cd8de3ff44
parent 526522 53e3c90cddeb8787cc257b5f1480e9d95515e935
child 526524 38dd57dc02d46875d3f150cc95ad44dd84a49a7d
push id37358
push useropoprus@mozilla.com
push dateWed, 29 Apr 2020 03:05:14 +0000
treeherdermozilla-central@6bb8423186c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw
bugs1628175
milestone77.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
Bug 1628175 - WebGL is drawn into the picture cache and then onto the screen r=gw Part 1 - support RGB external surfaces for promotion to compositor surfaces; add new shader permutations to handle all buffer kinds. Set the promotion flag when the pixel format has no alpha, or when the texture provider can guarantee all-solid alpha values. Differential Revision: https://phabricator.services.mozilla.com/D71120
gfx/layers/CompositorTypes.h
gfx/layers/ShareableCanvasRenderer.cpp
gfx/layers/client/ClientCanvasRenderer.cpp
gfx/layers/wr/AsyncImagePipelineManager.cpp
gfx/layers/wr/WebRenderCanvasRenderer.cpp
gfx/wr/webrender/res/composite.glsl
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/composite.rs
gfx/wr/webrender/src/gpu_types.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/image.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/shade.rs
gfx/wr/webrender_build/src/shader_features.rs
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -74,19 +74,23 @@ enum class TextureFlags : uint32_t {
   // The texture is used for snapshot.
   SNAPSHOT = 1 << 14,
   // Enable a non blocking read lock.
   NON_BLOCKING_READ_LOCK = 1 << 15,
   // Enable a blocking read lock.
   BLOCKING_READ_LOCK = 1 << 16,
   // Keep TextureClient alive when host side is used
   WAIT_HOST_USAGE_END = 1 << 17,
+  // The texture is guaranteed to have alpha 1.0 everywhere; some backends
+  // have trouble with RGBX/BGRX formats, so we use RGBA/BGRA but set this
+  // hint when we know alpha is opaque (eg. WebGL)
+  IS_OPAQUE = 1 << 18,
 
   // OR union of all valid bits
-  ALL_BITS = (1 << 18) - 1,
+  ALL_BITS = (1 << 19) - 1,
   // the default flags
   DEFAULT = NO_FLAGS
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TextureFlags)
 
 static inline bool TextureRequiresLocking(TextureFlags aFlags) {
   // If we're not double buffered, or uploading
   // within a transaction, then we need to support
--- a/gfx/layers/ShareableCanvasRenderer.cpp
+++ b/gfx/layers/ShareableCanvasRenderer.cpp
@@ -46,16 +46,19 @@ void ShareableCanvasRenderer::Initialize
 
   auto forwarder = GetForwarder();
 
   mFlags = TextureFlags::ORIGIN_BOTTOM_LEFT;
   if (!aData.mIsGLAlphaPremult) {
     mFlags |= TextureFlags::NON_PREMULTIPLIED;
   }
 
+  if (!aData.mHasAlpha) {
+    mFlags |= TextureFlags::IS_OPAQUE;
+  }
   UniquePtr<gl::SurfaceFactory> factory =
       gl::GLScreenBuffer::CreateFactory(mGLContext, caps, forwarder, mFlags);
   if (factory) {
     screen->Morph(std::move(factory));
   }
 }
 
 void ShareableCanvasRenderer::ClearCachedResources() {
--- a/gfx/layers/client/ClientCanvasRenderer.cpp
+++ b/gfx/layers/client/ClientCanvasRenderer.cpp
@@ -17,16 +17,20 @@ CompositableForwarder* ClientCanvasRende
 
 bool ClientCanvasRenderer::CreateCompositable() {
   if (!mCanvasClient) {
     TextureFlags flags = TextureFlags::DEFAULT;
     if (mOriginPos == gl::OriginPos::BottomLeft) {
       flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
     }
 
+    if (IsOpaque()) {
+      flags |= TextureFlags::IS_OPAQUE;
+    }
+
     if (!mIsAlphaPremultiplied) {
       flags |= TextureFlags::NON_PREMULTIPLIED;
     }
 
     mCanvasClient = CanvasClient::CreateCanvasClient(GetCanvasClientType(),
                                                      GetForwarder(), flags);
     if (!mCanvasClient) {
       return false;
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -391,19 +391,24 @@ void AsyncImagePipelineManager::ApplyAsy
     if (aPipeline->mScaleToSize.isSome()) {
       rect = LayoutDeviceRect(0, 0, aPipeline->mScaleToSize.value().width,
                               aPipeline->mScaleToSize.value().height);
     }
 
     if (aPipeline->mUseExternalImage) {
       MOZ_ASSERT(aPipeline->mCurrentTexture->AsWebRenderTextureHost());
       Range<wr::ImageKey> range_keys(&keys[0], keys.Length());
+      bool prefer_compositor_surface =
+          IsOpaque(aPipeline->mCurrentTexture->GetFormat()) ||
+          bool(aPipeline->mCurrentTexture->GetFlags() &
+               TextureFlags::IS_OPAQUE);
       aPipeline->mCurrentTexture->PushDisplayItems(
           builder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect),
-          aPipeline->mFilter, range_keys, /* aPreferCompositorSurface */ true);
+          aPipeline->mFilter, range_keys,
+          /* aPreferCompositorSurface */ prefer_compositor_surface);
       HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture);
     } else {
       MOZ_ASSERT(keys.Length() == 1);
       builder.PushImage(wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), true,
                         aPipeline->mFilter, keys[0]);
     }
   }
 
--- a/gfx/layers/wr/WebRenderCanvasRenderer.cpp
+++ b/gfx/layers/wr/WebRenderCanvasRenderer.cpp
@@ -35,16 +35,20 @@ void WebRenderCanvasRendererAsync::Initi
 
 bool WebRenderCanvasRendererAsync::CreateCompositable() {
   if (!mCanvasClient) {
     TextureFlags flags = TextureFlags::DEFAULT;
     if (mOriginPos == gl::OriginPos::BottomLeft) {
       flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
     }
 
+    if (IsOpaque()) {
+      flags |= TextureFlags::IS_OPAQUE;
+    }
+
     if (!mIsAlphaPremultiplied) {
       flags |= TextureFlags::NON_PREMULTIPLIED;
     }
 
     mCanvasClient = CanvasClient::CreateCanvasClient(GetCanvasClientType(),
                                                      GetForwarder(), flags);
     if (!mCanvasClient) {
       return false;
--- a/gfx/wr/webrender/res/composite.glsl
+++ b/gfx/wr/webrender/res/composite.glsl
@@ -18,16 +18,19 @@ flat varying vec4 vUVBounds_u;
 flat varying vec4 vUVBounds_v;
 #else
 flat varying vec4 vColor;
 flat varying float vLayer;
 varying vec2 vUv;
 #endif
 
 #ifdef WR_VERTEX_SHADER
+// CPU side data is in CompositeInstance (gpu_types.rs) and is
+// converted to GPU data using desc::COMPOSITE (renderer.rs) by
+// filling vaos.composite_vao with VertexArrayKind::Composite.
 PER_INSTANCE in vec4 aDeviceRect;
 PER_INSTANCE in vec4 aDeviceClipRect;
 PER_INSTANCE in vec4 aColor;
 PER_INSTANCE in vec4 aParams;
 PER_INSTANCE in vec3 aTextureLayers;
 
 #ifdef WR_FEATURE_YUV
 PER_INSTANCE in vec4 aUvRect0;
@@ -103,14 +106,18 @@ void main(void) {
         vUV_u,
         vUV_v,
         vUVBounds_y,
         vUVBounds_u,
         vUVBounds_v
     );
 #else
     // The color is just the texture sample modulated by a supplied color
-	vec4 texel = textureLod(sColor0, vec3(vUv, vLayer), 0.0);
+#   if defined(WR_FEATURE_TEXTURE_EXTERNAL) || defined(WR_FEATURE_TEXTURE_2D) || defined(WR_FEATURE_TEXTURE_RECT)
+    vec4 texel = TEX_SAMPLE(sColor0, vec3(vUv, vLayer));
+#   else
+    vec4 texel = textureLod(sColor0, vec3(vUv, vLayer), 0.0);
+#   endif
     vec4 color = vColor * texel;
 #endif
-	write_output(color);
+    write_output(color);
 }
 #endif
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -14,17 +14,17 @@ use crate::gpu_types::{BrushFlags, Brush
 use crate::gpu_types::{ClipMaskInstance, SplitCompositeInstance, BrushShaderKind};
 use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use crate::gpu_types::{ImageBrushData, get_shader_opacity};
 use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSource, Filter};
 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
 use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask};
 use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
-use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibilityFlags};
+use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveVisibility, PrimitiveVisibilityFlags};
 use crate::prim_store::{VECS_PER_SEGMENT, SpaceMapper};
 use crate::prim_store::image::ImageSource;
 use crate::render_target::RenderTargetContext;
 use crate::render_task_graph::{RenderTaskId, RenderTaskGraph};
 use crate::render_task::RenderTaskAddress;
 use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use crate::renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use crate::resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
@@ -719,16 +719,83 @@ impl BatchBuilder {
                     surface_spatial_node_index,
                     z_generator,
                     composite_state,
                 );
             }
         }
     }
 
+    // If an image is being drawn as a compositor surface, we don't want
+    // to draw the surface itself into the tile. Instead, we draw a transparent
+    // rectangle that writes to the z-buffer where this compositor surface is.
+    // That ensures we 'cut out' the part of the tile that has the compositor
+    // surface on it, allowing us to draw this tile as an overlay on top of
+    // the compositor surface.
+    // TODO(gw): There's a slight performance cost to doing this cutout rectangle
+    //           if we end up not needing to use overlay mode. Consider skipping
+    //           the cutout completely in this path.
+    fn emit_placeholder(
+        &mut self,
+        prim_rect: LayoutRect,
+        prim_info: &PrimitiveVisibility,
+        z_id: ZBufferId,
+        transform_id: TransformPaletteId,
+        batch_features: BatchFeatures,
+        ctx: &RenderTargetContext,
+        gpu_cache: &mut GpuCache,
+        render_tasks: &RenderTaskGraph,
+        prim_headers: &mut PrimitiveHeaders,
+    ) {
+        let batch_params = BrushBatchParameters::shared(
+            BrushBatchKind::Solid,
+            BatchTextures::no_texture(),
+            [get_shader_opacity(0.0), 0, 0, 0],
+            0,
+        );
+
+        let prim_cache_address = gpu_cache.get_address(
+            &ctx.globals.default_transparent_rect_handle,
+        );
+
+        let prim_header = PrimitiveHeader {
+            local_rect: prim_rect,
+            local_clip_rect: prim_info.combined_local_clip_rect,
+            specific_prim_address: prim_cache_address,
+            transform_id,
+        };
+
+        let prim_header_index = prim_headers.push(
+            &prim_header,
+            z_id,
+            batch_params.prim_user_data,
+        );
+
+        let bounding_rect = &prim_info.clip_chain.pic_clip_rect;
+        let transform_kind = transform_id.transform_kind();
+        let prim_vis_mask = prim_info.visibility_mask;
+
+        self.add_segmented_prim_to_batch(
+            None,
+            PrimitiveOpacity::translucent(),
+            &batch_params,
+            BlendMode::None,
+            BlendMode::None,
+            batch_features,
+            prim_header_index,
+            bounding_rect,
+            transform_kind,
+            render_tasks,
+            z_id,
+            prim_info.clip_task_index,
+            prim_vis_mask,
+            ctx,
+        );
+    }
+
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
     fn add_prim_to_batch(
         &mut self,
         prim_instance: &PrimitiveInstance,
         prim_spatial_node_index: SpatialNodeIndex,
@@ -1918,67 +1985,26 @@ impl BatchBuilder {
                     render_tasks,
                     z_id,
                     prim_info.clip_task_index,
                     prim_vis_mask,
                     ctx,
                 );
             }
             PrimitiveInstanceKind::YuvImage { data_handle, segment_instance_index, is_compositor_surface, .. } => {
-                // If this YUV image is being drawn as a compositor surface, we don't want
-                // to draw the YUV surface itself into the tile. Instead, we draw a transparent
-                // rectangle that writes to the z-buffer where this compositor surface is.
-                // That ensures we 'cut out' the part of the tile that has the compositor
-                // surface on it, allowing us to draw this tile as an overlay on top of
-                // the compositor surface.
-                // TODO(gw): There's a slight performance cost to doing this cutout rectangle
-                //           if we end up not needing to use overlay mode. Consider skipping
-                //           the cutout completely in this path.
                 if is_compositor_surface {
-                    let batch_params = BrushBatchParameters::shared(
-                        BrushBatchKind::Solid,
-                        BatchTextures::no_texture(),
-                        [get_shader_opacity(0.0), 0, 0, 0],
-                        0,
-                    );
-
-                    let prim_cache_address = gpu_cache.get_address(
-                        &ctx.globals.default_transparent_rect_handle,
-                    );
-
-                    let prim_header = PrimitiveHeader {
-                        local_rect: prim_rect,
-                        local_clip_rect: prim_info.combined_local_clip_rect,
-                        specific_prim_address: prim_cache_address,
-                        transform_id,
-                    };
-
-                    let prim_header_index = prim_headers.push(
-                        &prim_header,
-                        z_id,
-                        batch_params.prim_user_data,
-                    );
-
-                    self.add_segmented_prim_to_batch(
-                        None,
-                        PrimitiveOpacity::translucent(),
-                        &batch_params,
-                        BlendMode::None,
-                        BlendMode::None,
-                        batch_features,
-                        prim_header_index,
-                        bounding_rect,
-                        transform_kind,
-                        render_tasks,
-                        z_id,
-                        prim_info.clip_task_index,
-                        prim_vis_mask,
-                        ctx,
-                    );
-
+                    self.emit_placeholder(prim_rect,
+                                          prim_info,
+                                          z_id,
+                                          transform_id,
+                                          batch_features,
+                                          ctx,
+                                          gpu_cache,
+                                          render_tasks,
+                                          prim_headers);
                     return;
                 }
 
                 let yuv_image_data = &ctx.data_stores.yuv_image[data_handle].kind;
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
 
                 //yuv channel
@@ -2081,17 +2107,29 @@ impl BatchBuilder {
                     transform_kind,
                     render_tasks,
                     z_id,
                     prim_info.clip_task_index,
                     prim_vis_mask,
                     ctx,
                 );
             }
-            PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
+            PrimitiveInstanceKind::Image { data_handle, image_instance_index, is_compositor_surface, .. } => {
+                if is_compositor_surface {
+                    self.emit_placeholder(prim_rect,
+                                          prim_info,
+                                          z_id,
+                                          transform_id,
+                                          batch_features,
+                                          ctx,
+                                          gpu_cache,
+                                          render_tasks,
+                                          prim_headers);
+                    return;
+                }
                 let image_data = &ctx.data_stores.image[data_handle].kind;
                 let common_data = &ctx.data_stores.image[data_handle].common;
                 let image_instance = &ctx.prim_store.images[image_instance_index];
                 let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index);
                 let specified_blend_mode = match image_data.alpha_type {
                     AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
                     AlphaType::Alpha => BlendMode::Alpha,
                 };
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -82,78 +82,99 @@ pub struct CompositeTile {
     pub surface: CompositeTileSurface,
     pub rect: DeviceRect,
     pub clip_rect: DeviceRect,
     pub dirty_rect: DeviceRect,
     pub valid_rect: DeviceRect,
     pub z_id: ZBufferId,
 }
 
+pub enum ExternalSurfaceDependency {
+    Yuv {
+        image_dependencies: [ImageDependency; 3],
+        color_space: YuvColorSpace,
+        format: YuvFormat,
+        rescale: f32,
+    },
+    Rgb {
+        image_dependency: ImageDependency,
+    },
+}
+
 /// Describes information about drawing a primitive as a compositor surface.
 /// For now, we support only YUV images as compositor surfaces, but in future
 /// this will also support RGBA images.
 pub struct ExternalSurfaceDescriptor {
     pub local_rect: PictureRect,
     pub world_rect: WorldRect,
     pub device_rect: DeviceRect,
     pub local_clip_rect: PictureRect,
     pub clip_rect: DeviceRect,
-    pub image_dependencies: [ImageDependency; 3],
     pub image_rendering: ImageRendering,
-    pub yuv_color_space: YuvColorSpace,
-    pub yuv_format: YuvFormat,
-    pub yuv_rescale: f32,
     pub z_id: ZBufferId,
+    pub dependency: ExternalSurfaceDependency,
     /// If native compositing is enabled, the native compositor surface handle.
     /// Otherwise, this will be None
     pub native_surface_id: Option<NativeSurfaceId>,
     /// If the native surface needs to be updated, this will contain the size
     /// of the native surface as Some(size). If not dirty, this is None.
     pub update_params: Option<DeviceIntSize>,
 }
 
-/// Information about a plane in a YUV surface.
+/// Information about a plane in a YUV or RGB surface.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct YuvPlaneDescriptor {
+#[derive(Debug, Copy, Clone)]
+pub struct ExternalPlaneDescriptor {
     pub texture: TextureSource,
     pub texture_layer: i32,
     pub uv_rect: TexelRect,
 }
 
-impl YuvPlaneDescriptor {
+impl ExternalPlaneDescriptor {
     fn invalid() -> Self {
-        YuvPlaneDescriptor {
+        ExternalPlaneDescriptor {
             texture: TextureSource::Invalid,
             texture_layer: 0,
             uv_rect: TexelRect::invalid(),
         }
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Debug, Copy, Clone)]
 pub struct ResolvedExternalSurfaceIndex(pub usize);
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ResolvedExternalSurfaceColorData {
+    Yuv {
+        // YUV specific information
+        image_dependencies: [ImageDependency; 3],
+        planes: [ExternalPlaneDescriptor; 3],
+        color_space: YuvColorSpace,
+        format: YuvFormat,
+        rescale: f32,
+    },
+    Rgb {
+        image_dependency: ImageDependency,
+        plane: ExternalPlaneDescriptor,
+    },
+}
+
 /// An ExternalSurfaceDescriptor that has had image keys
 /// resolved to texture handles. This contains all the
 /// information that the compositor step in renderer
 /// needs to know.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ResolvedExternalSurface {
-    // YUV specific information
-    pub image_dependencies: [ImageDependency; 3],
-    pub yuv_planes: [YuvPlaneDescriptor; 3],
-    pub yuv_color_space: YuvColorSpace,
-    pub yuv_format: YuvFormat,
-    pub yuv_rescale: f32,
+    pub color_data: ResolvedExternalSurfaceColorData,
     pub image_buffer_kind: ImageBufferKind,
-
     // Update information for a native surface if it's dirty
     pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>,
 }
 
 /// Public interface specified in `RendererOptions` that configures
 /// how WR compositing will operate.
 pub enum CompositorConfig {
     /// Let WR draw tiles via normal batching. This requires no special OS support.
@@ -512,57 +533,75 @@ impl CompositeState {
                     tile_descriptors: opaque_tile_descriptors,
                 }
             );
         }
 
         // For each compositor surface that was promoted, build the
         // information required for the compositor to draw it
         for external_surface in &tile_cache.external_surfaces {
-            let mut yuv_planes = [
-                YuvPlaneDescriptor::invalid(),
-                YuvPlaneDescriptor::invalid(),
-                YuvPlaneDescriptor::invalid(),
+
+            let mut planes = [
+                ExternalPlaneDescriptor::invalid(),
+                ExternalPlaneDescriptor::invalid(),
+                ExternalPlaneDescriptor::invalid(),
             ];
 
-            // Step through the image keys, and build a yuv plane descriptor for each
-            let required_plane_count = external_surface.yuv_format.get_plane_num();
+            // Step through the image keys, and build a plane descriptor for each
+            let required_plane_count =
+                match external_surface.dependency {
+                    ExternalSurfaceDependency::Yuv { format, .. } => {
+                        format.get_plane_num()
+                    },
+                    ExternalSurfaceDependency::Rgb { .. } => {
+                        1
+                    }
+                };
             let mut valid_plane_count = 0;
 
+            let mut image_dependencies = [ImageDependency::INVALID; 3];
+
             for i in 0 .. required_plane_count {
-                let key = external_surface.image_dependencies[i].key;
-                let plane = &mut yuv_planes[i];
+                let dependency = match external_surface.dependency {
+                    ExternalSurfaceDependency::Yuv { image_dependencies, .. } => {
+                        image_dependencies[i]
+                    },
+                    ExternalSurfaceDependency::Rgb { image_dependency, .. } => {
+                        image_dependency
+                    }
+                };
+                image_dependencies[i] = dependency;
 
                 let request = ImageRequest {
-                    key,
+                    key: dependency.key,
                     rendering: external_surface.image_rendering,
                     tile: None,
                 };
 
                 let cache_item = resolve_image(
                     request,
                     resource_cache,
                     gpu_cache,
                     deferred_resolves,
                 );
 
                 if cache_item.texture_id != TextureSource::Invalid {
                     valid_plane_count += 1;
-
-                    *plane = YuvPlaneDescriptor {
+                    let plane = &mut planes[i];
+                    *plane = ExternalPlaneDescriptor {
                         texture: cache_item.texture_id,
                         texture_layer: cache_item.texture_layer,
                         uv_rect: cache_item.uv_rect.into(),
                     };
                 }
             }
 
             // Check if there are valid images added for each YUV plane
             if valid_plane_count < required_plane_count {
-                warn!("Warnings: skip a YUV compositor surface, found {}/{} valid images",
+                warn!("Warnings: skip a YUV/RGB compositor surface, found {}/{} valid images",
                     valid_plane_count,
                     required_plane_count,
                 );
                 continue;
             }
 
             let clip_rect = external_surface
                 .clip_rect
@@ -581,25 +620,47 @@ impl CompositeState {
             // to use.
             let update_params = external_surface.update_params.map(|surface_size| {
                 (
                     external_surface.native_surface_id.expect("bug: no native surface!"),
                     surface_size
                 )
             });
 
-            self.external_surfaces.push(ResolvedExternalSurface {
-                yuv_color_space: external_surface.yuv_color_space,
-                yuv_format: external_surface.yuv_format,
-                yuv_rescale: external_surface.yuv_rescale,
-                image_buffer_kind: get_buffer_kind(yuv_planes[0].texture),
-                image_dependencies: external_surface.image_dependencies,
-                yuv_planes,
-                update_params,
-            });
+            match external_surface.dependency {
+                ExternalSurfaceDependency::Yuv{ color_space, format, rescale, .. } => {
+
+                    let image_buffer_kind = get_buffer_kind(planes[0].texture);
+
+                    self.external_surfaces.push(ResolvedExternalSurface {
+                        color_data: ResolvedExternalSurfaceColorData::Yuv {
+                            image_dependencies,
+                            planes,
+                            color_space,
+                            format,
+                            rescale,
+                        },
+                        image_buffer_kind,
+                        update_params,
+                    });
+                },
+                ExternalSurfaceDependency::Rgb{ .. } => {
+
+                    let image_buffer_kind = get_buffer_kind(planes[0].texture);
+
+                    self.external_surfaces.push(ResolvedExternalSurface {
+                        color_data: ResolvedExternalSurfaceColorData::Rgb {
+                            image_dependency: image_dependencies[0],
+                            plane: planes[0],
+                        },
+                        image_buffer_kind,
+                        update_params,
+                    });
+                },
+            }
 
             let tile = CompositeTile {
                 surface,
                 rect: external_surface.device_rect,
                 valid_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()),
                 dirty_rect: external_surface.device_rect.translate(-external_surface.device_rect.origin.to_vector()),
                 clip_rect,
                 z_id: external_surface.z_id,
@@ -608,17 +669,17 @@ impl CompositeState {
             // Add a surface descriptor for each compositor surface. For the Draw
             // compositor, this is used to avoid composites being skipped by adding
             // a dependency on the compositor surface external image keys / generations.
             self.descriptor.surfaces.push(
                 CompositeSurfaceDescriptor {
                     surface_id: external_surface.native_surface_id,
                     offset: tile.rect.origin,
                     clip_rect: tile.clip_rect,
-                    image_dependencies: external_surface.image_dependencies,
+                    image_dependencies: image_dependencies,
                     tile_descriptors: Vec::new(),
                 }
             );
 
             self.push_tile(tile, true);
         }
 
         // Add alpha / overlay tiles after compositor surfaces
--- a/gfx/wr/webrender/src/gpu_types.rs
+++ b/gfx/wr/webrender/src/gpu_types.rs
@@ -231,16 +231,18 @@ impl ResolveInstanceData {
                 rect.size.width as f32,
                 rect.size.height as f32,
             ],
         }
     }
 }
 
 /// Vertex format for picture cache composite shader.
+/// When editing the members, update desc::COMPOSITE
+/// so its list of instance_attributes matches:
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct CompositeInstance {
     // Device space rectangle of surface
     rect: DeviceRect,
     // Device space clip rect for this surface
     clip_rect: DeviceRect,
     // Color for solid color tiles, white otherwise
@@ -275,16 +277,37 @@ impl CompositeInstance {
             yuv_color_space: 0.0,
             yuv_format: 0.0,
             yuv_rescale: 0.0,
             texture_layers: [layer, 0.0, 0.0],
             uv_rects: [TexelRect::invalid(); 3],
         }
     }
 
+    pub fn new_rgb(
+        rect: DeviceRect,
+        clip_rect: DeviceRect,
+        color: PremultipliedColorF,
+        layer: f32,
+        z_id: ZBufferId,
+        uv_rect: TexelRect,
+    ) -> Self {
+        CompositeInstance {
+            rect,
+            clip_rect,
+            color,
+            z_id: z_id.0 as f32,
+            yuv_color_space: 0.0,
+            yuv_format: 0.0,
+            yuv_rescale: 0.0,
+            texture_layers: [layer, 0.0, 0.0],
+            uv_rects: [uv_rect, uv_rect, TexelRect::invalid()],
+        }
+    }
+
     pub fn new_yuv(
         rect: DeviceRect,
         clip_rect: DeviceRect,
         z_id: ZBufferId,
         yuv_color_space: YuvColorSpace,
         yuv_format: YuvFormat,
         yuv_rescale: f32,
         texture_layers: [f32; 3],
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -92,24 +92,25 @@
 //! path before the compositor surface is drawn. Use of the per-tile valid and
 //! dirty rects ensure that we do a minimal amount of per-pixel work here to
 //! blend the overlay tile (this is not always optimal right now, but will be
 //! improved as a follow up).
 
 use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
 use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
 use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags};
+use api::{ImageRendering, ColorDepth, YuvColorSpace, YuvFormat};
 use api::units::*;
 use crate::box_shadow::BLUR_SAMPLE_SCALE;
 use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX,
     SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
 };
 use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
-use crate::composite::{ExternalSurfaceDescriptor};
+use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency};
 use crate::debug_colors;
 use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D, SideOffsets2D};
 use euclid::approxeq::ApproxEq;
 use crate::filterdata::SFilterData;
 use crate::frame_builder::{FrameBuilderConfig, FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
@@ -2247,17 +2248,17 @@ pub struct NativeSurface {
     pub opaque: NativeSurfaceId,
     /// Native surface for alpha tiles
     pub alpha: NativeSurfaceId,
 }
 
 /// Hash key for an external native compositor surface
 #[derive(PartialEq, Eq, Hash)]
 pub struct ExternalNativeSurfaceKey {
-    /// The YUV image keys that are used to draw this surface.
+    /// The YUV/RGB image keys that are used to draw this surface.
     pub image_keys: [ImageKey; 3],
     /// The current device size of the surface.
     pub size: DeviceIntSize,
 }
 
 /// Information about a native compositor surface cached between frames.
 pub struct ExternalNativeSurface {
     /// If true, the surface was used this frame. Used for a simple form
@@ -2928,16 +2929,246 @@ impl TileCacheInstance {
                     }
                 }
             }
         }
 
         world_culling_rect
     }
 
+    fn can_promote_to_surface(
+        &mut self,
+        flags: PrimitiveFlags,
+        prim_clip_chain: &ClipChainInstance,
+        prim_spatial_node_index: SpatialNodeIndex,
+        on_picture_surface: bool,
+        frame_context: &FrameVisibilityContext,
+    ) -> bool {
+        // Check if this primitive _wants_ to be promoted to a compositor surface.
+        if !flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
+            return false;
+        }
+
+        // For now, only support a small (arbitrary) number of compositor surfaces.
+        if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
+            return false;
+        }
+
+        // If a complex clip is being applied to this primitive, it can't be
+        // promoted directly to a compositor surface (we might be able to
+        // do this in limited cases in future, some native compositors do
+        // support rounded rect clips, for example)
+        if prim_clip_chain.needs_mask {
+            return false;
+        }
+
+        // If not on the same surface as the picture cache, it has some kind of
+        // complex effect (such as a filter, mix-blend-mode or 3d transform).
+        if !on_picture_surface {
+            return false;
+        }
+
+        // If the primitive is not axis-aligned with the root coordinate system,
+        // it can't be promoted to a native compositor surface (could potentially
+        // be supported in future on some platforms).
+        let prim_spatial_node = &frame_context.spatial_tree
+            .spatial_nodes[prim_spatial_node_index.0 as usize];
+        if prim_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
+            return false;
+        }
+
+        // If the transform has scale, we can't currently handle
+        // it in the native compositor - we can support this in future though.
+        if !self.map_local_to_surface.get_transform().is_simple_2d_translation() {
+            return false;
+        }
+        return true;
+    }
+
+    fn setup_compositor_surfaces_yuv(
+        &mut self,
+        prim_info: &mut PrimitiveDependencyInfo,
+        prim_rect: PictureRect,
+        frame_context: &FrameVisibilityContext,
+        image_dependencies: &[ImageDependency;3],
+        api_keys: &[ImageKey; 3],
+        resource_cache: &mut ResourceCache,
+        composite_state: &mut CompositeState,
+        image_rendering: ImageRendering,
+        color_depth: ColorDepth,
+        color_space: YuvColorSpace,
+        format: YuvFormat,
+    ) {
+        self.setup_compositor_surfaces_impl(
+            prim_info,
+            prim_rect,
+            frame_context,
+            ExternalSurfaceDependency::Yuv {
+                image_dependencies: *image_dependencies,
+                color_space,
+                format,
+                rescale: color_depth.rescaling_factor(),
+            },
+            api_keys,
+            resource_cache,
+            composite_state,
+            image_rendering,
+        );
+    }
+
+    fn setup_compositor_surfaces_rgb(
+        &mut self,
+        prim_info: &mut PrimitiveDependencyInfo,
+        prim_rect: PictureRect,
+        frame_context: &FrameVisibilityContext,
+        image_dependency: ImageDependency,
+        api_key: ImageKey,
+        resource_cache: &mut ResourceCache,
+        composite_state: &mut CompositeState,
+        image_rendering: ImageRendering,
+    ) {
+        let mut api_keys = [ImageKey::DUMMY; 3];
+        api_keys[0] = api_key;
+        self.setup_compositor_surfaces_impl(
+            prim_info,
+            prim_rect,
+            frame_context,
+            ExternalSurfaceDependency::Rgb {
+                image_dependency,
+            },
+            &api_keys,
+            resource_cache,
+            composite_state,
+            image_rendering,
+        );
+    }
+
+    fn setup_compositor_surfaces_impl(
+        &mut self,
+        prim_info: &mut PrimitiveDependencyInfo,
+        prim_rect: PictureRect,
+        frame_context: &FrameVisibilityContext,
+        dependency: ExternalSurfaceDependency,
+        api_keys: &[ImageKey; 3],
+        resource_cache: &mut ResourceCache,
+        composite_state: &mut CompositeState,
+        image_rendering: ImageRendering,
+    ) {
+        prim_info.is_compositor_surface = true;
+
+        let pic_to_world_mapper = SpaceMapper::new_with_target(
+            ROOT_SPATIAL_NODE_INDEX,
+            self.spatial_node_index,
+            frame_context.global_screen_world_rect,
+            frame_context.spatial_tree,
+        );
+
+        let world_rect = pic_to_world_mapper
+            .map(&prim_rect)
+            .expect("bug: unable to map the primitive to world space");
+        let world_clip_rect = pic_to_world_mapper
+            .map(&prim_info.prim_clip_rect)
+            .expect("bug: unable to map clip to world space");
+
+        let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
+        if !is_visible {
+            return;
+        }
+
+        // TODO(gw): Is there any case where if the primitive ends up on a fractional
+        //           boundary we want to _skip_ promoting to a compositor surface and
+        //           draw it as part of the content?
+        let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
+        let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
+
+        // When using native compositing, we need to find an existing native surface
+        // handle to use, or allocate a new one. For existing native surfaces, we can
+        // also determine whether this needs to be updated, depending on whether the
+        // image generation(s) of the planes have changed since last composite.
+        let (native_surface_id, update_params) = match composite_state.compositor_kind {
+            CompositorKind::Draw { .. } => {
+                (None, None)
+            }
+            CompositorKind::Native { .. } => {
+                let native_surface_size = device_rect.size.round().to_i32();
+
+                let key = ExternalNativeSurfaceKey {
+                    image_keys: *api_keys,
+                    size: native_surface_size,
+                };
+
+                let native_surface = self.external_native_surface_cache
+                    .entry(key)
+                    .or_insert_with(|| {
+                        // No existing surface, so allocate a new compositor surface and
+                        // a single compositor tile that covers the entire compositor surface.
+
+                        let native_surface_id = resource_cache.create_compositor_surface(
+                            DeviceIntPoint::zero(),
+                            native_surface_size,
+                            true,
+                        );
+
+                        let tile_id = NativeTileId {
+                            surface_id: native_surface_id,
+                            x: 0,
+                            y: 0,
+                        };
+
+                        resource_cache.create_compositor_tile(tile_id);
+
+                        ExternalNativeSurface {
+                            used_this_frame: true,
+                            native_surface_id,
+                            image_dependencies: [ImageDependency::INVALID; 3],
+                        }
+                    });
+
+                // Mark that the surface is referenced this frame so that the
+                // backing native surface handle isn't freed.
+                native_surface.used_this_frame = true;
+
+                // If the image dependencies match, there is no need to update
+                // the backing native surface.
+                let update_params = match dependency {
+                    ExternalSurfaceDependency::Yuv{ image_dependencies, .. } => {
+                       if image_dependencies == native_surface.image_dependencies {
+                           None
+                       } else {
+                           Some(native_surface_size)
+                       }
+                    },
+                    ExternalSurfaceDependency::Rgb{ image_dependency, .. } => {
+                       if image_dependency == native_surface.image_dependencies[0] {
+                           None
+                       } else {
+                           Some(native_surface_size)
+                       }
+                    },
+                };
+
+                (Some(native_surface.native_surface_id), update_params)
+            }
+        };
+
+        // Each compositor surface allocates a unique z-id
+        self.external_surfaces.push(ExternalSurfaceDescriptor {
+            local_rect: prim_info.prim_clip_rect,
+            world_rect,
+            local_clip_rect: prim_info.prim_clip_rect,
+            dependency,
+            image_rendering,
+            device_rect,
+            clip_rect,
+            z_id: composite_state.z_generator.next(),
+            native_surface_id,
+            update_params,
+        });
+    }
+
     /// Update the dependencies for each tile for a given primitive instance.
     pub fn update_prim_dependencies(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
         prim_spatial_node_index: SpatialNodeIndex,
         prim_clip_chain: Option<&ClipChainInstance>,
         local_prim_rect: LayoutRect,
         frame_context: &FrameVisibilityContext,
@@ -3059,16 +3290,17 @@ impl TileCacheInstance {
                 prim_info.spatial_nodes.push(clip_node.item.spatial_node_index);
             }
         }
 
         // Certain primitives may select themselves to be a backdrop candidate, which is
         // then applied below.
         let mut backdrop_candidate = None;
 
+
         // For pictures, we don't (yet) know the valid clip rect, so we can't correctly
         // use it to calculate the local bounding rect for the tiles. If we include them
         // then we may calculate a bounding rect that is too large, since it won't include
         // the clip bounds of the picture. Excluding them from the bounding rect here
         // fixes any correctness issues (the clips themselves are considered when we
         // consider the bounds of the primitives that are *children* of the picture),
         // however it does potentially result in some un-necessary invalidations of a
         // tile (in cases where the picture local rect affects the tile, but the clip
@@ -3108,21 +3340,34 @@ impl TileCacheInstance {
                 }
 
                 if color_binding_index != ColorBindingIndex::INVALID {
                     prim_info.color_binding = Some(color_bindings[color_binding_index].into());
                 }
 
                 prim_info.clip_by_tile = true;
             }
-            PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
-                let image_data = &data_stores.image[data_handle].kind;
+            PrimitiveInstanceKind::Image { data_handle, image_instance_index, ref mut is_compositor_surface, .. } => {
+                let image_key = &data_stores.image[data_handle];
+                let image_data = &image_key.kind;
                 let image_instance = &image_instances[image_instance_index];
                 let opacity_binding_index = image_instance.opacity_binding_index;
 
+                let mut promote_to_surface = false;
+                // If picture caching is disabled, we can't support any compositor surfaces.
+                if composite_state.picture_caching_is_enabled &&
+                   self.can_promote_to_surface(image_key.common.flags,
+                                                prim_clip_chain,
+                                                prim_spatial_node_index,
+                                                on_picture_surface,
+                                                frame_context) {
+                        promote_to_surface = true;
+                }
+                *is_compositor_surface = promote_to_surface;
+
                 if opacity_binding_index == OpacityBindingIndex::INVALID {
                     if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
                         // For an image to be a possible opaque backdrop, it must:
                         // - Have a valid, opaque image descriptor
                         // - Not use tiling (since they can fail to draw)
                         // - Not having any spacing / padding
                         if image_properties.descriptor.is_opaque() &&
                            image_properties.tiling.is_none() &&
@@ -3135,195 +3380,91 @@ impl TileCacheInstance {
                     }
                 } else {
                     let opacity_binding = &opacity_binding_store[opacity_binding_index];
                     for binding in &opacity_binding.bindings {
                         prim_info.opacity_bindings.push(OpacityBinding::from(*binding));
                     }
                 }
 
-                prim_info.images.push(ImageDependency {
-                    key: image_data.key,
-                    generation: resource_cache.get_image_generation(image_data.key),
-                });
+                if promote_to_surface {
+                    self.setup_compositor_surfaces_rgb(
+                        &mut prim_info,
+                        prim_rect,
+                        frame_context,
+                        ImageDependency {
+                            key: image_data.key,
+                            generation: resource_cache.get_image_generation(image_data.key),
+                        },
+                        image_data.key,
+                        resource_cache,
+                        composite_state,
+                        image_data.image_rendering,
+                    );
+                } else {
+                    prim_info.images.push(ImageDependency {
+                        key: image_data.key,
+                        generation: resource_cache.get_image_generation(image_data.key),
+                    });
+                }
             }
             PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => {
                 let prim_data = &data_stores.yuv_image[data_handle];
                 // TODO(gw): For now, we only support promoting YUV primitives to be compositor
                 //           surfaces. However, some videos are RGBA images. As a follow up,
                 //           extract the logic below and support RGBA compositor surfaces too.
                 let mut promote_to_surface = false;
 
-
                 // If picture caching is disabled, we can't support any compositor surfaces.
                 if composite_state.picture_caching_is_enabled {
-                    // Check if this primitive _wants_ to be promoted to a compositor surface.
-                    if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
-                        promote_to_surface = true;
-
-                        // For now, only support a small (arbitrary) number of compositor surfaces.
-                        if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
-                            promote_to_surface = false;
-                        }
-
-                        // If a complex clip is being applied to this primitive, it can't be
-                        // promoted directly to a compositor surface (we might be able to
-                        // do this in limited cases in future, some native compositors do
-                        // support rounded rect clips, for example)
-                        if prim_clip_chain.needs_mask {
-                            promote_to_surface = false;
-                        }
-
-                        // If not on the same surface as the picture cache, it has some kind of
-                        // complex effect (such as a filter, mix-blend-mode or 3d transform).
-                        if !on_picture_surface {
-                            promote_to_surface = false;
-                        }
-
-                        // If the primitive is not axis-aligned with the root coordinate system,
-                        // it can't be promoted to a native compositor surface (could potentially
-                        // be supported in future on some platforms).
-                        let prim_spatial_node = &frame_context.spatial_tree
-                            .spatial_nodes[prim_spatial_node_index.0 as usize];
-                        if prim_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
-                            promote_to_surface = false;
-                        }
-
-                        // If the transform has scale, we can't currently handle
-                        // it in the native compositor - we can support this in future though.
-                        if !self.map_local_to_surface.get_transform().is_simple_2d_translation() {
-                            promote_to_surface = false;
-                        }
-
-                        // TODO(gw): When we support RGBA images for external surfaces, we also
-                        //           need to check if opaque (YUV images are implicitly opaque).
-                    }
+                    promote_to_surface = self.can_promote_to_surface(
+                                                prim_data.common.flags,
+                                                prim_clip_chain,
+                                                prim_spatial_node_index,
+                                                on_picture_surface,
+                                                frame_context);
+
+                    // TODO(gw): When we support RGBA images for external surfaces, we also
+                    //           need to check if opaque (YUV images are implicitly opaque).
                 }
 
                 // Store on the YUV primitive instance whether this is a promoted surface.
                 // This is used by the batching code to determine whether to draw the
                 // image to the content tiles, or just a transparent z-write.
                 *is_compositor_surface = promote_to_surface;
 
                 // If this primitive is being promoted to a surface, construct an external
                 // surface descriptor for use later during batching and compositing. We only
                 // add the image keys for this primitive as a dependency if this is _not_
                 // a promoted surface, since we don't want the tiles to invalidate when the
                 // video content changes, if it's a compositor surface!
                 if promote_to_surface {
-                    prim_info.is_compositor_surface = true;
-
-                    let pic_to_world_mapper = SpaceMapper::new_with_target(
-                        ROOT_SPATIAL_NODE_INDEX,
-                        self.spatial_node_index,
-                        frame_context.global_screen_world_rect,
-                        frame_context.spatial_tree,
-                    );
-
-                    let world_rect = pic_to_world_mapper
-                        .map(&prim_rect)
-                        .expect("bug: unable to map the primitive to world space");
-                    let world_clip_rect = pic_to_world_mapper
-                        .map(&prim_info.prim_clip_rect)
-                        .expect("bug: unable to map clip to world space");
-
-                    let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
-                    if is_visible {
-                        // TODO(gw): Is there any case where if the primitive ends up on a fractional
-                        //           boundary we want to _skip_ promoting to a compositor surface and
-                        //           draw it as part of the content?
-                        let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
-                        let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
-
-                        // Build dependency for each YUV plane, with current image generation for
-                        // later detection of when the composited surface has changed.
-                        let mut image_dependencies = [ImageDependency::INVALID; 3];
-                        for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
-                            *dep = ImageDependency {
-                                key,
-                                generation: resource_cache.get_image_generation(key),
-                            }
+                    // Build dependency for each YUV plane, with current image generation for
+                    // later detection of when the composited surface has changed.
+                    let mut image_dependencies = [ImageDependency::INVALID; 3];
+                    for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
+                        *dep = ImageDependency {
+                            key,
+                            generation: resource_cache.get_image_generation(key),
                         }
-
-                        // When using native compositing, we need to find an existing native surface
-                        // handle to use, or allocate a new one. For existing native surfaces, we can
-                        // also determine whether this needs to be updated, depending on whether the
-                        // image generation(s) of the YUV planes have changed since last composite.
-                        let (native_surface_id, update_params) = match composite_state.compositor_kind {
-                            CompositorKind::Draw { .. } => {
-                                (None, None)
-                            }
-                            CompositorKind::Native { .. } => {
-                                let native_surface_size = device_rect.size.round().to_i32();
-
-                                let key = ExternalNativeSurfaceKey {
-                                    image_keys: prim_data.kind.yuv_key,
-                                    size: native_surface_size,
-                                };
-
-                                let native_surface = self.external_native_surface_cache
-                                    .entry(key)
-                                    .or_insert_with(|| {
-                                        // No existing surface, so allocate a new compositor surface and
-                                        // a single compositor tile that covers the entire compositor surface.
-
-                                        let native_surface_id = resource_cache.create_compositor_surface(
-                                            DeviceIntPoint::zero(),
-                                            native_surface_size,
-                                            true,
-                                        );
-
-                                        let tile_id = NativeTileId {
-                                            surface_id: native_surface_id,
-                                            x: 0,
-                                            y: 0,
-                                        };
-
-                                        resource_cache.create_compositor_tile(tile_id);
-
-                                        ExternalNativeSurface {
-                                            used_this_frame: true,
-                                            native_surface_id,
-                                            image_dependencies: [ImageDependency::INVALID; 3],
-                                        }
-                                    });
-
-                                // Mark that the surface is referenced this frame so that the
-                                // backing native surface handle isn't freed.
-                                native_surface.used_this_frame = true;
-
-                                // If the image dependencies match, there is no need to update
-                                // the backing native surface.
-                                let update_params = if image_dependencies == native_surface.image_dependencies {
-                                    None
-                                } else {
-                                    Some(native_surface_size)
-                                };
-
-                                (Some(native_surface.native_surface_id), update_params)
-                            }
-                        };
-
-                        // Each compositor surface allocates a unique z-id
-                        self.external_surfaces.push(ExternalSurfaceDescriptor {
-                            local_rect: prim_info.prim_clip_rect,
-                            world_rect,
-                            local_clip_rect: prim_info.prim_clip_rect,
-                            image_dependencies,
-                            image_rendering: prim_data.kind.image_rendering,
-                            device_rect,
-                            clip_rect,
-                            yuv_color_space: prim_data.kind.color_space,
-                            yuv_format: prim_data.kind.format,
-                            yuv_rescale: prim_data.kind.color_depth.rescaling_factor(),
-                            z_id: composite_state.z_generator.next(),
-                            native_surface_id,
-                            update_params,
-                        });
                     }
+
+                    self.setup_compositor_surfaces_yuv(
+                        &mut prim_info,
+                        prim_rect,
+                        frame_context,
+                        &image_dependencies,
+                        &prim_data.kind.yuv_key,
+                        resource_cache,
+                        composite_state,
+                        prim_data.kind.image_rendering,
+                        prim_data.kind.color_depth,
+                        prim_data.kind.color_space,
+                        prim_data.kind.format,
+                    );
                 } else {
                     prim_info.images.extend(
                         prim_data.kind.yuv_key.iter().map(|key| {
                             ImageDependency {
                                 key: *key,
                                 generation: resource_cache.get_image_generation(*key),
                             }
                         })
--- a/gfx/wr/webrender/src/prim_store/image.rs
+++ b/gfx/wr/webrender/src/prim_store/image.rs
@@ -327,16 +327,17 @@ impl InternablePrimitive for Image {
             segment_instance_index: SegmentInstanceIndex::INVALID,
             tight_local_clip_rect: LayoutRect::zero(),
             visible_tiles: Vec::new(),
         });
 
         PrimitiveInstanceKind::Image {
             data_handle,
             image_instance_index,
+            is_compositor_surface: false,
         }
     }
 }
 
 impl CreateShadow for Image {
     fn create_shadow(&self, shadow: &Shadow) -> Self {
         Image {
             tile_spacing: self.tile_spacing,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1423,16 +1423,17 @@ pub enum PrimitiveInstanceKind {
         data_handle: YuvImageDataHandle,
         segment_instance_index: SegmentInstanceIndex,
         is_compositor_surface: bool,
     },
     Image {
         /// Handle to the common interned data for this primitive.
         data_handle: ImageDataHandle,
         image_instance_index: ImageInstanceIndex,
+        is_compositor_surface: bool,
     },
     LinearGradient {
         /// Handle to the common interned data for this primitive.
         data_handle: LinearGradientDataHandle,
         gradient_index: LinearGradientIndex,
     },
     RadialGradient {
         /// Handle to the common interned data for this primitive.
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -43,17 +43,17 @@ use api::{RenderApiSender, RenderNotifie
 #[cfg(feature = "replay")]
 use api::ExternalImage;
 use api::units::*;
 pub use api::DebugFlags;
 use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile, ResolvedExternalSurface};
-use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat};
+use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat, ResolvedExternalSurfaceColorData};
 use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation};
 use crate::debug_colors;
 use crate::debug_render::{DebugItem, DebugRenderer};
 use crate::device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO};
 use crate::device::{DrawTarget, ExternalTexture, FBOId, ReadTarget, TextureSlot};
 use crate::device::{ShaderError, TextureFilter, TextureFlags,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use crate::device::ProgramCache;
@@ -4525,64 +4525,100 @@ impl Renderer {
                 0.0,
                 surface_size.width as f32,
                 0.0,
                 surface_size.height as f32,
                 self.device.ortho_near_plane(),
                 self.device.ortho_far_plane(),
             );
 
-            // Bind an appropriate YUV shader for the texture format kind
-            self.shaders
-                .borrow_mut()
-                .get_composite_shader(
-                    CompositeSurfaceFormat::Yuv,
-                    surface.image_buffer_kind,
-                ).bind(
-                    &mut self.device,
-                    &projection,
-                    &mut self.renderer_errors
-                );
-
-            let textures = BatchTextures {
-                colors: [
-                    surface.yuv_planes[0].texture,
-                    surface.yuv_planes[1].texture,
-                    surface.yuv_planes[2].texture,
-                ],
+            let ( textures, instance ) = match surface.color_data {
+                ResolvedExternalSurfaceColorData::Yuv{
+                        ref planes, color_space, format, rescale, .. } => {
+
+                    // Bind an appropriate YUV shader for the texture format kind
+                    self.shaders
+                        .borrow_mut()
+                        .get_composite_shader(
+                            CompositeSurfaceFormat::Yuv,
+                            surface.image_buffer_kind,
+                        ).bind(
+                            &mut self.device,
+                            &projection,
+                            &mut self.renderer_errors
+                        );
+
+                    let textures = BatchTextures {
+                        colors: [
+                            planes[0].texture,
+                            planes[1].texture,
+                            planes[2].texture,
+                        ],
+                    };
+
+                    // When the texture is an external texture, the UV rect is not known when
+                    // the external surface descriptor is created, because external textures
+                    // are not resolved until the lock() callback is invoked at the start of
+                    // the frame render. To handle this, query the texture resolver for the
+                    // UV rect if it's an external texture, otherwise use the default UV rect.
+                    let uv_rects = [
+                        self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect),
+                        self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect),
+                        self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect),
+                    ];
+
+                    let instance = CompositeInstance::new_yuv(
+                        surface_rect.to_f32(),
+                        surface_rect.to_f32(),
+                        // z-id is not relevant when updating a native compositor surface.
+                        // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
+                        ZBufferId(0),
+                        color_space,
+                        format,
+                        rescale,
+                        [
+                            planes[0].texture_layer as f32,
+                            planes[1].texture_layer as f32,
+                            planes[2].texture_layer as f32,
+                        ],
+                        uv_rects,
+                    );
+
+                    ( textures, instance )
+                },
+                ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => {
+
+                    self.shaders
+                        .borrow_mut()
+                        .get_composite_shader(
+                            CompositeSurfaceFormat::Rgba,
+                            surface.image_buffer_kind,
+                        ).bind(
+                            &mut self.device,
+                            &projection,
+                            &mut self.renderer_errors
+                        );
+
+                    let textures = BatchTextures::color(plane.texture);
+
+                    let uv_rect = self.texture_resolver.get_uv_rect(&textures.colors[0], plane.uv_rect);
+
+                    let instance = CompositeInstance::new_rgb(
+                        surface_rect.to_f32(),
+                        surface_rect.to_f32(),
+                        PremultipliedColorF::WHITE,
+                        plane.texture_layer as f32,
+                        ZBufferId(0),
+                        uv_rect,
+                    );
+
+                    ( textures, instance )
+                },
             };
 
-            // When the texture is an external texture, the UV rect is not known when
-            // the external surface descriptor is created, because external textures
-            // are not resolved until the lock() callback is invoked at the start of
-            // the frame render. To handle this, query the texture resolver for the
-            // UV rect if it's an external texture, otherwise use the default UV rect.
-            let uv_rects = [
-                self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
-                self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
-                self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
-            ];
-
-            let instance = CompositeInstance::new_yuv(
-                surface_rect.to_f32(),
-                surface_rect.to_f32(),
-                // z-id is not relevant when updating a native compositor surface.
-                // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
-                ZBufferId(0),
-                surface.yuv_color_space,
-                surface.yuv_format,
-                surface.yuv_rescale,
-                [
-                    surface.yuv_planes[0].texture_layer as f32,
-                    surface.yuv_planes[1].texture_layer as f32,
-                    surface.yuv_planes[2].texture_layer as f32,
-                ],
-                uv_rects,
-            );
-
             self.draw_instanced_batch(
                 &[instance],
                 VertexArrayKind::Composite,
                 &textures,
                 &mut results.stats,
             );
 
             self.compositor_config
@@ -4681,53 +4717,74 @@ impl Renderer {
                         ),
                         BatchTextures::color(texture),
                         (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray),
                     )
                 }
                 CompositeTileSurface::ExternalSurface { external_surface_index } => {
                     let surface = &external_surfaces[external_surface_index.0];
 
-                    let textures = BatchTextures {
-                        colors: [
-                            surface.yuv_planes[0].texture,
-                            surface.yuv_planes[1].texture,
-                            surface.yuv_planes[2].texture,
-                        ],
-                    };
-
-                    // When the texture is an external texture, the UV rect is not known when
-                    // the external surface descriptor is created, because external textures
-                    // are not resolved until the lock() callback is invoked at the start of
-                    // the frame render. To handle this, query the texture resolver for the
-                    // UV rect if it's an external texture, otherwise use the default UV rect.
-                    let uv_rects = [
-                        self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect),
-                        self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect),
-                        self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect),
-                    ];
-
-                    (
-                        CompositeInstance::new_yuv(
-                            tile.rect,
-                            clip_rect,
-                            tile.z_id,
-                            surface.yuv_color_space,
-                            surface.yuv_format,
-                            surface.yuv_rescale,
-                            [
-                                surface.yuv_planes[0].texture_layer as f32,
-                                surface.yuv_planes[1].texture_layer as f32,
-                                surface.yuv_planes[2].texture_layer as f32,
-                            ],
-                            uv_rects,
-                        ),
-                        textures,
-                        (CompositeSurfaceFormat::Yuv, surface.image_buffer_kind),
-                    )
+                    match surface.color_data {
+                        ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, rescale, .. } => {
+
+                            let textures = BatchTextures {
+                                colors: [
+                                    planes[0].texture,
+                                    planes[1].texture,
+                                    planes[2].texture,
+                                ],
+                            };
+
+                            // When the texture is an external texture, the UV rect is not known when
+                            // the external surface descriptor is created, because external textures
+                            // are not resolved until the lock() callback is invoked at the start of
+                            // the frame render. To handle this, query the texture resolver for the
+                            // UV rect if it's an external texture, otherwise use the default UV rect.
+                            let uv_rects = [
+                                self.texture_resolver.get_uv_rect(&textures.colors[0], planes[0].uv_rect),
+                                self.texture_resolver.get_uv_rect(&textures.colors[1], planes[1].uv_rect),
+                                self.texture_resolver.get_uv_rect(&textures.colors[2], planes[2].uv_rect),
+                            ];
+
+                            (
+                                CompositeInstance::new_yuv(
+                                    tile.rect,
+                                    clip_rect,
+                                    tile.z_id,
+                                    color_space,
+                                    format,
+                                    rescale,
+                                    [
+                                        planes[0].texture_layer as f32,
+                                        planes[1].texture_layer as f32,
+                                        planes[2].texture_layer as f32,
+                                    ],
+                                    uv_rects,
+                                ),
+                                textures,
+                                (CompositeSurfaceFormat::Yuv, surface.image_buffer_kind),
+                            )
+                        },
+                        ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => {
+                            let uv_rect = self.texture_resolver.get_uv_rect(&plane.texture, plane.uv_rect);
+
+                            (
+                                CompositeInstance::new_rgb(
+                                    tile.rect,
+                                    clip_rect,
+                                    PremultipliedColorF::WHITE,
+                                    plane.texture_layer as f32,
+                                    tile.z_id,
+                                    uv_rect,
+                                ),
+                                BatchTextures::color(plane.texture),
+                                (CompositeSurfaceFormat::Rgba, surface.image_buffer_kind),
+                            )
+                        },
+                    }
                 }
                 CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
                     unreachable!("bug: found native surface in simple composite path");
                 }
             };
 
             // Flush batch if shader params or textures changed
             let flush_batch = !current_textures.is_compatible_with(&textures) ||
--- a/gfx/wr/webrender/src/shade.rs
+++ b/gfx/wr/webrender/src/shade.rs
@@ -574,17 +574,21 @@ pub struct Shaders {
 
     // Composite shaders.  These are very simple shaders used to composite
     // picture cache tiles into the framebuffer on platforms that do not have an
     // OS Compositor (or we cannot use it).  Such an OS Compositor (such as
     // DirectComposite or CoreAnimation) handles the composition of the picture
     // cache tiles at a lower level (e.g. in DWM for Windows); in that case we
     // directly hand the picture cache surfaces over to the OS Compositor, and
     // our own Composite shaders below never run.
-    pub composite_rgba: LazilyCompiledShader,
+    // To composite external (RGB) surfaces we need various permutations of
+    // shaders with WR_FEATURE flags on or off based on the type of image
+    // buffer we're sourcing from (see IMAGE_BUFFER_KINDS).
+    pub composite_rgba: Vec<Option<LazilyCompiledShader>>,
+    // The same set of composite shaders but with WR_FEATURE_YUV added.
     pub composite_yuv: Vec<Option<LazilyCompiledShader>>,
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
         options: &RendererOptions,
@@ -879,60 +883,75 @@ impl Shaders {
                 use_pixel_local_storage,
             )?);
 
             image_features.clear();
         }
 
         // All yuv_image configuration.
         let mut yuv_features = Vec::new();
+        let mut rgba_features = Vec::new();
         let yuv_shader_num = IMAGE_BUFFER_KINDS.len();
         let mut brush_yuv_image = Vec::new();
         let mut composite_yuv = Vec::new();
+        let mut composite_rgba = Vec::new();
         // PrimitiveShader is not clonable. Use push() to initialize the vec.
         for _ in 0 .. yuv_shader_num {
             brush_yuv_image.push(None);
             composite_yuv.push(None);
+            composite_rgba.push(None);
         }
         for image_buffer_kind in &IMAGE_BUFFER_KINDS {
             if image_buffer_kind.has_platform_support(&gl_type) {
                 yuv_features.push("YUV");
 
                 let feature_string = image_buffer_kind.get_feature_string();
                 if feature_string != "" {
                     yuv_features.push(feature_string);
+                    rgba_features.push(feature_string);
                 }
 
                 let brush_shader = BrushShader::new(
                     "brush_yuv_image",
                     device,
                     &yuv_features,
                     options.precache_flags,
                     &shader_list,
                     false /* advanced blend */,
                     false /* dual source */,
                     use_pixel_local_storage,
                 )?;
 
-                let composite_shader = LazilyCompiledShader::new(
+                let composite_yuv_shader = LazilyCompiledShader::new(
                     ShaderKind::Composite,
                     "composite",
                     &yuv_features,
                     device,
                     options.precache_flags,
                     &shader_list,
                 )?;
 
-                let index = Self::get_yuv_shader_index(
+                let composite_rgba_shader = LazilyCompiledShader::new(
+                    ShaderKind::Composite,
+                    "composite",
+                    &rgba_features,
+                    device,
+                    options.precache_flags,
+                    &shader_list,
+                )?;
+
+                let index = Self::get_compositing_shader_index(
                     *image_buffer_kind,
                 );
                 brush_yuv_image[index] = Some(brush_shader);
-                composite_yuv[index] = Some(composite_shader);
+                composite_yuv[index] = Some(composite_yuv_shader);
+                composite_rgba[index] = Some(composite_rgba_shader);
 
                 yuv_features.clear();
+                rgba_features.clear()
             }
         }
 
         let cs_line_decoration = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::LineDecoration),
             "cs_line_decoration",
             &[],
             device,
@@ -962,25 +981,16 @@ impl Shaders {
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_solid",
             &[],
             device,
             options.precache_flags,
             &shader_list,
         )?;
 
-        let composite_rgba = LazilyCompiledShader::new(
-            ShaderKind::Composite,
-            "composite",
-            &[],
-            device,
-            options.precache_flags,
-            &shader_list,
-        )?;
-
         Ok(Shaders {
             cs_blur_a8,
             cs_blur_rgba8,
             cs_border_segment,
             cs_line_decoration,
             cs_gradient,
             cs_border_solid,
             cs_scale,
@@ -1004,32 +1014,34 @@ impl Shaders {
             ps_text_run,
             ps_text_run_dual_source,
             ps_split_composite,
             composite_rgba,
             composite_yuv,
         })
     }
 
-    fn get_yuv_shader_index(buffer_kind: ImageBufferKind) -> usize {
+    fn get_compositing_shader_index(buffer_kind: ImageBufferKind) -> usize {
         buffer_kind as usize
     }
 
     pub fn get_composite_shader(
         &mut self,
         format: CompositeSurfaceFormat,
         buffer_kind: ImageBufferKind,
     ) -> &mut LazilyCompiledShader {
         match format {
             CompositeSurfaceFormat::Rgba => {
-                debug_assert_eq!(buffer_kind, ImageBufferKind::Texture2DArray);
-                &mut self.composite_rgba
+                let shader_index = Self::get_compositing_shader_index(buffer_kind);
+                self.composite_rgba[shader_index]
+                    .as_mut()
+                    .expect("bug: unsupported rgba shader requested")
             }
             CompositeSurfaceFormat::Yuv => {
-                let shader_index = Self::get_yuv_shader_index(buffer_kind);
+                let shader_index = Self::get_compositing_shader_index(buffer_kind);
                 self.composite_yuv[shader_index]
                     .as_mut()
                     .expect("bug: unsupported yuv shader requested")
             }
         }
     }
 
     pub fn get(&mut self, key: &BatchKey, features: BatchFeatures, debug_flags: DebugFlags) -> &mut LazilyCompiledShader {
@@ -1067,17 +1079,17 @@ impl Shaders {
                     BrushBatchKind::RadialGradient => {
                         &mut self.brush_radial_gradient
                     }
                     BrushBatchKind::LinearGradient => {
                         &mut self.brush_linear_gradient
                     }
                     BrushBatchKind::YuvImage(image_buffer_kind, ..) => {
                         let shader_index =
-                            Self::get_yuv_shader_index(image_buffer_kind);
+                            Self::get_compositing_shader_index(image_buffer_kind);
                         self.brush_yuv_image[shader_index]
                             .as_mut()
                             .expect("Unsupported YUV shader kind")
                     }
                     BrushBatchKind::Opacity => {
                         &mut self.brush_opacity
                     }
                 };
@@ -1134,17 +1146,21 @@ impl Shaders {
                 shader.deinit(device);
             }
         }
         self.cs_border_solid.deinit(device);
         self.cs_gradient.deinit(device);
         self.cs_line_decoration.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
-        self.composite_rgba.deinit(device);
+        for shader in self.composite_rgba {
+            if let Some(shader) = shader {
+                shader.deinit(device);
+            }
+        }
         for shader in self.composite_yuv {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
     }
 }
 
--- a/gfx/wr/webrender_build/src/shader_features.rs
+++ b/gfx/wr/webrender_build/src/shader_features.rs
@@ -108,18 +108,22 @@ pub fn get_shader_features(flags: Shader
         if flags.contains(ShaderFeatureFlags::DUAL_SOURCE_BLENDING) {
             let dual_source_features = concat_features(&brush_alpha_features, "DUAL_SOURCE_BLENDING");
             image_features.push(concat_features(&fast, &dual_source_features));
             image_features.push(concat_features(&slow, &dual_source_features));
         }
     }
     shaders.insert("brush_image", image_features);
 
+    let mut composite_features: Vec<String> = Vec::new();
+    for texture_type in &texture_types {
+        let base = concat_features("", texture_type);
+        composite_features.push(base.clone());
+    }
     // YUV image brush shaders
-    let mut composite_features: Vec<String> = vec!["".to_string()];
     let mut yuv_features: Vec<String> = Vec::new();
     for texture_type in &texture_types {
         let base = concat_features("YUV", texture_type);
         composite_features.push(base.clone());
         yuv_features.push(base.clone());
         yuv_features.push(concat_features(&base, &brush_alpha_features));
         yuv_features.push(concat_features(&base, "DEBUG_OVERDRAW"));
     }