1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "RemoteBackbuffer.h"
7 #include "GeckoProfiler.h"
8 #include "nsThreadUtils.h"
9 #include "mozilla/Span.h"
11 #include <type_traits>
15 namespace remote_backbuffer
{
17 // This number can be adjusted as a time-memory tradeoff
18 constexpr uint8_t kMaxDirtyRects
= 8;
21 explicit IpcSafeRect(const gfx::IntRect
& aRect
)
22 : x(aRect
.x
), y(aRect
.y
), width(aRect
.width
), height(aRect
.height
) {}
29 enum class ResponseResult
{
37 enum class SharedDataType
{
39 BorrowRequestAllowSameBuffer
,
45 struct BorrowResponseData
{
46 ResponseResult result
;
52 struct PresentRequestData
{
53 uint8_t lenDirtyRects
;
54 IpcSafeRect dirtyRects
[kMaxDirtyRects
];
57 struct PresentResponseData
{
58 ResponseResult result
;
62 SharedDataType dataType
;
64 BorrowResponseData borrowResponse
;
65 PresentRequestData presentRequest
;
66 PresentResponseData presentResponse
;
70 static_assert(std::is_trivially_copyable
<SharedData
>::value
&&
71 std::is_standard_layout
<SharedData
>::value
,
72 "SharedData must be safe to pass over IPC boundaries");
77 : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {}
81 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData
));
85 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
89 bool Initialize(int32_t aWidth
, int32_t aHeight
) {
90 MOZ_ASSERT(aWidth
> 0);
91 MOZ_ASSERT(aHeight
> 0);
96 DWORD bufferSize
= static_cast<DWORD
>(mHeight
* GetStride());
98 mFileMapping
= ::CreateFileMappingW(
99 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
,
100 0 /*sizeHigh*/, bufferSize
, nullptr /*name*/);
105 void* mappedFilePtr
=
106 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
107 0 /*offsetLow*/, 0 /*bytesToMap*/);
108 if (!mappedFilePtr
) {
112 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
117 bool InitializeRemote(int32_t aWidth
, int32_t aHeight
, HANDLE aFileMapping
) {
118 MOZ_ASSERT(aWidth
> 0);
119 MOZ_ASSERT(aHeight
> 0);
120 MOZ_ASSERT(aFileMapping
);
124 mFileMapping
= aFileMapping
;
126 void* mappedFilePtr
=
127 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
128 0 /*offsetLow*/, 0 /*bytesToMap*/);
129 if (!mappedFilePtr
) {
133 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
138 HBITMAP
CreateDIBSection() {
139 BITMAPINFO bitmapInfo
= {};
140 bitmapInfo
.bmiHeader
.biSize
= sizeof(bitmapInfo
.bmiHeader
);
141 bitmapInfo
.bmiHeader
.biWidth
= mWidth
;
142 bitmapInfo
.bmiHeader
.biHeight
= -mHeight
;
143 bitmapInfo
.bmiHeader
.biPlanes
= 1;
144 bitmapInfo
.bmiHeader
.biBitCount
= 32;
145 bitmapInfo
.bmiHeader
.biCompression
= BI_RGB
;
146 void* dummy
= nullptr;
147 return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo
,
148 DIB_RGB_COLORS
, &dummy
, mFileMapping
,
152 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
153 MOZ_ASSERT(aTargetProcess
);
155 HANDLE fileMapping
= nullptr;
156 if (!::DuplicateHandle(GetCurrentProcess(), mFileMapping
, aTargetProcess
,
157 &fileMapping
, 0 /*desiredAccess*/,
158 FALSE
/*inheritHandle*/, DUPLICATE_SAME_ACCESS
)) {
164 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
165 return gfx::Factory::CreateDrawTargetForData(
166 gfx::BackendType::CAIRO
, mPixelData
, IntSize(mWidth
, mHeight
),
167 GetStride(), gfx::SurfaceFormat::B8G8R8A8
);
170 void CopyPixelsFrom(const SharedImage
& other
) {
171 const unsigned char* src
= other
.mPixelData
;
172 unsigned char* dst
= mPixelData
;
174 int32_t width
= std::min(mWidth
, other
.mWidth
);
175 int32_t height
= std::min(mHeight
, other
.mHeight
);
177 for (int32_t row
= 0; row
< height
; ++row
) {
178 memcpy(dst
, src
, static_cast<uint32_t>(width
* kBytesPerPixel
));
179 src
+= other
.GetStride();
184 int32_t GetWidth() { return mWidth
; }
186 int32_t GetHeight() { return mHeight
; }
188 SharedImage(const SharedImage
&) = delete;
189 SharedImage(SharedImage
&&) = delete;
190 SharedImage
& operator=(const SharedImage
&) = delete;
191 SharedImage
& operator=(SharedImage
&&) = delete;
194 static constexpr int32_t kBytesPerPixel
= 4;
196 int32_t GetStride() const {
197 // DIB requires 32-bit row alignment
198 return (((mWidth
* kBytesPerPixel
) + 3) / 4) * 4;
204 unsigned char* mPixelData
;
207 class PresentableSharedImage
{
209 PresentableSharedImage()
211 mDeviceContext(nullptr),
212 mDIBSection(nullptr),
213 mSavedObject(nullptr) {}
215 ~PresentableSharedImage() {
217 MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext
, mSavedObject
));
221 MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection
));
224 if (mDeviceContext
) {
225 MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext
));
229 bool Initialize(int32_t aWidth
, int32_t aHeight
) {
230 if (!mSharedImage
.Initialize(aWidth
, aHeight
)) {
234 mDeviceContext
= ::CreateCompatibleDC(nullptr);
235 if (!mDeviceContext
) {
239 mDIBSection
= mSharedImage
.CreateDIBSection();
244 mSavedObject
= ::SelectObject(mDeviceContext
, mDIBSection
);
252 bool PresentToWindow(HWND aWindowHandle
, TransparencyMode aTransparencyMode
,
253 Span
<const IpcSafeRect
> aDirtyRects
) {
254 if (aTransparencyMode
== TransparencyMode::Transparent
) {
255 // If our window is a child window or a child-of-a-child, the window
256 // that needs to be updated is the top level ancestor of the tree
257 HWND topLevelWindow
= WinUtils::GetTopLevelHWND(aWindowHandle
, true);
258 MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow
, GWL_EXSTYLE
) &
261 BLENDFUNCTION bf
= {AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
262 POINT srcPos
= {0, 0};
263 RECT clientRect
= {};
264 if (!::GetClientRect(aWindowHandle
, &clientRect
)) {
267 MOZ_ASSERT(clientRect
.left
== 0);
268 MOZ_ASSERT(clientRect
.top
== 0);
269 int32_t width
= clientRect
.right
;
270 int32_t height
= clientRect
.bottom
;
271 SIZE winSize
= {width
, height
};
272 // Window resize could cause the client area to be different than
273 // mSharedImage's size. If the client area doesn't match,
274 // PresentToWindow() returns false without calling UpdateLayeredWindow().
275 // Another call to UpdateLayeredWindow() will follow shortly, since the
276 // resize will eventually force the backbuffer to repaint itself again.
277 // When client area is larger than mSharedImage's size,
278 // UpdateLayeredWindow() draws the window completely invisible. But it
279 // does not return false.
280 if (width
!= mSharedImage
.GetWidth() ||
281 height
!= mSharedImage
.GetHeight()) {
285 return !!::UpdateLayeredWindow(
286 topLevelWindow
, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize
,
287 mDeviceContext
, &srcPos
, 0 /*colorKey*/, &bf
, ULW_ALPHA
);
290 IntRect sharedImageRect
{0, 0, mSharedImage
.GetWidth(),
291 mSharedImage
.GetHeight()};
295 HDC windowDC
= ::GetDC(aWindowHandle
);
300 for (auto& ipcDirtyRect
: aDirtyRects
) {
301 IntRect dirtyRect
{ipcDirtyRect
.x
, ipcDirtyRect
.y
, ipcDirtyRect
.width
,
302 ipcDirtyRect
.height
};
303 IntRect bltRect
= dirtyRect
.Intersect(sharedImageRect
);
305 if (!::BitBlt(windowDC
, bltRect
.x
/*dstX*/, bltRect
.y
/*dstY*/,
306 bltRect
.width
, bltRect
.height
, mDeviceContext
,
307 bltRect
.x
/*srcX*/, bltRect
.y
/*srcY*/, SRCCOPY
)) {
313 MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle
, windowDC
));
318 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
319 return mSharedImage
.CreateRemoteFileMapping(aTargetProcess
);
322 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
323 return mSharedImage
.CreateDrawTarget();
326 void CopyPixelsFrom(const PresentableSharedImage
& other
) {
327 mSharedImage
.CopyPixelsFrom(other
.mSharedImage
);
330 int32_t GetWidth() { return mSharedImage
.GetWidth(); }
332 int32_t GetHeight() { return mSharedImage
.GetHeight(); }
334 PresentableSharedImage(const PresentableSharedImage
&) = delete;
335 PresentableSharedImage(PresentableSharedImage
&&) = delete;
336 PresentableSharedImage
& operator=(const PresentableSharedImage
&) = delete;
337 PresentableSharedImage
& operator=(PresentableSharedImage
&&) = delete;
340 SharedImage mSharedImage
;
343 HGDIOBJ mSavedObject
;
347 : mWindowHandle(nullptr),
348 mTargetProcess(nullptr),
349 mFileMapping(nullptr),
350 mRequestReadyEvent(nullptr),
351 mResponseReadyEvent(nullptr),
352 mSharedDataPtr(nullptr),
353 mStopServiceThread(false),
354 mServiceThread(nullptr),
357 Provider::~Provider() {
360 if (mServiceThread
) {
361 mStopServiceThread
= true;
362 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
363 MOZ_ALWAYS_TRUE(PR_JoinThread(mServiceThread
) == PR_SUCCESS
);
366 if (mSharedDataPtr
) {
367 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
370 if (mResponseReadyEvent
) {
371 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
374 if (mRequestReadyEvent
) {
375 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
379 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
382 if (mTargetProcess
) {
383 MOZ_ALWAYS_TRUE(::CloseHandle(mTargetProcess
));
387 bool Provider::Initialize(HWND aWindowHandle
, DWORD aTargetProcessId
,
388 TransparencyMode aTransparencyMode
) {
389 MOZ_ASSERT(aWindowHandle
);
390 MOZ_ASSERT(aTargetProcessId
);
392 mWindowHandle
= aWindowHandle
;
394 mTargetProcess
= ::OpenProcess(PROCESS_DUP_HANDLE
, FALSE
/*inheritHandle*/,
396 if (!mTargetProcess
) {
400 mFileMapping
= ::CreateFileMappingW(
401 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
, 0 /*sizeHigh*/,
402 static_cast<DWORD
>(sizeof(SharedData
)), nullptr /*name*/);
408 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
409 FALSE
/*initialState*/, nullptr /*name*/);
410 if (!mRequestReadyEvent
) {
414 mResponseReadyEvent
=
415 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
416 FALSE
/*initialState*/, nullptr /*name*/);
417 if (!mResponseReadyEvent
) {
421 void* mappedFilePtr
=
422 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
423 0 /*offsetLow*/, 0 /*bytesToMap*/);
424 if (!mappedFilePtr
) {
428 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
430 mStopServiceThread
= false;
432 // Use a raw NSPR OS-level thread here instead of nsThread because we are
433 // performing low-level synchronization across processes using Win32 Events,
434 // and nsThread is designed around an incompatible "in-process task queue"
436 mServiceThread
= PR_CreateThread(
437 PR_USER_THREAD
, [](void* p
) { static_cast<Provider
*>(p
)->ThreadMain(); },
438 this, PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
, PR_JOINABLE_THREAD
,
439 0 /*default stack size*/);
440 if (!mServiceThread
) {
444 mTransparencyMode
= uint32_t(aTransparencyMode
);
449 Maybe
<RemoteBackbufferHandles
> Provider::CreateRemoteHandles() {
451 RemoteBackbufferHandles(ipc::FileDescriptor(mFileMapping
),
452 ipc::FileDescriptor(mRequestReadyEvent
),
453 ipc::FileDescriptor(mResponseReadyEvent
)));
456 void Provider::UpdateTransparencyMode(TransparencyMode aTransparencyMode
) {
457 mTransparencyMode
= uint32_t(aTransparencyMode
);
460 void Provider::ThreadMain() {
461 AUTO_PROFILER_REGISTER_THREAD("RemoteBackbuffer");
462 NS_SetCurrentThreadName("RemoteBackbuffer");
466 AUTO_PROFILER_THREAD_SLEEP
;
467 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent
, INFINITE
) ==
471 if (mStopServiceThread
) {
475 switch (mSharedDataPtr
->dataType
) {
476 case SharedDataType::BorrowRequest
:
477 case SharedDataType::BorrowRequestAllowSameBuffer
: {
478 BorrowResponseData responseData
= {};
480 HandleBorrowRequest(&responseData
,
481 mSharedDataPtr
->dataType
==
482 SharedDataType::BorrowRequestAllowSameBuffer
);
484 mSharedDataPtr
->dataType
= SharedDataType::BorrowResponse
;
485 mSharedDataPtr
->data
.borrowResponse
= responseData
;
487 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
491 case SharedDataType::PresentRequest
: {
492 PresentRequestData requestData
= mSharedDataPtr
->data
.presentRequest
;
493 PresentResponseData responseData
= {};
495 HandlePresentRequest(requestData
, &responseData
);
497 mSharedDataPtr
->dataType
= SharedDataType::PresentResponse
;
498 mSharedDataPtr
->data
.presentResponse
= responseData
;
500 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
510 void Provider::HandleBorrowRequest(BorrowResponseData
* aResponseData
,
511 bool aAllowSameBuffer
) {
512 MOZ_ASSERT(aResponseData
);
514 aResponseData
->result
= ResponseResult::Error
;
516 RECT clientRect
= {};
517 if (!::GetClientRect(mWindowHandle
, &clientRect
)) {
521 MOZ_ASSERT(clientRect
.left
== 0);
522 MOZ_ASSERT(clientRect
.top
== 0);
524 int32_t width
= clientRect
.right
? clientRect
.right
: 1;
525 int32_t height
= clientRect
.bottom
? clientRect
.bottom
: 1;
527 bool needNewBackbuffer
= !aAllowSameBuffer
|| !mBackbuffer
||
528 (mBackbuffer
->GetWidth() != width
) ||
529 (mBackbuffer
->GetHeight() != height
);
531 if (!needNewBackbuffer
) {
532 aResponseData
->result
= ResponseResult::BorrowSameBuffer
;
536 auto newBackbuffer
= std::make_unique
<PresentableSharedImage
>();
537 if (!newBackbuffer
->Initialize(width
, height
)) {
541 // Preserve the contents of the old backbuffer (if it exists)
543 newBackbuffer
->CopyPixelsFrom(*mBackbuffer
);
547 HANDLE remoteFileMapping
=
548 newBackbuffer
->CreateRemoteFileMapping(mTargetProcess
);
549 if (!remoteFileMapping
) {
553 aResponseData
->result
= ResponseResult::BorrowSuccess
;
554 aResponseData
->width
= width
;
555 aResponseData
->height
= height
;
556 aResponseData
->fileMapping
= remoteFileMapping
;
558 mBackbuffer
= std::move(newBackbuffer
);
561 void Provider::HandlePresentRequest(const PresentRequestData
& aRequestData
,
562 PresentResponseData
* aResponseData
) {
563 MOZ_ASSERT(aResponseData
);
565 Span
rectSpan(aRequestData
.dirtyRects
, kMaxDirtyRects
);
567 aResponseData
->result
= ResponseResult::Error
;
573 if (!mBackbuffer
->PresentToWindow(
574 mWindowHandle
, GetTransparencyMode(),
575 rectSpan
.First(aRequestData
.lenDirtyRects
))) {
579 aResponseData
->result
= ResponseResult::PresentSuccess
;
583 : mFileMapping(nullptr),
584 mRequestReadyEvent(nullptr),
585 mResponseReadyEvent(nullptr),
586 mSharedDataPtr(nullptr),
592 if (mSharedDataPtr
) {
593 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
596 if (mResponseReadyEvent
) {
597 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
600 if (mRequestReadyEvent
) {
601 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
605 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
609 bool Client::Initialize(const RemoteBackbufferHandles
& aRemoteHandles
) {
610 MOZ_ASSERT(aRemoteHandles
.fileMapping().IsValid());
611 MOZ_ASSERT(aRemoteHandles
.requestReadyEvent().IsValid());
612 MOZ_ASSERT(aRemoteHandles
.responseReadyEvent().IsValid());
614 // FIXME: Due to PCompositorWidget using virtual Recv methods,
615 // RemoteBackbufferHandles is passed by const reference, and cannot have its
616 // signature customized, meaning that we need to clone the handles here.
618 // Once PCompositorWidget is migrated to use direct call semantics, the
619 // signature can be changed to accept RemoteBackbufferHandles by rvalue
620 // reference or value, and the DuplicateHandle calls here can be avoided.
621 mFileMapping
= aRemoteHandles
.fileMapping().ClonePlatformHandle().release();
623 aRemoteHandles
.requestReadyEvent().ClonePlatformHandle().release();
624 mResponseReadyEvent
=
625 aRemoteHandles
.responseReadyEvent().ClonePlatformHandle().release();
627 void* mappedFilePtr
=
628 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
629 0 /*offsetLow*/, 0 /*bytesToMap*/);
630 if (!mappedFilePtr
) {
634 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
639 already_AddRefed
<gfx::DrawTarget
> Client::BorrowDrawTarget() {
640 mSharedDataPtr
->dataType
= mBackbuffer
641 ? SharedDataType::BorrowRequestAllowSameBuffer
642 : SharedDataType::BorrowRequest
;
644 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
645 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
648 if (mSharedDataPtr
->dataType
!= SharedDataType::BorrowResponse
) {
652 BorrowResponseData responseData
= mSharedDataPtr
->data
.borrowResponse
;
654 if ((responseData
.result
!= ResponseResult::BorrowSameBuffer
) &&
655 (responseData
.result
!= ResponseResult::BorrowSuccess
)) {
659 if (responseData
.result
== ResponseResult::BorrowSuccess
) {
662 auto newBackbuffer
= std::make_unique
<SharedImage
>();
663 if (!newBackbuffer
->InitializeRemote(responseData
.width
,
665 responseData
.fileMapping
)) {
669 mBackbuffer
= std::move(newBackbuffer
);
672 MOZ_ASSERT(mBackbuffer
);
674 return mBackbuffer
->CreateDrawTarget();
677 bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion
) {
678 mSharedDataPtr
->dataType
= SharedDataType::PresentRequest
;
680 // Simplify the region until it has <= kMaxDirtyRects
681 aDirtyRegion
.SimplifyOutward(kMaxDirtyRects
);
683 Span
rectSpan(mSharedDataPtr
->data
.presentRequest
.dirtyRects
, kMaxDirtyRects
);
685 uint8_t rectIndex
= 0;
686 for (auto iter
= aDirtyRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
687 rectSpan
[rectIndex
] = IpcSafeRect(iter
.Get());
691 mSharedDataPtr
->data
.presentRequest
.lenDirtyRects
= rectIndex
;
693 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
694 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
697 if (mSharedDataPtr
->dataType
!= SharedDataType::PresentResponse
) {
701 if (mSharedDataPtr
->data
.presentResponse
.result
!=
702 ResponseResult::PresentSuccess
) {
709 } // namespace remote_backbuffer
710 } // namespace widget
711 } // namespace mozilla