1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MacIOSurfaceHelpers.h"
8 #include "MacIOSurfaceImage.h"
9 #include "gfxPlatform.h"
10 #include "mozilla/layers/CompositableClient.h"
11 #include "mozilla/layers/CompositableForwarder.h"
12 #include "mozilla/layers/MacIOSurfaceTextureClientOGL.h"
13 #include "mozilla/layers/TextureForwarder.h"
14 #include "mozilla/StaticPrefs_layers.h"
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/Unused.h"
17 #include "YCbCrUtils.h"
19 using namespace mozilla::layers
;
20 using namespace mozilla::gfx
;
22 TextureClient
* MacIOSurfaceImage::GetTextureClient(
23 KnowsCompositor
* aKnowsCompositor
) {
24 if (!mTextureClient
) {
25 BackendType backend
= BackendType::NONE
;
27 IsDRM() ? TextureFlags::DRM_SOURCE
: TextureFlags::DEFAULT
;
28 mTextureClient
= TextureClient::CreateWithData(
29 MacIOSurfaceTextureData::Create(mSurface
, backend
), flags
,
30 aKnowsCompositor
->GetTextureForwarder());
32 return mTextureClient
;
35 ColorDepth
MacIOSurfaceImage::GetColorDepth() const {
37 return gfx::ColorDepth::COLOR_8
;
39 return mSurface
->GetColorDepth();
42 already_AddRefed
<SourceSurface
> MacIOSurfaceImage::GetAsSourceSurface() {
43 return CreateSourceSurfaceFromMacIOSurface(mSurface
);
46 nsresult
MacIOSurfaceImage::BuildSurfaceDescriptorBuffer(
47 SurfaceDescriptorBuffer
& aSdBuffer
, BuildSdbFlags aFlags
,
48 const std::function
<MemoryOrShmem(uint32_t)>& aAllocate
) {
49 return CreateSurfaceDescriptorBufferFromMacIOSurface(mSurface
, aSdBuffer
,
53 static inline uint16_t safeShift10BitBy6(const uint16_t& a10BitLSB
) {
54 // a10BitLSB is a 10-bit value packed into the least significant bits of
55 // a 16 bit value. This function asserts that the 6 MSBs are zero, then
56 // shifts the 10 LSBs by 6 to become the MSBs.
57 MOZ_ASSERT((a10BitLSB
& 0b1111'1100'0000'0000) == 0);
58 return a10BitLSB
<< 6;
61 bool MacIOSurfaceImage::SetData(ImageContainer
* aContainer
,
62 const PlanarYCbCrData
& aData
) {
63 MOZ_ASSERT(!mSurface
);
65 if (aData
.mYSkip
!= 0 || aData
.mCbSkip
!= 0 || aData
.mCrSkip
!= 0 ||
66 !(aData
.mYUVColorSpace
== YUVColorSpace::BT601
||
67 aData
.mYUVColorSpace
== YUVColorSpace::BT709
||
68 aData
.mYUVColorSpace
== YUVColorSpace::BT2020
) ||
69 !(aData
.mColorRange
== ColorRange::FULL
||
70 aData
.mColorRange
== ColorRange::LIMITED
) ||
71 !(aData
.mColorDepth
== ColorDepth::COLOR_8
||
72 aData
.mColorDepth
== ColorDepth::COLOR_10
)) {
76 // We can only support 4:2:2 and 4:2:0 formats currently.
77 switch (aData
.mChromaSubsampling
) {
78 case ChromaSubsampling::HALF_WIDTH
:
79 case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT
:
85 RefPtr
<MacIOSurfaceRecycleAllocator
> allocator
=
86 aContainer
->GetMacIOSurfaceRecycleAllocator();
88 auto ySize
= aData
.YDataSize();
89 auto cbcrSize
= aData
.CbCrDataSize();
90 RefPtr
<MacIOSurface
> surf
= allocator
->Allocate(
91 ySize
, cbcrSize
, aData
.mChromaSubsampling
, aData
.mYUVColorSpace
,
92 aData
.mTransferFunction
, aData
.mColorRange
, aData
.mColorDepth
);
94 if (NS_WARN_IF(!surf
) || NS_WARN_IF(!surf
->Lock(false))) {
98 if (surf
->GetFormat() == SurfaceFormat::YUY2
) {
99 // If the CbCrSize's height is half of the YSize's height, then we'll
100 // need to duplicate the CbCr data on every second row.
101 size_t heightScale
= ySize
.height
/ cbcrSize
.height
;
103 // The underlying IOSurface has format
104 // kCVPixelFormatType_422YpCbCr8FullRange or
105 // kCVPixelFormatType_422YpCbCr8_yuvs, which uses a 4:2:2 Y`0 Cb Y`1 Cr
106 // layout. See CVPixelBuffer.h for the full list of format descriptions.
107 MOZ_ASSERT(ySize
.height
> 0);
108 uint8_t* dst
= (uint8_t*)surf
->GetBaseAddressOfPlane(0);
109 size_t stride
= surf
->GetBytesPerRow(0);
110 for (size_t i
= 0; i
< (size_t)ySize
.height
; i
++) {
111 // Compute the row addresses. If the input was 4:2:0, then
112 // we divide i by 2, so that each source row of CbCr maps to
114 uint8_t* rowYSrc
= aData
.mYChannel
+ aData
.mYStride
* i
;
116 aData
.mCbChannel
+ aData
.mCbCrStride
* (i
/ heightScale
);
118 aData
.mCrChannel
+ aData
.mCbCrStride
* (i
/ heightScale
);
119 uint8_t* rowDst
= dst
+ stride
* i
;
121 // Iterate across the CbCr width (which we have guaranteed to be half of
122 // the surface width), and write two 16bit pixels each time.
123 for (size_t j
= 0; j
< (size_t)cbcrSize
.width
; j
++) {
141 } else if (surf
->GetFormat() == SurfaceFormat::NV12
) {
142 MOZ_ASSERT(ySize
.height
> 0);
143 uint8_t* dst
= (uint8_t*)surf
->GetBaseAddressOfPlane(0);
144 size_t stride
= surf
->GetBytesPerRow(0);
145 for (size_t i
= 0; i
< (size_t)ySize
.height
; i
++) {
146 uint8_t* rowSrc
= aData
.mYChannel
+ aData
.mYStride
* i
;
147 uint8_t* rowDst
= dst
+ stride
* i
;
148 memcpy(rowDst
, rowSrc
, ySize
.width
);
151 // Copy and interleave the Cb and Cr channels.
152 MOZ_ASSERT(cbcrSize
.height
> 0);
153 dst
= (uint8_t*)surf
->GetBaseAddressOfPlane(1);
154 stride
= surf
->GetBytesPerRow(1);
155 for (size_t i
= 0; i
< (size_t)cbcrSize
.height
; i
++) {
156 uint8_t* rowCbSrc
= aData
.mCbChannel
+ aData
.mCbCrStride
* i
;
157 uint8_t* rowCrSrc
= aData
.mCrChannel
+ aData
.mCbCrStride
* i
;
158 uint8_t* rowDst
= dst
+ stride
* i
;
160 for (size_t j
= 0; j
< (size_t)cbcrSize
.width
; j
++) {
170 } else if (surf
->GetFormat() == SurfaceFormat::P010
) {
171 MOZ_ASSERT(ySize
.height
> 0);
172 auto dst
= reinterpret_cast<uint16_t*>(surf
->GetBaseAddressOfPlane(0));
173 size_t stride
= surf
->GetBytesPerRow(0) / 2;
174 for (size_t i
= 0; i
< (size_t)ySize
.height
; i
++) {
175 auto rowSrc
= reinterpret_cast<const uint16_t*>(aData
.mYChannel
+
177 auto rowDst
= dst
+ stride
* i
;
179 for (const auto j
: IntegerRange(ySize
.width
)) {
182 *rowDst
= safeShift10BitBy6(*rowSrc
);
188 // Copy and interleave the Cb and Cr channels.
189 MOZ_ASSERT(cbcrSize
.height
> 0);
190 dst
= (uint16_t*)surf
->GetBaseAddressOfPlane(1);
191 stride
= surf
->GetBytesPerRow(1) / 2;
192 for (size_t i
= 0; i
< (size_t)cbcrSize
.height
; i
++) {
194 (uint16_t*)(aData
.mCbChannel
+ aData
.mCbCrStride
* i
);
196 (uint16_t*)(aData
.mCrChannel
+ aData
.mCbCrStride
* i
);
197 uint16_t* rowDst
= dst
+ stride
* i
;
199 for (const auto j
: IntegerRange(cbcrSize
.width
)) {
202 *rowDst
= safeShift10BitBy6(*rowCbSrc
);
206 *rowDst
= safeShift10BitBy6(*rowCrSrc
);
211 } else if (surf
->GetFormat() == SurfaceFormat::NV16
) {
212 MOZ_ASSERT(aData
.mColorDepth
== ColorDepth::COLOR_10
,
213 "Currently NV16 only supports 10-bit color.");
214 MOZ_ASSERT(ySize
.height
> 0);
215 auto dst
= reinterpret_cast<uint16_t*>(surf
->GetBaseAddressOfPlane(0));
216 size_t stride
= surf
->GetBytesPerRow(0) / 2;
217 for (size_t i
= 0; i
< (size_t)ySize
.height
; i
++) {
218 auto rowSrc
= reinterpret_cast<const uint16_t*>(aData
.mYChannel
+
220 auto rowDst
= dst
+ stride
* i
;
222 for (const auto j
: IntegerRange(ySize
.width
)) {
225 *rowDst
= safeShift10BitBy6(*rowSrc
);
231 // Copy and interleave the Cb and Cr channels.
232 MOZ_ASSERT(cbcrSize
.height
> 0);
233 MOZ_ASSERT(cbcrSize
.height
== ySize
.height
,
234 "4:2:2 CbCr should have same height as Y.");
235 dst
= (uint16_t*)surf
->GetBaseAddressOfPlane(1);
236 stride
= surf
->GetBytesPerRow(1) / 2;
237 for (size_t i
= 0; i
< (size_t)cbcrSize
.height
; i
++) {
239 (uint16_t*)(aData
.mCbChannel
+ aData
.mCbCrStride
* i
);
241 (uint16_t*)(aData
.mCrChannel
+ aData
.mCbCrStride
* i
);
242 uint16_t* rowDst
= dst
+ stride
* i
;
244 for (const auto j
: IntegerRange(cbcrSize
.width
)) {
247 *rowDst
= safeShift10BitBy6(*rowCbSrc
);
251 *rowDst
= safeShift10BitBy6(*rowCrSrc
);
260 mPictureRect
= aData
.mPictureRect
;
264 already_AddRefed
<MacIOSurface
> MacIOSurfaceRecycleAllocator::Allocate(
265 const gfx::IntSize aYSize
, const gfx::IntSize
& aCbCrSize
,
266 gfx::ChromaSubsampling aChromaSubsampling
,
267 gfx::YUVColorSpace aYUVColorSpace
, gfx::TransferFunction aTransferFunction
,
268 gfx::ColorRange aColorRange
, gfx::ColorDepth aColorDepth
) {
269 // To avoid checking every property of every surface, we just cache the
270 // parameters used during the last allocation. If any of these have changed,
271 // dump the cached surfaces and update our cached parameters.
272 if (mYSize
!= aYSize
|| mCbCrSize
!= aCbCrSize
||
273 mChromaSubsampling
!= aChromaSubsampling
||
274 mYUVColorSpace
!= aYUVColorSpace
||
275 mTransferFunction
!= aTransferFunction
|| mColorRange
!= aColorRange
||
276 mColorDepth
!= aColorDepth
) {
279 mCbCrSize
= aCbCrSize
;
280 mChromaSubsampling
= aChromaSubsampling
;
281 mYUVColorSpace
= aYUVColorSpace
;
282 mTransferFunction
= aTransferFunction
;
283 mColorRange
= aColorRange
;
284 mColorDepth
= aColorDepth
;
287 // Scan for an unused surface, and reuse that if one is available.
288 for (auto& surf
: mSurfaces
) {
289 if (::IOSurfaceIsInUse(surf
.get())) {
293 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
294 Maybe
<OSType
> pixelFormat
= MacIOSurface::ChoosePixelFormat(
295 aChromaSubsampling
, aColorRange
, aColorDepth
);
296 MOZ_DIAGNOSTIC_ASSERT(pixelFormat
.isSome());
297 MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetPixelFormat(surf
.get()) ==
299 MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetWidthOfPlane(surf
.get(), 0) ==
300 (size_t)aYSize
.width
);
301 MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetHeightOfPlane(surf
.get(), 0) ==
302 (size_t)aYSize
.height
);
303 if (*pixelFormat
!= kCVPixelFormatType_422YpCbCr8_yuvs
&&
304 *pixelFormat
!= kCVPixelFormatType_422YpCbCr8FullRange
) {
305 MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetWidthOfPlane(surf
.get(), 1) ==
306 (size_t)aCbCrSize
.width
);
307 MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetHeightOfPlane(surf
.get(), 1) ==
308 (size_t)aCbCrSize
.height
);
312 return MakeAndAddRef
<MacIOSurface
>(surf
, false, aYUVColorSpace
);
315 // Time to decide if we are creating a single planar or bi-planar surface.
316 // We limit ourselves to macOS's single planar and bi-planar formats for
317 // simplicity reasons, possibly gaining some small memory or performance
318 // benefit relative to the tri-planar formats. We try and use as few
319 // planes as possible.
320 // 4:2:0 formats are always bi-planar, because there is no 4:2:0 single
322 // 4:2:2 formats with 8 bit color are single planar, otherwise bi-planar.
324 RefPtr
<MacIOSurface
> result
;
325 if (aChromaSubsampling
== gfx::ChromaSubsampling::HALF_WIDTH
&&
326 aColorDepth
== gfx::ColorDepth::COLOR_8
) {
327 result
= MacIOSurface::CreateSinglePlanarSurface(
328 aYSize
, aYUVColorSpace
, aTransferFunction
, aColorRange
);
330 result
= MacIOSurface::CreateBiPlanarSurface(
331 aYSize
, aCbCrSize
, aChromaSubsampling
, aYUVColorSpace
,
332 aTransferFunction
, aColorRange
, aColorDepth
);
336 mSurfaces
.Length() < StaticPrefs::layers_iosurfaceimage_recycle_limit()) {
337 mSurfaces
.AppendElement(result
->GetIOSurfaceRef());
340 return result
.forget();