Bug 1422966 - Implement SurfaceWayland for Wayland, r?jhorak
WindowSurfaceWayland is Wayland implementation of WindowSurface class.
One WindowSurfaceWayland object manages drawing of one nsWindow so
those are tied 1:1. It implements base Lock() and Commit() interfaces
from WindowSurface. At Wayland side it represents one wl_surface object.
To perform visualiation of nsWindow, WindowSurfaceWayland contains
one wl_surface and two wl_buffer (by WindowBackBuffer) objects
(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.
MozReview-Commit-ID: 9NoamtF87e6
--- a/widget/gtk/WindowSurfaceWayland.cpp
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -512,17 +512,17 @@ WindowBackBuffer::Detach()
{
mAttached = false;
}
bool
WindowBackBuffer::SetImageDataFromBackBuffer(
class WindowBackBuffer* aSourceBuffer)
{
- if (!MatchSize(aSourceBuffer)) {
+ if (!IsMatchingSize(aSourceBuffer)) {
Resize(aSourceBuffer->mWidth, aSourceBuffer->mHeight);
}
mShmPool.SetImageDataFromPool(aSourceBuffer->mShmPool,
aSourceBuffer->mWidth * aSourceBuffer->mHeight * BUFFER_BPP);
return true;
}
@@ -533,10 +533,205 @@ WindowBackBuffer::Lock(const LayoutDevic
gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
return gfxPlatform::CreateDrawTargetForData(static_cast<unsigned char*>(mShmPool.GetImageData()),
lockSize,
BUFFER_BPP * mWidth,
mWaylandDisplay->GetSurfaceFormat());
}
+static void
+frame_callback_handler(void *data, struct wl_callback *callback, uint32_t time)
+{
+ auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
+ surface->FrameCallbackHandler();
+}
+
+static const struct wl_callback_listener frame_listener = {
+ frame_callback_handler
+};
+
+WindowSurfaceWayland::WindowSurfaceWayland(nsWindow *aWindow)
+ : mWindow(aWindow)
+ , mWaylandDisplay(WaylandDisplayGet(aWidget->GetWaylandDisplay()))
+ , mFrontBuffer(nullptr)
+ , mBackBuffer(nullptr)
+ , mFrameCallback(nullptr)
+ , mFrameCallbackSurface(nullptr)
+ , mDelayedCommit(false)
+ , mFullScreenDamage(false)
+ , mIsMainThread(NS_IsMainThread())
+{
+}
+
+WindowSurfaceWayland::~WindowSurfaceWayland()
+{
+ delete mFrontBuffer;
+ delete mBackBuffer;
+
+ if (mFrameCallback) {
+ wl_callback_destroy(mFrameCallback);
+ }
+
+ if (!mIsMainThread) {
+ // We can be destroyed from main thread even though we was created/used
+ // in compositor thread. We have to unref/delete WaylandDisplay in compositor
+ // thread then.
+ MessageLoop::current()->PostTask(
+ NewRunnableFunction(&WaylandDisplayRelease, mWaylandDisplay->GetDisplay()));
+ } else {
+ WaylandDisplayRelease(mWaylandDisplay->GetDisplay());
+ }
+}
+
+WindowBackBuffer*
+WindowSurfaceWayland::GetBufferToDraw(int aWidth, int aHeight)
+{
+ if (!mFrontBuffer) {
+ mFrontBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+ mBackBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+ return mFrontBuffer;
+ }
+
+ if (!mFrontBuffer->IsAttached()) {
+ if (!mFrontBuffer->IsMatchingSize(aWidth, aHeight)) {
+ mFrontBuffer->Resize(aWidth, aHeight);
+ }
+ return mFrontBuffer;
+ }
+
+ // Front buffer is used by compositor, draw to back buffer
+ if (mBackBuffer->IsAttached()) {
+ NS_WARNING("No drawing buffer available");
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mDelayedCommit,
+ "Uncommitted buffer switch, screen artifacts ahead.");
+
+ WindowBackBuffer *tmp = mFrontBuffer;
+ mFrontBuffer = mBackBuffer;
+ mBackBuffer = tmp;
+
+ if (mBackBuffer->IsMatchingSize(aWidth, aHeight)) {
+ // Former front buffer has the same size as a requested one.
+ // Gecko may expect a content already drawn on screen so copy
+ // existing data to the new buffer.
+ mFrontBuffer->Sync(mBackBuffer);
+ // When buffer switches we need to damage whole screen
+ // (https://bugzilla.redhat.com/show_bug.cgi?id=1418260)
+ mFullScreenDamage = true;
+ } else {
+ // Former buffer has different size from the new request. Only resize
+ // the new buffer and leave gecko to render new whole content.
+ mFrontBuffer->Resize(aWidth, aHeight);
+ }
+
+ return mFrontBuffer;
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceWayland::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+ MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+ // We allocate back buffer to widget size but return only
+ // portion requested by aRegion.
+ LayoutDeviceIntRect rect = mWindow->GetBounds();
+ WindowBackBuffer* buffer = GetBufferToDraw(rect.width,
+ rect.height);
+ if (!buffer) {
+ NS_WARNING("No drawing buffer available");
+ return nullptr;
+ }
+
+ return buffer->Lock(aRegion);
+}
+
+void
+WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+ MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+ wl_surface* waylandSurface = mWindow->GetWaylandSurface();
+ if (!waylandSurface) {
+ // Target window is already destroyed - don't bother to render there.
+ return;
+ }
+ wl_proxy_set_queue((struct wl_proxy *)waylandSurface,
+ mWaylandDisplay->GetEventQueue());
+
+ if (mFullScreenDamage) {
+ LayoutDeviceIntRect rect = mWindow->GetBounds();
+ wl_surface_damage(waylandSurface, 0, 0, rect.width, rect.height);
+ mFullScreenDamage = false;
+ } else {
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const mozilla::LayoutDeviceIntRect &r = iter.Get();
+ wl_surface_damage(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+ // Frame callback is always connected to actual wl_surface. When the surface
+ // is unmapped/deleted the frame callback is never called. Unfortunatelly
+ // we don't know if the frame callback is not going to be called.
+ // But our mozcontainer code deletes wl_surface when the GdkWindow is hidden
+ // creates a new one when is visible.
+ if (mFrameCallback && mFrameCallbackSurface == waylandSurface) {
+ // Do nothing here - we have a valid wl_surface and the buffer will be
+ // commited to compositor in next frame callback event.
+ mDelayedCommit = true;
+ return;
+ } else {
+ if (mFrameCallback) {
+ // Delete frame callback connected to obsoleted wl_surface.
+ wl_callback_destroy(mFrameCallback);
+ }
+
+ mFrameCallback = wl_surface_frame(waylandSurface);
+ wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+ mFrameCallbackSurface = waylandSurface;
+
+ // Let the wayland know of the current scaling factor for the hdpi
+ // displays
+ wl_surface_set_buffer_scale(waylandSurface, mWindow->GdkScaleFactor());
+
+ // There's no pending frame callback so we can draw immediately
+ // and create frame callback for possible subsequent drawing.
+ mFrontBuffer->Attach(waylandSurface);
+ mDelayedCommit = false;
+ }
+}
+
+void
+WindowSurfaceWayland::FrameCallbackHandler()
+{
+ MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+ if (mFrameCallback) {
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+ mFrameCallbackSurface = nullptr;
+ }
+
+ if (mDelayedCommit) {
+ wl_surface* waylandSurface = mWindow->GetWaylandSurface();
+ if (!waylandSurface) {
+ // Target window is already destroyed - don't bother to render there.
+ NS_WARNING("No drawing buffer available");
+ return;
+ }
+ wl_proxy_set_queue((struct wl_proxy *)waylandSurface,
+ mWaylandDisplay->GetEventQueue());
+
+ // Send pending surface to compositor and register frame callback
+ // for possible subsequent drawing.
+ mFrameCallback = wl_surface_frame(waylandSurface);
+ wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+ mFrameCallbackSurface = waylandSurface;
+
+ mFrontBuffer->Attach(waylandSurface);
+ mDelayedCommit = false;
+ }
+}
+
} // namespace widget
} // namespace mozilla
--- a/widget/gtk/WindowSurfaceWayland.h
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -69,21 +69,21 @@ public:
void Attach(wl_surface* aSurface);
void Detach();
bool IsAttached() { return mAttached; }
bool Resize(int aWidth, int aHeight);
bool SetImageDataFromBackBuffer(class WindowBackBuffer* aSourceBuffer);
- bool MatchSize(int aWidth, int aHeight)
+ bool IsMatchingSize(int aWidth, int aHeight)
{
return aWidth == mWidth && aHeight == mHeight;
}
- bool MatchSize(class WindowBackBuffer *aBuffer)
+ bool IsMatchingSize(class WindowBackBuffer *aBuffer)
{
return aBuffer->mWidth == mWidth && aBuffer->mHeight == mHeight;
}
private:
void Create(int aWidth, int aHeight);
void Release();
@@ -94,12 +94,38 @@ private:
// and passes it to wayland compositor by wl_surface object.
wl_buffer* mWaylandBuffer;
int mWidth;
int mHeight;
bool mAttached;
nsWaylandDisplay* mWaylandDisplay;
};
+// WindowSurfaceWayland is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWayland : public WindowSurface {
+public:
+ WindowSurfaceWayland(nsWindow *aWindow);
+ ~WindowSurfaceWayland();
+
+ already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+ void FrameCallbackHandler();
+
+private:
+ WindowBackBuffer* GetBufferToDraw(int aWidth, int aHeight);
+
+ // TODO: Do we need to hold a reference to nsWindow object?
+ nsWindow* mWindow;
+ nsWaylandDisplay* mWaylandDisplay;
+ WindowBackBuffer* mFrontBuffer;
+ WindowBackBuffer* mBackBuffer;
+ wl_callback* mFrameCallback;
+ wl_surface* mFrameCallbackSurface;
+ bool mDelayedCommit;
+ bool mFullScreenDamage;
+ bool mIsMainThread;
+};
+
} // namespace widget
} // namespace mozilla
#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H