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"
10 #include "mozilla/gfx/Point.h"
13 #include <type_traits>
17 namespace remote_backbuffer
{
19 // This number can be adjusted as a time-memory tradeoff
20 constexpr uint8_t kMaxDirtyRects
= 8;
23 explicit IpcSafeRect(const gfx::IntRect
& aRect
)
24 : x(aRect
.x
), y(aRect
.y
), width(aRect
.width
), height(aRect
.height
) {}
31 enum class ResponseResult
{
39 enum class SharedDataType
{
41 BorrowRequestAllowSameBuffer
,
47 struct BorrowResponseData
{
48 ResponseResult result
;
54 struct PresentRequestData
{
55 uint8_t lenDirtyRects
;
56 IpcSafeRect dirtyRects
[kMaxDirtyRects
];
59 struct PresentResponseData
{
60 ResponseResult result
;
64 SharedDataType dataType
;
66 BorrowResponseData borrowResponse
;
67 PresentRequestData presentRequest
;
68 PresentResponseData presentResponse
;
72 static_assert(std::is_trivially_copyable
<SharedData
>::value
&&
73 std::is_standard_layout
<SharedData
>::value
,
74 "SharedData must be safe to pass over IPC boundaries");
79 : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {}
83 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData
));
87 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
91 bool Initialize(int32_t aWidth
, int32_t aHeight
) {
92 MOZ_ASSERT(aWidth
> 0);
93 MOZ_ASSERT(aHeight
> 0);
98 DWORD bufferSize
= static_cast<DWORD
>(mHeight
* GetStride());
100 mFileMapping
= ::CreateFileMappingW(
101 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
,
102 0 /*sizeHigh*/, bufferSize
, nullptr /*name*/);
107 void* mappedFilePtr
=
108 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
109 0 /*offsetLow*/, 0 /*bytesToMap*/);
110 if (!mappedFilePtr
) {
114 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
119 bool InitializeRemote(int32_t aWidth
, int32_t aHeight
, HANDLE aFileMapping
) {
120 MOZ_ASSERT(aWidth
> 0);
121 MOZ_ASSERT(aHeight
> 0);
122 MOZ_ASSERT(aFileMapping
);
126 mFileMapping
= aFileMapping
;
128 void* mappedFilePtr
=
129 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
130 0 /*offsetLow*/, 0 /*bytesToMap*/);
131 if (!mappedFilePtr
) {
135 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
140 HBITMAP
CreateDIBSection() {
141 BITMAPINFO bitmapInfo
= {};
142 bitmapInfo
.bmiHeader
.biSize
= sizeof(bitmapInfo
.bmiHeader
);
143 bitmapInfo
.bmiHeader
.biWidth
= mWidth
;
144 bitmapInfo
.bmiHeader
.biHeight
= -mHeight
;
145 bitmapInfo
.bmiHeader
.biPlanes
= 1;
146 bitmapInfo
.bmiHeader
.biBitCount
= 32;
147 bitmapInfo
.bmiHeader
.biCompression
= BI_RGB
;
148 void* dummy
= nullptr;
149 return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo
,
150 DIB_RGB_COLORS
, &dummy
, mFileMapping
,
154 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
155 MOZ_ASSERT(aTargetProcess
);
157 HANDLE fileMapping
= nullptr;
158 if (!::DuplicateHandle(GetCurrentProcess(), mFileMapping
, aTargetProcess
,
159 &fileMapping
, 0 /*desiredAccess*/,
160 FALSE
/*inheritHandle*/, DUPLICATE_SAME_ACCESS
)) {
166 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
167 return gfx::Factory::CreateDrawTargetForData(
168 gfx::BackendType::CAIRO
, mPixelData
, gfx::IntSize(mWidth
, mHeight
),
169 GetStride(), gfx::SurfaceFormat::B8G8R8A8
);
172 void CopyPixelsFrom(const SharedImage
& other
) {
173 const unsigned char* src
= other
.mPixelData
;
174 unsigned char* dst
= mPixelData
;
176 int32_t width
= std::min(mWidth
, other
.mWidth
);
177 int32_t height
= std::min(mHeight
, other
.mHeight
);
179 for (int32_t row
= 0; row
< height
; ++row
) {
180 memcpy(dst
, src
, static_cast<uint32_t>(width
* kBytesPerPixel
));
181 src
+= other
.GetStride();
186 int32_t GetWidth() { return mWidth
; }
188 int32_t GetHeight() { return mHeight
; }
190 SharedImage(const SharedImage
&) = delete;
191 SharedImage(SharedImage
&&) = delete;
192 SharedImage
& operator=(const SharedImage
&) = delete;
193 SharedImage
& operator=(SharedImage
&&) = delete;
196 static constexpr int32_t kBytesPerPixel
= 4;
198 int32_t GetStride() const {
199 // DIB requires 32-bit row alignment
200 return (((mWidth
* kBytesPerPixel
) + 3) / 4) * 4;
206 unsigned char* mPixelData
;
209 class PresentableSharedImage
{
211 PresentableSharedImage()
213 mDeviceContext(nullptr),
214 mDIBSection(nullptr),
215 mSavedObject(nullptr) {}
217 ~PresentableSharedImage() {
219 MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext
, mSavedObject
));
223 MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection
));
226 if (mDeviceContext
) {
227 MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext
));
231 bool Initialize(int32_t aWidth
, int32_t aHeight
) {
232 if (!mSharedImage
.Initialize(aWidth
, aHeight
)) {
236 mDeviceContext
= ::CreateCompatibleDC(nullptr);
237 if (!mDeviceContext
) {
241 mDIBSection
= mSharedImage
.CreateDIBSection();
246 mSavedObject
= ::SelectObject(mDeviceContext
, mDIBSection
);
254 bool PresentToWindow(HWND aWindowHandle
, TransparencyMode aTransparencyMode
,
255 Span
<const IpcSafeRect
> aDirtyRects
) {
256 if (aTransparencyMode
== TransparencyMode::Transparent
) {
257 // If our window is a child window or a child-of-a-child, the window
258 // that needs to be updated is the top level ancestor of the tree
259 HWND topLevelWindow
= WinUtils::GetTopLevelHWND(aWindowHandle
, true);
260 MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow
, GWL_EXSTYLE
) &
263 BLENDFUNCTION bf
= {AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
264 POINT srcPos
= {0, 0};
265 RECT clientRect
= {};
266 if (!::GetClientRect(aWindowHandle
, &clientRect
)) {
269 MOZ_ASSERT(clientRect
.left
== 0);
270 MOZ_ASSERT(clientRect
.top
== 0);
271 int32_t width
= clientRect
.right
;
272 int32_t height
= clientRect
.bottom
;
273 SIZE winSize
= {width
, height
};
274 // Window resize could cause the client area to be different than
275 // mSharedImage's size. If the client area doesn't match,
276 // PresentToWindow() returns false without calling UpdateLayeredWindow().
277 // Another call to UpdateLayeredWindow() will follow shortly, since the
278 // resize will eventually force the backbuffer to repaint itself again.
279 // When client area is larger than mSharedImage's size,
280 // UpdateLayeredWindow() draws the window completely invisible. But it
281 // does not return false.
282 if (width
!= mSharedImage
.GetWidth() ||
283 height
!= mSharedImage
.GetHeight()) {
287 return !!::UpdateLayeredWindow(
288 topLevelWindow
, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize
,
289 mDeviceContext
, &srcPos
, 0 /*colorKey*/, &bf
, ULW_ALPHA
);
292 gfx::IntRect sharedImageRect
{0, 0, mSharedImage
.GetWidth(),
293 mSharedImage
.GetHeight()};
297 HDC windowDC
= ::GetDC(aWindowHandle
);
302 for (auto& ipcDirtyRect
: aDirtyRects
) {
303 gfx::IntRect dirtyRect
{ipcDirtyRect
.x
, ipcDirtyRect
.y
, ipcDirtyRect
.width
,
304 ipcDirtyRect
.height
};
305 gfx::IntRect bltRect
= dirtyRect
.Intersect(sharedImageRect
);
307 if (!::BitBlt(windowDC
, bltRect
.x
/*dstX*/, bltRect
.y
/*dstY*/,
308 bltRect
.width
, bltRect
.height
, mDeviceContext
,
309 bltRect
.x
/*srcX*/, bltRect
.y
/*srcY*/, SRCCOPY
)) {
315 MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle
, windowDC
));
320 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
321 return mSharedImage
.CreateRemoteFileMapping(aTargetProcess
);
324 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
325 return mSharedImage
.CreateDrawTarget();
328 void CopyPixelsFrom(const PresentableSharedImage
& other
) {
329 mSharedImage
.CopyPixelsFrom(other
.mSharedImage
);
332 int32_t GetWidth() { return mSharedImage
.GetWidth(); }
334 int32_t GetHeight() { return mSharedImage
.GetHeight(); }
336 PresentableSharedImage(const PresentableSharedImage
&) = delete;
337 PresentableSharedImage(PresentableSharedImage
&&) = delete;
338 PresentableSharedImage
& operator=(const PresentableSharedImage
&) = delete;
339 PresentableSharedImage
& operator=(PresentableSharedImage
&&) = delete;
342 SharedImage mSharedImage
;
345 HGDIOBJ mSavedObject
;
349 : mWindowHandle(nullptr),
350 mTargetProcess(nullptr),
351 mFileMapping(nullptr),
352 mRequestReadyEvent(nullptr),
353 mResponseReadyEvent(nullptr),
354 mSharedDataPtr(nullptr),
355 mStopServiceThread(false),
356 mServiceThread(nullptr),
359 Provider::~Provider() {
362 if (mServiceThread
) {
363 mStopServiceThread
= true;
364 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
365 MOZ_ALWAYS_TRUE(PR_JoinThread(mServiceThread
) == PR_SUCCESS
);
368 if (mSharedDataPtr
) {
369 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
372 if (mResponseReadyEvent
) {
373 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
376 if (mRequestReadyEvent
) {
377 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
381 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
384 if (mTargetProcess
) {
385 MOZ_ALWAYS_TRUE(::CloseHandle(mTargetProcess
));
389 bool Provider::Initialize(HWND aWindowHandle
, DWORD aTargetProcessId
,
390 TransparencyMode aTransparencyMode
) {
391 MOZ_ASSERT(aWindowHandle
);
392 MOZ_ASSERT(aTargetProcessId
);
394 mWindowHandle
= aWindowHandle
;
396 mTargetProcess
= ::OpenProcess(PROCESS_DUP_HANDLE
, FALSE
/*inheritHandle*/,
398 if (!mTargetProcess
) {
402 mFileMapping
= ::CreateFileMappingW(
403 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
, 0 /*sizeHigh*/,
404 static_cast<DWORD
>(sizeof(SharedData
)), nullptr /*name*/);
410 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
411 FALSE
/*initialState*/, nullptr /*name*/);
412 if (!mRequestReadyEvent
) {
416 mResponseReadyEvent
=
417 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
418 FALSE
/*initialState*/, nullptr /*name*/);
419 if (!mResponseReadyEvent
) {
423 void* mappedFilePtr
=
424 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
425 0 /*offsetLow*/, 0 /*bytesToMap*/);
426 if (!mappedFilePtr
) {
430 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
432 mStopServiceThread
= false;
434 // Use a raw NSPR OS-level thread here instead of nsThread because we are
435 // performing low-level synchronization across processes using Win32 Events,
436 // and nsThread is designed around an incompatible "in-process task queue"
438 mServiceThread
= PR_CreateThread(
439 PR_USER_THREAD
, [](void* p
) { static_cast<Provider
*>(p
)->ThreadMain(); },
440 this, PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
, PR_JOINABLE_THREAD
,
441 0 /*default stack size*/);
442 if (!mServiceThread
) {
446 mTransparencyMode
= uint32_t(aTransparencyMode
);
451 Maybe
<RemoteBackbufferHandles
> Provider::CreateRemoteHandles() {
453 RemoteBackbufferHandles(ipc::FileDescriptor(mFileMapping
),
454 ipc::FileDescriptor(mRequestReadyEvent
),
455 ipc::FileDescriptor(mResponseReadyEvent
)));
458 void Provider::UpdateTransparencyMode(TransparencyMode aTransparencyMode
) {
459 mTransparencyMode
= uint32_t(aTransparencyMode
);
462 void Provider::ThreadMain() {
463 AUTO_PROFILER_REGISTER_THREAD("RemoteBackbuffer");
464 NS_SetCurrentThreadName("RemoteBackbuffer");
468 AUTO_PROFILER_THREAD_SLEEP
;
469 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent
, INFINITE
) ==
473 if (mStopServiceThread
) {
477 switch (mSharedDataPtr
->dataType
) {
478 case SharedDataType::BorrowRequest
:
479 case SharedDataType::BorrowRequestAllowSameBuffer
: {
480 BorrowResponseData responseData
= {};
482 HandleBorrowRequest(&responseData
,
483 mSharedDataPtr
->dataType
==
484 SharedDataType::BorrowRequestAllowSameBuffer
);
486 mSharedDataPtr
->dataType
= SharedDataType::BorrowResponse
;
487 mSharedDataPtr
->data
.borrowResponse
= responseData
;
489 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
493 case SharedDataType::PresentRequest
: {
494 PresentRequestData requestData
= mSharedDataPtr
->data
.presentRequest
;
495 PresentResponseData responseData
= {};
497 HandlePresentRequest(requestData
, &responseData
);
499 mSharedDataPtr
->dataType
= SharedDataType::PresentResponse
;
500 mSharedDataPtr
->data
.presentResponse
= responseData
;
502 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
512 void Provider::HandleBorrowRequest(BorrowResponseData
* aResponseData
,
513 bool aAllowSameBuffer
) {
514 MOZ_ASSERT(aResponseData
);
516 aResponseData
->result
= ResponseResult::Error
;
518 RECT clientRect
= {};
519 if (!::GetClientRect(mWindowHandle
, &clientRect
)) {
523 MOZ_ASSERT(clientRect
.left
== 0);
524 MOZ_ASSERT(clientRect
.top
== 0);
526 int32_t width
= clientRect
.right
? clientRect
.right
: 1;
527 int32_t height
= clientRect
.bottom
? clientRect
.bottom
: 1;
529 bool needNewBackbuffer
= !aAllowSameBuffer
|| !mBackbuffer
||
530 (mBackbuffer
->GetWidth() != width
) ||
531 (mBackbuffer
->GetHeight() != height
);
533 if (!needNewBackbuffer
) {
534 aResponseData
->result
= ResponseResult::BorrowSameBuffer
;
538 auto newBackbuffer
= std::make_unique
<PresentableSharedImage
>();
539 if (!newBackbuffer
->Initialize(width
, height
)) {
543 // Preserve the contents of the old backbuffer (if it exists)
545 newBackbuffer
->CopyPixelsFrom(*mBackbuffer
);
549 HANDLE remoteFileMapping
=
550 newBackbuffer
->CreateRemoteFileMapping(mTargetProcess
);
551 if (!remoteFileMapping
) {
555 aResponseData
->result
= ResponseResult::BorrowSuccess
;
556 aResponseData
->width
= width
;
557 aResponseData
->height
= height
;
558 aResponseData
->fileMapping
= remoteFileMapping
;
560 mBackbuffer
= std::move(newBackbuffer
);
563 void Provider::HandlePresentRequest(const PresentRequestData
& aRequestData
,
564 PresentResponseData
* aResponseData
) {
565 MOZ_ASSERT(aResponseData
);
567 Span
rectSpan(aRequestData
.dirtyRects
, kMaxDirtyRects
);
569 aResponseData
->result
= ResponseResult::Error
;
575 if (!mBackbuffer
->PresentToWindow(
576 mWindowHandle
, GetTransparencyMode(),
577 rectSpan
.First(aRequestData
.lenDirtyRects
))) {
581 aResponseData
->result
= ResponseResult::PresentSuccess
;
585 : mFileMapping(nullptr),
586 mRequestReadyEvent(nullptr),
587 mResponseReadyEvent(nullptr),
588 mSharedDataPtr(nullptr),
594 if (mSharedDataPtr
) {
595 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
598 if (mResponseReadyEvent
) {
599 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
602 if (mRequestReadyEvent
) {
603 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
607 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
611 bool Client::Initialize(const RemoteBackbufferHandles
& aRemoteHandles
) {
612 MOZ_ASSERT(aRemoteHandles
.fileMapping().IsValid());
613 MOZ_ASSERT(aRemoteHandles
.requestReadyEvent().IsValid());
614 MOZ_ASSERT(aRemoteHandles
.responseReadyEvent().IsValid());
616 // FIXME: Due to PCompositorWidget using virtual Recv methods,
617 // RemoteBackbufferHandles is passed by const reference, and cannot have its
618 // signature customized, meaning that we need to clone the handles here.
620 // Once PCompositorWidget is migrated to use direct call semantics, the
621 // signature can be changed to accept RemoteBackbufferHandles by rvalue
622 // reference or value, and the DuplicateHandle calls here can be avoided.
623 mFileMapping
= aRemoteHandles
.fileMapping().ClonePlatformHandle().release();
625 aRemoteHandles
.requestReadyEvent().ClonePlatformHandle().release();
626 mResponseReadyEvent
=
627 aRemoteHandles
.responseReadyEvent().ClonePlatformHandle().release();
629 void* mappedFilePtr
=
630 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
631 0 /*offsetLow*/, 0 /*bytesToMap*/);
632 if (!mappedFilePtr
) {
636 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
641 already_AddRefed
<gfx::DrawTarget
> Client::BorrowDrawTarget() {
642 mSharedDataPtr
->dataType
= mBackbuffer
643 ? SharedDataType::BorrowRequestAllowSameBuffer
644 : SharedDataType::BorrowRequest
;
646 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
647 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
650 if (mSharedDataPtr
->dataType
!= SharedDataType::BorrowResponse
) {
654 BorrowResponseData responseData
= mSharedDataPtr
->data
.borrowResponse
;
656 if ((responseData
.result
!= ResponseResult::BorrowSameBuffer
) &&
657 (responseData
.result
!= ResponseResult::BorrowSuccess
)) {
661 if (responseData
.result
== ResponseResult::BorrowSuccess
) {
664 auto newBackbuffer
= std::make_unique
<SharedImage
>();
665 if (!newBackbuffer
->InitializeRemote(responseData
.width
,
667 responseData
.fileMapping
)) {
671 mBackbuffer
= std::move(newBackbuffer
);
674 MOZ_ASSERT(mBackbuffer
);
676 return mBackbuffer
->CreateDrawTarget();
679 bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion
) {
680 mSharedDataPtr
->dataType
= SharedDataType::PresentRequest
;
682 // Simplify the region until it has <= kMaxDirtyRects
683 aDirtyRegion
.SimplifyOutward(kMaxDirtyRects
);
685 Span
rectSpan(mSharedDataPtr
->data
.presentRequest
.dirtyRects
, kMaxDirtyRects
);
687 uint8_t rectIndex
= 0;
688 for (auto iter
= aDirtyRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
689 rectSpan
[rectIndex
] = IpcSafeRect(iter
.Get());
693 mSharedDataPtr
->data
.presentRequest
.lenDirtyRects
= rectIndex
;
695 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
696 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
699 if (mSharedDataPtr
->dataType
!= SharedDataType::PresentResponse
) {
703 if (mSharedDataPtr
->data
.presentResponse
.result
!=
704 ResponseResult::PresentSuccess
) {
711 } // namespace remote_backbuffer
712 } // namespace widget
713 } // namespace mozilla