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
) {
94 MOZ_ASSERT(aWidth
> 0);
95 MOZ_ASSERT(aHeight
> 0);
100 DWORD bufferSize
= static_cast<DWORD
>(mHeight
* GetStride());
102 mFileMapping
= ::CreateFileMappingW(
103 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
,
104 0 /*sizeHigh*/, bufferSize
, nullptr /*name*/);
109 void* mappedFilePtr
=
110 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
111 0 /*offsetLow*/, 0 /*bytesToMap*/);
112 if (!mappedFilePtr
) {
116 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
121 bool InitializeRemote(int32_t aWidth
, int32_t aHeight
, HANDLE aFileMapping
) {
122 MOZ_ASSERT(aWidth
> 0);
123 MOZ_ASSERT(aHeight
> 0);
124 MOZ_ASSERT(aFileMapping
);
128 mFileMapping
= aFileMapping
;
130 void* mappedFilePtr
=
131 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
132 0 /*offsetLow*/, 0 /*bytesToMap*/);
133 if (!mappedFilePtr
) {
137 mPixelData
= reinterpret_cast<unsigned char*>(mappedFilePtr
);
142 HBITMAP
CreateDIBSection() {
143 BITMAPINFO bitmapInfo
= {};
144 bitmapInfo
.bmiHeader
.biSize
= sizeof(bitmapInfo
.bmiHeader
);
145 bitmapInfo
.bmiHeader
.biWidth
= mWidth
;
146 bitmapInfo
.bmiHeader
.biHeight
= -mHeight
;
147 bitmapInfo
.bmiHeader
.biPlanes
= 1;
148 bitmapInfo
.bmiHeader
.biBitCount
= 32;
149 bitmapInfo
.bmiHeader
.biCompression
= BI_RGB
;
150 void* dummy
= nullptr;
151 return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo
,
152 DIB_RGB_COLORS
, &dummy
, mFileMapping
,
156 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
157 MOZ_ASSERT(aTargetProcess
);
159 HANDLE fileMapping
= nullptr;
160 if (!::DuplicateHandle(GetCurrentProcess(), mFileMapping
, aTargetProcess
,
161 &fileMapping
, 0 /*desiredAccess*/,
162 FALSE
/*inheritHandle*/, DUPLICATE_SAME_ACCESS
)) {
168 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
169 return gfx::Factory::CreateDrawTargetForData(
170 gfx::BackendType::CAIRO
, mPixelData
, gfx::IntSize(mWidth
, mHeight
),
171 GetStride(), gfx::SurfaceFormat::B8G8R8A8
);
174 void CopyPixelsFrom(const SharedImage
& other
) {
175 const unsigned char* src
= other
.mPixelData
;
176 unsigned char* dst
= mPixelData
;
178 int32_t width
= std::min(mWidth
, other
.mWidth
);
179 int32_t height
= std::min(mHeight
, other
.mHeight
);
181 for (int32_t row
= 0; row
< height
; ++row
) {
182 memcpy(dst
, src
, static_cast<uint32_t>(width
* kBytesPerPixel
));
183 src
+= other
.GetStride();
188 int32_t GetWidth() const { return mWidth
; }
190 int32_t GetHeight() const { return mHeight
; }
192 SharedImage(const SharedImage
&) = delete;
193 SharedImage(SharedImage
&&) = delete;
194 SharedImage
& operator=(const SharedImage
&) = delete;
195 SharedImage
& operator=(SharedImage
&&) = delete;
198 static constexpr int32_t kBytesPerPixel
= 4;
200 int32_t GetStride() const {
201 // DIB requires 32-bit row alignment
202 return (((mWidth
* kBytesPerPixel
) + 3) / 4) * 4;
208 unsigned char* mPixelData
;
211 class PresentableSharedImage
{
213 PresentableSharedImage()
215 mDeviceContext(nullptr),
216 mDIBSection(nullptr),
217 mSavedObject(nullptr) {}
219 ~PresentableSharedImage() {
221 MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext
, mSavedObject
));
225 MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection
));
228 if (mDeviceContext
) {
229 MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext
));
233 bool Initialize(int32_t aWidth
, int32_t aHeight
) {
234 if (!mSharedImage
.Initialize(aWidth
, aHeight
)) {
238 mDeviceContext
= ::CreateCompatibleDC(nullptr);
239 if (!mDeviceContext
) {
243 mDIBSection
= mSharedImage
.CreateDIBSection();
248 mSavedObject
= ::SelectObject(mDeviceContext
, mDIBSection
);
256 bool PresentToWindow(HWND aWindowHandle
, TransparencyMode aTransparencyMode
,
257 Span
<const IpcSafeRect
> aDirtyRects
) {
258 if (aTransparencyMode
== TransparencyMode::Transparent
) {
259 // If our window is a child window or a child-of-a-child, the window
260 // that needs to be updated is the top level ancestor of the tree
261 HWND topLevelWindow
= WinUtils::GetTopLevelHWND(aWindowHandle
, true);
262 MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow
, GWL_EXSTYLE
) &
265 BLENDFUNCTION bf
= {AC_SRC_OVER
, 0, 255, AC_SRC_ALPHA
};
266 POINT srcPos
= {0, 0};
267 RECT clientRect
= {};
268 if (!::GetClientRect(aWindowHandle
, &clientRect
)) {
271 MOZ_ASSERT(clientRect
.left
== 0);
272 MOZ_ASSERT(clientRect
.top
== 0);
273 int32_t width
= clientRect
.right
;
274 int32_t height
= clientRect
.bottom
;
275 SIZE winSize
= {width
, height
};
276 // Window resize could cause the client area to be different than
277 // mSharedImage's size. If the client area doesn't match,
278 // PresentToWindow() returns false without calling UpdateLayeredWindow().
279 // Another call to UpdateLayeredWindow() will follow shortly, since the
280 // resize will eventually force the backbuffer to repaint itself again.
281 // When client area is larger than mSharedImage's size,
282 // UpdateLayeredWindow() draws the window completely invisible. But it
283 // does not return false.
284 if (width
!= mSharedImage
.GetWidth() ||
285 height
!= mSharedImage
.GetHeight()) {
289 return !!::UpdateLayeredWindow(
290 topLevelWindow
, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize
,
291 mDeviceContext
, &srcPos
, 0 /*colorKey*/, &bf
, ULW_ALPHA
);
294 gfx::IntRect sharedImageRect
{0, 0, mSharedImage
.GetWidth(),
295 mSharedImage
.GetHeight()};
299 HDC windowDC
= ::GetDC(aWindowHandle
);
304 for (auto& ipcDirtyRect
: aDirtyRects
) {
305 gfx::IntRect dirtyRect
{ipcDirtyRect
.x
, ipcDirtyRect
.y
, ipcDirtyRect
.width
,
306 ipcDirtyRect
.height
};
307 gfx::IntRect bltRect
= dirtyRect
.Intersect(sharedImageRect
);
309 if (!::BitBlt(windowDC
, bltRect
.x
/*dstX*/, bltRect
.y
/*dstY*/,
310 bltRect
.width
, bltRect
.height
, mDeviceContext
,
311 bltRect
.x
/*srcX*/, bltRect
.y
/*srcY*/, SRCCOPY
)) {
317 MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle
, windowDC
));
322 HANDLE
CreateRemoteFileMapping(HANDLE aTargetProcess
) {
323 return mSharedImage
.CreateRemoteFileMapping(aTargetProcess
);
326 already_AddRefed
<gfx::DrawTarget
> CreateDrawTarget() {
327 return mSharedImage
.CreateDrawTarget();
330 void CopyPixelsFrom(const PresentableSharedImage
& other
) {
331 mSharedImage
.CopyPixelsFrom(other
.mSharedImage
);
334 int32_t GetWidth() { return mSharedImage
.GetWidth(); }
336 int32_t GetHeight() { return mSharedImage
.GetHeight(); }
338 PresentableSharedImage(const PresentableSharedImage
&) = delete;
339 PresentableSharedImage(PresentableSharedImage
&&) = delete;
340 PresentableSharedImage
& operator=(const PresentableSharedImage
&) = delete;
341 PresentableSharedImage
& operator=(PresentableSharedImage
&&) = delete;
344 SharedImage mSharedImage
;
347 HGDIOBJ mSavedObject
;
351 : mWindowHandle(nullptr),
352 mTargetProcess(nullptr),
353 mFileMapping(nullptr),
354 mRequestReadyEvent(nullptr),
355 mResponseReadyEvent(nullptr),
356 mSharedDataPtr(nullptr),
357 mStopServiceThread(false),
358 mServiceThread(nullptr),
361 Provider::~Provider() {
364 if (mServiceThread
) {
365 mStopServiceThread
= true;
366 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
367 MOZ_ALWAYS_TRUE(PR_JoinThread(mServiceThread
) == PR_SUCCESS
);
370 if (mSharedDataPtr
) {
371 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
374 if (mResponseReadyEvent
) {
375 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
378 if (mRequestReadyEvent
) {
379 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
383 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
386 if (mTargetProcess
) {
387 MOZ_ALWAYS_TRUE(::CloseHandle(mTargetProcess
));
391 bool Provider::Initialize(HWND aWindowHandle
, DWORD aTargetProcessId
,
392 TransparencyMode aTransparencyMode
) {
393 MOZ_ASSERT(aWindowHandle
);
394 MOZ_ASSERT(aTargetProcessId
);
396 mWindowHandle
= aWindowHandle
;
398 mTargetProcess
= ::OpenProcess(PROCESS_DUP_HANDLE
, FALSE
/*inheritHandle*/,
400 if (!mTargetProcess
) {
404 mFileMapping
= ::CreateFileMappingW(
405 INVALID_HANDLE_VALUE
, nullptr /*secattr*/, PAGE_READWRITE
, 0 /*sizeHigh*/,
406 static_cast<DWORD
>(sizeof(SharedData
)), nullptr /*name*/);
412 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
413 FALSE
/*initialState*/, nullptr /*name*/);
414 if (!mRequestReadyEvent
) {
418 mResponseReadyEvent
=
419 ::CreateEventW(nullptr /*secattr*/, FALSE
/*manualReset*/,
420 FALSE
/*initialState*/, nullptr /*name*/);
421 if (!mResponseReadyEvent
) {
425 void* mappedFilePtr
=
426 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
427 0 /*offsetLow*/, 0 /*bytesToMap*/);
428 if (!mappedFilePtr
) {
432 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
434 mStopServiceThread
= false;
436 // Use a raw NSPR OS-level thread here instead of nsThread because we are
437 // performing low-level synchronization across processes using Win32 Events,
438 // and nsThread is designed around an incompatible "in-process task queue"
440 mServiceThread
= PR_CreateThread(
441 PR_USER_THREAD
, [](void* p
) { static_cast<Provider
*>(p
)->ThreadMain(); },
442 this, PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
, PR_JOINABLE_THREAD
,
443 0 /*default stack size*/);
444 if (!mServiceThread
) {
448 mTransparencyMode
= uint32_t(aTransparencyMode
);
453 Maybe
<RemoteBackbufferHandles
> Provider::CreateRemoteHandles() {
455 RemoteBackbufferHandles(ipc::FileDescriptor(mFileMapping
),
456 ipc::FileDescriptor(mRequestReadyEvent
),
457 ipc::FileDescriptor(mResponseReadyEvent
)));
460 void Provider::UpdateTransparencyMode(TransparencyMode aTransparencyMode
) {
461 mTransparencyMode
= uint32_t(aTransparencyMode
);
464 void Provider::ThreadMain() {
465 AUTO_PROFILER_REGISTER_THREAD("RemoteBackbuffer");
466 NS_SetCurrentThreadName("RemoteBackbuffer");
470 AUTO_PROFILER_THREAD_SLEEP
;
471 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent
, INFINITE
) ==
475 if (mStopServiceThread
) {
479 switch (mSharedDataPtr
->dataType
) {
480 case SharedDataType::BorrowRequest
:
481 case SharedDataType::BorrowRequestAllowSameBuffer
: {
482 BorrowResponseData responseData
= {};
484 HandleBorrowRequest(&responseData
,
485 mSharedDataPtr
->dataType
==
486 SharedDataType::BorrowRequestAllowSameBuffer
);
488 mSharedDataPtr
->dataType
= SharedDataType::BorrowResponse
;
489 mSharedDataPtr
->data
.borrowResponse
= responseData
;
491 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
495 case SharedDataType::PresentRequest
: {
496 PresentRequestData requestData
= mSharedDataPtr
->data
.presentRequest
;
497 PresentResponseData responseData
= {};
499 HandlePresentRequest(requestData
, &responseData
);
501 mSharedDataPtr
->dataType
= SharedDataType::PresentResponse
;
502 mSharedDataPtr
->data
.presentResponse
= responseData
;
504 MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent
));
514 void Provider::HandleBorrowRequest(BorrowResponseData
* aResponseData
,
515 bool aAllowSameBuffer
) {
516 MOZ_ASSERT(aResponseData
);
518 aResponseData
->result
= ResponseResult::Error
;
521 if (!::GetClientRect(mWindowHandle
, &clientRect
)) {
525 MOZ_ASSERT(clientRect
.left
== 0);
526 MOZ_ASSERT(clientRect
.top
== 0);
528 const int32_t width
= std::max(int32_t(clientRect
.right
), 1);
529 const int32_t height
= std::max(int32_t(clientRect
.bottom
), 1);
531 bool needNewBackbuffer
= !aAllowSameBuffer
|| !mBackbuffer
||
532 mBackbuffer
->GetWidth() != width
||
533 mBackbuffer
->GetHeight() != height
;
535 if (!needNewBackbuffer
) {
536 aResponseData
->result
= ResponseResult::BorrowSameBuffer
;
540 auto newBackbuffer
= std::make_unique
<PresentableSharedImage
>();
541 if (!newBackbuffer
->Initialize(width
, height
)) {
545 // Preserve the contents of the old backbuffer (if it exists)
547 newBackbuffer
->CopyPixelsFrom(*mBackbuffer
);
551 HANDLE remoteFileMapping
=
552 newBackbuffer
->CreateRemoteFileMapping(mTargetProcess
);
553 if (!remoteFileMapping
) {
557 aResponseData
->result
= ResponseResult::BorrowSuccess
;
558 aResponseData
->width
= width
;
559 aResponseData
->height
= height
;
560 aResponseData
->fileMapping
= remoteFileMapping
;
562 mBackbuffer
= std::move(newBackbuffer
);
565 void Provider::HandlePresentRequest(const PresentRequestData
& aRequestData
,
566 PresentResponseData
* aResponseData
) {
567 MOZ_ASSERT(aResponseData
);
569 Span
rectSpan(aRequestData
.dirtyRects
, kMaxDirtyRects
);
571 aResponseData
->result
= ResponseResult::Error
;
577 if (!mBackbuffer
->PresentToWindow(
578 mWindowHandle
, GetTransparencyMode(),
579 rectSpan
.First(aRequestData
.lenDirtyRects
))) {
583 aResponseData
->result
= ResponseResult::PresentSuccess
;
587 : mFileMapping(nullptr),
588 mRequestReadyEvent(nullptr),
589 mResponseReadyEvent(nullptr),
590 mSharedDataPtr(nullptr),
596 if (mSharedDataPtr
) {
597 MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr
));
600 if (mResponseReadyEvent
) {
601 MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent
));
604 if (mRequestReadyEvent
) {
605 MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent
));
609 MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping
));
613 bool Client::Initialize(const RemoteBackbufferHandles
& aRemoteHandles
) {
614 MOZ_ASSERT(aRemoteHandles
.fileMapping().IsValid());
615 MOZ_ASSERT(aRemoteHandles
.requestReadyEvent().IsValid());
616 MOZ_ASSERT(aRemoteHandles
.responseReadyEvent().IsValid());
618 // FIXME: Due to PCompositorWidget using virtual Recv methods,
619 // RemoteBackbufferHandles is passed by const reference, and cannot have its
620 // signature customized, meaning that we need to clone the handles here.
622 // Once PCompositorWidget is migrated to use direct call semantics, the
623 // signature can be changed to accept RemoteBackbufferHandles by rvalue
624 // reference or value, and the DuplicateHandle calls here can be avoided.
625 mFileMapping
= aRemoteHandles
.fileMapping().ClonePlatformHandle().release();
627 aRemoteHandles
.requestReadyEvent().ClonePlatformHandle().release();
628 mResponseReadyEvent
=
629 aRemoteHandles
.responseReadyEvent().ClonePlatformHandle().release();
631 void* mappedFilePtr
=
632 ::MapViewOfFile(mFileMapping
, FILE_MAP_ALL_ACCESS
, 0 /*offsetHigh*/,
633 0 /*offsetLow*/, 0 /*bytesToMap*/);
634 if (!mappedFilePtr
) {
638 mSharedDataPtr
= reinterpret_cast<SharedData
*>(mappedFilePtr
);
643 already_AddRefed
<gfx::DrawTarget
> Client::BorrowDrawTarget() {
644 mSharedDataPtr
->dataType
= mBackbuffer
645 ? SharedDataType::BorrowRequestAllowSameBuffer
646 : SharedDataType::BorrowRequest
;
648 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
649 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
652 if (mSharedDataPtr
->dataType
!= SharedDataType::BorrowResponse
) {
656 BorrowResponseData responseData
= mSharedDataPtr
->data
.borrowResponse
;
658 if ((responseData
.result
!= ResponseResult::BorrowSameBuffer
) &&
659 (responseData
.result
!= ResponseResult::BorrowSuccess
)) {
663 if (responseData
.result
== ResponseResult::BorrowSuccess
) {
666 auto newBackbuffer
= std::make_unique
<SharedImage
>();
667 if (!newBackbuffer
->InitializeRemote(responseData
.width
,
669 responseData
.fileMapping
)) {
673 mBackbuffer
= std::move(newBackbuffer
);
676 MOZ_ASSERT(mBackbuffer
);
678 return mBackbuffer
->CreateDrawTarget();
681 bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion
) {
682 mSharedDataPtr
->dataType
= SharedDataType::PresentRequest
;
684 // Simplify the region until it has <= kMaxDirtyRects
685 aDirtyRegion
.SimplifyOutward(kMaxDirtyRects
);
687 Span
rectSpan(mSharedDataPtr
->data
.presentRequest
.dirtyRects
, kMaxDirtyRects
);
689 uint8_t rectIndex
= 0;
690 for (auto iter
= aDirtyRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
691 rectSpan
[rectIndex
] = IpcSafeRect(iter
.Get());
695 mSharedDataPtr
->data
.presentRequest
.lenDirtyRects
= rectIndex
;
697 MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent
));
698 MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent
, INFINITE
) ==
701 if (mSharedDataPtr
->dataType
!= SharedDataType::PresentResponse
) {
705 if (mSharedDataPtr
->data
.presentResponse
.result
!=
706 ResponseResult::PresentSuccess
) {
713 } // namespace remote_backbuffer
714 } // namespace widget
715 } // namespace mozilla