Bug 1653166 - Add rotation support to computed reference frames and use them for <video>. r=aosmond
authorMatt Woodrow <mwoodrow@mozilla.com>
Tue, 04 Aug 2020 01:15:04 +0000
changeset 543157 ff35f5c6ce13937708af80acdccf18ef2a207e75
parent 543156 4ed6ec0c8220321b13c108ffaa7ab6cfc8d7698f
child 543158 2ce5cfe5cbe9c4331b547293db3b1e188d006de9
push id37666
push usermalexandru@mozilla.com
push dateTue, 04 Aug 2020 09:13:27 +0000
treeherdermozilla-central@7cb90fa4f485 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaosmond
bugs1653166
milestone81.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 1653166 - Add rotation support to computed reference frames and use them for <video>. r=aosmond Differential Revision: https://phabricator.services.mozilla.com/D85104
gfx/layers/ImageContainer.h
gfx/layers/ipc/WebRenderMessages.ipdlh
gfx/layers/wr/AsyncImagePipelineManager.cpp
gfx/layers/wr/AsyncImagePipelineManager.h
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderCommandBuilder.cpp
gfx/layers/wr/WebRenderMessageUtils.h
gfx/layers/wr/WebRenderUserData.cpp
gfx/layers/wr/WebRenderUserData.h
gfx/webrender_bindings/src/bindings.rs
gfx/wr/webrender/src/scene_building.rs
gfx/wr/webrender_api/src/display_item.rs
gfx/wr/webrender_api/src/display_list.rs
layout/generic/nsHTMLCanvasFrame.cpp
layout/generic/nsVideoFrame.cpp
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -28,16 +28,17 @@
 #include "nsTArray.h"          // for nsTArray
 #include "mozilla/Atomics.h"
 #include "mozilla/WeakPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "nsDataHashtable.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/UniquePtr.h"
+#include "MediaInfo.h"
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 /**
  * We need to be able to hold a reference to a Moz2D SourceSurface from Image
  * subclasses. Whilst SourceSurface is atomic refcounted and thus safe to
  * AddRef/Release on any thread, it is potentially a problem since clean up code
  * may need to run on a the main thread.
  *
@@ -531,16 +532,20 @@ class ImageContainer final : public Supp
   const gfx::IntSize& GetScaleHint() const { return mScaleHint; }
 
   void SetTransformHint(const gfx::Matrix& aTransformHint) {
     mTransformHint = aTransformHint;
   }
 
   const gfx::Matrix& GetTransformHint() const { return mTransformHint; }
 
+  void SetRotation(VideoInfo::Rotation aRotation) { mRotation = aRotation; }
+
+  VideoInfo::Rotation GetRotation() const { return mRotation; }
+
   void SetImageFactory(ImageFactory* aFactory) {
     RecursiveMutexAutoLock lock(mRecursiveMutex);
     mImageFactory = aFactory ? aFactory : new ImageFactory();
   }
 
   ImageFactory* GetImageFactory() const { return mImageFactory; }
 
   void EnsureRecycleAllocatorForRDD(KnowsCompositor* aKnowsCompositor);
@@ -655,16 +660,18 @@ class ImageContainer final : public Supp
   // this container can set an alternative image factory that will be used to
   // create images for this container.
   RefPtr<ImageFactory> mImageFactory;
 
   gfx::IntSize mScaleHint;
 
   gfx::Matrix mTransformHint;
 
+  VideoInfo::Rotation mRotation = VideoInfo::Rotation::kDegree_0;
+
   RefPtr<BufferRecycleBin> mRecycleBin;
 
   // This member points to an ImageClient if this ImageContainer was
   // sucessfully created with ENABLE_ASYNC, or points to null otherwise.
   // 'unsuccessful' in this case only means that the ImageClient could not
   // be created, most likely because off-main-thread compositing is not enabled.
   // In this case the ImageContainer is perfectly usable, but it will forward
   // frames to the compositor through transactions in the main thread rather
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -24,16 +24,17 @@ using mozilla::wr::FontKey from "mozilla
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::BlobImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h";
 using mozilla::LayoutDeviceRect from "Units.h";
 using mozilla::LayoutDeviceSize from "Units.h";
 using mozilla::ImageIntRect from "Units.h";
 using mozilla::gfx::Rect from "mozilla/gfx/Rect.h";
+using mozilla::VideoInfo::Rotation from "MediaInfo.h";
 using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace layers {
 
 struct RefCountedShmem {
   Shmem buffer;
@@ -75,16 +76,17 @@ struct OpReleaseTextureOfImage {
   ImageKey key;
 };
 
 struct OpUpdateAsyncImagePipeline {
   PipelineId pipelineId;
   LayoutDeviceRect scBounds;
   Matrix4x4 scTransform;
   MaybeIntSize scaleToSize;
+  Rotation rotation;
   ImageRendering filter;
   MixBlendMode mixBlendMode;
   LayoutDeviceSize scaleFromSize;
 };
 
 struct OpUpdatedAsyncImagePipeline {
   PipelineId pipelineId;
 };
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -176,28 +176,29 @@ void AsyncImagePipelineManager::RemoveAs
     entry.Remove();
     RemovePipeline(aPipelineId, epoch);
   }
 }
 
 void AsyncImagePipelineManager::UpdateAsyncImagePipeline(
     const wr::PipelineId& aPipelineId, const LayoutDeviceRect& aScBounds,
     const gfx::Matrix4x4& aScTransform, const gfx::MaybeIntSize& aScaleToSize,
-    const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode,
+    const VideoInfo::Rotation aRotation, const wr::ImageRendering& aFilter,
+    const wr::MixBlendMode& aMixBlendMode,
     const LayoutDeviceSize& aScaleFromSize) {
   if (mDestroyed) {
     return;
   }
   AsyncImagePipeline* pipeline =
       mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
   if (!pipeline) {
     return;
   }
   pipeline->mInitialised = true;
-  pipeline->Update(aScBounds, aScTransform, aScaleToSize, aFilter,
+  pipeline->Update(aScBounds, aScTransform, aScaleToSize, aRotation, aFilter,
                    aMixBlendMode, aScaleFromSize);
 }
 
 Maybe<TextureHost::ResourceUpdateOp> AsyncImagePipelineManager::UpdateImageKeys(
     const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
     AsyncImagePipeline* aPipeline, nsTArray<wr::ImageKey>& aKeys,
     wr::TransactionBuilder& aSceneBuilderTxn,
     wr::TransactionBuilder& aMaybeFastTxn) {
@@ -328,16 +329,30 @@ void AsyncImagePipelineManager::ApplyAsy
     if (!pipeline->mImageHost->GetAsyncRef()) {
       continue;
     }
     ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aSceneBuilderTxn,
                                aFastTxn);
   }
 }
 
+wr::WrRotation ToWrRotation(VideoInfo::Rotation aRotation) {
+  switch (aRotation) {
+    case VideoInfo::Rotation::kDegree_0:
+      return wr::WrRotation::Degree0;
+    case VideoInfo::Rotation::kDegree_90:
+      return wr::WrRotation::Degree90;
+    case VideoInfo::Rotation::kDegree_180:
+      return wr::WrRotation::Degree180;
+    case VideoInfo::Rotation::kDegree_270:
+      return wr::WrRotation::Degree270;
+  }
+  return wr::WrRotation::Degree0;
+}
+
 void AsyncImagePipelineManager::ApplyAsyncImageForPipeline(
     const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
     AsyncImagePipeline* aPipeline, wr::TransactionBuilder& aSceneBuilderTxn,
     wr::TransactionBuilder& aMaybeFastTxn) {
   nsTArray<wr::ImageKey> keys;
   auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, keys,
                             aSceneBuilderTxn, aMaybeFastTxn);
 
@@ -367,21 +382,23 @@ void AsyncImagePipelineManager::ApplyAsy
   wr::DisplayListBuilder builder(aPipelineId);
 
   float opacity = 1.0f;
   wr::StackingContextParams params;
   params.opacity = &opacity;
   params.mix_blend_mode = aPipeline->mMixBlendMode;
 
   wr::WrComputedTransformData computedTransform;
-  if (!aPipeline->mScaleFromSize.IsEmpty()) {
+  if (!aPipeline->mScaleFromSize.IsEmpty() ||
+      aPipeline->mRotation != VideoInfo::Rotation::kDegree_0) {
     MOZ_ASSERT(scTransform.IsIdentity());
     computedTransform.vertical_flip =
         aPipeline->mCurrentTexture && aPipeline->mCurrentTexture->NeedsYFlip();
     computedTransform.scale_from = wr::ToLayoutSize(aPipeline->mScaleFromSize);
+    computedTransform.rotation = ToWrRotation(aPipeline->mRotation);
     params.computed_transform = &computedTransform;
   } else {
     if (aPipeline->mCurrentTexture &&
         aPipeline->mCurrentTexture->NeedsYFlip()) {
       scTransform
           .PreTranslate(0, aPipeline->mCurrentTexture->GetSize().height, 0)
           .PreScale(1, -1, 1);
     }
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -98,16 +98,17 @@ class AsyncImagePipelineManager final {
                              WebRenderImageHost* aImageHost);
   void RemoveAsyncImagePipeline(const wr::PipelineId& aPipelineId,
                                 wr::TransactionBuilder& aTxn);
 
   void UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
                                 const LayoutDeviceRect& aScBounds,
                                 const gfx::Matrix4x4& aScTransform,
                                 const gfx::MaybeIntSize& aScaleToSize,
+                                VideoInfo::Rotation aRotation,
                                 const wr::ImageRendering& aFilter,
                                 const wr::MixBlendMode& aMixBlendMode,
                                 const LayoutDeviceSize& aScaleFromSize);
   void ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aSceneBuilderTxn,
                                      wr::TransactionBuilder& aFastTxn);
   void ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId,
                                   wr::TransactionBuilder& aTxn,
                                   wr::TransactionBuilder& aTxnForImageBridge);
@@ -177,38 +178,42 @@ class AsyncImagePipelineManager final {
     WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge = nullptr;
   };
 
   struct AsyncImagePipeline {
     AsyncImagePipeline();
     void Update(const LayoutDeviceRect& aScBounds,
                 const gfx::Matrix4x4& aScTransform,
                 const gfx::MaybeIntSize& aScaleToSize,
+                VideoInfo::Rotation aRotation,
                 const wr::ImageRendering& aFilter,
                 const wr::MixBlendMode& aMixBlendMode,
                 const LayoutDeviceSize& aScaleFromSize) {
-      mIsChanged |=
-          !mScBounds.IsEqualEdges(aScBounds) || mScTransform != aScTransform ||
-          mScaleToSize != aScaleToSize || mFilter != aFilter ||
-          mMixBlendMode != aMixBlendMode || mScaleFromSize != aScaleFromSize;
+      mIsChanged |= !mScBounds.IsEqualEdges(aScBounds) ||
+                    mScTransform != aScTransform ||
+                    mScaleToSize != aScaleToSize || mRotation != aRotation ||
+                    mFilter != aFilter || mMixBlendMode != aMixBlendMode ||
+                    mScaleFromSize != aScaleFromSize;
       mScBounds = aScBounds;
       mScTransform = aScTransform;
       mScaleToSize = aScaleToSize;
+      mRotation = aRotation;
       mFilter = aFilter;
       mMixBlendMode = aMixBlendMode;
       mScaleFromSize = aScaleFromSize;
     }
 
     bool mInitialised;
     bool mIsChanged;
     bool mUseExternalImage;
     LayoutDeviceRect mScBounds;
     LayoutDeviceSize mScaleFromSize;
     gfx::Matrix4x4 mScTransform;
     gfx::MaybeIntSize mScaleToSize;
+    VideoInfo::Rotation mRotation;
     wr::ImageRendering mFilter;
     wr::MixBlendMode mMixBlendMode;
     RefPtr<WebRenderImageHost> mImageHost;
     CompositableTextureHostRef mCurrentTexture;
     nsTArray<wr::ImageKey> mKeys;
   };
 
   void ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch,
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1348,17 +1348,17 @@ bool WebRenderBridgeParent::ProcessWebRe
         ReleaseTextureOfImage(op.key());
         break;
       }
       case WebRenderParentCommand::TOpUpdateAsyncImagePipeline: {
         const OpUpdateAsyncImagePipeline& op =
             cmd.get_OpUpdateAsyncImagePipeline();
         mAsyncImageManager->UpdateAsyncImagePipeline(
             op.pipelineId(), op.scBounds(), op.scTransform(), op.scaleToSize(),
-            op.filter(), op.mixBlendMode(), op.scaleFromSize());
+            op.rotation(), op.filter(), op.mixBlendMode(), op.scaleFromSize());
         mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn,
                                                        txnForImageBridge);
         break;
       }
       case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: {
         const OpUpdatedAsyncImagePipeline& op =
             cmd.get_OpUpdatedAsyncImagePipeline();
         aTxn.InvalidateRenderedFrame();
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1851,28 +1851,24 @@ Maybe<wr::ImageKey> WebRenderCommandBuil
       CreateOrRecycleWebRenderUserData<WebRenderImageData>(aItem);
   MOZ_ASSERT(imageData);
 
   if (aContainer->IsAsync()) {
     MOZ_ASSERT(aAsyncImageBounds);
 
     LayoutDeviceRect rect = aAsyncImageBounds.value();
     LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), rect.Size());
-    gfx::MaybeIntSize scaleToSize;
-    if (!aContainer->GetScaleHint().IsEmpty()) {
-      scaleToSize = Some(aContainer->GetScaleHint());
-    }
-    gfx::Matrix4x4 transform =
-        gfx::Matrix4x4::From2D(aContainer->GetTransformHint());
+    gfx::Matrix4x4 transform;
     // TODO!
     // We appear to be using the image bridge for a lot (most/all?) of
     // layers-free image handling and that breaks frame consistency.
     imageData->CreateAsyncImageWebRenderCommands(
-        aBuilder, aContainer, aSc, rect, scBounds, transform, scaleToSize,
-        aRendering, wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden());
+        aBuilder, aContainer, aSc, rect, scBounds, transform,
+        gfx::MaybeIntSize(), aContainer->GetRotation(), aRendering,
+        wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden());
     return Nothing();
   }
 
   AutoLockImage autoLock(aContainer);
   if (!autoLock.HasImage()) {
     return Nothing();
   }
   mozilla::layers::Image* image = autoLock.GetImage();
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -7,16 +7,17 @@
 #ifndef GFX_WEBRENDERMESSAGEUTILS_H
 #define GFX_WEBRENDERMESSAGEUTILS_H
 
 #include "chrome/common/ipc_message_utils.h"
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/webrender/webrender_ffi.h"
 #include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/dom/MediaIPCUtils.h"
 
 namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::wr::ByteBuffer> {
   typedef mozilla::wr::ByteBuffer paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -227,18 +227,19 @@ already_AddRefed<ImageClient> WebRenderI
   RefPtr<ImageClient> imageClient = mImageClient;
   return imageClient.forget();
 }
 
 void WebRenderImageData::CreateAsyncImageWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer,
     const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds,
     const LayoutDeviceRect& aSCBounds, const gfx::Matrix4x4& aSCTransform,
-    const gfx::MaybeIntSize& aScaleToSize, const wr::ImageRendering& aFilter,
-    const wr::MixBlendMode& aMixBlendMode, bool aIsBackfaceVisible) {
+    const gfx::MaybeIntSize& aScaleToSize, VideoInfo::Rotation aRotation,
+    const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode,
+    bool aIsBackfaceVisible) {
   MOZ_ASSERT(aContainer->IsAsync());
 
   if (mPipelineId.isSome() && mContainer != aContainer) {
     // In this case, we need to remove the existed pipeline and create new one
     // because the ImageContainer is changed.
     WrBridge()->RemovePipelineIdForCompositable(mPipelineId.ref());
     mPipelineId.reset();
   }
@@ -248,31 +249,39 @@ void WebRenderImageData::CreateAsyncImag
     mPipelineId =
         Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
     WrBridge()->AddPipelineIdForAsyncCompositable(
         mPipelineId.ref(), aContainer->GetAsyncContainerHandle());
     mContainer = aContainer;
   }
   MOZ_ASSERT(!mImageClient);
 
+  LayoutDeviceSize scaleFromSize;
+  AutoLockImage autoLock(aContainer);
+  if (autoLock.HasImage()) {
+    mozilla::layers::Image* image = autoLock.GetImage();
+    gfx::IntSize size = image->GetSize();
+    scaleFromSize = LayoutDeviceSize(size.width, size.height);
+  }
+
   // Push IFrame for async image pipeline.
   //
   // We don't push a stacking context for this async image pipeline here.
   // Instead, we do it inside the iframe that hosts the image. As a result,
   // a bunch of the calculations normally done as part of that stacking
   // context need to be done manually and pushed over to the parent side,
   // where it will be done when we build the display list for the iframe.
   // That happens in AsyncImagePipelineManager.
   wr::LayoutRect r = wr::ToLayoutRect(aBounds);
   aBuilder.PushIFrame(r, aIsBackfaceVisible, mPipelineId.ref(),
                       /*ignoreMissingPipelines*/ false);
 
   WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline(
-      mPipelineId.value(), aSCBounds, aSCTransform, aScaleToSize, aFilter,
-      aMixBlendMode, LayoutDeviceSize()));
+      mPipelineId.value(), aSCBounds, aSCTransform, aScaleToSize, aRotation,
+      aFilter, aMixBlendMode, scaleFromSize));
 }
 
 void WebRenderImageData::CreateImageClientIfNeeded() {
   if (!mImageClient) {
     mImageClient = ImageClient::CreateImageClient(
         CompositableType::IMAGE, WrBridge(), TextureFlags::DEFAULT);
     if (!mImageClient) {
       return;
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -153,18 +153,19 @@ class WebRenderImageData : public WebRen
   Maybe<wr::ImageKey> UpdateImageKey(ImageContainer* aContainer,
                                      wr::IpcResourceUpdateQueue& aResources,
                                      bool aFallback = false);
 
   void CreateAsyncImageWebRenderCommands(
       mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer,
       const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds,
       const LayoutDeviceRect& aSCBounds, const gfx::Matrix4x4& aSCTransform,
-      const gfx::MaybeIntSize& aScaleToSize, const wr::ImageRendering& aFilter,
-      const wr::MixBlendMode& aMixBlendMode, bool aIsBackfaceVisible);
+      const gfx::MaybeIntSize& aScaleToSize, VideoInfo::Rotation aRotation,
+      const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode,
+      bool aIsBackfaceVisible);
 
   void CreateImageClientIfNeeded();
 
   bool IsAsync() { return mPipelineId.isSome(); }
 
   bool UsingSharedSurface(ContainerProducerID aProducerId) const;
 
   void ClearImageKey();
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -485,16 +485,17 @@ pub type WrColorProperty = WrAnimationPr
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct WrWindowId(u64);
 
 #[repr(C)]
 #[derive(Debug)]
 pub struct WrComputedTransformData {
     pub scale_from: LayoutSize,
     pub vertical_flip: bool,
+    pub rotation: WrRotation,
 }
 
 fn get_proc_address(glcontext_ptr: *mut c_void, name: &str) -> *const c_void {
     extern "C" {
         fn get_proc_address_from_glcontext(glcontext_ptr: *mut c_void, procname: *const c_char) -> *const c_void;
     }
 
     let symbol_name = CString::new(name).unwrap();
@@ -2312,16 +2313,25 @@ pub extern "C" fn wr_dp_clear_save(state
 #[repr(u8)]
 #[derive(PartialEq, Eq, Debug)]
 pub enum WrReferenceFrameKind {
     Transform,
     Perspective,
     Zoom,
 }
 
+#[repr(u8)]
+#[derive(PartialEq, Eq, Debug)]
+pub enum WrRotation {
+    Degree0,
+    Degree90,
+    Degree180,
+    Degree270,
+}
+
 /// IMPORTANT: If you add fields to this struct, you need to also add initializers
 /// for those fields in WebRenderAPI.h.
 #[repr(C)]
 pub struct WrStackingContextParams {
     pub clip: WrStackingContextClip,
     pub animation: *const WrAnimationProperty,
     pub opacity: *const f32,
     pub computed_transform: *const WrComputedTransformData,
@@ -2439,21 +2449,28 @@ pub extern "C" fn wr_dp_push_stacking_co
             transform_binding,
             reference_frame_kind,
         );
 
         bounds.origin = LayoutPoint::zero();
         result.id = wr_spatial_id.0;
         assert_ne!(wr_spatial_id.0, 0);
     } else if let Some(data) = computed_ref {
+        let rotation = match data.rotation {
+            WrRotation::Degree0 => Rotation::Degree0,
+            WrRotation::Degree90 => Rotation::Degree90,
+            WrRotation::Degree180 => Rotation::Degree180,
+            WrRotation::Degree270 => Rotation::Degree270,
+        };
         wr_spatial_id = state.frame_builder.dl_builder.push_computed_frame(
             bounds.origin,
             wr_spatial_id,
             Some(data.scale_from),
             data.vertical_flip,
+            rotation,
         );
 
         bounds.origin = LayoutPoint::zero();
         result.id = wr_spatial_id.0;
         assert_ne!(wr_spatial_id.0, 0);
     }
 
     state.frame_builder.dl_builder.push_stacking_context(
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -6,17 +6,17 @@ use api::{AlphaType, BorderDetails, Bord
 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
 use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData, SharedFontInstanceMap};
 use api::{FilterOp, FilterPrimitive, FontInstanceKey, FontSize, GlyphInstance, GlyphOptions, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode, StackingContextFlags};
 use api::{PropertyBinding, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpaceAndClipInfo, SpatialId, StickyFrameDisplayItem, ImageMask};
 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
-use api::{ReferenceTransformBinding};
+use api::{ReferenceTransformBinding, Rotation};
 use api::image_tiling::simplify_repeated_primitive;
 use api::units::*;
 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind};
 use crate::clip::{ClipInternData, ClipNodeKind, ClipInstance};
 use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
 use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use crate::glyph_rasterizer::FontInstance;
 use crate::hit_test::{HitTestingItem, HitTestingScene};
@@ -477,35 +477,56 @@ impl<'a> SceneBuilder<'a> {
                     DisplayItem::PushReferenceFrame(ref info) => {
                         profile_scope!("build_reference_frame");
                         let parent_space = self.get_space(info.parent_spatial_id);
                         let mut subtraversal = item.sub_iter();
                         let current_offset = self.current_offset(parent_space);
 
                         let transform = match info.reference_frame.transform {
                             ReferenceTransformBinding::Static { binding } => binding,
-                            ReferenceTransformBinding::Computed { scale_from, vertical_flip } => {
+                            ReferenceTransformBinding::Computed { scale_from, vertical_flip, rotation } => {
+                                let content_size = &self.iframe_size.last().unwrap();
+
                                 let mut transform = if let Some(scale_from) = scale_from {
-                                    let content_size = &self.iframe_size.last().unwrap();
-                                    LayoutTransform::create_scale(
-                                        content_size.width / scale_from.width,
-                                        content_size.height / scale_from.height,
-                                        1.0
-                                    )
+                                    // If we have a 90/270 degree rotation, then scale_from
+                                    // and content_size are in different coordinate spaces and
+                                    // we need to swap width/height for them to be correct.
+                                    match rotation {
+                                        Rotation::Degree0 |
+                                        Rotation::Degree180 => {
+                                            LayoutTransform::create_scale(
+                                                content_size.width / scale_from.width,
+                                                content_size.height / scale_from.height,
+                                                1.0
+                                            )
+                                        },
+                                        Rotation::Degree90 |
+                                        Rotation::Degree270 => {
+                                            LayoutTransform::create_scale(
+                                                content_size.height / scale_from.width,
+                                                content_size.width / scale_from.height,
+                                                1.0
+                                            )
+
+                                        }
+                                    }
                                 } else {
                                     LayoutTransform::identity()
                                 };
 
                                 if vertical_flip {
                                     let content_size = &self.iframe_size.last().unwrap();
                                     transform = transform
                                         .post_translate(LayoutVector3D::new(0.0, content_size.height, 0.0))
                                         .pre_scale(1.0, -1.0, 1.0);
                                 }
 
+                                let rotate = rotation.to_matrix(**content_size);
+                                let transform = transform.post_transform(&rotate);
+
                                 PropertyBinding::Value(transform)
                             },
                         };
 
                         self.push_reference_frame(
                             info.reference_frame.id,
                             Some(parent_space),
                             bc.pipeline_id,
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use euclid::SideOffsets2D;
+use euclid::{SideOffsets2D, Angle};
 use peek_poke::PeekPoke;
 use std::ops::Not;
 // local imports
 use crate::font;
 use crate::api::{PipelineId, PropertyBinding};
 use crate::color::ColorF;
 use crate::image::{ColorDepth, ImageKey};
 use crate::units::*;
@@ -717,27 +717,63 @@ pub enum ReferenceFrameKind {
     Transform,
     /// A perspective transform, that optionally scrolls relative to a specific scroll node
     Perspective {
         scrolling_relative_to: Option<ExternalScrollId>,
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
+pub enum Rotation {
+    Degree0,
+    Degree90,
+    Degree180,
+    Degree270,
+}
+
+impl Rotation {
+    pub fn to_matrix(
+        &self,
+        size: LayoutSize,
+    ) -> LayoutTransform {
+        let (shift_center_to_origin, angle) = match self {
+            Rotation::Degree0 => {
+              (LayoutTransform::create_translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(0.))
+            },
+            Rotation::Degree90 => {
+              (LayoutTransform::create_translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(90.))
+            },
+            Rotation::Degree180 => {
+              (LayoutTransform::create_translation(-size.width / 2., -size.height / 2., 0.), Angle::degrees(180.))
+            },
+            Rotation::Degree270 => {
+              (LayoutTransform::create_translation(-size.height / 2., -size.width / 2., 0.), Angle::degrees(270.))
+            },
+        };
+        let shift_origin_to_center = LayoutTransform::create_translation(size.width / 2., size.height / 2., 0.);
+
+        LayoutTransform::create_rotation(0., 0., -1.0, angle)
+            .pre_transform(&shift_center_to_origin)
+            .post_transform(&shift_origin_to_center)
+    }
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, PeekPoke)]
 pub enum ReferenceTransformBinding {
     /// Standard reference frame which contains a precomputed transform.
     Static {
         binding: PropertyBinding<LayoutTransform>,
     },
     /// Computed reference frame which dynamically calculates the transform
     /// based on the given parameters. The reference is the content size of
     /// the parent iframe, which is affected by snapping.
     Computed {
         scale_from: Option<LayoutSize>,
         vertical_flip: bool,
+        rotation: Rotation,
     },
 }
 
 impl Default for ReferenceTransformBinding {
     fn default() -> Self {
         ReferenceTransformBinding::Static {
             binding: Default::default(),
         }
@@ -1593,16 +1629,17 @@ impl_default_for_enums! {
     BorderStyle => None,
     BoxShadowClipMode => Outset,
     ExtendMode => Clamp,
     FilterOp => Identity,
     ComponentTransferFuncType => Identity,
     ClipMode => Clip,
     ClipId => ClipId::invalid(),
     ReferenceFrameKind => Transform,
+    Rotation => Degree0,
     TransformStyle => Flat,
     RasterSpace => Local(f32::default()),
     MixBlendMode => Normal,
     ImageRendering => Auto,
     AlphaType => Alpha,
     YuvColorSpace => Rec601,
     ColorRange => Limited,
     YuvData => NV12(ImageKey::default(), ImageKey::default()),
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -1540,27 +1540,29 @@ impl DisplayListBuilder {
     }
 
     pub fn push_computed_frame(
         &mut self,
         origin: LayoutPoint,
         parent_spatial_id: di::SpatialId,
         scale_from: Option<LayoutSize>,
         vertical_flip: bool,
+        rotation: di::Rotation,
     ) -> di::SpatialId {
         let id = self.generate_spatial_index();
 
         let item = di::DisplayItem::PushReferenceFrame(di::ReferenceFrameDisplayListItem {
             parent_spatial_id,
             origin,
             reference_frame: di::ReferenceFrame {
                 transform_style: di::TransformStyle::Flat,
                 transform: di::ReferenceTransformBinding::Computed {
                     scale_from,
-                    vertical_flip
+                    vertical_flip,
+                    rotation,
                 },
                 kind: di::ReferenceFrameKind::Transform,
                 id,
             },
         });
 
         self.push_item(&item);
         id
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -169,17 +169,18 @@ class nsDisplayCanvas final : public nsP
         MaybeIntSize scaleToSize;
         LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), bounds.Size());
         wr::ImageRendering filter = wr::ToImageRendering(
             nsLayoutUtils::GetSamplingFilterForFrame(mFrame));
         wr::MixBlendMode mixBlendMode = wr::MixBlendMode::Normal;
         aManager->WrBridge()->AddWebRenderParentCommand(
             OpUpdateAsyncImagePipeline(
                 data->GetPipelineId().value(), scBounds, scTransform,
-                scaleToSize, filter, mixBlendMode,
+                scaleToSize, VideoInfo::Rotation::kDegree_0, filter,
+                mixBlendMode,
                 LayoutDeviceSize(canvasSizeInPx.width, canvasSizeInPx.height)));
         break;
       }
       case CanvasContextType::WebGPU: {
         nsHTMLCanvasFrame* canvasFrame =
             static_cast<nsHTMLCanvasFrame*>(mFrame);
         HTMLCanvasElement* canvasElement =
             static_cast<HTMLCanvasElement*>(canvasFrame->GetContent());
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -456,29 +456,17 @@ class nsDisplayVideo : public nsPaintedD
         area, intrinsicSize, aspectRatio, Frame()->StylePosition());
 
     gfxRect destGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
     destGFXRect.Round();
     if (destGFXRect.IsEmpty()) {
       return true;
     }
 
-    VideoInfo::Rotation rotationDeg = element->RotationDegrees();
-    IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()),
-                      static_cast<int32_t>(destGFXRect.Height()));
-    // scaleHint is set regardless of rotation, so swap w/h if needed.
-    SwapScaleWidthHeightForRotation(scaleHint, rotationDeg);
-    container->SetScaleHint(scaleHint);
-
-    Matrix transformHint;
-    if (rotationDeg != VideoInfo::Rotation::kDegree_0) {
-      transformHint = ComputeRotationMatrix(destGFXRect.Width(),
-                                            destGFXRect.Height(), rotationDeg);
-    }
-    container->SetTransformHint(transformHint);
+    container->SetRotation(element->RotationDegrees());
 
     // If the image container is empty, we don't want to fallback. Any other
     // failure will be due to resource constraints and fallback is unlikely to
     // help us. Hence we can ignore the return value from PushImage.
     LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
                           destGFXRect.height);
     aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
                                          aSc, rect, rect);