mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
c7acbaf043
Differential Revision: https://phabricator.services.mozilla.com/D173814
418 lines
14 KiB
C++
418 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "WindowSurfaceWaylandMultiBuffer.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <prenv.h>
|
|
|
|
#include "gfx2DGlue.h"
|
|
#include "gfxPlatform.h"
|
|
#include "MozContainer.h"
|
|
#include "GtkCompositorWidget.h"
|
|
#include "mozilla/gfx/DataSurfaceHelpers.h"
|
|
#include "mozilla/gfx/Tools.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "mozilla/WidgetUtils.h"
|
|
|
|
#undef LOG
|
|
#ifdef MOZ_LOGGING
|
|
# include "mozilla/Logging.h"
|
|
# include "Units.h"
|
|
extern mozilla::LazyLogModule gWidgetWaylandLog;
|
|
# define LOGWAYLAND(...) \
|
|
MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
|
#else
|
|
# define LOGWAYLAND(...)
|
|
#endif /* MOZ_LOGGING */
|
|
|
|
namespace mozilla::widget {
|
|
|
|
/*
|
|
Wayland multi-thread rendering scheme
|
|
|
|
Every rendering thread (main thread, compositor thread) contains its own
|
|
nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
|
|
|
|
WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
|
|
WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
|
|
|
|
----------------------
|
|
| Wayland compositor |
|
|
----------------------
|
|
^
|
|
|
|
|
----------------------
|
|
| nsWaylandDisplay |
|
|
----------------------
|
|
^ ^
|
|
| |
|
|
| |
|
|
| --------------------------------- ------------------
|
|
| | WindowSurfaceWayland |<------>| nsWindow |
|
|
| | | ------------------
|
|
| | ----------------------- |
|
|
| | | WaylandBufferSHM | |
|
|
| | | | |
|
|
| | | ------------------- | |
|
|
| | | | WaylandShmPool | | |
|
|
| | | ------------------- | |
|
|
| | ----------------------- |
|
|
| | |
|
|
| | ----------------------- |
|
|
| | | WaylandBufferSHM | |
|
|
| | | | |
|
|
| | | ------------------- | |
|
|
| | | | WaylandShmPool | | |
|
|
| | | ------------------- | |
|
|
| | ----------------------- |
|
|
| ---------------------------------
|
|
|
|
|
|
|
|
--------------------------------- ------------------
|
|
| WindowSurfaceWayland |<------>| nsWindow |
|
|
| | ------------------
|
|
| ----------------------- |
|
|
| | WaylandBufferSHM | |
|
|
| | | |
|
|
| | ------------------- | |
|
|
| | | WaylandShmPool | | |
|
|
| | ------------------- | |
|
|
| ----------------------- |
|
|
| |
|
|
| ----------------------- |
|
|
| | WaylandBufferSHM | |
|
|
| | | |
|
|
| | ------------------- | |
|
|
| | | WaylandShmPool | | |
|
|
| | ------------------- | |
|
|
| ----------------------- |
|
|
---------------------------------
|
|
|
|
|
|
nsWaylandDisplay
|
|
|
|
Is our connection to Wayland display server,
|
|
holds our display connection (wl_display) and event queue (wl_event_queue).
|
|
|
|
nsWaylandDisplay is created for every thread which sends data to Wayland
|
|
compositor. Wayland events for main thread is served by default Gtk+ loop,
|
|
for other threads (compositor) we must create wl_event_queue and run event loop.
|
|
|
|
|
|
WindowSurfaceWayland
|
|
|
|
Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
|
|
we implement Lock() and Commit() interfaces from WindowSurface
|
|
for actual drawing.
|
|
|
|
One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
|
|
At Wayland level it holds one wl_surface object.
|
|
|
|
To perform visualiation of nsWindow, WindowSurfaceWayland contains one
|
|
wl_surface and two wl_buffer objects (owned by WaylandBufferSHM)
|
|
as we use double buffering. When nsWindow drawing is finished to wl_buffer,
|
|
the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
|
|
|
|
When there's no wl_buffer available for drawing (all wl_buffers are locked in
|
|
compositor for instance) we store the drawing to WindowImageSurface object
|
|
and draw later when wl_buffer becomes available or discard the
|
|
WindowImageSurface cache when whole screen is invalidated.
|
|
|
|
WaylandBufferSHM
|
|
|
|
Is a class which provides a wl_buffer for drawing.
|
|
Wl_buffer is a main Wayland object with actual graphics data.
|
|
Wl_buffer basically represent one complete window screen.
|
|
When double buffering is involved every window (GdkWindow for instance)
|
|
utilises two wl_buffers which are cycled. One is filed with data by application
|
|
and one is rendered by compositor.
|
|
|
|
WaylandBufferSHM is implemented by shared memory (shm).
|
|
It owns wl_buffer object, owns WaylandShmPool
|
|
(which provides the shared memory) and ties them together.
|
|
|
|
WaylandShmPool
|
|
|
|
WaylandShmPool acts as a manager of shared memory for WaylandBufferSHM.
|
|
Allocates it, holds reference to it and releases it.
|
|
|
|
We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
|
|
between us and wayland compositor. We draw our graphics data to the shm and
|
|
handle to wayland compositor by WaylandBufferSHM/WindowSurfaceWayland
|
|
(wl_buffer/wl_surface).
|
|
*/
|
|
|
|
using gfx::DataSourceSurface;
|
|
|
|
#define BACK_BUFFER_NUM 3
|
|
|
|
WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(
|
|
RefPtr<nsWindow> aWindow, GtkCompositorWidget* aCompositorWidget)
|
|
: mSurfaceLock("WindowSurfaceWayland lock"),
|
|
mWindow(std::move(aWindow)),
|
|
mCompositorWidget(aCompositorWidget),
|
|
mFrameInProcess(false),
|
|
mCallbackRequested(false) {}
|
|
|
|
bool WindowSurfaceWaylandMB::MaybeUpdateWindowSize() {
|
|
// We want to get window size from compositor widget as it matches window
|
|
// size used by parent RenderCompositorSWGL rendrer.
|
|
// For main thread rendering mCompositorWidget is not available so get
|
|
// window size directly from nsWindow.
|
|
LayoutDeviceIntSize newWindowSize = mCompositorWidget
|
|
? mCompositorWidget->GetClientSize()
|
|
: mWindow->GetClientSize();
|
|
if (mWindowSize != newWindowSize) {
|
|
mWindowSize = newWindowSize;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<DrawTarget> WindowSurfaceWaylandMB::Lock(
|
|
const LayoutDeviceIntRegion& aInvalidRegion) {
|
|
MutexAutoLock lock(mSurfaceLock);
|
|
|
|
#ifdef MOZ_LOGGING
|
|
gfx::IntRect lockRect = aInvalidRegion.GetBounds().ToUnknownRect();
|
|
LOGWAYLAND("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d",
|
|
(void*)mWindow.get(), lockRect.x, lockRect.y, lockRect.width,
|
|
lockRect.height, aInvalidRegion.GetNumRects());
|
|
#endif
|
|
|
|
if (mWindow->GetWindowType() == WindowType::Invisible) {
|
|
return nullptr;
|
|
}
|
|
mFrameInProcess = true;
|
|
|
|
CollectPendingSurfaces(lock);
|
|
|
|
if (MaybeUpdateWindowSize()) {
|
|
LOGWAYLAND(" new window size [%d x %d]", mWindowSize.width,
|
|
mWindowSize.height);
|
|
if (mInProgressBuffer) {
|
|
ReturnBufferToPool(lock, mInProgressBuffer);
|
|
mInProgressBuffer = nullptr;
|
|
}
|
|
if (mFrontBuffer) {
|
|
ReturnBufferToPool(lock, mFrontBuffer);
|
|
mFrontBuffer = nullptr;
|
|
}
|
|
mAvailableBuffers.Clear();
|
|
}
|
|
|
|
if (!mInProgressBuffer) {
|
|
if (mFrontBuffer && !mFrontBuffer->IsAttached()) {
|
|
mInProgressBuffer = mFrontBuffer;
|
|
} else {
|
|
mInProgressBuffer = ObtainBufferFromPool(lock, mWindowSize);
|
|
if (!mInProgressBuffer) {
|
|
return nullptr;
|
|
}
|
|
if (mFrontBuffer) {
|
|
HandlePartialUpdate(lock, aInvalidRegion);
|
|
ReturnBufferToPool(lock, mFrontBuffer);
|
|
}
|
|
}
|
|
mFrontBuffer = nullptr;
|
|
mFrontBufferInvalidRegion.SetEmpty();
|
|
}
|
|
|
|
RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
|
|
return dt.forget();
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::HandlePartialUpdate(
|
|
const MutexAutoLock& aProofOfLock,
|
|
const LayoutDeviceIntRegion& aInvalidRegion) {
|
|
LayoutDeviceIntRegion copyRegion;
|
|
if (mInProgressBuffer->GetBufferAge() == 2) {
|
|
copyRegion.Sub(mFrontBufferInvalidRegion, aInvalidRegion);
|
|
} else {
|
|
LayoutDeviceIntSize frontBufferSize = mFrontBuffer->GetSize();
|
|
copyRegion = LayoutDeviceIntRegion(LayoutDeviceIntRect(
|
|
0, 0, frontBufferSize.width, frontBufferSize.height));
|
|
copyRegion.SubOut(aInvalidRegion);
|
|
}
|
|
|
|
if (!copyRegion.IsEmpty()) {
|
|
RefPtr<DataSourceSurface> dataSourceSurface =
|
|
mozilla::gfx::CreateDataSourceSurfaceFromData(
|
|
mFrontBuffer->GetSize().ToUnknownSize(),
|
|
mFrontBuffer->GetSurfaceFormat(),
|
|
(const uint8_t*)mFrontBuffer->GetShmPool()->GetImageData(),
|
|
mFrontBuffer->GetSize().width *
|
|
BytesPerPixel(mFrontBuffer->GetSurfaceFormat()));
|
|
RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
|
|
|
|
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
LayoutDeviceIntRect r = iter.Get();
|
|
dt->CopySurface(dataSourceSurface, r.ToUnknownRect(),
|
|
gfx::IntPoint(r.x, r.y));
|
|
}
|
|
}
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::Commit(
|
|
const LayoutDeviceIntRegion& aInvalidRegion) {
|
|
MutexAutoLock lock(mSurfaceLock);
|
|
Commit(lock, aInvalidRegion);
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::Commit(
|
|
const MutexAutoLock& aProofOfLock,
|
|
const LayoutDeviceIntRegion& aInvalidRegion) {
|
|
#ifdef MOZ_LOGGING
|
|
gfx::IntRect invalidRect = aInvalidRegion.GetBounds().ToUnknownRect();
|
|
LOGWAYLAND(
|
|
"WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
|
|
"Window [%d x %d]\n",
|
|
(void*)mWindow.get(), invalidRect.x, invalidRect.y, invalidRect.width,
|
|
invalidRect.height, mWindowSize.width, mWindowSize.height);
|
|
#endif
|
|
|
|
if (!mInProgressBuffer) {
|
|
// invisible window
|
|
return;
|
|
}
|
|
mFrameInProcess = false;
|
|
|
|
MozContainer* container = mWindow->GetMozContainer();
|
|
MozContainerSurfaceLock MozContainerLock(container);
|
|
struct wl_surface* waylandSurface = MozContainerLock.GetSurface();
|
|
if (!waylandSurface) {
|
|
LOGWAYLAND(
|
|
"WindowSurfaceWaylandMB::Commit [%p] frame queued: can't lock "
|
|
"wl_surface\n",
|
|
(void*)mWindow.get());
|
|
if (!mCallbackRequested) {
|
|
RefPtr<WindowSurfaceWaylandMB> self(this);
|
|
moz_container_wayland_add_initial_draw_callback_locked(
|
|
container, [self, aInvalidRegion]() -> void {
|
|
MutexAutoLock lock(self->mSurfaceLock);
|
|
if (!self->mFrameInProcess) {
|
|
self->Commit(lock, aInvalidRegion);
|
|
}
|
|
self->mCallbackRequested = false;
|
|
});
|
|
mCallbackRequested = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (moz_container_wayland_is_commiting_to_parent(container)) {
|
|
// When committing to parent surface we must use wl_surface_damage().
|
|
// A parent surface is created as v.3 object which does not support
|
|
// wl_surface_damage_buffer().
|
|
wl_surface_damage(waylandSurface, 0, 0, INT32_MAX, INT32_MAX);
|
|
} else {
|
|
for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
|
|
LayoutDeviceIntRect r = iter.Get();
|
|
wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
|
|
}
|
|
}
|
|
|
|
// aProofOfLock is a kind of substitution of MozContainerSurfaceLock.
|
|
// MozContainer is locked but MozContainerSurfaceLock doen't convert to
|
|
// MutexAutoLock& so we use aProofOfLock here.
|
|
moz_container_wayland_set_scale_factor_locked(aProofOfLock, container);
|
|
|
|
// It's possible that scale factor changed between Lock() and Commit()
|
|
// but window size is the same.
|
|
// Don't attach such buffer as it may have incorrect size,
|
|
// we'll paint new content soon.
|
|
if (moz_container_wayland_size_matches_scale_factor_locked(
|
|
aProofOfLock, container, mWindowSize.width, mWindowSize.height)) {
|
|
mInProgressBuffer->AttachAndCommit(waylandSurface);
|
|
}
|
|
|
|
mInProgressBuffer->ResetBufferAge();
|
|
mFrontBuffer = mInProgressBuffer;
|
|
mFrontBufferInvalidRegion = aInvalidRegion;
|
|
mInProgressBuffer = nullptr;
|
|
|
|
EnforcePoolSizeLimit(aProofOfLock);
|
|
IncrementBufferAge(aProofOfLock);
|
|
|
|
if (wl_display_flush(WaylandDisplayGet()->GetDisplay()) == -1) {
|
|
LOGWAYLAND("WindowSurfaceWaylandMB::Commit [%p] flush failed\n",
|
|
(void*)mWindow.get());
|
|
}
|
|
}
|
|
|
|
RefPtr<WaylandBufferSHM> WindowSurfaceWaylandMB::ObtainBufferFromPool(
|
|
const MutexAutoLock& aProofOfLock, const LayoutDeviceIntSize& aSize) {
|
|
if (!mAvailableBuffers.IsEmpty()) {
|
|
RefPtr<WaylandBufferSHM> buffer = mAvailableBuffers.PopLastElement();
|
|
mInUseBuffers.AppendElement(buffer);
|
|
return buffer;
|
|
}
|
|
|
|
RefPtr<WaylandBufferSHM> buffer = WaylandBufferSHM::Create(aSize);
|
|
if (buffer) {
|
|
mInUseBuffers.AppendElement(buffer);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::ReturnBufferToPool(
|
|
const MutexAutoLock& aProofOfLock,
|
|
const RefPtr<WaylandBufferSHM>& aBuffer) {
|
|
if (aBuffer->IsAttached()) {
|
|
mPendingBuffers.AppendElement(aBuffer);
|
|
} else if (aBuffer->IsMatchingSize(mWindowSize)) {
|
|
mAvailableBuffers.AppendElement(aBuffer);
|
|
}
|
|
mInUseBuffers.RemoveElement(aBuffer);
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::EnforcePoolSizeLimit(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
// Enforce the pool size limit, removing least-recently-used entries as
|
|
// necessary.
|
|
while (mAvailableBuffers.Length() > BACK_BUFFER_NUM) {
|
|
mAvailableBuffers.RemoveElementAt(0);
|
|
}
|
|
|
|
NS_WARNING_ASSERTION(mPendingBuffers.Length() < BACK_BUFFER_NUM,
|
|
"Are we leaking pending buffers?");
|
|
NS_WARNING_ASSERTION(mInUseBuffers.Length() < BACK_BUFFER_NUM,
|
|
"Are we leaking in-use buffers?");
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::CollectPendingSurfaces(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
mPendingBuffers.RemoveElementsBy([&](auto& buffer) {
|
|
if (!buffer->IsAttached()) {
|
|
if (buffer->IsMatchingSize(mWindowSize)) {
|
|
mAvailableBuffers.AppendElement(std::move(buffer));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void WindowSurfaceWaylandMB::IncrementBufferAge(
|
|
const MutexAutoLock& aProofOfLock) {
|
|
for (const RefPtr<WaylandBufferSHM>& buffer : mInUseBuffers) {
|
|
buffer->IncrementBufferAge();
|
|
}
|
|
for (const RefPtr<WaylandBufferSHM>& buffer : mPendingBuffers) {
|
|
buffer->IncrementBufferAge();
|
|
}
|
|
for (const RefPtr<WaylandBufferSHM>& buffer : mAvailableBuffers) {
|
|
buffer->IncrementBufferAge();
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla::widget
|