From 01b5738a5d42c95a9d75bfa81bb0cbc0181615e5 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 4 Aug 2020 01:19:59 +0000 Subject: [PATCH] Bug 1653166 - Add transforms to WebRender Compositor API. r=gw,mstange,sotaro Differential Revision: https://phabricator.services.mozilla.com/D84328 --- gfx/layers/NativeLayer.h | 11 ++- gfx/layers/NativeLayerCA.h | 5 ++ gfx/layers/NativeLayerCA.mm | 82 ++++++++++++++----- gfx/webrender_bindings/DCLayerTree.cpp | 47 ++++++++--- gfx/webrender_bindings/DCLayerTree.h | 3 +- gfx/webrender_bindings/RenderCompositor.cpp | 4 +- gfx/webrender_bindings/RenderCompositor.h | 3 +- .../RenderCompositorANGLE.cpp | 8 +- .../RenderCompositorANGLE.h | 3 +- .../RenderCompositorNative.cpp | 18 ++-- .../RenderCompositorNative.h | 3 +- gfx/webrender_bindings/src/bindings.rs | 7 +- gfx/webrender_bindings/src/swgl_bindings.rs | 68 +++++++-------- gfx/wr/webrender/src/composite.rs | 44 ++++++++-- gfx/wr/webrender/src/lib.rs | 2 +- gfx/wr/webrender/src/picture.rs | 49 +++++++++-- gfx/wr/webrender/src/renderer.rs | 7 +- gfx/wr/webrender/src/util.rs | 9 ++ 18 files changed, 268 insertions(+), 105 deletions(-) diff --git a/gfx/layers/NativeLayer.h b/gfx/layers/NativeLayer.h index 88ec6999e3c9..c674464fd95d 100644 --- a/gfx/layers/NativeLayer.h +++ b/gfx/layers/NativeLayer.h @@ -115,13 +115,20 @@ class NativeLayer { virtual bool IsOpaque() = 0; // The location of the layer, in integer device pixels. + // This is applied to the layer, before the transform is applied. virtual void SetPosition(const gfx::IntPoint& aPosition) = 0; virtual gfx::IntPoint GetPosition() = 0; + // Sets a transformation to apply to the Layer. This gets applied to + // coordinates with the position applied, but before clipping is + // applied. + virtual void SetTransform(const gfx::Matrix4x4& aTransform) = 0; + virtual gfx::Matrix4x4 GetTransform() = 0; + virtual gfx::IntRect GetRect() = 0; - // Set an optional clip rect on the layer. The clip rect is in the same - // coordinate space as the layer rect. + // Set an optional clip rect on the layer. The clip rect is in post-transform + // coordinate space virtual void SetClipRect(const Maybe& aClipRect) = 0; virtual Maybe ClipRect() = 0; diff --git a/gfx/layers/NativeLayerCA.h b/gfx/layers/NativeLayerCA.h index aeb0c06a0f69..18d0eab492b5 100644 --- a/gfx/layers/NativeLayerCA.h +++ b/gfx/layers/NativeLayerCA.h @@ -185,6 +185,8 @@ class NativeLayerCA : public NativeLayer { gfx::IntSize GetSize() override; void SetPosition(const gfx::IntPoint& aPosition) override; gfx::IntPoint GetPosition() override; + void SetTransform(const gfx::Matrix4x4& aTransform) override; + gfx::Matrix4x4 GetTransform() override; gfx::IntRect GetRect() override; RefPtr NextSurfaceAsDrawTarget( const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, @@ -269,6 +271,7 @@ class NativeLayerCA : public NativeLayer { // before the call. void ApplyChanges(const gfx::IntSize& aSize, bool aIsOpaque, const gfx::IntPoint& aPosition, + const gfx::Matrix4x4& aTransform, const gfx::IntRect& aDisplayRect, const Maybe& aClipRect, float aBackingScale, bool aSurfaceIsFlipped, @@ -284,6 +287,7 @@ class NativeLayerCA : public NativeLayer { CALayer* mOpaquenessTintLayer = nullptr; // strong bool mMutatedPosition = true; + bool mMutatedTransform = true; bool mMutatedDisplayRect = true; bool mMutatedClipRect = true; bool mMutatedBackingScale = true; @@ -352,6 +356,7 @@ class NativeLayerCA : public NativeLayer { Representation mOffscreenRepresentation; gfx::IntPoint mPosition; + gfx::Matrix4x4 mTransform; gfx::IntRect mDisplayRect; const gfx::IntSize mSize; Maybe mClipRect; diff --git a/gfx/layers/NativeLayerCA.mm b/gfx/layers/NativeLayerCA.mm index 4b60c668a0b1..307fb2f8fa13 100644 --- a/gfx/layers/NativeLayerCA.mm +++ b/gfx/layers/NativeLayerCA.mm @@ -32,6 +32,7 @@ using gfx::IntPoint; using gfx::IntSize; using gfx::IntRect; using gfx::IntRegion; +using gfx::Matrix4x4; using gfx::SurfaceFormat; using gl::GLContext; using gl::GLContextCGL; @@ -427,6 +428,21 @@ IntPoint NativeLayerCA::GetPosition() { return mPosition; } +void NativeLayerCA::SetTransform(const Matrix4x4& aTransform) { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(aTransform.IsRectilinear()); + + if (aTransform != mTransform) { + mTransform = aTransform; + ForAllRepresentations([&](Representation& r) { r.mMutatedTransform = true; }); + } +} + +Matrix4x4 NativeLayerCA::GetTransform() { + MutexAutoLock lock(mMutex); + return mTransform; +} + IntRect NativeLayerCA::GetRect() { MutexAutoLock lock(mMutex); return IntRect(mPosition, mSize); @@ -671,7 +687,7 @@ void NativeLayerCA::ForAllRepresentations(F aFn) { void NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation) { MutexAutoLock lock(mMutex); GetRepresentation(aRepresentation) - .ApplyChanges(mSize, mIsOpaque, mPosition, mDisplayRect, mClipRect, mBackingScale, + .ApplyChanges(mSize, mIsOpaque, mPosition, mTransform, mDisplayRect, mClipRect, mBackingScale, mSurfaceIsFlipped, mFrontSurface ? mFrontSurface->mSurface : nullptr); } @@ -680,12 +696,10 @@ CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) { return GetRepresentation(aRepresentation).UnderlyingCALayer(); } -void NativeLayerCA::Representation::ApplyChanges(const IntSize& aSize, bool aIsOpaque, - const IntPoint& aPosition, - const IntRect& aDisplayRect, - const Maybe& aClipRect, - float aBackingScale, bool aSurfaceIsFlipped, - CFTypeRefPtr aFrontSurface) { +void NativeLayerCA::Representation::ApplyChanges( + const IntSize& aSize, bool aIsOpaque, const IntPoint& aPosition, const Matrix4x4& aTransform, + const IntRect& aDisplayRect, const Maybe& aClipRect, float aBackingScale, + bool aSurfaceIsFlipped, CFTypeRefPtr aFrontSurface) { if (!mWrappingCALayer) { mWrappingCALayer = [[CALayer layer] retain]; mWrappingCALayer.position = NSZeroPoint; @@ -747,17 +761,26 @@ void NativeLayerCA::Representation::ApplyChanges(const IntSize& aSize, bool aIsO mContentCALayer.contentsScale = aBackingScale; } - if (mMutatedBackingScale || mMutatedPosition || mMutatedDisplayRect || mMutatedClipRect) { - auto clipFromDisplayRect = aDisplayRect.IsEqualInterior(IntRect({}, aSize)) - ? Nothing() - : Some(aDisplayRect + aPosition); + if (mMutatedBackingScale || mMutatedPosition || mMutatedDisplayRect || mMutatedClipRect || + mMutatedTransform || mMutatedSurfaceIsFlipped) { + Maybe clipFromDisplayRect; + if (!aDisplayRect.IsEqualInterior(IntRect({}, aSize))) { + // When the display rect is a subset of the layer, then we want to guarantee that no + // pixels outside that rect are sampled, since they might be uninitialized. + // Transforming the display rect into a post-transform clip only maintains this if + // it's an integer translation, which is all we support for this case currently. + MOZ_ASSERT(aTransform.Is2DIntegerTranslation()); + clipFromDisplayRect = + Some(RoundedToInt(aTransform.TransformBounds(IntRectToRect(aDisplayRect + aPosition)))); + } + auto effectiveClip = IntersectMaybeRects(aClipRect, clipFromDisplayRect); - auto globalClipOrigin = effectiveClip ? effectiveClip->TopLeft() : aPosition; - auto globalLayerOrigin = aPosition; - auto clipToLayerOffset = globalLayerOrigin - globalClipOrigin; + auto globalClipOrigin = effectiveClip ? effectiveClip->TopLeft() : IntPoint(); + auto clipToLayerOffset = -globalClipOrigin; mWrappingCALayer.position = CGPointMake(globalClipOrigin.x / aBackingScale, globalClipOrigin.y / aBackingScale); + if (effectiveClip) { mWrappingCALayer.masksToBounds = YES; mWrappingCALayer.bounds = CGRectMake(0, 0, effectiveClip->Width() / aBackingScale, @@ -767,18 +790,36 @@ void NativeLayerCA::Representation::ApplyChanges(const IntSize& aSize, bool aIsO } mContentCALayer.position = CGPointMake(clipToLayerOffset.x / aBackingScale, clipToLayerOffset.y / aBackingScale); + mContentCALayer.position = CGPointMake(0, 0); if (mOpaquenessTintLayer) { mOpaquenessTintLayer.position = mContentCALayer.position; } - } - if (mMutatedBackingScale || mMutatedSurfaceIsFlipped) { + Matrix4x4 transform = aTransform; + transform.PreTranslate(aPosition.x, aPosition.y, 0); + transform.PostTranslate(clipToLayerOffset.x, clipToLayerOffset.y, 0); + if (aSurfaceIsFlipped) { - CGFloat height = aSize.height / aBackingScale; - mContentCALayer.affineTransform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, height); - } else { - mContentCALayer.affineTransform = CGAffineTransformIdentity; + transform.PreTranslate(0, aSize.height, 0).PreScale(1, -1, 1); } + + CATransform3D transformCA{transform._11, + transform._12, + transform._13, + transform._14, + transform._21, + transform._22, + transform._23, + transform._24, + transform._31, + transform._32, + transform._33, + transform._34, + transform._41 / aBackingScale, + transform._42 / aBackingScale, + transform._43, + transform._44}; + mContentCALayer.transform = transformCA; } if (mMutatedFrontSurface) { @@ -786,6 +827,7 @@ void NativeLayerCA::Representation::ApplyChanges(const IntSize& aSize, bool aIsO } mMutatedPosition = false; + mMutatedTransform = false; mMutatedBackingScale = false; mMutatedSurfaceIsFlipped = false; mMutatedDisplayRect = false; diff --git a/gfx/webrender_bindings/DCLayerTree.cpp b/gfx/webrender_bindings/DCLayerTree.cpp index 72f011111edc..ba826dbca570 100644 --- a/gfx/webrender_bindings/DCLayerTree.cpp +++ b/gfx/webrender_bindings/DCLayerTree.cpp @@ -335,8 +335,18 @@ void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) { surface->DestroyTile(aX, aY); } +template +static inline D2D1_RECT_F D2DRect(const T& aRect) { + return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()); +} + +static inline D2D1_MATRIX_3X2_F D2DMatrix(const gfx::Matrix& aTransform) { + return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21, + aTransform._22, aTransform._31, aTransform._32); +} + void DCLayerTree::AddSurface(wr::NativeSurfaceId aId, - wr::DeviceIntPoint aPosition, + const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect) { auto it = mDCSurfaces.find(aId); MOZ_RELEASE_ASSERT(it != mDCSurfaces.end()); @@ -344,22 +354,33 @@ void DCLayerTree::AddSurface(wr::NativeSurfaceId aId, const auto visual = surface->GetVisual(); wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset(); - aPosition.x -= virtualOffset.x; - aPosition.y -= virtualOffset.y; - // Place the visual - this changes frame to frame based on scroll position - // of the slice. - visual->SetOffsetX(aPosition.x); - visual->SetOffsetY(aPosition.y); + gfx::Matrix transform(aTransform.m11, aTransform.m12, aTransform.m21, + aTransform.m22, aTransform.m41, aTransform.m42); + transform.PreTranslate(-virtualOffset.x, -virtualOffset.y); + // The DirectComposition API applies clipping *before* any transforms/offset, + // whereas we want the clip applied after. + // Right now, we only support rectilinear transforms, and then we transform + // our clip into pre-transform coordinate space for it to be applied there. + // DirectComposition does have an option for pre-transform clipping, if you + // create an explicit IDCompositionEffectGroup object and set a 3D transform + // on that. I suspect that will perform worse though, so we should only do + // that for complex transforms (which are never provided right now). + MOZ_ASSERT(transform.IsRectilinear()); + gfx::Rect clip = transform.Inverse().TransformBounds( + gfx::Rect(aClipRect.origin.x, aClipRect.origin.y, aClipRect.size.width, + aClipRect.size.height)); + clip.Round(); // Set the clip rect - converting from world space to the pre-offset space // that DC requires for rectangle clips. - D2D_RECT_F clip_rect; - clip_rect.left = aClipRect.origin.x - aPosition.x; - clip_rect.top = aClipRect.origin.y - aPosition.y; - clip_rect.right = clip_rect.left + aClipRect.size.width; - clip_rect.bottom = clip_rect.top + aClipRect.size.height; - visual->SetClip(clip_rect); + visual->SetClip(D2DRect(clip)); + + // TODO: The input matrix is a 4x4, but we only support a 3x2 at + // the D3D API level (unless we QI to IDCompositionVisual3, which might + // not be available?). + // Should we assert here, or restrict at the WR API level. + visual->SetTransform(D2DMatrix(transform)); mCurrentLayers.push_back(aId); } diff --git a/gfx/webrender_bindings/DCLayerTree.h b/gfx/webrender_bindings/DCLayerTree.h index af7bccf6129b..97976ba4f0ad 100644 --- a/gfx/webrender_bindings/DCLayerTree.h +++ b/gfx/webrender_bindings/DCLayerTree.h @@ -70,7 +70,8 @@ class DCLayerTree { void DestroySurface(NativeSurfaceId aId); void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY); void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY); - void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition, + void AddSurface(wr::NativeSurfaceId aId, + const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect); gl::GLContext* GetGLContext() const { return mGL; } diff --git a/gfx/webrender_bindings/RenderCompositor.cpp b/gfx/webrender_bindings/RenderCompositor.cpp index 1f5f1ccc3d4c..c71b6fca953c 100644 --- a/gfx/webrender_bindings/RenderCompositor.cpp +++ b/gfx/webrender_bindings/RenderCompositor.cpp @@ -29,10 +29,10 @@ namespace mozilla { namespace wr { void wr_compositor_add_surface(void* aCompositor, wr::NativeSurfaceId aId, - wr::DeviceIntPoint aPosition, + const wr::CompositorSurfaceTransform* aTransform, wr::DeviceIntRect aClipRect) { RenderCompositor* compositor = static_cast(aCompositor); - compositor->AddSurface(aId, aPosition, aClipRect); + compositor->AddSurface(aId, *aTransform, aClipRect); } void wr_compositor_begin_frame(void* aCompositor) { diff --git a/gfx/webrender_bindings/RenderCompositor.h b/gfx/webrender_bindings/RenderCompositor.h index 403363b4a7d1..392897382407 100644 --- a/gfx/webrender_bindings/RenderCompositor.h +++ b/gfx/webrender_bindings/RenderCompositor.h @@ -108,7 +108,8 @@ class RenderCompositor { virtual void DestroySurface(NativeSurfaceId aId) {} virtual void CreateTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) {} virtual void DestroyTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) {} - virtual void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition, + virtual void AddSurface(wr::NativeSurfaceId aId, + const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect) {} virtual void EnableNativeCompositor(bool aEnable) {} virtual void DeInit() {} diff --git a/gfx/webrender_bindings/RenderCompositorANGLE.cpp b/gfx/webrender_bindings/RenderCompositorANGLE.cpp index c04a5d832043..287c896a2b96 100644 --- a/gfx/webrender_bindings/RenderCompositorANGLE.cpp +++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp @@ -864,10 +864,10 @@ void RenderCompositorANGLE::DestroyTile(wr::NativeSurfaceId aId, int aX, mDCLayerTree->DestroyTile(aId, aX, aY); } -void RenderCompositorANGLE::AddSurface(wr::NativeSurfaceId aId, - wr::DeviceIntPoint aPosition, - wr::DeviceIntRect aClipRect) { - mDCLayerTree->AddSurface(aId, aPosition, aClipRect); +void RenderCompositorANGLE::AddSurface( + wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform, + wr::DeviceIntRect aClipRect) { + mDCLayerTree->AddSurface(aId, aTransform, aClipRect); } CompositorCapabilities RenderCompositorANGLE::GetCompositorCapabilities() { diff --git a/gfx/webrender_bindings/RenderCompositorANGLE.h b/gfx/webrender_bindings/RenderCompositorANGLE.h index c426d7ef5e5f..faf25f654573 100644 --- a/gfx/webrender_bindings/RenderCompositorANGLE.h +++ b/gfx/webrender_bindings/RenderCompositorANGLE.h @@ -81,7 +81,8 @@ class RenderCompositorANGLE : public RenderCompositor { void DestroySurface(NativeSurfaceId aId) override; void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override; void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override; - void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition, + void AddSurface(wr::NativeSurfaceId aId, + const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect) override; void EnableNativeCompositor(bool aEnable) override; CompositorCapabilities GetCompositorCapabilities() override; diff --git a/gfx/webrender_bindings/RenderCompositorNative.cpp b/gfx/webrender_bindings/RenderCompositorNative.cpp index 9548043d85a3..9aa124d9ca34 100644 --- a/gfx/webrender_bindings/RenderCompositorNative.cpp +++ b/gfx/webrender_bindings/RenderCompositorNative.cpp @@ -245,26 +245,32 @@ void RenderCompositorNative::DestroyTile(wr::NativeSurfaceId aId, int aX, layer->DiscardBackbuffers(); } -void RenderCompositorNative::AddSurface(wr::NativeSurfaceId aId, - wr::DeviceIntPoint aPosition, - wr::DeviceIntRect aClipRect) { +void RenderCompositorNative::AddSurface( + wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform, + wr::DeviceIntRect aClipRect) { MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer); auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); const Surface& surface = surfaceCursor->second; + Matrix4x4 transform( + aTransform.m11, aTransform.m12, aTransform.m13, aTransform.m14, + aTransform.m21, aTransform.m22, aTransform.m23, aTransform.m24, + aTransform.m31, aTransform.m32, aTransform.m33, aTransform.m34, + aTransform.m41, aTransform.m42, aTransform.m43, aTransform.m44); + for (auto it = surface.mNativeLayers.begin(); it != surface.mNativeLayers.end(); ++it) { RefPtr layer = it->second; gfx::IntSize layerSize = layer->GetSize(); - gfx::IntPoint layerPosition( - aPosition.x + surface.mTileSize.width * it->first.mX, - aPosition.y + surface.mTileSize.height * it->first.mY); + gfx::IntPoint layerPosition(surface.mTileSize.width * it->first.mX, + surface.mTileSize.height * it->first.mY); layer->SetPosition(layerPosition); gfx::IntRect clipRect(aClipRect.origin.x, aClipRect.origin.y, aClipRect.size.width, aClipRect.size.height); layer->SetClipRect(Some(clipRect)); + layer->SetTransform(transform); mAddedLayers.AppendElement(layer); mAddedPixelCount += layerSize.width * layerSize.height; diff --git a/gfx/webrender_bindings/RenderCompositorNative.h b/gfx/webrender_bindings/RenderCompositorNative.h index 707a18352314..d48be296648b 100644 --- a/gfx/webrender_bindings/RenderCompositorNative.h +++ b/gfx/webrender_bindings/RenderCompositorNative.h @@ -53,7 +53,8 @@ class RenderCompositorNative : public RenderCompositor { void DestroySurface(NativeSurfaceId aId) override; void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override; void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override; - void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition, + void AddSurface(wr::NativeSurfaceId aId, + const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect) override; CompositorCapabilities GetCompositorCapabilities() override; diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs index 1f1f6c10b939..79754af33c0a 100644 --- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -38,6 +38,7 @@ use webrender::{ CompositorCapabilities, CompositorConfig, DebugFlags, Device, FastHashMap, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, RendererStats, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod, WrShaders, ONE_TIME_USAGE_HINT, + CompositorSurfaceTransform, }; use wr_malloc_size_of::MallocSizeOfOps; @@ -1211,7 +1212,7 @@ extern "C" { fn wr_compositor_add_surface( compositor: *mut c_void, id: NativeSurfaceId, - position: DeviceIntPoint, + transform: &CompositorSurfaceTransform, clip_rect: DeviceIntRect, ); fn wr_compositor_end_frame(compositor: *mut c_void); @@ -1294,9 +1295,9 @@ impl Compositor for WrCompositor { } } - fn add_surface(&mut self, id: NativeSurfaceId, position: DeviceIntPoint, clip_rect: DeviceIntRect) { + fn add_surface(&mut self, id: NativeSurfaceId, transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect) { unsafe { - wr_compositor_add_surface(self.0, id, position, clip_rect); + wr_compositor_add_surface(self.0, id, &transform, clip_rect); } } diff --git a/gfx/webrender_bindings/src/swgl_bindings.rs b/gfx/webrender_bindings/src/swgl_bindings.rs index 3237e1bb443b..0945d6b8079c 100644 --- a/gfx/webrender_bindings/src/swgl_bindings.rs +++ b/gfx/webrender_bindings/src/swgl_bindings.rs @@ -12,7 +12,8 @@ use std::rc::Rc; use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::thread; use webrender::{ - api::units::*, Compositor, CompositorCapabilities, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, ThreadListener, + api::units::*, Compositor, CompositorCapabilities, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, + ThreadListener, CompositorSurfaceTransform }; #[no_mangle] @@ -62,8 +63,8 @@ pub struct SwTile { } impl SwTile { - fn origin(&self, surface: &SwSurface, position: &DeviceIntPoint) -> DeviceIntPoint { - DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height) + position.to_vector() + fn origin(&self, surface: &SwSurface) -> DeviceIntPoint { + DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height) } /// Bounds used for determining overlap dependencies. This may either be the @@ -73,10 +74,10 @@ impl SwTile { fn overlap_rect( &self, surface: &SwSurface, - position: &DeviceIntPoint, + transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option { - let origin = self.origin(surface, position); + let origin = self.origin(surface); // If the tile was invalidated this frame, then we don't have precise // bounds. Instead, just use the default surface tile size. let bounds = if self.invalid.get() { @@ -84,7 +85,8 @@ impl SwTile { } else { self.valid_rect.translate(origin.to_vector()) }; - bounds.intersection(clip_rect) + let device_rect = transform.transform_rect(&bounds.to_f32().cast_unit()).unwrap().round_out().to_i32(); + device_rect.cast_unit().intersection(clip_rect) } /// Determine if the tile's bounds may overlap the dependency rect if it were @@ -92,11 +94,11 @@ impl SwTile { fn may_overlap( &self, surface: &SwSurface, - position: &DeviceIntPoint, + transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, dep_rect: &DeviceIntRect, ) -> bool { - self.overlap_rect(surface, position, clip_rect) + self.overlap_rect(surface, transform, clip_rect) .map_or(false, |r| r.intersects(dep_rect)) } @@ -106,13 +108,12 @@ impl SwTile { fn composite_rects( &self, surface: &SwSurface, - position: &DeviceIntPoint, + transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option<(DeviceIntRect, DeviceIntRect)> { - let valid = self.valid_rect.translate(self.origin(surface, position).to_vector()); - valid - .intersection(clip_rect) - .map(|r| (r.translate(-valid.origin.to_vector()), r)) + let valid = self.valid_rect.translate(self.origin(surface).to_vector()); + let valid = transform.transform_rect(&valid.to_f32().cast_unit()).unwrap().round_out().to_i32(); + valid.cast_unit().intersection(clip_rect).map(|r| (r.translate(-valid.origin.to_vector().cast_unit()), r)) } } @@ -392,7 +393,7 @@ pub struct SwCompositor { native_gl: Option>, compositor: Option, surfaces: HashMap, - frame_surfaces: Vec<(NativeSurfaceId, DeviceIntPoint, DeviceIntRect)>, + frame_surfaces: Vec<(NativeSurfaceId, CompositorSurfaceTransform, DeviceIntRect)>, cur_tile: NativeTileId, draw_tile: Option, /// The maximum tile size required for any of the allocated surfaces. @@ -485,7 +486,7 @@ impl SwCompositor { /// in composition. fn get_overlaps(&self, overlap_rect: &DeviceIntRect) -> u32 { let mut overlaps = 0; - for &(ref id, ref position, ref clip_rect) in &self.frame_surfaces { + for &(ref id, ref transform, ref clip_rect) in &self.frame_surfaces { // If the surface's clip rect doesn't overlap the tile's rect, // then there is no need to check any tiles within the surface. if !overlap_rect.intersects(clip_rect) { @@ -495,7 +496,8 @@ impl SwCompositor { for tile in &surface.tiles { // If there is a deferred tile that might overlap the destination rectangle, // record the overlap. - if tile.overlaps.get() > 0 && tile.may_overlap(surface, position, clip_rect, overlap_rect) { + if tile.overlaps.get() > 0 && + tile.may_overlap(surface, transform, clip_rect, overlap_rect) { overlaps += 1; } } @@ -508,12 +510,12 @@ impl SwCompositor { fn queue_composite( &self, surface: &SwSurface, - position: &DeviceIntPoint, + transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, tile: &SwTile, ) { if let Some(ref composite_thread) = self.composite_thread { - if let Some((src_rect, dst_rect)) = tile.composite_rects(surface, position, clip_rect) { + if let Some((src_rect, dst_rect)) = tile.composite_rects(surface, transform, clip_rect) { if let Some(texture) = self.gl.lock_texture(tile.color_id) { let framebuffer = self.locked_framebuffer.clone().unwrap(); composite_thread.queue_composite(texture, framebuffer, src_rect, dst_rect, surface.is_opaque); @@ -526,14 +528,14 @@ impl SwCompositor { /// within the surface being queued for composition this frame. If the tile is immediately /// ready to composite, then queue that now. Otherwise, set its draw order index for later /// composition. - fn init_composites(&mut self, id: &NativeSurfaceId, position: &DeviceIntPoint, clip_rect: &DeviceIntRect) { + fn init_composites(&mut self, id: &NativeSurfaceId, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect) { if self.composite_thread.is_none() { return; } if let Some(surface) = self.surfaces.get(&id) { for tile in &surface.tiles { - if let Some(overlap_rect) = tile.overlap_rect(surface, position, clip_rect) { + if let Some(overlap_rect) = tile.overlap_rect(surface, transform, clip_rect) { let mut overlaps = self.get_overlaps(&overlap_rect); // Record an extra overlap for an invalid tile to track the tile's dependency // on its own future update. @@ -542,7 +544,7 @@ impl SwCompositor { } if overlaps == 0 { // Not dependent on any tiles, so go ahead and composite now. - self.queue_composite(surface, position, clip_rect, tile); + self.queue_composite(surface, transform, clip_rect, tile); } else { // Has a dependency on some invalid tiles, so need to defer composition. tile.overlaps.set(overlaps); @@ -565,7 +567,7 @@ impl SwCompositor { .iter() .skip_while(|&(ref id, _, _)| *id != tile_id.surface_id); let overlap_rect = match frame_surfaces.next() { - Some(&(_, ref position, ref clip_rect)) => { + Some(&(_, ref transform, ref clip_rect)) => { // Remove invalid tile's update dependency. if tile.invalid.get() { tile.overlaps.set(tile.overlaps.get() - 1); @@ -575,9 +577,9 @@ impl SwCompositor { return; } // Otherwise, the tile's dependencies are all resolved, so composite it. - self.queue_composite(surface, position, clip_rect, tile); + self.queue_composite(surface, transform, clip_rect, tile); // Finally, get the tile's overlap rect used for tracking dependencies - match tile.overlap_rect(surface, position, clip_rect) { + match tile.overlap_rect(surface, transform, clip_rect) { Some(overlap_rect) => overlap_rect, None => return, } @@ -591,7 +593,7 @@ impl SwCompositor { let mut flushed_rects = vec![overlap_rect]; // Check surfaces following the update in the frame list and see if they would overlap it. - for &(ref id, ref position, ref clip_rect) in frame_surfaces { + for &(ref id, ref transform, ref clip_rect) in frame_surfaces { // If the clip rect doesn't overlap the conservative bounds, we can skip the whole surface. if !flushed_bounds.intersects(clip_rect) { continue; @@ -605,7 +607,7 @@ impl SwCompositor { continue; } // Get this tile's overlap rect for tracking dependencies - let overlap_rect = match tile.overlap_rect(surface, position, clip_rect) { + let overlap_rect = match tile.overlap_rect(surface, transform, clip_rect) { Some(overlap_rect) => overlap_rect, None => continue, }; @@ -624,7 +626,7 @@ impl SwCompositor { // If the count hit zero, it is ready to composite. tile.overlaps.set(overlaps); if overlaps == 0 { - self.queue_composite(surface, position, clip_rect, tile); + self.queue_composite(surface, transform, clip_rect, tile); // Record that the tile got flushed to update any downwind dependencies. flushed_bounds = flushed_bounds.union(&overlap_rect); flushed_rects.push(overlap_rect); @@ -946,15 +948,15 @@ impl Compositor for SwCompositor { } } - fn add_surface(&mut self, id: NativeSurfaceId, position: DeviceIntPoint, clip_rect: DeviceIntRect) { + fn add_surface(&mut self, id: NativeSurfaceId, transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect) { if let Some(compositor) = &mut self.compositor { - compositor.add_surface(id, position, clip_rect); + compositor.add_surface(id, transform, clip_rect); } // Compute overlap dependencies and issue any initial composite jobs for the SwComposite thread. - self.init_composites(&id, &position, &clip_rect); + self.init_composites(&id, &transform, &clip_rect); - self.frame_surfaces.push((id, position, clip_rect)); + self.frame_surfaces.push((id, transform, clip_rect)); } fn end_frame(&mut self) { @@ -967,7 +969,7 @@ impl Compositor for SwCompositor { draw_tile.enable(&viewport); let mut blend = false; native_gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA); - for &(ref id, ref position, ref clip_rect) in &self.frame_surfaces { + for &(ref id, ref transform, ref clip_rect) in &self.frame_surfaces { if let Some(surface) = self.surfaces.get(id) { if surface.is_opaque { if blend { @@ -979,7 +981,7 @@ impl Compositor for SwCompositor { blend = true; } for tile in &surface.tiles { - if let Some((src_rect, dst_rect)) = tile.composite_rects(surface, position, clip_rect) { + if let Some((src_rect, dst_rect)) = tile.composite_rects(surface, transform, clip_rect) { draw_tile.draw(&viewport, &dst_rect, &src_rect, surface, tile); } } diff --git a/gfx/wr/webrender/src/composite.rs b/gfx/wr/webrender/src/composite.rs index 99b68eb9db08..cca7ae4f9fc3 100644 --- a/gfx/wr/webrender/src/composite.rs +++ b/gfx/wr/webrender/src/composite.rs @@ -4,8 +4,9 @@ use api::{ColorF, YuvColorSpace, YuvFormat, ImageRendering}; use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect}; -use api::units::{DevicePixelScale, DevicePoint, PictureRect, TexelRect}; +use api::units::{DevicePixelScale, DevicePoint, PictureRect, TexelRect, DevicePixel}; use crate::batch::{resolve_image, get_buffer_kind}; +use euclid::Transform3D; use crate::gpu_cache::GpuCache; use crate::gpu_types::{ZBufferId, ZBufferIdGenerator}; use crate::internal_types::TextureSource; @@ -106,10 +107,10 @@ pub enum ExternalSurfaceDependency { /// 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 transform: CompositorSurfaceTransform, pub image_rendering: ImageRendering, pub z_id: ZBufferId, pub dependency: ExternalSurfaceDependency, @@ -268,6 +269,15 @@ impl CompositorKind { CompositorKind::Native { virtual_surface_size, .. } => *virtual_surface_size, } } + + // We currently only support transforms for Native compositors, + // bug 1655639 is filed for adding support to Draw. + pub fn supports_transforms(&self) -> bool { + match self { + CompositorKind::Draw { .. } => false, + CompositorKind::Native { .. } => true, + } + } } /// The backing surface kind for a tile. Same as `TileSurface`, minus @@ -311,6 +321,7 @@ pub struct CompositeSurfaceDescriptor { pub surface_id: Option, pub offset: DevicePoint, pub clip_rect: DeviceRect, + pub transform: CompositorSurfaceTransform, // A list of image keys and generations that this compositor surface // depends on. This avoids composites being skipped when the only // thing that has changed is the generation of an compositor surface @@ -553,6 +564,9 @@ impl CompositeState { surface_id: tile_cache.native_surface.as_ref().map(|s| s.opaque), offset: tile_cache.device_position, clip_rect: device_clip_rect, + transform: CompositorSurfaceTransform::create_translation(tile_cache.device_position.x, + tile_cache.device_position.y, + 0.0), image_dependencies: [ImageDependency::INVALID; 3], tile_descriptors: opaque_tile_descriptors, } @@ -670,11 +684,13 @@ impl CompositeState { let image_buffer_kind = get_buffer_kind(planes[0].texture); + // Only propagate flip_y if the compositor doesn't support transforms, + // since otherwise it'll be handled as part of the transform. self.external_surfaces.push(ResolvedExternalSurface { color_data: ResolvedExternalSurfaceColorData::Rgb { image_dependency: image_dependencies[0], plane: planes[0], - flip_y, + flip_y: flip_y && !self.compositor_kind.supports_transforms(), }, image_buffer_kind, update_params, @@ -682,12 +698,19 @@ impl CompositeState { }, } + // Just use the device_rect for the tile's clip, since we'll clip + // in the compositor instead. + let tile_clip = if self.compositor_kind.supports_transforms() { + external_surface.device_rect + } else { + clip_rect + }; 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, + clip_rect: tile_clip, z_id: external_surface.z_id, }; @@ -698,7 +721,8 @@ impl CompositeState { CompositeSurfaceDescriptor { surface_id: external_surface.native_surface_id, offset: tile.rect.origin, - clip_rect: tile.clip_rect, + clip_rect, + transform: external_surface.transform, image_dependencies: image_dependencies, tile_descriptors: Vec::new(), } @@ -714,6 +738,9 @@ impl CompositeState { surface_id: tile_cache.native_surface.as_ref().map(|s| s.alpha), offset: tile_cache.device_position, clip_rect: device_clip_rect, + transform: CompositorSurfaceTransform::create_translation(tile_cache.device_position.x, + tile_cache.device_position.y, + 0.0), image_dependencies: [ImageDependency::INVALID; 3], tile_descriptors: alpha_tile_descriptors, } @@ -810,6 +837,10 @@ pub struct CompositorCapabilities { pub virtual_surface_size: i32, } +/// The transform type to apply to Compositor surfaces. +pub struct CompositorSurfacePixel; +pub type CompositorSurfaceTransform = Transform3D; + /// Defines an interface to a native (OS level) compositor. If supplied /// by the client application, then picture cache slices will be /// composited by the OS compositor, rather than drawn via WR batches. @@ -891,11 +922,10 @@ pub trait Compositor { // We might need to change the interface to maintain a visual // tree that can be mutated? // TODO(gw): We might need to add a concept of a hierachy in future. - // TODO(gw): In future, expand to support a more complete transform matrix. fn add_surface( &mut self, id: NativeSurfaceId, - position: DeviceIntPoint, + transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect, ); diff --git a/gfx/wr/webrender/src/lib.rs b/gfx/wr/webrender/src/lib.rs index daace09c72e3..bfdd399093a2 100644 --- a/gfx/wr/webrender/src/lib.rs +++ b/gfx/wr/webrender/src/lib.rs @@ -205,7 +205,7 @@ extern crate webrender_build; #[doc(hidden)] pub use crate::composite::{CompositorConfig, Compositor, CompositorCapabilities}; -pub use crate::composite::{NativeSurfaceId, NativeTileId, NativeSurfaceInfo}; +pub use crate::composite::{NativeSurfaceId, NativeTileId, NativeSurfaceInfo, CompositorSurfaceTransform}; pub use crate::device::{UploadMethod, VertexUsageHint, get_gl_target, get_unoptimized_shader_source}; pub use crate::device::{ProgramBinary, ProgramCache, ProgramCacheObserver, FormatDesc}; pub use crate::device::Device; diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs index 1b22d62981fe..2e4f2617e0ca 100644 --- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -107,7 +107,7 @@ use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId}; use crate::composite::{ExternalSurfaceDescriptor, ExternalSurfaceDependency}; use crate::debug_colors; -use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D, SideOffsets2D}; +use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Vector3D, Rect, Transform3D, SideOffsets2D}; use euclid::approxeq::ApproxEq; use crate::filterdata::SFilterData; use crate::intern::ItemUid; @@ -2947,6 +2947,8 @@ impl TileCacheInstance { &mut self, prim_info: &mut PrimitiveDependencyInfo, prim_rect: PictureRect, + local_prim_rect: LayoutRect, + prim_spatial_node_index: SpatialNodeIndex, frame_context: &FrameVisibilityContext, image_dependencies: &[ImageDependency;3], api_keys: &[ImageKey; 3], @@ -2960,6 +2962,8 @@ impl TileCacheInstance { self.setup_compositor_surfaces_impl( prim_info, prim_rect, + local_prim_rect, + prim_spatial_node_index, frame_context, ExternalSurfaceDependency::Yuv { image_dependencies: *image_dependencies, @@ -2978,6 +2982,8 @@ impl TileCacheInstance { &mut self, prim_info: &mut PrimitiveDependencyInfo, prim_rect: PictureRect, + local_prim_rect: LayoutRect, + prim_spatial_node_index: SpatialNodeIndex, frame_context: &FrameVisibilityContext, image_dependency: ImageDependency, api_key: ImageKey, @@ -2991,6 +2997,8 @@ impl TileCacheInstance { self.setup_compositor_surfaces_impl( prim_info, prim_rect, + local_prim_rect, + prim_spatial_node_index, frame_context, ExternalSurfaceDependency::Rgb { image_dependency, @@ -3009,6 +3017,8 @@ impl TileCacheInstance { &mut self, prim_info: &mut PrimitiveDependencyInfo, prim_rect: PictureRect, + local_prim_rect: LayoutRect, + prim_spatial_node_index: SpatialNodeIndex, frame_context: &FrameVisibilityContext, dependency: ExternalSurfaceDependency, api_keys: &[ImageKey; 3], @@ -3025,9 +3035,6 @@ impl TileCacheInstance { 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_box.to_rect()) .expect("bug: unable to map clip to world space"); @@ -3040,7 +3047,33 @@ impl TileCacheInstance { // 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 (device_rect, transform) = match composite_state.compositor_kind { + CompositorKind::Draw { .. } => { + let world_rect = pic_to_world_mapper + .map(&prim_rect) + .expect("bug: unable to map the primitive to world space"); + let device_rect = (world_rect * frame_context.global_device_pixel_scale).round(); + + (device_rect, Transform3D::identity()) + } + CompositorKind::Native { .. } => { + // If we have a Native Compositor, then we can support doing the transformation + // as part of compositing. Use the local prim rect for the external surface, and + // compute the full local to device transform to provide to the compositor. + let surface_to_world_mapper : SpaceMapper = SpaceMapper::new_with_target( + ROOT_SPATIAL_NODE_INDEX, + prim_spatial_node_index, + frame_context.global_screen_world_rect, + frame_context.spatial_tree, + ); + let prim_origin = Vector3D::new(local_prim_rect.origin.x, local_prim_rect.origin.y, 0.0); + let world_to_device_scale = Transform3D::from_scale(frame_context.global_device_pixel_scale); + let transform = surface_to_world_mapper.get_transform().pre_translate(prim_origin).post_transform(&world_to_device_scale); + + (local_prim_rect.cast_unit(), transform) + } + }; + let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); if device_rect.size.width >= MAX_COMPOSITOR_SURFACES_SIZE || @@ -3121,12 +3154,12 @@ impl TileCacheInstance { // Each compositor surface allocates a unique z-id self.external_surfaces.push(ExternalSurfaceDescriptor { local_rect: prim_info.prim_clip_box.to_rect(), - world_rect, local_clip_rect: prim_info.prim_clip_box.to_rect(), dependency, image_rendering, device_rect, clip_rect, + transform: transform.cast_unit(), z_id: composite_state.z_generator.next(), native_surface_id, update_params, @@ -3338,6 +3371,8 @@ impl TileCacheInstance { promote_to_surface = self.setup_compositor_surfaces_rgb( &mut prim_info, prim_rect, + local_prim_rect, + prim_spatial_node_index, frame_context, ImageDependency { key: image_data.key, @@ -3402,6 +3437,8 @@ impl TileCacheInstance { promote_to_surface = self.setup_compositor_surfaces_yuv( &mut prim_info, prim_rect, + local_prim_rect, + prim_spatial_node_index, frame_context, &image_dependencies, &prim_data.kind.yuv_key, diff --git a/gfx/wr/webrender/src/renderer.rs b/gfx/wr/webrender/src/renderer.rs index 97132a23ab2a..e4e95fb217af 100644 --- a/gfx/wr/webrender/src/renderer.rs +++ b/gfx/wr/webrender/src/renderer.rs @@ -49,7 +49,7 @@ use core::time::Duration; 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::{CompositeState, CompositeTileSurface, CompositeTile, ResolvedExternalSurface, CompositorSurfaceTransform}; use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeSurfaceFormat, ResolvedExternalSurfaceColorData}; use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation}; use crate::c_str; @@ -3374,7 +3374,7 @@ impl Renderer { compositor.add_surface( NativeSurfaceId::DEBUG_OVERLAY, - DeviceIntPoint::zero(), + CompositorSurfaceTransform::identity(), DeviceIntRect::new( DeviceIntPoint::zero(), self.debug_overlay_state.current_size.unwrap(), @@ -4726,7 +4726,6 @@ impl Renderer { uv_rect.uv0.y = uv_rect.uv1.y; uv_rect.uv1.y = y; } - let instance = CompositeInstance::new_rgb( surface_rect.to_f32(), surface_rect.to_f32(), @@ -7737,7 +7736,7 @@ impl CompositeState { for surface in &self.descriptor.surfaces { compositor.add_surface( surface.surface_id.expect("bug: no native surface allocated"), - surface.offset.to_i32(), + surface.transform, surface.clip_rect.to_i32(), ); } diff --git a/gfx/wr/webrender/src/util.rs b/gfx/wr/webrender/src/util.rs index 056cadbeae6a..7c7c9567a130 100644 --- a/gfx/wr/webrender/src/util.rs +++ b/gfx/wr/webrender/src/util.rs @@ -333,6 +333,8 @@ pub trait MatrixHelpers { /// Turn Z transformation into identity. This is useful when crossing "flat" /// transform styled stacking contexts upon traversing the coordinate systems. fn flatten_z_output(&mut self); + + fn cast_unit(&self) -> Transform3D; } impl MatrixHelpers for Transform3D { @@ -472,6 +474,13 @@ impl MatrixHelpers for Transform3D { self.m33 = 1.0; self.m43 = 0.0; } + + fn cast_unit(&self) -> Transform3D { + Transform3D::row_major(self.m11, self.m12, self.m13, self.m14, + self.m21, self.m22, self.m23, self.m24, + self.m31, self.m32, self.m33, self.m34, + self.m41, self.m42, self.m43, self.m44) + } } pub trait PointHelpers