Discussion:
[PATCH weston v3 00/13] Sub-surfaces v3
Pekka Paalanen
2013-04-25 10:57:40 UTC
Permalink
Hi all,

this is the v3 of the sub-surfaces patch series, and it is no longer
an RFC. This means that I consider this series ready for merging into
Weston. The server side sub-surface protocol implementation is feature
complete. Once the protocol is deemed stable, we will move it into
Wayland core. Until then, you need to copy protocol/subsurface.xml if
you want to use it in other projects.

The previous series, v2, was announced in
http://lists.freedesktop.org/archives/wayland-devel/2013-February/007590.html
Personally, I believe that all the previously open issues are now
sufficiently explored, that there should not be any big surprises.

The major changes in v3 are:

- Improved protocol object destruction rules: wl_subsurface simply
becomes inert, if the corresponding wl_surface is destroyed.

- Added support for nested sub-surfaces. You can now have
sub-sub-surfaces, and so on.

- Renamed the commit modes, and adjusted their behaviour to support
nested sub-surfaces better. If a sub-surface is synchronized, all
its children will act synchronized, too.

- Completed the server side protocol implementation with sub-surface
nesting support, and double-buffered sub-surface z-order updates.

And of course bug fixes, and a rebase on top of the current upstream
master branch.


The following changes since commit 2e437207435a1b8720b5c623ccfc773e171ccf68:

window: Add a log handler for window.c clients (2013-04-16 20:46:59 -0400)

are available in the git repository at:

git://git.collabora.co.uk/git/user/pq/weston.git subsurface-v3

for you to fetch changes up to 098b2911ead4ad0d9726d1bc192a87ca8ad9295d:

window: add DBG code for leaf management and redraws (2013-04-25 12:10:18 +0300)


Giulio Camuffo (2):
shell: enable moving and resizing of a surface when clicking on a
subsurface
shell: account for the subsurfaces when going fullscreen or maximizing

Pekka Paalanen (11):
protocol: add sub-surfaces
compositor: introduce sub-surfaces
tests: add sub-surface protocol tests
shell: keyboard focus and restacking fixes for sub-surfaces
window: implement shm triple-buffering
window: create sub-surfaces
window: implement per-surface redraws
clients: add subsurfaces demo
window: prevent EGL sub-surface deadlock
window: throttle resizing to the main surface
window: add DBG code for leaf management and redraws

clients/.gitignore | 3 +
clients/Makefile.am | 12 +
clients/subsurfaces.c | 792 ++++++++++++++++++++++++++++++++++++++++++++++++
clients/window.c | 460 ++++++++++++++++++++++++----
clients/window.h | 19 ++
configure.ac | 3 +
protocol/subsurface.xml | 236 +++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.c | 747 ++++++++++++++++++++++++++++++++++++++++++++-
src/compositor.h | 58 ++++
src/shell.c | 172 ++++++++---
tests/.gitignore | 3 +
tests/Makefile.am | 9 +-
tests/subsurface-test.c | 325 ++++++++++++++++++++
15 files changed, 2732 insertions(+), 114 deletions(-)
create mode 100644 clients/subsurfaces.c
create mode 100644 protocol/subsurface.xml
create mode 100644 tests/subsurface-test.c

This patch series applies on top of the current upstream master branch
without any additional patches. The series is browsable in cgit:
http://cgit.collabora.com/git/user/pq/weston.git/log/?h=subsurface-v3

To do (free to take, not necessarily me):

- A Weston demo client with window decorations stitched from 4
sub-surfaces, handling input in all sub-surface. (Giulio already has
an example in Qt.)

- Nested sub-surface tests and a demo. Nesting is currently untested.

- Fix full-surface alpha for windows that have sub-surfaces.

- Investigate if the shell "black surfaces" could be implemented as
server-side sub-surfaces more easily than they are now.

I have not forgotten about the clipping & scaling protocol extension,
and I have some ideas for it, but it is free for taking, too. If
you're interested in taking a shot at any of these items, let me know
so we can avoid overlapping work.

When you build this series, remember --with-cairo-glesv2, or the
subsurfaces demo application may not get built.


Thanks,
pq
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:41 UTC
Permalink
Add protocol for sub-surfaces, wl_subcompositor as the global interface,
and wl_subsurface as the per-surface interface extension.

This patch is meant to be reverted, once sub-surfaces are moved into
Wayland core.

Changes in v2:

- Rewrite wl_subcompositor.get_subsurface description, and move mapping
and commit details into wl_subsurface description. Check the wording
in wl_subsurface.set_position description.

- Add wl_subsurface.set_commit_mode request, and document it, with the
commit_mode enum. Add bad_value error code for wl_subsurface.

- Moved the protocol into Weston repository so we can land it upstream
sooner for public exposure. It is to be moved into Wayland core later.

- Add destroy requests to both wl_subcompositor and wl_subsurface, and
document them. Experience has showed, that interfaces should always
have a destructor unless there is a good and future-proof reason to not
have it.

Changes in v3:

- Specify, that wl_subsurface will become inert, if the corresponding
wl_surface is destroyed, instead of requiring a certain destruction
order.

- Replaced wl_subsurface.set_commit_mode with wl_subsurface.set_sync and
wl_subsurface.set_desync. Parent-cached commit mode is now called
synchronized, and independent mode is desynchronized. Removed
commit_mode enum, and bad_value error.

- Added support for nested sub-surfaces.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/.gitignore | 2 +
clients/Makefile.am | 4 +
clients/window.h | 1 +
protocol/subsurface.xml | 236 ++++++++++++++++++++++++++++++++++++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.h | 1 +
tests/.gitignore | 2 +
tests/Makefile.am | 4 +
9 files changed, 257 insertions(+)
create mode 100644 protocol/subsurface.xml

diff --git a/clients/.gitignore b/clients/.gitignore
index dcd4564..16088e8 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -20,6 +20,8 @@ simple-egl
simple-shm
simple-touch
smoke
+subsurface-client-protocol.h
+subsurface-protocol.c
tablet-shell-client-protocol.h
tablet-shell-protocol.c
text-client-protocol.h
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 8c9bcd4..5f83acd 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -81,6 +81,8 @@ libtoytoolkit_la_SOURCES = \
window.h \
text-cursor-position-protocol.c \
text-cursor-position-client-protocol.h \
+ subsurface-protocol.c \
+ subsurface-client-protocol.h \
workspaces-protocol.c \
workspaces-client-protocol.h

@@ -185,6 +187,8 @@ BUILT_SOURCES = \
desktop-shell-protocol.c \
tablet-shell-client-protocol.h \
tablet-shell-protocol.c \
+ subsurface-client-protocol.h \
+ subsurface-protocol.c \
workspaces-client-protocol.h \
workspaces-protocol.c

diff --git a/clients/window.h b/clients/window.h
index c2946d8..815b3f1 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -27,6 +27,7 @@
#include <wayland-client.h>
#include <cairo.h>
#include "../shared/config-parser.h"
+#include "subsurface-client-protocol.h"

#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])

diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag icon,
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child sub-surfaces
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent surface.
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied on the
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
+ parent surface's own state is applied. This ensures atomic
+ updates of the parent and all its synchronized sub-surfaces.
+ Applying the cached state will invalidate the cache, so further
+ parent surface commits do not (re-)apply old state.
+
+ See wl_subsurface for the recursive effect of this mode.
+ </description>
+ </request>
+
+ <request name="set_desync">
+ <description summary="set sub-surface to desynchronized mode">
+ Change the commit behaviour of the sub-surface to desynchronized
+ mode, also described as independent or freely running mode.
+
+ In desynchronized mode, wl_surface.commit on a sub-surface will
+ apply the pending state directly, without caching, as happens
+ normally with a wl_surface. Calling wl_surface.commit on the
+ parent surface has no effect on the sub-surface's wl_surface
+ state. This mode allows a sub-surface to be updated on its own.
+
+ If cached state exists when wl_surface.commit is called in
+ desynchronized mode, the pending state is added to the cached
+ state, and applied as whole. This invalidates the cache.
+
+ Note: even if a sub-surface is set to desynchronized, a parent
+ sub-surface may override it to behave as synchronized. For details,
+ see wl_subsurface.
+ </description>
+ </request>
+
+ </interface>
+</protocol>
diff --git a/src/.gitignore b/src/.gitignore
index 8c0fea6..ee62b84 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -18,3 +18,6 @@ workspaces-protocol.c
workspaces-server-protocol.h
input-method-protocol.c
input-method-server-protocol.h
+subsurface-server-protocol.h
+subsurface-protocol.c
+
diff --git a/src/Makefile.am b/src/Makefile.am
index d33ebc5..c7056a2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -34,6 +34,8 @@ weston_SOURCES = \
input-method-server-protocol.h \
workspaces-protocol.c \
workspaces-server-protocol.h \
+ subsurface-protocol.c \
+ subsurface-server-protocol.h \
bindings.c \
animation.c \
gl-renderer.h \
@@ -266,6 +268,8 @@ BUILT_SOURCES = \
input-method-server-protocol.h \
workspaces-server-protocol.h \
workspaces-protocol.c \
+ subsurface-server-protocol.h \
+ subsurface-protocol.c \
git-version.h

CLEANFILES = $(BUILT_SOURCES)
diff --git a/src/compositor.h b/src/compositor.h
index 1e999a6..0ef097b 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -35,6 +35,7 @@ extern "C" {
#include "version.h"
#include "matrix.h"
#include "config-parser.h"
+#include "subsurface-server-protocol.h"

#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])

diff --git a/tests/.gitignore b/tests/.gitignore
index 05bc024..fa19888 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -11,3 +11,5 @@ keyboard-test
event-test
button-test
xwayland-test
+subsurface-client-protocol.h
+subsurface-protocol.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2729545..a42a1fc 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -64,6 +64,8 @@ weston_test_client_src = \
weston-test-client-helper.h \
wayland-test-protocol.c \
wayland-test-client-protocol.h \
+ subsurface-protocol.c \
+ subsurface-client-protocol.h \
$(weston_test_runner_src)
weston_test_client_libs = \
$(SIMPLE_CLIENT_LIBS) \
@@ -113,6 +115,8 @@ endif
EXTRA_DIST = weston-tests-env

BUILT_SOURCES = \
+ subsurface-protocol.c \
+ subsurface-client-protocol.h \
wayland-test-protocol.c \
wayland-test-server-protocol.h \
wayland-test-client-protocol.h
--
1.8.1.5
David Herrmann
2013-04-25 14:47:15 UTC
Permalink
Hi Pekka
Post by Pekka Paalanen
Add protocol for sub-surfaces, wl_subcompositor as the global interface,
and wl_subsurface as the per-surface interface extension.
This patch is meant to be reverted, once sub-surfaces are moved into
Wayland core.
- Rewrite wl_subcompositor.get_subsurface description, and move mapping
and commit details into wl_subsurface description. Check the wording
in wl_subsurface.set_position description.
- Add wl_subsurface.set_commit_mode request, and document it, with the
commit_mode enum. Add bad_value error code for wl_subsurface.
- Moved the protocol into Weston repository so we can land it upstream
sooner for public exposure. It is to be moved into Wayland core later.
- Add destroy requests to both wl_subcompositor and wl_subsurface, and
document them. Experience has showed, that interfaces should always
have a destructor unless there is a good and future-proof reason to not
have it.
- Specify, that wl_subsurface will become inert, if the corresponding
wl_surface is destroyed, instead of requiring a certain destruction
order.
- Replaced wl_subsurface.set_commit_mode with wl_subsurface.set_sync and
wl_subsurface.set_desync. Parent-cached commit mode is now called
synchronized, and independent mode is desynchronized. Removed
commit_mode enum, and bad_vBut if we introduce other protocol operations that cause state to be applied, a recursive definition is alue error.
- Added support for nested sub-surfaces.
Nice! \o/
Post by Pekka Paalanen
Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/.gitignore | 2 +
clients/Makefile.am | 4 +
clients/window.h | 1 +
protocol/subsurface.xml | 236 ++++++++++++++++++++++++++++++++++++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.h | 1 +
tests/.gitignore | 2 +
tests/Makefile.am | 4 +
9 files changed, 257 insertions(+)
create mode 100644 protocol/subsurface.xml
diff --git a/clients/.gitignore b/clients/.gitignore
index dcd4564..16088e8 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -20,6 +20,8 @@ simple-egl
simple-shm
simple-touch
smoke
+subsurface-client-protocol.h
+subsurface-protocol.c
tablet-shell-client-protocol.h
tablet-shell-protocol.c
text-client-protocol.h
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 8c9bcd4..5f83acd 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -81,6 +81,8 @@ libtoytoolkit_la_SOURCES = \
window.h \
text-cursor-position-protocol.c \
text-cursor-position-client-protocol.h \
+ subsurface-protocol.c \
+ subsurface-client-protocol.h \
workspaces-protocol.c \
workspaces-client-protocol.h
@@ -185,6 +187,8 @@ BUILT_SOURCES = \
desktop-shell-protocol.c \
tablet-shell-client-protocol.h \
tablet-shell-protocol.c \
+ subsurface-client-protocol.h \
+ subsurface-protocol.c \
workspaces-client-protocol.h \
workspaces-protocol.c
diff --git a/clients/window.h b/clients/window.h
index c2946d8..815b3f1 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -27,6 +27,7 @@
#include <wayland-client.h>
#include <cairo.h>
#include "../shared/config-parser.h"
+#include "subsurface-client-protocol.h"
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag icon,
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
in the case that you described earlier:

C.commit
B.commit
C.commit
A.commit

(assuming A<-B<-C stacking)

According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child sub-surfaces
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent surface.
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.

I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied on the
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
On the first sight this seems to be identical scenarios, but it's not:

wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied when
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
With the current implementation this is identical to:
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.

So whether "wl_surface.commit is called" or whether a "surface's state
is applied" are no longer identical. The first implies the second, but
not vice versa.

Other than these few documentation details, the extension looks really
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!

Regards
David
Pekka Paalanen
2013-04-26 09:02:52 UTC
Permalink
On Thu, 25 Apr 2013 16:47:15 +0200
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
Add protocol for sub-surfaces, wl_subcompositor as the global interface,
and wl_subsurface as the per-surface interface extension.
This patch is meant to be reverted, once sub-surfaces are moved into
Wayland core.
- Rewrite wl_subcompositor.get_subsurface description, and move mapping
and commit details into wl_subsurface description. Check the wording
in wl_subsurface.set_position description.
- Add wl_subsurface.set_commit_mode request, and document it, with the
commit_mode enum. Add bad_value error code for wl_subsurface.
- Moved the protocol into Weston repository so we can land it upstream
sooner for public exposure. It is to be moved into Wayland core later.
- Add destroy requests to both wl_subcompositor and wl_subsurface, and
document them. Experience has showed, that interfaces should always
have a destructor unless there is a good and future-proof reason to not
have it.
- Specify, that wl_subsurface will become inert, if the corresponding
wl_surface is destroyed, instead of requiring a certain destruction
order.
- Replaced wl_subsurface.set_commit_mode with wl_subsurface.set_sync and
wl_subsurface.set_desync. Parent-cached commit mode is now called
synchronized, and independent mode is desynchronized. Removed
commit_mode enum, and bad_vBut if we introduce other protocol operations that cause state to be applied, a recursive definition is alue error.
- Added support for nested sub-surfaces.
Nice! \o/
Post by Pekka Paalanen
Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/.gitignore | 2 +
clients/Makefile.am | 4 +
clients/window.h | 1 +
protocol/subsurface.xml | 236 ++++++++++++++++++++++++++++++++++++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.h | 1 +
tests/.gitignore | 2 +
tests/Makefile.am | 4 +
9 files changed, 257 insertions(+)
create mode 100644 protocol/subsurface.xml
diff --git a/clients/.gitignore b/clients/.gitignore
index dcd4564..16088e8 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -20,6 +20,8 @@ simple-egl
simple-shm
simple-touch
smoke
+subsurface-client-protocol.h
+subsurface-protocol.c
tablet-shell-client-protocol.h
tablet-shell-protocol.c
text-client-protocol.h
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 8c9bcd4..5f83acd 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -81,6 +81,8 @@ libtoytoolkit_la_SOURCES = \
window.h \
text-cursor-position-protocol.c \
text-cursor-position-client-protocol.h \
+ subsurface-protocol.c \
+ subsurface-client-protocol.h \
workspaces-protocol.c \
workspaces-client-protocol.h
@@ -185,6 +187,8 @@ BUILT_SOURCES = \
desktop-shell-protocol.c \
tablet-shell-client-protocol.h \
tablet-shell-protocol.c \
+ subsurface-client-protocol.h \
+ subsurface-protocol.c \
workspaces-client-protocol.h \
workspaces-protocol.c
diff --git a/clients/window.h b/clients/window.h
index c2946d8..815b3f1 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -27,6 +27,7 @@
#include <wayland-client.h>
#include <cairo.h>
#include "../shared/config-parser.h"
+#include "subsurface-client-protocol.h"
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag icon,
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
example), then:

C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C

Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.

A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.

Maybe I should explain the difference between the commit mode and the
effective commit mode here?

I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child sub-surfaces
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent surface.
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.

No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.

I'll add a note here.

I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied on the
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?

I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied when
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's state
is applied" are no longer identical. The first implies the second, but
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
Post by David Herrmann
Other than these few documentation details, the extension looks really
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.


Thank you, David!
- pq
Pekka Paalanen
2013-04-26 11:12:16 UTC
Permalink
On Fri, 26 Apr 2013 12:02:52 +0300
Post by Pekka Paalanen
On Thu, 25 Apr 2013 16:47:15 +0200
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
Add protocol for sub-surfaces, wl_subcompositor as the global interface,
and wl_subsurface as the per-surface interface extension.
This patch is meant to be reverted, once sub-surfaces are moved into
Wayland core.
- Rewrite wl_subcompositor.get_subsurface description, and move mapping
and commit details into wl_subsurface description. Check the wording
in wl_subsurface.set_position description.
- Add wl_subsurface.set_commit_mode request, and document it, with the
commit_mode enum. Add bad_value error code for wl_subsurface.
- Moved the protocol into Weston repository so we can land it upstream
sooner for public exposure. It is to be moved into Wayland core later.
- Add destroy requests to both wl_subcompositor and wl_subsurface, and
document them. Experience has showed, that interfaces should always
have a destructor unless there is a good and future-proof reason to not
have it.
- Specify, that wl_subsurface will become inert, if the corresponding
wl_surface is destroyed, instead of requiring a certain destruction
order.
- Replaced wl_subsurface.set_commit_mode with wl_subsurface.set_sync and
wl_subsurface.set_desync. Parent-cached commit mode is now called
synchronized, and independent mode is desynchronized. Removed
commit_mode enum, and bad_vBut if we introduce other protocol operations that cause state to be applied, a recursive definition is alue error.
- Added support for nested sub-surfaces.
Nice! \o/
Post by Pekka Paalanen
Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/.gitignore | 2 +
clients/Makefile.am | 4 +
clients/window.h | 1 +
protocol/subsurface.xml | 236 ++++++++++++++++++++++++++++++++++++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.h | 1 +
tests/.gitignore | 2 +
tests/Makefile.am | 4 +
9 files changed, 257 insertions(+)
create mode 100644 protocol/subsurface.xml
...
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag icon,
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C
Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.
A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.
Maybe I should explain the difference between the commit mode and the
effective commit mode here?
I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child sub-surfaces
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent surface.
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.
No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.
I'll add a note here.
I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied on the
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?
I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied when
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's state
is applied" are no longer identical. The first implies the second, but
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
Post by David Herrmann
Other than these few documentation details, the extension looks really
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.
Thank you, David!
- pq
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6


Thanks,
pq
David Herrmann
2013-04-26 12:09:01 UTC
Permalink
Hi Pekka
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing capabilities.
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and create
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound) window.
+ For window management purposes, this set of wl_surface objects is
+ to be considered as a single window, and it should also behave as
+ such.
+
+ The aim of sub-surfaces is to offload some of the compositing work
+ within a window from clients to the compositor. A prime example is
+ a video player with decorations and video in separate wl_surface
+ objects. This should allow the compositor to pass YUV video buffer
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor interface">
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag icon,
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has been
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is applied
+ and the parent surface is mapped. The order of which one happens
+ first is irrelevant. A sub-surface is hidden if the parent becomes
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
Sorry for not being clear, but all my comments were about the
synchronized mode. The unsynchronized mode is trivial in this regard.
But I think you got it right.
Post by Pekka Paalanen
Post by Pekka Paalanen
C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C
Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
ehen schlo? feschtle gefeiert?
July 15, 2012 at 10:59pm ? Like
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.
A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.
Maybe I should explain the difference between the commit mode and the
effective commit mode here?
I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed by
+ wl_subsurface requests, as opposed to wl_surface requests. This
+ state includes the sub-surface position relative to the parent
+ surface (wl_subsurface.set_position), and the stacking order of
+ the parent and its sub-surfaces (wl_subsurface.place_above and
+ .place_below). This state is applied when the parent surface's
+ wl_surface state is applied, regardless of the sub-surface's mode.
+ As the exception, set_sync and set_desync are effective immediately.
+
+ The main surface can thought to be always in desynchronized mode,
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will behave as
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively throughout the
+ tree of surfaces. This means, that one can set a sub-surface into
+ synchronized mode, and then assume that all its child sub-surfaces
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is destroyed, the
+ wl_subsurface object becomes inert. Note, that destroying either object
+ takes effect immediately. If you need to synchronize the removal
+ of a sub-surface to the parent surface update, unmap the sub-surface
+ first by attaching a NULL wl_buffer, update parent, and then destroy
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the sub-surface is
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface object
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's association
+ to the parent is deleted, and the wl_surface loses its role as
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent surface.
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.
No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.
I'll add a note here.
I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent surface"/>
+ <arg name="y" type="int" summary="coordinate in the parent surface"/>
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the sub-surfaces.
+ The reference surface must be one of the sibling surfaces, or the
+ parent surface. Using any other surface, including this sub-surface,
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied on the
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?
I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference surface.
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to synchronized
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface will
+ accumulate the committed state in a cache, but the state will
+ not be applied and hence will not change the compositor output.
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied when
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's state
is applied" are no longer identical. The first implies the second, but
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
I think it is pretty clear that those are different steps now. But we
need to be careful to not assume that they're identical. That's why I
thought this description is misleading.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Other than these few documentation details, the extension looks really
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.
Thank you, David!
- pq
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.

Thanks
David
Jason Ekstrand
2013-04-26 14:14:26 UTC
Permalink
pq,
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby
granted
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this
permission
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO
THIS
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR
ANY
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing
capabilities.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ A wl_surface, that has sub-surfaces associated, is called the
+ parent surface. Sub-surfaces can be arbitrarily nested and
create
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound)
window.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ For window management purposes, this set of wl_surface
objects is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to be considered as a single window, and it should also
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ such.
+
+ The aim of sub-surfaces is to offload some of the
compositing work
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ within a window from clients to the compositor. A prime
example is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a video player with decorations and video in separate
wl_surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ objects. This should allow the compositor to pass YUV video
buffer
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor
interface">
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag
icon,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has
been
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is
applied
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ and the parent surface is mapped. The order of which one
happens
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first is irrelevant. A sub-surface is hidden if the parent
becomes
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync.
Synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode caches wl_surface state to be applied on the next parent
+ surface's commit, and desynchronized mode applies the pending
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
Sorry for not being clear, but all my comments were about the
synchronized mode. The unsynchronized mode is trivial in this regard.
But I think you got it right.
Post by Pekka Paalanen
Post by Pekka Paalanen
C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C
Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
ehen schlo? feschtle gefeiert?
July 15, 2012 at 10:59pm ? Like
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.
A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.
Maybe I should explain the difference between the commit mode and the
effective commit mode here?
I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is managed
by
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface requests, as opposed to wl_surface requests.
This
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ state includes the sub-surface position relative to the
parent
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ surface (wl_subsurface.set_position), and the stacking order
of
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the parent and its sub-surfaces (wl_subsurface.place_above
and
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ .place_below). This state is applied when the parent
surface's
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state is applied, regardless of the sub-surface's
mode.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ As the exception, set_sync and set_desync are effective
immediately.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The main surface can thought to be always in desynchronized
mode,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively
throughout the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ tree of surfaces. This means, that one can set a sub-surface
into
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ synchronized mode, and then assume that all its child
sub-surfaces
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is
destroyed, the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface object becomes inert. Note, that destroying
either object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ takes effect immediately. If you need to synchronize the
removal
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ of a sub-surface to the parent surface update, unmap the
sub-surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first by attaching a NULL wl_buffer, update parent, and then
destroy
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the
sub-surface is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface
object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's
association
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to the parent is deleted, and the wl_surface loses its role
as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.
No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.
I'll add a note here.
I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="y" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the
sub-surfaces.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The reference surface must be one of the sibling surfaces,
or the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ parent surface. Using any other surface, including this
sub-surface,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied
on the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?
I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to
synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ accumulate the committed state in a cache, but the state
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ not be applied and hence will not change the compositor
output.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after the
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied when
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's state
is applied" are no longer identical. The first implies the second, but
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
I think it is pretty clear that those are different steps now. But we
need to be careful to not assume that they're identical. That's why I
thought this description is misleading.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Other than these few documentation details, the extension looks really
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.
Thank you, David!
- pq
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes solve my
concerns as well. I'm still not sure I'm 100% happy with how it requires
you to build strange trees in certain cases. That said, I think it looks
pretty good and certainly good enough to go into weston so we can all start
playing with it. I'm hoping I can get sub-surfaces implemented in my java
compositor soonish. Looking good!
--Jason Ekstrand

Thanks
Post by David Herrmann
David
_______________________________________________
wayland-devel mailing list
wayland-devel at lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/wayland-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130426/d4bdb474/attachment-0001.html>
Jason Ekstrand
2013-04-26 16:38:12 UTC
Permalink
Post by Jason Ekstrand
pq,
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby
granted
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ without fee, provided that the above copyright notice appear
in
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ all copies and that both that copyright notice and this
permission
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ notice appear in supporting documentation, and that the name
of
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO
THIS
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
FOR ANY
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing
capabilities.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ A wl_surface, that has sub-surfaces associated, is called
the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ parent surface. Sub-surfaces can be arbitrarily nested and
create
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound)
window.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ For window management purposes, this set of wl_surface
objects is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to be considered as a single window, and it should also
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ such.
+
+ The aim of sub-surfaces is to offload some of the
compositing work
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ within a window from clients to the compositor. A prime
example is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a video player with decorations and video in separate
wl_surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ objects. This should allow the compositor to pass YUV video
buffer
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor
interface">
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag
icon,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a sub-surface"/>
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has
been
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is
applied
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ and the parent surface is mapped. The order of which one
happens
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first is irrelevant. A sub-surface is hidden if the parent
becomes
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ hidden, or if a NULL wl_buffer is applied. These rules apply
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync.
Synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode caches wl_surface state to be applied on the next
parent
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ surface's commit, and desynchronized mode applies the
pending
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
I had to look at the code to actually understand which implementation
you chose. It is not clear to me (from that description) what happens
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
Sorry for not being clear, but all my comments were about the
synchronized mode. The unsynchronized mode is trivial in this regard.
But I think you got it right.
Post by Pekka Paalanen
Post by Pekka Paalanen
C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C
Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
ehen schlo? feschtle gefeiert?
July 15, 2012 at 10:59pm ? Like
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.
A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.
Maybe I should explain the difference between the commit mode and the
effective commit mode here?
I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in the
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is
managed by
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface requests, as opposed to wl_surface requests.
This
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ state includes the sub-surface position relative to the
parent
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ surface (wl_subsurface.set_position), and the stacking
order of
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the parent and its sub-surfaces (wl_subsurface.place_above
and
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ .place_below). This state is applied when the parent
surface's
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state is applied, regardless of the
sub-surface's mode.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ As the exception, set_sync and set_desync are effective
immediately.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The main surface can thought to be always in desynchronized
mode,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively
throughout the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ tree of surfaces. This means, that one can set a
sub-surface into
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ synchronized mode, and then assume that all its child
sub-surfaces
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is
destroyed, the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface object becomes inert. Note, that destroying
either object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ takes effect immediately. If you need to synchronize the
removal
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ of a sub-surface to the parent surface update, unmap the
sub-surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first by attaching a NULL wl_buffer, update parent, and
then destroy
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the
sub-surface is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface
object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's
association
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to the parent is deleted, and the wl_surface loses its
role as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the parent"/>
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin (top-left
+ corner pixel) will be at the location x, y of the parent
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The next wl_surface.commit on the parent surface will reset
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the child
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no clipping"
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.
No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.
I'll add a note here.
I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="y" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back just
+ above the reference surface, changing the z-order of the
sub-surfaces.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The reference surface must be one of the sibling surfaces,
or the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ parent surface. Using any other surface, including this
sub-surface,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied
on the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?
I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized mode">
+ Change the commit behaviour of the sub-surface to
synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ accumulate the committed state in a cache, but the state
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ not be applied and hence will not change the compositor
output.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after
the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
On the first sight this seems to be identical scenarios, but it's
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied
when
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's
state
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
is applied" are no longer identical. The first implies the second,
but
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
I think it is pretty clear that those are different steps now. But we
need to be careful to not assume that they're identical. That's why I
thought this description is misleading.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Other than these few documentation details, the extension looks
really
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
nice. I doubt that we need the multi-layer-cache that we were talking
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.
Thank you, David!
- pq
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes solve my
concerns as well. I'm still not sure I'm 100% happy with how it requires
you to build strange trees in certain cases. That said, I think it looks
pretty good and certainly good enough to go into weston so we can all start
playing with it. I'm hoping I can get sub-surfaces implemented in my java
compositor soonish. Looking good!
--Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to what I
meant by "strange trees". Consider an EGL video surface with SHM CSD (see
attachment). To make things more complicated, let's say that the video
surface is controlled by an external library or (worse yet) is a foreign
surface controlled by a different process. Due to current commit behavior
and the way commits cascade, the EGL surface (which really is the primary
surface in this case) can't be used as the primary surface. Instead, you
would have to use one of the decoration surfaces as the parent surface
(again, see the picture).

One solution to this would be to allow some sort of surface that contains
no actual pixels but gets mapped anyway. Right now you can hack this with
a 1x1 transparent buffer or something similar. However, I would rather not
see such hacks common place. One way to achieve this would be to have some
sort of empty buffer that has a size but no data. Then you could attach
one of these empty buffers to a surface to get it mapped.

The other advantage of an "empty buffer" is that, if it's used as the main
window, could be the full size of the window instead of being 1x1. This
simplifies the problem of handling input across subsurfaces as you can have
the parent surface handle them. It would also allow us (if we wanted) to
clip subsurfaces to the parent surface without causing any major problems.
Not that we necessarily should, but it would leave open the option.

Hope that's more clear,
--Jason Ekstrand
Post by Jason Ekstrand
Thanks
Post by David Herrmann
David
_______________________________________________
wayland-devel mailing list
wayland-devel at lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/wayland-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130426/3afbcfb6/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: subsurfaces-tree.svg
Type: image/svg+xml
Size: 14705 bytes
Desc: not available
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130426/3afbcfb6/attachment-0001.svg>
Jason Ekstrand
2013-04-27 20:18:29 UTC
Permalink
Sorry to spam the list, but I had another idea kicking around my head this
weekend that I thought was worth sharing. Please note that I don't think
this is a stopper. I just think it's worth throwing out there and seeing
if others think it's useful.

I don't think there are any major holes in commit behavior in the current
protocol. That said, there seems to be some confusion on commit modes and
how they interact with the tree of subalgebras that I think is justified.
I think this could be (somewhat) solved by the following simplification. We
could replace the explicit commit modes that then impose commit modes on
the children with a single cache_child_commits request which would begin
caching the data for child surfaces until commit is called on the same
surface. In terms of the current mechanism, cache_child_commits would set
synced and commit would automatically unset synced.

The advantage I see to this approach is that it makes it more clear that it
affects the entire tree and how it does so. Also, it requires the client
to explicitly put any synced commits between a cached_child_commits and
commit which I personally think is cleaner. The disadvantage is that it is
a little less flexible in that you can't cache single children. However,
if you can have "dummy nodes" (see previous ML posts), this shouldn't be a
problem in most cases.

Again, I don't think this is a show-stopper and I think the protocol as-is
is ok. I just thought it might be worth mentioning.
--Jason Ekstrand
Post by Jason Ekstrand
Post by Jason Ekstrand
pq,
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml
new file mode 100644
index 0000000..60b4002
--- /dev/null
+++ b/protocol/subsurface.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="subsurface">
+
+ <copyright>
+ Copyright ? 2012-2013 Collabora, Ltd.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby
granted
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ without fee, provided that the above copyright notice appear
in
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ all copies and that both that copyright notice and this
permission
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ notice appear in supporting documentation, and that the name
of
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for
any
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO
THIS
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
FOR ANY
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ THIS SOFTWARE.
+ </copyright>
+
+ <interface name="wl_subcompositor" version="1">
+ <description summary="sub-surface compositing">
+ The global interface exposing sub-surface compositing
capabilities.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ A wl_surface, that has sub-surfaces associated, is called
the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ parent surface. Sub-surfaces can be arbitrarily nested and
create
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a tree of sub-surfaces.
+
+ The root surface in a tree of sub-surfaces is the main
+ surface. The main surface cannot be a sub-surface, because
+ sub-surfaces must always have a parent.
+
+ A main surface with its sub-surfaces forms a (compound)
window.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ For window management purposes, this set of wl_surface
objects is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to be considered as a single window, and it should also
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ such.
+
+ The aim of sub-surfaces is to offload some of the
compositing work
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ within a window from clients to the compositor. A prime
example is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a video player with decorations and video in separate
wl_surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ objects. This should allow the compositor to pass YUV
video buffer
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ processing to dedicated overlay hardware when possible.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="unbind from the subcompositor
interface">
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ Informs the server that the client will not be using this
+ protocol object anymore. This does not affect any other
+ objects, wl_subsurface objects included.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="the to-be sub-surface is invalid"/>
+ <entry name="bad_parent" value="1"
+ summary="the given parent is a sub-surface"/>
+ </enum>
+
+ <request name="get_subsurface">
+ <description summary="give a surface the role sub-surface">
+ Create a sub-surface interface for the given surface, and
+ associate it with the given parent surface. This turns a
+ plain wl_surface into a sub-surface.
+
+ The to-be sub-surface must not already have a dedicated
+ purpose, like any shell surface type, cursor image, drag
icon,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ or sub-surface. Otherwise a protocol error is raised.
+ </description>
+
+ <arg name="id" type="new_id" interface="wl_subsurface"
+ summary="the new subsurface object id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface to be turned into a
sub-surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="parent" type="object" interface="wl_surface"
+ summary="the parent surface"/>
+ </request>
+ </interface>
+
+ <interface name="wl_subsurface" version="1">
+ <description summary="sub-surface interface to a wl_surface">
+ An additional interface to a wl_surface object, which has
been
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ made a sub-surface. A sub-surface has one parent surface.
+
+ A sub-surface becomes mapped, when a non-NULL wl_buffer is
applied
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ and the parent surface is mapped. The order of which one
happens
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first is irrelevant. A sub-surface is hidden if the parent
becomes
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ hidden, or if a NULL wl_buffer is applied. These rules
apply
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ recursively through the tree of surfaces.
+
+ The behaviour of wl_surface.commit request on a sub-surface
+ depends on the sub-surface's mode. The possible modes are
+ synchronized and desynchronized, see methods
+ wl_subsurface.set_sync and wl_subsurface.set_desync.
Synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode caches wl_surface state to be applied on the next
parent
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ surface's commit, and desynchronized mode applies the
pending
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
I had to look at the code to actually understand which
implementation
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
you chose. It is not clear to me (from that description) what
happens
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
C.commit
B.commit
C.commit
A.commit
(assuming A<-B<-C stacking)
That really depends on the commit modes of each surface, but if we
assume A is main, and B and C are synchronized (IIRC that was the
Sorry for not being clear, but all my comments were about the
synchronized mode. The unsynchronized mode is trivial in this regard.
But I think you got it right.
Post by Pekka Paalanen
Post by Pekka Paalanen
C.commit(buffer C1)
- C1 becomes cached in C
B.commit(buffer B1)
- B1 becomes cached in B
C.commit(buffer C2)
- C2 becomes cached in C, and C1 is released
A.commit(buffer A1)
- A1 becomes current in A, B1 becomes current in B, C2 becomes current
in C
Actually, C's commit mode is irrelevant. As long as B is synchronized,
C will behave as synchronized.
ehen schlo? feschtle gefeiert?
July 15, 2012 at 10:59pm ? Like
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
According to your implementation you apply a sub-surface state if
wl_surface.commit is called on _any_ ancestor. I think you should
mention that explicitly. This description could mean both, but your
description below is definitely misleading.
Hmm, no. I only apply the new state in children, if the children are
effectively in synchronized mode.
A sub-surface has a commit mode, which is either desynchronized or
synchronized. However, the effective commit mode is synchronized, if
any of sub-surface's ancestors are in synchronized mode.
Maybe I should explain the difference between the commit mode and the
effective commit mode here?
I'm uncertain how to clarify what you are asking. Can you propose
something here?
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state directly. A sub-surface is initially in
the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ synchronized mode.
+
+ Sub-surfaces have also other kind of state, which is
managed by
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface requests, as opposed to wl_surface requests.
This
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ state includes the sub-surface position relative to the
parent
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ surface (wl_subsurface.set_position), and the stacking
order of
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the parent and its sub-surfaces (wl_subsurface.place_above
and
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ .place_below). This state is applied when the parent
surface's
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_surface state is applied, regardless of the
sub-surface's mode.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ As the exception, set_sync and set_desync are effective
immediately.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The main surface can thought to be always in
desynchronized mode,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ since it does not have a parent in the sub-surfaces sense.
+
+ Even if a sub-surface is in desynchronized mode, it will
behave as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ in synchronized mode, if its parent surface behaves as in
+ synchronized mode. This rule is applied recursively
throughout the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ tree of surfaces. This means, that one can set a
sub-surface into
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ synchronized mode, and then assume that all its child
sub-surfaces
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ are synchronized, too, without explicitly setting them.
+
+ If the wl_surface associated with the wl_subsurface is
destroyed, the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ wl_subsurface object becomes inert. Note, that destroying
either object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ takes effect immediately. If you need to synchronize the
removal
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ of a sub-surface to the parent surface update, unmap the
sub-surface
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ first by attaching a NULL wl_buffer, update parent, and
then destroy
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the sub-surface.
+
+ If the parent wl_surface object is destroyed, the
sub-surface is
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ unmapped.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove sub-surface interface">
+ The sub-surface interface is removed from the wl_surface
object
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ that was turned into a sub-surface with
+ wl_subcompositor.get_subsurface request. The wl_surface's
association
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ to the parent is deleted, and the wl_surface loses its
role as
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ a sub-surface. The wl_surface is unmapped.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="bad_surface" value="0"
+ summary="wl_surface is not a sibling or the
parent"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ </enum>
+
+ <request name="set_position">
+ <description summary="reposition the sub-surface">
+ This schedules a sub-surface position change.
+ The sub-surface will be moved so, that its origin
(top-left
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ corner pixel) will be at the location x, y of the parent
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+
+ The next wl_surface.commit on the parent surface will
reset
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ the sub-surface's position to the scheduled coordinates.
+
+ The initial position is 0, 0.
Your patch doesn't mention what happens if the parent doesn't fully
include the sub-surface. I think it should be clear whether the
child
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
is clipped or not.
I know you postponed the rotation/clipping extension, but we should
still define the behavior for raw sub-surfaces. I guess "no
clipping"
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
is the best option. Or is that implied by not mentioning it?
I thought not mentioning anything implies no clipping, and that is how
it is implemented. Sub-surface area is in no way restricted by the
parent or ancestors.
No clipping is essential for stitching window decorations from
sub-surfaces, while eliminating surface overlap, for instance.
I'll add a note here.
I never suggested rotation. It is the clipping and scaling extension
that will follow separately, and is applicable to any wl_surface.
Post by David Herrmann
Post by Pekka Paalanen
+ </description>
+
+ <arg name="x" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="y" type="int" summary="coordinate in the parent
surface"/>
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ </request>
+
+ <request name="place_above">
+ <description summary="restack the sub-surface">
+ This sub-surface is taken from the stack, and put back
just
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ above the reference surface, changing the z-order of the
sub-surfaces.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The reference surface must be one of the sibling
surfaces, or the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ parent surface. Using any other surface, including this
sub-surface,
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ will cause a protocol error.
+
+ The z-order is double-buffered state, and will be applied
on the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ next commit of the parent surface.
+ See wl_surface.commit and wl_subcompositor.get_subsurface.
+ </description>
+
Maybe I missed it, but what's the initial z-order for new
sub-surfaces? I guess it's "top-most" but I think it should be
mentioned either here or in the wl_subsurface description.
Yeah, it is top-most of the particular parent's children. The logical
place to document it is in wl_subsurface.place_above, right?
I'll add it.
Post by David Herrmann
Post by Pekka Paalanen
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="place_below">
+ <description summary="restack the sub-surface">
+ The sub-surface is placed just below of the reference
surface.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ See wl_subsurface.place_above.
+ </description>
+
+ <arg name="sibling" type="object" interface="wl_surface"
+ summary="the reference surface"/>
+ </request>
+
+ <request name="set_sync">
+ <description summary="set sub-surface to synchronized
mode">
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ Change the commit behaviour of the sub-surface to
synchronized
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ mode, also described as the parent dependant mode.
+
+ In synchronized mode, wl_surface.commit on a sub-surface
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ accumulate the committed state in a cache, but the state
will
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ not be applied and hence will not change the compositor
output.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Post by Pekka Paalanen
+ The cached state is applied to the sub-surface when
+ wl_surface.commit is called on the parent surface, after
the
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
This is the description that I was talking about. It's misleading. A
sub-surface state is _not_ necessarily applied _only_ when
wl_surface.commit is called on the parent. Instead it is applied if
the parent's state is applied.
Right, I need to fix that. Left-overs from before nesting support.
Post by David Herrmann
On the first sight this seems to be identical scenarios, but it's
wl_surface.commit causes a state and all synchronized sub-surface
states to be applied. That means, a state of a surface is applied
when
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
wl_surface.commit is called on _any_ ancestor (direct or indirect
parent) not only the direct parent.
wl_surface.commit is called on the top-most synchronized parent. All
other commits in between are invisible to the client.
Yeah.
Post by David Herrmann
So whether "wl_surface.commit is called" or whether a "surface's
state
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
is applied" are no longer identical. The first implies the second,
but
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
not vice versa.
Actually, neither implies the other. Btw. is it clear enough, that
commit and apply are separate steps now?
I think it is pretty clear that those are different steps now. But we
need to be careful to not assume that they're identical. That's why I
thought this description is misleading.
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
Other than these few documentation details, the extension looks
really
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
nice. I doubt that we need the multi-layer-cache that we were
talking
Post by Pekka Paalanen
Post by Pekka Paalanen
Post by David Herrmann
about with v2 so I like this proposal a lot!
Oh yeah, the multi-layer cache would be madness on resource usage.
Thank you, David!
- pq
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes solve my
concerns as well. I'm still not sure I'm 100% happy with how it requires
you to build strange trees in certain cases. That said, I think it looks
pretty good and certainly good enough to go into weston so we can all start
playing with it. I'm hoping I can get sub-surfaces implemented in my java
compositor soonish. Looking good!
--Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to what I
meant by "strange trees". Consider an EGL video surface with SHM CSD (see
attachment). To make things more complicated, let's say that the video
surface is controlled by an external library or (worse yet) is a foreign
surface controlled by a different process. Due to current commit behavior
and the way commits cascade, the EGL surface (which really is the primary
surface in this case) can't be used as the primary surface. Instead, you
would have to use one of the decoration surfaces as the parent surface
(again, see the picture).
One solution to this would be to allow some sort of surface that contains
no actual pixels but gets mapped anyway. Right now you can hack this with
a 1x1 transparent buffer or something similar. However, I would rather not
see such hacks common place. One way to achieve this would be to have some
sort of empty buffer that has a size but no data. Then you could attach
one of these empty buffers to a surface to get it mapped.
The other advantage of an "empty buffer" is that, if it's used as the main
window, could be the full size of the window instead of being 1x1. This
simplifies the problem of handling input across subsurfaces as you can have
the parent surface handle them. It would also allow us (if we wanted) to
clip subsurfaces to the parent surface without causing any major problems.
Not that we necessarily should, but it would leave open the option.
Hope that's more clear,
--Jason Ekstrand
Post by Jason Ekstrand
Thanks
Post by David Herrmann
David
_______________________________________________
wayland-devel mailing list
wayland-devel at lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/wayland-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130427/ce0a441b/attachment-0001.html>
Pekka Paalanen
2013-04-29 07:11:46 UTC
Permalink
On Sat, 27 Apr 2013 15:18:29 -0500
Post by Jason Ekstrand
Sorry to spam the list, but I had another idea kicking around my head
this weekend that I thought was worth sharing. Please note that I
don't think this is a stopper. I just think it's worth throwing out
there and seeing if others think it's useful.
I don't think there are any major holes in commit behavior in the
current protocol. That said, there seems to be some confusion on
commit modes and how they interact with the tree of subalgebras that
I think is justified. I think this could be (somewhat) solved by the
following simplification. We could replace the explicit commit modes
that then impose commit modes on the children with a single
cache_child_commits request which would begin caching the data for
child surfaces until commit is called on the same surface. In terms
of the current mechanism, cache_child_commits would set synced and
commit would automatically unset synced.
The advantage I see to this approach is that it makes it more clear
that it affects the entire tree and how it does so. Also, it
requires the client to explicitly put any synced commits between a
cached_child_commits and commit which I personally think is cleaner.
The disadvantage is that it is a little less flexible in that you
can't cache single children. However, if you can have "dummy
nodes" (see previous ML posts), this shouldn't be a problem in most
cases.
Again, I don't think this is a show-stopper and I think the protocol
as-is is ok. I just thought it might be worth mentioning.
--Jason Ekstrand
Hi Jason,

isn't this racy? If you want to keep all children always synchronized,
don't you have to re-send cache_child_commits every time you commit the
main surface, and hope that the child components to do not manage to
send a new commit between your commit and cache_child_commits?

We could just rename set_sync and set_desync to something more obvious,
any suggestions?


Thanks,
pq
David Herrmann
2013-04-29 07:43:37 UTC
Permalink
Hi
Post by Pekka Paalanen
On Sat, 27 Apr 2013 15:18:29 -0500
Post by Jason Ekstrand
Sorry to spam the list, but I had another idea kicking around my head
this weekend that I thought was worth sharing. Please note that I
don't think this is a stopper. I just think it's worth throwing out
there and seeing if others think it's useful.
I don't think there are any major holes in commit behavior in the
current protocol. That said, there seems to be some confusion on
commit modes and how they interact with the tree of subalgebras that
I think is justified. I think this could be (somewhat) solved by the
following simplification. We could replace the explicit commit modes
that then impose commit modes on the children with a single
cache_child_commits request which would begin caching the data for
child surfaces until commit is called on the same surface. In terms
of the current mechanism, cache_child_commits would set synced and
commit would automatically unset synced.
The advantage I see to this approach is that it makes it more clear
that it affects the entire tree and how it does so. Also, it
requires the client to explicitly put any synced commits between a
cached_child_commits and commit which I personally think is cleaner.
The disadvantage is that it is a little less flexible in that you
can't cache single children. However, if you can have "dummy
nodes" (see previous ML posts), this shouldn't be a problem in most
cases.
Again, I don't think this is a show-stopper and I think the protocol
as-is is ok. I just thought it might be worth mentioning.
--Jason Ekstrand
Hi Jason,
isn't this racy? If you want to keep all children always synchronized,
don't you have to re-send cache_child_commits every time you commit the
main surface, and hope that the child components to do not manage to
send a new commit between your commit and cache_child_commits?
We could just rename set_sync and set_desync to something more obvious,
any suggestions?
So the reason for this proposal is to make the protocol more obvious
and easier to understand? Imo, that's a reason to improve
documentation, not to change the protocol. And honestly, I don't think
the design is hard to understand. All my concerns were about
documentation, not about the design. Or what exactly is the "hard to
understand" part of the current design?

Cheers
David
Jason Ekstrand
2013-04-29 18:09:06 UTC
Permalink
Post by Pekka Paalanen
On Sat, 27 Apr 2013 15:18:29 -0500
Post by Jason Ekstrand
Sorry to spam the list, but I had another idea kicking around my head
this weekend that I thought was worth sharing. Please note that I
don't think this is a stopper. I just think it's worth throwing out
there and seeing if others think it's useful.
I don't think there are any major holes in commit behavior in the
current protocol. That said, there seems to be some confusion on
commit modes and how they interact with the tree of subalgebras that
I think is justified. I think this could be (somewhat) solved by the
following simplification. We could replace the explicit commit modes
that then impose commit modes on the children with a single
cache_child_commits request which would begin caching the data for
child surfaces until commit is called on the same surface. In terms
of the current mechanism, cache_child_commits would set synced and
commit would automatically unset synced.
The advantage I see to this approach is that it makes it more clear
that it affects the entire tree and how it does so. Also, it
requires the client to explicitly put any synced commits between a
cached_child_commits and commit which I personally think is cleaner.
The disadvantage is that it is a little less flexible in that you
can't cache single children. However, if you can have "dummy
nodes" (see previous ML posts), this shouldn't be a problem in most
cases.
Again, I don't think this is a show-stopper and I think the protocol
as-is is ok. I just thought it might be worth mentioning.
--Jason Ekstrand
Hi Jason,
isn't this racy? If you want to keep all children always synchronized,
don't you have to re-send cache_child_commits every time you commit the
main surface, and hope that the child components to do not manage to
send a new commit between your commit and cache_child_commits?
I'm not really seeing a race. If you want to keep everything syncronized,
you have to syncronize it anyway. Specifically, the children are going to
wait on the parent commit in some way before they redraw, so they can wait
for the parent to call cache_child_commits again. The case I know of that
gets strange is where you have a parent P and, say, three children C1, C2,
and D where C1 and C2 are synchronized but D is not. My suggestion doesn't
handle that case quite as well, but you can still do it as follows:

C1.cache_child_commits
draw(C1)
C2.cache_child_commits
draw(C2)
P.cache_child_commits
C1.commit
C2.commit
P.commit

I realize that this is a bit more complicated in that case.

We could just rename set_sync and set_desync to something more obvious,
Post by Pekka Paalanen
any suggestions?
I don't know. Maybe set_sync and unset_sync or set_desync is the best way
to go. The "cache children" concept makes more sense in my brain but my
brain can be a strange place some days.
Post by Pekka Paalanen
Thanks,
pq
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130429/ef09e2ec/attachment.html>
Pekka Paalanen
2013-04-29 06:58:05 UTC
Permalink
On Fri, 26 Apr 2013 11:38:12 -0500
On Fri, Apr 26, 2013 at 9:14 AM, Jason Ekstrand
pq,
On Fri, Apr 26, 2013 at 7:09 AM, David Herrmann
Post by David Herrmann
Hi Pekka
...
Post by David Herrmann
Post by Pekka Paalanen
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes
solve my concerns as well. I'm still not sure I'm 100% happy with
how it requires you to build strange trees in certain cases. That
said, I think it looks pretty good and certainly good enough to go
into weston so we can all start playing with it. I'm hoping I can
get sub-surfaces implemented in my java compositor soonish.
Looking good! --Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to
what I meant by "strange trees". Consider an EGL video surface with
SHM CSD (see attachment). To make things more complicated, let's say
that the video surface is controlled by an external library or (worse
yet) is a foreign surface controlled by a different process. Due to
current commit behavior and the way commits cascade, the EGL surface
(which really is the primary surface in this case) can't be used as
the primary surface. Instead, you would have to use one of the
decoration surfaces as the parent surface (again, see the picture).
Thank you, that makes your concern crystal clear.
One solution to this would be to allow some sort of surface that
contains no actual pixels but gets mapped anyway. Right now you can
hack this with a 1x1 transparent buffer or something similar.
However, I would rather not see such hacks common place. One way to
achieve this would be to have some sort of empty buffer that has a
size but no data. Then you could attach one of these empty buffers
to a surface to get it mapped.
I consider a special empty wl_buffer with fake size a hack, just like
the 1x1 completely transparent wl_buffer, except worse, because it needs
more special protocol to create.
The other advantage of an "empty buffer" is that, if it's used as the
main window, could be the full size of the window instead of being
1x1. This simplifies the problem of handling input across
subsurfaces as you can have the parent surface handle them. It would
also allow us (if we wanted) to clip subsurfaces to the parent
surface without causing any major problems. Not that we necessarily
should, but it would leave open the option.
The 1x1 buffer could be scaled to any size with a generic crop & scale
extension, but that of course is yet another optional extension to
depend on.

Let's see how others feel about this, is the awkward surface structure
too bad.

Like I said in another email, I'm starting to think we are now
beginning to accumulate hacks on top of hacks, because the original
design turned out to be not flexible enough. We don't have an abstract
window object in the protocol, which might have provided the sub-surface
functionality in a straightforward way. A wl_window, defining what we
now call the surface-local coordinate system, having no content or size
on its own but attach point for an arbitrary number of wl_surfaces,
being the item for role assignment.... but doing that in the shell level
of the protocol instead of core would be problematic for re-usable
components like video sink elements, since it does not provide nesting
with wl_surfaces.

Or maybe we'd need both... a shell level window object--a replacement
for wl_shell_surface, which would not be just an additional interface
to a wl_surface, but an object on its own right. And then wl_subsurface
that I've been developing, to support shell-agnostic nesting.

*sigh* *shrug*


Cheers,
pq
David Herrmann
2013-04-29 07:40:56 UTC
Permalink
Hi Pekka
Post by Pekka Paalanen
On Fri, 26 Apr 2013 11:38:12 -0500
On Fri, Apr 26, 2013 at 9:14 AM, Jason Ekstrand
pq,
On Fri, Apr 26, 2013 at 7:09 AM, David Herrmann
Post by David Herrmann
Hi Pekka
...
Post by David Herrmann
Post by Pekka Paalanen
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes
solve my concerns as well. I'm still not sure I'm 100% happy with
how it requires you to build strange trees in certain cases. That
said, I think it looks pretty good and certainly good enough to go
into weston so we can all start playing with it. I'm hoping I can
get sub-surfaces implemented in my java compositor soonish.
Looking good! --Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to
what I meant by "strange trees". Consider an EGL video surface with
SHM CSD (see attachment). To make things more complicated, let's say
that the video surface is controlled by an external library or (worse
yet) is a foreign surface controlled by a different process. Due to
current commit behavior and the way commits cascade, the EGL surface
(which really is the primary surface in this case) can't be used as
the primary surface. Instead, you would have to use one of the
decoration surfaces as the parent surface (again, see the picture).
Thank you, that makes your concern crystal clear.
One solution to this would be to allow some sort of surface that
contains no actual pixels but gets mapped anyway. Right now you can
hack this with a 1x1 transparent buffer or something similar.
However, I would rather not see such hacks common place. One way to
achieve this would be to have some sort of empty buffer that has a
size but no data. Then you could attach one of these empty buffers
to a surface to get it mapped.
I consider a special empty wl_buffer with fake size a hack, just like
the 1x1 completely transparent wl_buffer, except worse, because it needs
more special protocol to create.
The other advantage of an "empty buffer" is that, if it's used as the
main window, could be the full size of the window instead of being
1x1. This simplifies the problem of handling input across
subsurfaces as you can have the parent surface handle them. It would
also allow us (if we wanted) to clip subsurfaces to the parent
surface without causing any major problems. Not that we necessarily
should, but it would leave open the option.
The 1x1 buffer could be scaled to any size with a generic crop & scale
extension, but that of course is yet another optional extension to
depend on.
Let's see how others feel about this, is the awkward surface structure
too bad.
Like I said in another email, I'm starting to think we are now
beginning to accumulate hacks on top of hacks, because the original
design turned out to be not flexible enough. We don't have an abstract
window object in the protocol, which might have provided the sub-surface
functionality in a straightforward way. A wl_window, defining what we
now call the surface-local coordinate system, having no content or size
on its own but attach point for an arbitrary number of wl_surfaces,
being the item for role assignment.... but doing that in the shell level
of the protocol instead of core would be problematic for re-usable
components like video sink elements, since it does not provide nesting
with wl_surfaces.
Just as a side-note: a wl_window object doesn't really help here.
Assume you have an independent client library that provides two
sub-surfaces that ought to be displayed right next to each other (like
a video-screen with a video-list-sidebar). You have two choices here:

1) make the video-screen the parent of the sidebar
2) provide two independent sub-surfaces to the caller

Maybe the example isn't chosen well enough, but in my eyes both
solutions seem wrong. Instead, a dummy wl_surface that can be used by
the library as explicit parent would solve this case in a nice way.

And always keep in mind that windows are not static. So in Jason's
case, switching the window to fullscreen causes the decorations to be
hidden and a client would have to de-couple the video-screen from the
parent and instead make it the main-window (while fullscreen).

So a wl_window object would again require a wl_subwindow object and it
would basically just be a wl_surface with a transparent buffer. So why
not add a call to bind a dummy 100% transparent buffer to a surface.
This allows clients to insert invisible parent windows anywhere they
want so it is easier to move/change/restack sub-surfaces. And it
allows compositors to handle these surfaces as special surfaces to
avoid overhead.

Regards
David
Pekka Paalanen
2013-04-29 10:16:05 UTC
Permalink
On Mon, 29 Apr 2013 09:40:56 +0200
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
On Fri, 26 Apr 2013 11:38:12 -0500
On Fri, Apr 26, 2013 at 9:14 AM, Jason Ekstrand
pq,
On Fri, Apr 26, 2013 at 7:09 AM, David Herrmann
Post by David Herrmann
Hi Pekka
...
Post by David Herrmann
Post by Pekka Paalanen
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes
solve my concerns as well. I'm still not sure I'm 100% happy
with how it requires you to build strange trees in certain
cases. That said, I think it looks pretty good and certainly
good enough to go into weston so we can all start playing with
it. I'm hoping I can get sub-surfaces implemented in my java
compositor soonish. Looking good! --Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to
what I meant by "strange trees". Consider an EGL video surface
with SHM CSD (see attachment). To make things more complicated,
let's say that the video surface is controlled by an external
library or (worse yet) is a foreign surface controlled by a
different process. Due to current commit behavior and the way
commits cascade, the EGL surface (which really is the primary
surface in this case) can't be used as the primary surface.
Instead, you would have to use one of the decoration surfaces as
the parent surface (again, see the picture).
Thank you, that makes your concern crystal clear.
One solution to this would be to allow some sort of surface that
contains no actual pixels but gets mapped anyway. Right now you
can hack this with a 1x1 transparent buffer or something similar.
However, I would rather not see such hacks common place. One way
to achieve this would be to have some sort of empty buffer that
has a size but no data. Then you could attach one of these empty
buffers to a surface to get it mapped.
I consider a special empty wl_buffer with fake size a hack, just
like the 1x1 completely transparent wl_buffer, except worse,
because it needs more special protocol to create.
The other advantage of an "empty buffer" is that, if it's used as
the main window, could be the full size of the window instead of
being 1x1. This simplifies the problem of handling input across
subsurfaces as you can have the parent surface handle them. It
would also allow us (if we wanted) to clip subsurfaces to the
parent surface without causing any major problems. Not that we
necessarily should, but it would leave open the option.
The 1x1 buffer could be scaled to any size with a generic crop &
scale extension, but that of course is yet another optional
extension to depend on.
Let's see how others feel about this, is the awkward surface
structure too bad.
Like I said in another email, I'm starting to think we are now
beginning to accumulate hacks on top of hacks, because the original
design turned out to be not flexible enough. We don't have an
abstract window object in the protocol, which might have provided
the sub-surface functionality in a straightforward way. A
wl_window, defining what we now call the surface-local coordinate
system, having no content or size on its own but attach point for
an arbitrary number of wl_surfaces, being the item for role
assignment.... but doing that in the shell level of the protocol
instead of core would be problematic for re-usable components like
video sink elements, since it does not provide nesting with
wl_surfaces.
Just as a side-note: a wl_window object doesn't really help here.
Assume you have an independent client library that provides two
sub-surfaces that ought to be displayed right next to each other (like
1) make the video-screen the parent of the sidebar
2) provide two independent sub-surfaces to the caller
Maybe the example isn't chosen well enough, but in my eyes both
solutions seem wrong. Instead, a dummy wl_surface that can be used by
the library as explicit parent would solve this case in a nice way.
And always keep in mind that windows are not static. So in Jason's
case, switching the window to fullscreen causes the decorations to be
hidden and a client would have to de-couple the video-screen from the
parent and instead make it the main-window (while fullscreen).
So a wl_window object would again require a wl_subwindow object and it
would basically just be a wl_surface with a transparent buffer. So why
not add a call to bind a dummy 100% transparent buffer to a surface.
This allows clients to insert invisible parent windows anywhere they
want so it is easier to move/change/restack sub-surfaces. And it
allows compositors to handle these surfaces as special surfaces to
avoid overhead.
I am sad to realize, that even how ugly it is, dummy wl_surfaces are
probably the best way to deal with it. To organize a tree of surfaces...

For the shell, you want a wl_surface that is not really a surface, so
you can plug and unplug decoration surfaces at will, without giving the
primary commit control to any real surface, that might not be in your
direct control.

So, if we go on that path, could we do without dummy wl_buffers?
Could we just remove all the mapping rules from the sub-surface
protocol? Then the dummy surfaces would not need any wl_buffer. The
problem is, the main surface could not be dummy, as it needs to get
mapped by shell, which is triggered by committing a non-NULL wl_buffer.

I'm starting to question the starting points of this whole design:
independent library components obviously cannot be independent due to
the input protocol, if they ever want input. Trying to make
application<->plugin APIs simpler by complicating the protocol.
Arbitrary nesting with arbitrary and independent actors (components) on
each level.

Maybe we should not have wl_surface.commit, or the implicit
"roles" for wl_surfaces, that create unobvious error cases. We would
have wl_shell_surface.commit; wl_pointer_surface.commit;
wl_drag_surface.commit; The ability to attach an arbitrary number of
surfaces to a wl_shell_surface, and just one to the others. No nesting,
since input does not go to surfaces anyway, it just references them. ...

Have I been solving a right problem to begin with?
Was it incorrect to assume, that the protocol can be extended piece by
piece?
Am I getting too existential? Someone hit me with a large trout. :-p


- pq
Jason Ekstrand
2013-04-29 17:11:56 UTC
Permalink
Post by Pekka Paalanen
On Mon, 29 Apr 2013 09:40:56 +0200
Post by David Herrmann
Hi Pekka
Post by Pekka Paalanen
On Fri, 26 Apr 2013 11:38:12 -0500
On Fri, Apr 26, 2013 at 9:14 AM, Jason Ekstrand
pq,
On Fri, Apr 26, 2013 at 7:09 AM, David Herrmann
Post by David Herrmann
Hi Pekka
...
Post by David Herrmann
Post by Pekka Paalanen
How's this?
http://cgit.collabora.com/git/user/pq/weston.git/commit/?h=subsurface-wip2&id=0dce7236606e0a433390c970560c3050247c4e60&context=6
Post by David Herrmann
Post by Pekka Paalanen
Post by David Herrmann
Perfect, that solves all my concerns.
My thoughts were basically the same as David's and those changes
solve my concerns as well. I'm still not sure I'm 100% happy
with how it requires you to build strange trees in certain
cases. That said, I think it looks pretty good and certainly
good enough to go into weston so we can all start playing with
it. I'm hoping I can get sub-surfaces implemented in my java
compositor soonish. Looking good! --Jason Ekstrand
Pekka asked me to send an e-mail with a little clarification as to
what I meant by "strange trees". Consider an EGL video surface
with SHM CSD (see attachment). To make things more complicated,
let's say that the video surface is controlled by an external
library or (worse yet) is a foreign surface controlled by a
different process. Due to current commit behavior and the way
commits cascade, the EGL surface (which really is the primary
surface in this case) can't be used as the primary surface.
Instead, you would have to use one of the decoration surfaces as
the parent surface (again, see the picture).
Thank you, that makes your concern crystal clear.
One solution to this would be to allow some sort of surface that
contains no actual pixels but gets mapped anyway. Right now you
can hack this with a 1x1 transparent buffer or something similar.
However, I would rather not see such hacks common place. One way
to achieve this would be to have some sort of empty buffer that
has a size but no data. Then you could attach one of these empty
buffers to a surface to get it mapped.
I consider a special empty wl_buffer with fake size a hack, just
like the 1x1 completely transparent wl_buffer, except worse,
because it needs more special protocol to create.
The other advantage of an "empty buffer" is that, if it's used as
the main window, could be the full size of the window instead of
being 1x1. This simplifies the problem of handling input across
subsurfaces as you can have the parent surface handle them. It
would also allow us (if we wanted) to clip subsurfaces to the
parent surface without causing any major problems. Not that we
necessarily should, but it would leave open the option.
The 1x1 buffer could be scaled to any size with a generic crop &
scale extension, but that of course is yet another optional
extension to depend on.
Let's see how others feel about this, is the awkward surface
structure too bad.
Like I said in another email, I'm starting to think we are now
beginning to accumulate hacks on top of hacks, because the original
design turned out to be not flexible enough. We don't have an
abstract window object in the protocol, which might have provided
the sub-surface functionality in a straightforward way. A
wl_window, defining what we now call the surface-local coordinate
system, having no content or size on its own but attach point for
an arbitrary number of wl_surfaces, being the item for role
assignment.... but doing that in the shell level of the protocol
instead of core would be problematic for re-usable components like
video sink elements, since it does not provide nesting with
wl_surfaces.
Just as a side-note: a wl_window object doesn't really help here.
Assume you have an independent client library that provides two
sub-surfaces that ought to be displayed right next to each other (like
1) make the video-screen the parent of the sidebar
2) provide two independent sub-surfaces to the caller
Maybe the example isn't chosen well enough, but in my eyes both
solutions seem wrong. Instead, a dummy wl_surface that can be used by
the library as explicit parent would solve this case in a nice way.
And always keep in mind that windows are not static. So in Jason's
case, switching the window to fullscreen causes the decorations to be
hidden and a client would have to de-couple the video-screen from the
parent and instead make it the main-window (while fullscreen).
So a wl_window object would again require a wl_subwindow object and it
would basically just be a wl_surface with a transparent buffer. So why
not add a call to bind a dummy 100% transparent buffer to a surface.
This allows clients to insert invisible parent windows anywhere they
want so it is easier to move/change/restack sub-surfaces. And it
allows compositors to handle these surfaces as special surfaces to
avoid overhead.
I am sad to realize, that even how ugly it is, dummy wl_surfaces are
probably the best way to deal with it. To organize a tree of surfaces...
For the shell, you want a wl_surface that is not really a surface, so
you can plug and unplug decoration surfaces at will, without giving the
primary commit control to any real surface, that might not be in your
direct control.
So, if we go on that path, could we do without dummy wl_buffers?
Could we just remove all the mapping rules from the sub-surface
protocol? Then the dummy surfaces would not need any wl_buffer. The
problem is, the main surface could not be dummy, as it needs to get
mapped by shell, which is triggered by committing a non-NULL wl_buffer.
Really, I think what we need to fix this problem is simply an invisible
surface. I don't really care if this is implemented with an invisible
buffer or with some sort of "map without buffer" or "attach invisible"
request on wl_surface. There are a lot of different ways to do it, but
they all achieve the same thing. I don't really want to do any of this 1x1
stuff because that will make a hack a standard code path.
Post by Pekka Paalanen
independent library components obviously cannot be independent due to
the input protocol, if they ever want input. Trying to make
application<->plugin APIs simpler by complicating the protocol.
Arbitrary nesting with arbitrary and independent actors (components) on
each level.
Yeah, we may want to just say that the main surface gets all the input
(That means it has to be full-sized, but if we have invisible surfaces,
that's not a problem). Then we can just let the client make the input
cascade (the toolkits all do anyway).
Post by Pekka Paalanen
Maybe we should not have wl_surface.commit, or the implicit
"roles" for wl_surfaces, that create unobvious error cases. We would
have wl_shell_surface.commit; wl_pointer_surface.commit;
wl_drag_surface.commit; The ability to attach an arbitrary number of
surfaces to a wl_shell_surface, and just one to the others. No nesting,
since input does not go to surfaces anyway, it just references them. ...
Have I been solving a right problem to begin with?
Yes, but perhaps we should only bother solving it for visual output, not
input. That might make it more sensible.
Post by Pekka Paalanen
Was it incorrect to assume, that the protocol can be extended piece by
piece?
I actually think your "roles" concept is great. As long as we restrict to
one role per surface, it should work ok.
Post by Pekka Paalanen
Am I getting too existential? Someone hit me with a large trout. :-p
Yes, you are. Done (see IRC).

Thanks,
--Jason Ekstrand
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130429/1b6db7c6/attachment.html>
Pekka Paalanen
2013-04-25 10:57:42 UTC
Permalink
Implement the basic protocol for sub-surfaces:
- expose wl_subcompositor global interface
- error checking on protocol calls
- associate a parent wl_surface to a sub-surface
- introduce the sub-surface role, which is exclusive
- an implementation of the wl_subsurface interface
- allow nesting of sub-surfaces
- proper surface transformation inheritance from parent to sub-surfaces
- two different modes of wl_surface.commit for sub-surfaces
- hook sub-surfaces up to repaint by modifying the repaint list code

Struct weston_subsurface is dynamically allocated. For sub-surfaces, it
is completely populated.

For parent surfaces, weston_subsurface acts only as a link for stacking
order purposes. The wl_resource is unused, parent_destroy_listener is
not registered, the transform is not linked, etc.

Sub-surfaces are not added directly into layers for display or input.
Instead, they are hooked up via the sub-surface list present in parent
weston_surface. This way sub-surfaces are inherently linked to the
parent surface, and cannot be displayed unless the parent is mapped,
too. This also eases restacking, as only the parent will be in a layer
list. Also, only the main surface should be subject to shell actions.

The surface list rebuilding in weston_output_repaint() is modified to
process sub-surface lists, if they are non-empty. The sub-surface list
always contains the parent, too, unless empty. The collection of
frame_callback_list is moved to a later loop, to streamline the surface
list rebuild functions.

Features still lacking are:
- full-surface alpha support for compound windows

Changes in v2:
- fix a bug in surface mapping: commit a sub-surface would cause the
main surface to never be mapped.
- remove debug printfs
- detect attempt of making a surface its own parent
- always zero-alloc weston_subsurface
- apply wl_subsurface.set_position in commit, not immediately
- add weston_surface_to_subsurface()
- implement sub-surface commit modes parent-cached and independent
- implement wl_subcompositor.destroy and wl_subsurface.destroy

Changes in v3:
- rebased, and use the new transform inheritance code
- squashed the commit "add sub-surfaces to repaint list"
- fixed a buffer reference leak in commit_from_cache()
- Rewrite the sub-surface destructor code, and make it leave the
wl_subsurface protocol object inert, if one destroys the corresponding
wl_surface.
- replaced set_commit_mode with set_sync and set_desync
- allowed sub-surface nesting, and fixed repaint accordingly
- implemented nested sub-surface commit modes
- Made the sub-surface order changes from wl_subsurface.place_above and
.place_below to be applied when the parent surface state is applied,
instead of immediately. This conforms with the protocol specification
now.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/compositor.c | 736 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
src/compositor.h | 54 ++++
2 files changed, 774 insertions(+), 16 deletions(-)

diff --git a/src/compositor.c b/src/compositor.c
index 693df2c..000f6bf 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -262,6 +262,9 @@ region_init_infinite(pixman_region32_t *region)
UINT32_MAX, UINT32_MAX);
}

+static struct weston_subsurface *
+weston_surface_to_subsurface(struct weston_surface *surface);
+
WL_EXPORT struct weston_surface *
weston_surface_create(struct weston_compositor *compositor)
{
@@ -314,6 +317,9 @@ weston_surface_create(struct weston_compositor *compositor)
region_init_infinite(&surface->pending.input);
wl_list_init(&surface->pending.frame_callback_list);

+ wl_list_init(&surface->subsurface_list);
+ wl_list_init(&surface->subsurface_list_pending);
+
return surface;
}

@@ -1008,6 +1014,8 @@ destroy_surface(struct wl_resource *resource)
struct weston_frame_callback *cb, *next;

assert(wl_list_empty(&surface->geometry.child_list));
+ assert(wl_list_empty(&surface->subsurface_list_pending));
+ assert(wl_list_empty(&surface->subsurface_list));

if (weston_surface_is_mapped(surface))
weston_surface_unmap(surface);
@@ -1205,11 +1213,50 @@ compositor_accumulate_damage(struct weston_compositor *ec)
}

static void
+surface_list_add(struct weston_compositor *compositor,
+ struct weston_surface *surface)
+{
+ struct weston_subsurface *sub;
+
+ if (wl_list_empty(&surface->subsurface_list)) {
+ weston_surface_update_transform(surface);
+ wl_list_insert(compositor->surface_list.prev, &surface->link);
+ return;
+ }
+
+ wl_list_for_each(sub, &surface->subsurface_list, parent_link) {
+ if (!weston_surface_is_mapped(sub->surface))
+ continue;
+
+ if (sub->surface == surface) {
+ weston_surface_update_transform(sub->surface);
+ wl_list_insert(compositor->surface_list.prev,
+ &sub->surface->link);
+ } else {
+ surface_list_add(compositor, sub->surface);
+ }
+ }
+}
+
+static void
+weston_compositor_build_surface_list(struct weston_compositor *compositor)
+{
+ struct weston_surface *surface;
+ struct weston_layer *layer;
+
+ wl_list_init(&compositor->surface_list);
+ wl_list_for_each(layer, &compositor->layer_list, link) {
+ wl_list_for_each(surface, &layer->surface_list, layer_link) {
+ surface_list_add(compositor, surface);
+ }
+ }
+}
+
+static void
weston_output_repaint(struct weston_output *output, uint32_t msecs)
{
struct weston_compositor *ec = output->compositor;
struct weston_surface *es;
- struct weston_layer *layer;
struct weston_animation *animation, *next;
struct weston_frame_callback *cb, *cnext;
struct wl_list frame_callback_list;
@@ -1218,19 +1265,7 @@ weston_output_repaint(struct weston_output *output, uint32_t msecs)
weston_compositor_update_drag_surfaces(ec);

/* Rebuild the surface list and update surface transforms up front. */
- wl_list_init(&ec->surface_list);
- wl_list_init(&frame_callback_list);
- wl_list_for_each(layer, &ec->layer_list, link) {
- wl_list_for_each(es, &layer->surface_list, layer_link) {
- weston_surface_update_transform(es);
- wl_list_insert(ec->surface_list.prev, &es->link);
- if (es->output == output) {
- wl_list_insert_list(&frame_callback_list,
- &es->frame_callback_list);
- wl_list_init(&es->frame_callback_list);
- }
- }
- }
+ weston_compositor_build_surface_list(ec);

if (output->assign_planes && !output->disable_planes)
output->assign_planes(output);
@@ -1238,6 +1273,15 @@ weston_output_repaint(struct weston_output *output, uint32_t msecs)
wl_list_for_each(es, &ec->surface_list, link)
weston_surface_move_to_plane(es, &ec->primary_plane);

+ wl_list_init(&frame_callback_list);
+ wl_list_for_each(es, &ec->surface_list, link) {
+ if (es->output == output) {
+ wl_list_insert_list(&frame_callback_list,
+ &es->frame_callback_list);
+ wl_list_init(&es->frame_callback_list);
+ }
+ }
+
compositor_accumulate_damage(ec);

pixman_region32_init(&output_damage);
@@ -1464,9 +1508,20 @@ surface_set_input_region(struct wl_client *client,
}

static void
-surface_commit(struct wl_client *client, struct wl_resource *resource)
+weston_surface_commit_subsurface_order(struct weston_surface *surface)
+{
+ struct weston_subsurface *sub;
+
+ wl_list_for_each_reverse(sub, &surface->subsurface_list_pending,
+ parent_link_pending) {
+ wl_list_remove(&sub->parent_link);
+ wl_list_insert(&surface->subsurface_list, &sub->parent_link);
+ }
+}
+
+static void
+weston_surface_commit(struct weston_surface *surface)
{
- struct weston_surface *surface = resource->data;
pixman_region32_t opaque;
int buffer_width = 0;
int buffer_height = 0;
@@ -1531,10 +1586,38 @@ surface_commit(struct wl_client *client, struct wl_resource *resource)
&surface->pending.frame_callback_list);
wl_list_init(&surface->pending.frame_callback_list);

+ weston_surface_commit_subsurface_order(surface);
+
weston_surface_schedule_repaint(surface);
}

static void
+weston_subsurface_commit(struct weston_subsurface *sub);
+
+static void
+weston_subsurface_parent_commit(struct weston_subsurface *sub,
+ int parent_is_synchronized);
+
+static void
+surface_commit(struct wl_client *client, struct wl_resource *resource)
+{
+ struct weston_surface *surface = resource->data;
+ struct weston_subsurface *sub = weston_surface_to_subsurface(surface);
+
+ if (sub) {
+ weston_subsurface_commit(sub);
+ return;
+ }
+
+ weston_surface_commit(surface);
+
+ wl_list_for_each(sub, &surface->subsurface_list, parent_link) {
+ if (sub->surface != surface)
+ weston_subsurface_parent_commit(sub, 0);
+ }
+}
+
+static void
surface_set_buffer_transform(struct wl_client *client,
struct wl_resource *resource, int transform)
{
@@ -1653,6 +1736,623 @@ static const struct wl_compositor_interface compositor_interface = {
};

static void
+weston_subsurface_commit_from_cache(struct weston_subsurface *sub)
+{
+ struct weston_surface *surface = sub->surface;
+ pixman_region32_t opaque;
+ int buffer_width = 0;
+ int buffer_height = 0;
+
+ /* wl_surface.set_buffer_rotation */
+ surface->buffer_transform = sub->cached.buffer_transform;
+
+ /* wl_surface.attach */
+ if (sub->cached.buffer_ref.buffer || sub->cached.newly_attached)
+ weston_surface_attach(surface, sub->cached.buffer_ref.buffer);
+ weston_buffer_reference(&sub->cached.buffer_ref, NULL);
+
+ if (surface->buffer_ref.buffer) {
+ buffer_width = weston_surface_buffer_width(surface);
+ buffer_height = weston_surface_buffer_height(surface);
+ }
+
+ if (surface->configure && sub->cached.newly_attached)
+ surface->configure(surface, sub->cached.sx, sub->cached.sy,
+ buffer_width, buffer_height);
+ sub->cached.sx = 0;
+ sub->cached.sy = 0;
+ sub->cached.newly_attached = 0;
+
+ /* wl_surface.damage */
+ pixman_region32_union(&surface->damage, &surface->damage,
+ &sub->cached.damage);
+ pixman_region32_intersect_rect(&surface->damage, &surface->damage,
+ 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+ empty_region(&sub->cached.damage);
+
+ /* wl_surface.set_opaque_region */
+ pixman_region32_init_rect(&opaque, 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+ pixman_region32_intersect(&opaque,
+ &opaque, &sub->cached.opaque);
+
+ if (!pixman_region32_equal(&opaque, &surface->opaque)) {
+ pixman_region32_copy(&surface->opaque, &opaque);
+ weston_surface_geometry_dirty(surface);
+ }
+
+ pixman_region32_fini(&opaque);
+
+ /* wl_surface.set_input_region */
+ pixman_region32_fini(&surface->input);
+ pixman_region32_init_rect(&surface->input, 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+ pixman_region32_intersect(&surface->input,
+ &surface->input, &sub->cached.input);
+
+ /* wl_surface.frame */
+ wl_list_insert_list(&surface->frame_callback_list,
+ &sub->cached.frame_callback_list);
+ wl_list_init(&sub->cached.frame_callback_list);
+
+ weston_surface_commit_subsurface_order(surface);
+
+ weston_surface_schedule_repaint(surface);
+
+ sub->cached.has_data = 0;
+}
+
+static void
+weston_subsurface_commit_to_cache(struct weston_subsurface *sub)
+{
+ struct weston_surface *surface = sub->surface;
+
+ /*
+ * If this commit would cause the surface to move by the
+ * attach(dx, dy) parameters, the old damage region must be
+ * translated to correspond to the new surface coordinate system
+ * origin.
+ */
+ pixman_region32_translate(&sub->cached.damage,
+ -surface->pending.sx, -surface->pending.sy);
+ pixman_region32_union(&sub->cached.damage, &sub->cached.damage,
+ &surface->pending.damage);
+ empty_region(&surface->pending.damage);
+
+ if (surface->pending.newly_attached) {
+ sub->cached.newly_attached = 1;
+ weston_buffer_reference(&sub->cached.buffer_ref,
+ surface->pending.buffer);
+ }
+ sub->cached.sx += surface->pending.sx;
+ sub->cached.sy += surface->pending.sy;
+ surface->pending.sx = 0;
+ surface->pending.sy = 0;
+ surface->pending.newly_attached = 0;
+
+ sub->cached.buffer_transform = surface->pending.buffer_transform;
+
+ pixman_region32_copy(&sub->cached.opaque, &surface->pending.opaque);
+
+ pixman_region32_copy(&sub->cached.input, &surface->pending.input);
+
+ wl_list_insert_list(&sub->cached.frame_callback_list,
+ &surface->pending.frame_callback_list);
+ wl_list_init(&surface->pending.frame_callback_list);
+
+ sub->cached.has_data = 1;
+}
+
+static int
+weston_subsurface_is_synchronized(struct weston_subsurface *sub)
+{
+ while (sub) {
+ if (sub->synchronized)
+ return 1;
+
+ if (!sub->parent)
+ return 0;
+
+ sub = weston_surface_to_subsurface(sub->parent);
+ }
+
+ return 0;
+}
+
+static void
+weston_subsurface_commit(struct weston_subsurface *sub)
+{
+ struct weston_surface *surface = sub->surface;
+ struct weston_subsurface *tmp;
+
+ /* Recursive check for effectively synchronized. */
+ if (weston_subsurface_is_synchronized(sub)) {
+ weston_subsurface_commit_to_cache(sub);
+ } else {
+ if (sub->cached.has_data) {
+ /* flush accumulated state from cache */
+ weston_subsurface_commit_to_cache(sub);
+ weston_subsurface_commit_from_cache(sub);
+ } else {
+ weston_surface_commit(surface);
+ }
+
+ wl_list_for_each(tmp, &surface->subsurface_list, parent_link) {
+ if (tmp->surface != surface)
+ weston_subsurface_parent_commit(tmp, 0);
+ }
+ }
+}
+
+static void
+weston_subsurface_parent_commit(struct weston_subsurface *sub,
+ int parent_is_synchronized)
+{
+ struct weston_surface *surface = sub->surface;
+ struct weston_subsurface *tmp;
+
+ if (sub->position.set) {
+ weston_surface_set_position(sub->surface,
+ sub->position.x, sub->position.y);
+ sub->position.set = 0;
+ }
+
+ if (!parent_is_synchronized && !sub->synchronized)
+ return;
+
+ /* From now on, commit_from_cache the whole sub-tree, regardless of
+ * the synchronized mode of each child. This sub-surface or some
+ * of its ancestors were synchronized, so we are synchronized
+ * all the way down.
+ */
+
+ if (sub->cached.has_data)
+ weston_subsurface_commit_from_cache(sub);
+
+ wl_list_for_each(tmp, &surface->subsurface_list, parent_link) {
+ if (tmp->surface != surface)
+ weston_subsurface_parent_commit(tmp, 1);
+ }
+}
+
+static void
+subsurface_configure(struct weston_surface *surface, int32_t dx, int32_t dy,
+ int32_t width, int32_t height)
+{
+ struct weston_compositor *compositor = surface->compositor;
+
+ weston_surface_configure(surface,
+ surface->geometry.x + dx,
+ surface->geometry.y + dy,
+ width, height);
+
+ /* No need to check parent mappedness, because if parent is not
+ * mapped, parent is not in a visible layer, so this sub-surface
+ * will not be drawn either.
+ */
+ if (!weston_surface_is_mapped(surface)) {
+ wl_list_init(&surface->layer_link);
+
+ /* Cannot call weston_surface_update_transform(),
+ * because that would call it also for the parent surface,
+ * which might not be mapped yet. That would lead to
+ * inconsistent state, where the window could never be
+ * mapped.
+ *
+ * Instead just assing any output, to make
+ * weston_surface_is_mapped() return true, so that when the
+ * parent surface does get mapped, this one will get
+ * included, too. See surface_list_add().
+ */
+ assert(!wl_list_empty(&compositor->output_list));
+ surface->output = container_of(compositor->output_list.next,
+ struct weston_output, link);
+ }
+}
+
+static struct weston_subsurface *
+weston_surface_to_subsurface(struct weston_surface *surface)
+{
+ if (surface->configure == subsurface_configure)
+ return surface->configure_private;
+
+ return NULL;
+}
+
+static void
+subsurface_set_position(struct wl_client *client,
+ struct wl_resource *resource, int32_t x, int32_t y)
+{
+ struct weston_subsurface *sub = resource->data;
+
+ if (!sub)
+ return;
+
+ sub->position.x = x;
+ sub->position.y = y;
+ sub->position.set = 1;
+}
+
+static struct weston_subsurface *
+subsurface_from_surface(struct weston_surface *surface)
+{
+ struct weston_subsurface *sub;
+
+ sub = weston_surface_to_subsurface(surface);
+ if (sub)
+ return sub;
+
+ wl_list_for_each(sub, &surface->subsurface_list, parent_link)
+ if (sub->surface == surface)
+ return sub;
+
+ return NULL;
+}
+
+static struct weston_subsurface *
+subsurface_sibling_check(struct weston_subsurface *sub,
+ struct weston_surface *surface,
+ const char *request)
+{
+ struct weston_subsurface *sibling;
+
+ sibling = subsurface_from_surface(surface);
+
+ if (!sibling) {
+ wl_resource_post_error(sub->resource,
+ WL_SUBSURFACE_ERROR_BAD_SURFACE,
+ "%s: wl_surface@%d is not a parent or sibling",
+ request, surface->surface.resource.object.id);
+ return NULL;
+ }
+
+ if (sibling->parent != sub->parent) {
+ wl_resource_post_error(sub->resource,
+ WL_SUBSURFACE_ERROR_BAD_SURFACE,
+ "%s: wl_surface@%d has a different parent",
+ request, surface->surface.resource.object.id);
+ return NULL;
+ }
+
+ return sibling;
+}
+
+static void
+subsurface_place_above(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *sibling_resource)
+{
+ struct weston_subsurface *sub = resource->data;
+ struct weston_surface *surface = sibling_resource->data;
+ struct weston_subsurface *sibling;
+
+ if (!sub)
+ return;
+
+ sibling = subsurface_sibling_check(sub, surface, "place_above");
+ if (!sibling)
+ return;
+
+ wl_list_remove(&sub->parent_link_pending);
+ wl_list_insert(sibling->parent_link_pending.prev,
+ &sub->parent_link_pending);
+}
+
+static void
+subsurface_place_below(struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *sibling_resource)
+{
+ struct weston_subsurface *sub = resource->data;
+ struct weston_surface *surface = sibling_resource->data;
+ struct weston_subsurface *sibling;
+
+ if (!sub)
+ return;
+
+ sibling = subsurface_sibling_check(sub, surface, "place_below");
+ if (!sibling)
+ return;
+
+ wl_list_remove(&sub->parent_link_pending);
+ wl_list_insert(&sibling->parent_link_pending,
+ &sub->parent_link_pending);
+}
+
+static void
+subsurface_set_sync(struct wl_client *client, struct wl_resource *resource)
+{
+ struct weston_subsurface *sub = resource->data;
+
+ if (sub)
+ sub->synchronized = 1;
+}
+
+static void
+subsurface_set_desync(struct wl_client *client, struct wl_resource *resource)
+{
+ struct weston_subsurface *sub = resource->data;
+
+ if (sub)
+ sub->synchronized = 0;
+}
+
+static void
+weston_subsurface_cache_init(struct weston_subsurface *sub)
+{
+ pixman_region32_init(&sub->cached.damage);
+ pixman_region32_init(&sub->cached.opaque);
+ pixman_region32_init(&sub->cached.input);
+ wl_list_init(&sub->cached.frame_callback_list);
+ sub->cached.buffer_ref.buffer = NULL;
+}
+
+static void
+weston_subsurface_cache_fini(struct weston_subsurface *sub)
+{
+ struct weston_frame_callback *cb, *tmp;
+
+ wl_list_for_each_safe(cb, tmp, &sub->cached.frame_callback_list, link)
+ wl_resource_destroy(&cb->resource);
+
+ weston_buffer_reference(&sub->cached.buffer_ref, NULL);
+ pixman_region32_fini(&sub->cached.damage);
+ pixman_region32_fini(&sub->cached.opaque);
+ pixman_region32_fini(&sub->cached.input);
+}
+
+static void
+weston_subsurface_unlink_parent(struct weston_subsurface *sub)
+{
+ wl_list_remove(&sub->parent_link);
+ wl_list_remove(&sub->parent_link_pending);
+ wl_list_remove(&sub->parent_destroy_listener.link);
+ sub->parent = NULL;
+}
+
+static void
+weston_subsurface_destroy(struct weston_subsurface *sub);
+
+static void
+subsurface_handle_surface_destroy(struct wl_listener *listener, void *data)
+{
+ struct weston_subsurface *sub =
+ container_of(listener, struct weston_subsurface,
+ surface_destroy_listener);
+ assert(data == &sub->surface->surface.resource);
+
+ /* The protocol object (wl_resource) is left inert. */
+ if (sub->resource)
+ sub->resource->data = NULL;
+
+ weston_subsurface_destroy(sub);
+}
+
+static void
+subsurface_handle_parent_destroy(struct wl_listener *listener, void *data)
+{
+ struct weston_subsurface *sub =
+ container_of(listener, struct weston_subsurface,
+ parent_destroy_listener);
+ assert(data == &sub->parent->surface.resource);
+ assert(sub->surface != sub->parent);
+
+ if (weston_surface_is_mapped(sub->surface))
+ weston_surface_unmap(sub->surface);
+
+ weston_subsurface_unlink_parent(sub);
+}
+
+static void
+subsurface_resource_destroy(struct wl_resource *resource)
+{
+ struct weston_subsurface *sub = resource->data;
+
+ if (sub)
+ weston_subsurface_destroy(sub);
+
+ free(resource);
+}
+
+static void
+subsurface_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static void
+weston_subsurface_link_parent(struct weston_subsurface *sub,
+ struct weston_surface *parent)
+{
+ sub->parent = parent;
+ sub->parent_destroy_listener.notify = subsurface_handle_parent_destroy;
+ wl_signal_add(&parent->surface.resource.destroy_signal,
+ &sub->parent_destroy_listener);
+
+ wl_list_insert(&parent->subsurface_list, &sub->parent_link);
+ wl_list_insert(&parent->subsurface_list_pending,
+ &sub->parent_link_pending);
+}
+
+static void
+weston_subsurface_link_surface(struct weston_subsurface *sub,
+ struct weston_surface *surface)
+{
+ sub->surface = surface;
+ sub->surface_destroy_listener.notify =
+ subsurface_handle_surface_destroy;
+ wl_signal_add(&surface->surface.resource.destroy_signal,
+ &sub->surface_destroy_listener);
+}
+
+static void
+weston_subsurface_destroy(struct weston_subsurface *sub)
+{
+ assert(sub->surface);
+
+ if (sub->resource) {
+ assert(weston_surface_to_subsurface(sub->surface) == sub);
+ assert(sub->parent_destroy_listener.notify ==
+ subsurface_handle_parent_destroy);
+
+ weston_surface_set_transform_parent(sub->surface, NULL);
+ if (sub->parent)
+ weston_subsurface_unlink_parent(sub);
+
+ weston_subsurface_cache_fini(sub);
+
+ sub->surface->configure = NULL;
+ sub->surface->configure_private = NULL;
+ } else {
+ /* the dummy weston_subsurface for the parent itself */
+ assert(sub->parent_destroy_listener.notify == NULL);
+ wl_list_remove(&sub->parent_link);
+ wl_list_remove(&sub->parent_link_pending);
+ }
+
+ wl_list_remove(&sub->surface_destroy_listener.link);
+ free(sub);
+}
+
+static const struct wl_subsurface_interface subsurface_implementation = {
+ subsurface_destroy,
+ subsurface_set_position,
+ subsurface_place_above,
+ subsurface_place_below,
+ subsurface_set_sync,
+ subsurface_set_desync
+};
+
+static struct weston_subsurface *
+weston_subsurface_create(uint32_t id, struct weston_surface *surface,
+ struct weston_surface *parent)
+{
+ struct weston_subsurface *sub;
+
+ sub = calloc(1, sizeof *sub);
+ if (!sub)
+ return NULL;
+
+ sub->resource = wl_client_add_object(surface->surface.resource.client,
+ &wl_subsurface_interface,
+ &subsurface_implementation,
+ id, sub);
+ if (!sub->resource) {
+ free(sub);
+ return NULL;
+ }
+
+ sub->resource->destroy = subsurface_resource_destroy;
+ weston_subsurface_link_surface(sub, surface);
+ weston_subsurface_link_parent(sub, parent);
+ weston_subsurface_cache_init(sub);
+ sub->synchronized = 1;
+ weston_surface_set_transform_parent(surface, parent);
+
+ return sub;
+}
+
+/* Create a dummy subsurface for having the parent itself in its
+ * sub-surface lists. Makes stacking order manipulation easy.
+ */
+static struct weston_subsurface *
+weston_subsurface_create_for_parent(struct weston_surface *parent)
+{
+ struct weston_subsurface *sub;
+
+ sub = calloc(1, sizeof *sub);
+ if (!sub)
+ return NULL;
+
+ weston_subsurface_link_surface(sub, parent);
+ sub->parent = parent;
+ wl_list_insert(&parent->subsurface_list, &sub->parent_link);
+ wl_list_insert(&parent->subsurface_list_pending,
+ &sub->parent_link_pending);
+
+ return sub;
+}
+
+static void
+subcompositor_get_subsurface(struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t id,
+ struct wl_resource *surface_resource,
+ struct wl_resource *parent_resource)
+{
+ struct weston_surface *surface = surface_resource->data;
+ struct weston_surface *parent = parent_resource->data;
+ struct weston_subsurface *sub;
+ static const char where[] = "get_subsurface: wl_subsurface@";
+
+ if (surface == parent) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d cannot be its own parent",
+ where, id, surface_resource->object.id);
+ return;
+ }
+
+ if (weston_surface_to_subsurface(surface)) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d is already a sub-surface",
+ where, id, surface_resource->object.id);
+ return;
+ }
+
+ if (surface->configure) {
+ wl_resource_post_error(resource,
+ WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+ "%s%d: wl_surface@%d already has a role",
+ where, id, surface_resource->object.id);
+ return;
+ }
+
+ /* make sure the parent is in its own list */
+ if (wl_list_empty(&parent->subsurface_list)) {
+ if (!weston_subsurface_create_for_parent(parent)) {
+ wl_resource_post_no_memory(resource);
+ return;
+ }
+ }
+
+ sub = weston_subsurface_create(id, surface, parent);
+ if (!sub) {
+ wl_resource_post_no_memory(resource);
+ return;
+ }
+
+ surface->configure = subsurface_configure;
+ surface->configure_private = sub;
+}
+
+static void
+subcompositor_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy(resource);
+}
+
+static const struct wl_subcompositor_interface subcompositor_interface = {
+ subcompositor_destroy,
+ subcompositor_get_subsurface
+};
+
+static void
+bind_subcompositor(struct wl_client *client,
+ void *data, uint32_t version, uint32_t id)
+{
+ struct weston_compositor *compositor = data;
+
+ wl_client_add_object(client, &wl_subcompositor_interface,
+ &subcompositor_interface, id, compositor);
+}
+
+static void
weston_compositor_dpms(struct weston_compositor *compositor,
enum dpms_enum state)
{
@@ -3159,6 +3859,10 @@ weston_compositor_init(struct weston_compositor *ec,
ec, compositor_bind))
return -1;

+ if (!wl_display_add_global(display, &wl_subcompositor_interface,
+ ec, bind_subcompositor))
+ return -1;
+
wl_list_init(&ec->surface_list);
wl_list_init(&ec->plane_list);
wl_list_init(&ec->layer_list);
diff --git a/src/compositor.h b/src/compositor.h
index 0ef097b..f102a4a 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -373,6 +373,53 @@ struct weston_region {
pixman_region32_t region;
};

+struct weston_subsurface {
+ struct wl_resource *resource;
+
+ /* guaranteed to be valid and non-NULL */
+ struct weston_surface *surface;
+ struct wl_listener surface_destroy_listener;
+
+ /* can be NULL */
+ struct weston_surface *parent;
+ struct wl_listener parent_destroy_listener;
+ struct wl_list parent_link;
+ struct wl_list parent_link_pending;
+
+ struct {
+ int32_t x;
+ int32_t y;
+ int set;
+ } position;
+
+ struct {
+ int has_data;
+
+ /* wl_surface.attach */
+ int newly_attached;
+ struct weston_buffer_reference buffer_ref;
+ int32_t sx;
+ int32_t sy;
+
+ /* wl_surface.damage */
+ pixman_region32_t damage;
+
+ /* wl_surface.set_opaque_region */
+ pixman_region32_t opaque;
+
+ /* wl_surface.set_input_region */
+ pixman_region32_t input;
+
+ /* wl_surface.frame */
+ struct wl_list frame_callback_list;
+
+ /* wl_surface.set_buffer_transform */
+ uint32_t buffer_transform;
+ } cached;
+
+ int synchronized;
+};
+
/* Using weston_surface transformations
*
* To add a transformation to a surface, create a struct weston_transform, and
@@ -505,6 +552,13 @@ struct weston_surface {
*/
void (*configure)(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height);
void *configure_private;
+
+ /* Parent's list of its sub-surfaces, weston_subsurface:parent_link.
+ * Contains also the parent itself as a dummy weston_subsurface,
+ * if the list is not empty.
+ */
+ struct wl_list subsurface_list; /* weston_subsurface::parent_link */
+ struct wl_list subsurface_list_pending; /* ...::parent_link_pending */
};

enum weston_key_state_update {
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:43 UTC
Permalink
For testing the protocol behaviour only:
- linking a surface to a parent does not fail
- position and placement requests do not fail
- bad linking and arguments do fail
- passing a surface as a sibling from a different set fails
- different destruction sequences do not crash
- setting a surface as its own parent fails
- nesting succeeds

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
tests/.gitignore | 1 +
tests/Makefile.am | 5 +-
tests/subsurface-test.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 330 insertions(+), 1 deletion(-)
create mode 100644 tests/subsurface-test.c

diff --git a/tests/.gitignore b/tests/.gitignore
index fa19888..fac1f7b 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,3 +13,4 @@ button-test
xwayland-test
subsurface-client-protocol.h
subsurface-protocol.c
+subsurface-test
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a42a1fc..9f711cf 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -9,6 +9,7 @@ weston_tests = \
event-test \
button-test \
text-test \
+ subsurface-test \
$(xwayland_test)

AM_TESTS_ENVIRONMENT = \
@@ -86,8 +87,10 @@ text_test_SOURCES = \
$(weston_test_client_src)
text_test_LDADD = $(weston_test_client_libs)

-xwayland_test_SOURCES = xwayland-test.c $(weston_test_client_src)
+subsurface_test_SOURCES = subsurface-test.c $(weston_test_client_src)
+subsurface_test_LDADD = $(weston_test_client_libs)

+xwayland_test_SOURCES = xwayland-test.c $(weston_test_client_src)
xwayland_test_LDADD = $(weston_test_client_libs) $(XWAYLAND_TEST_LIBS)

if ENABLE_XWAYLAND_TEST
diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c
new file mode 100644
index 0000000..8f1fd14
--- /dev/null
+++ b/tests/subsurface-test.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright ? 2012 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "weston-test-client-helper.h"
+#include "subsurface-client-protocol.h"
+
+#define NUM_SUBSURFACES 3
+
+struct compound_surface {
+ struct wl_subcompositor *subco;
+ struct wl_surface *parent;
+ struct wl_surface *child[NUM_SUBSURFACES];
+ struct wl_subsurface *sub[NUM_SUBSURFACES];
+};
+
+static struct wl_subcompositor *
+get_subcompositor(struct client *client)
+{
+ struct global *g;
+ struct global *global_sub = NULL;
+ struct wl_subcompositor *sub;
+
+ wl_list_for_each(g, &client->global_list, link) {
+ if (strcmp(g->interface, "wl_subcompositor"))
+ continue;
+
+ if (global_sub)
+ assert(0 && "multiple wl_subcompositor objects");
+
+ global_sub = g;
+ }
+
+ assert(global_sub && "no wl_subcompositor found");
+
+ assert(global_sub->version == 1);
+
+ sub = wl_registry_bind(client->wl_registry, global_sub->name,
+ &wl_subcompositor_interface, 1);
+ assert(sub);
+
+ return sub;
+}
+
+static void
+populate_compound_surface(struct compound_surface *com, struct client *client)
+{
+ int i;
+
+ com->subco = get_subcompositor(client);
+
+ com->parent = wl_compositor_create_surface(client->wl_compositor);
+
+ for (i = 0; i < NUM_SUBSURFACES; i++) {
+ com->child[i] =
+ wl_compositor_create_surface(client->wl_compositor);
+ com->sub[i] =
+ wl_subcompositor_get_subsurface(com->subco,
+ com->child[i],
+ com->parent);
+ }
+}
+
+TEST(test_subsurface_basic_protocol)
+{
+ struct client *client;
+ struct compound_surface com1;
+ struct compound_surface com2;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com1, client);
+ populate_compound_surface(&com2, client);
+
+ client_roundtrip(client);
+}
+
+TEST(test_subsurface_position_protocol)
+{
+ struct client *client;
+ struct compound_surface com;
+ int i;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com, client);
+ for (i = 0; i < NUM_SUBSURFACES; i++)
+ wl_subsurface_set_position(com.sub[i],
+ (i + 2) * 20, (i + 2) * 10);
+
+ client_roundtrip(client);
+}
+
+TEST(test_subsurface_placement_protocol)
+{
+ struct client *client;
+ struct compound_surface com;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com, client);
+
+ wl_subsurface_place_above(com.sub[0], com.child[1]);
+ wl_subsurface_place_above(com.sub[1], com.parent);
+ wl_subsurface_place_below(com.sub[2], com.child[0]);
+ wl_subsurface_place_below(com.sub[1], com.parent);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_paradox)
+{
+ struct client *client;
+ struct wl_surface *parent;
+ struct wl_subcompositor *subco;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ subco = get_subcompositor(client);
+ parent = wl_compositor_create_surface(client->wl_compositor);
+
+ /* surface is its own parent */
+ wl_subcompositor_get_subsurface(subco, parent, parent);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_identical_link)
+{
+ struct client *client;
+ struct compound_surface com;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com, client);
+
+ /* surface is already a subsurface */
+ wl_subcompositor_get_subsurface(com.subco, com.child[0], com.parent);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_change_link)
+{
+ struct client *client;
+ struct compound_surface com;
+ struct wl_surface *stranger;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ stranger = wl_compositor_create_surface(client->wl_compositor);
+ populate_compound_surface(&com, client);
+
+ /* surface is already a subsurface */
+ wl_subcompositor_get_subsurface(com.subco, com.child[0], stranger);
+
+ client_roundtrip(client);
+}
+
+TEST(test_subsurface_nesting)
+{
+ struct client *client;
+ struct compound_surface com;
+ struct wl_surface *stranger;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ stranger = wl_compositor_create_surface(client->wl_compositor);
+ populate_compound_surface(&com, client);
+
+ /* parent is a sub-surface */
+ wl_subcompositor_get_subsurface(com.subco, stranger, com.child[0]);
+
+ client_roundtrip(client);
+}
+
+TEST(test_subsurface_nesting_parent)
+{
+ struct client *client;
+ struct compound_surface com;
+ struct wl_surface *stranger;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ stranger = wl_compositor_create_surface(client->wl_compositor);
+ populate_compound_surface(&com, client);
+
+ /* surface is already a parent */
+ wl_subcompositor_get_subsurface(com.subco, com.parent, stranger);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_place_above_stranger)
+{
+ struct client *client;
+ struct compound_surface com;
+ struct wl_surface *stranger;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ stranger = wl_compositor_create_surface(client->wl_compositor);
+ populate_compound_surface(&com, client);
+
+ /* bad sibling */
+ wl_subsurface_place_above(com.sub[0], stranger);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_place_below_stranger)
+{
+ struct client *client;
+ struct compound_surface com;
+ struct wl_surface *stranger;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ stranger = wl_compositor_create_surface(client->wl_compositor);
+ populate_compound_surface(&com, client);
+
+ /* bad sibling */
+ wl_subsurface_place_below(com.sub[0], stranger);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_place_above_foreign)
+{
+ struct client *client;
+ struct compound_surface com1;
+ struct compound_surface com2;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com1, client);
+ populate_compound_surface(&com2, client);
+
+ /* bad sibling */
+ wl_subsurface_place_above(com1.sub[0], com2.child[0]);
+
+ client_roundtrip(client);
+}
+
+FAIL_TEST(test_subsurface_place_below_foreign)
+{
+ struct client *client;
+ struct compound_surface com1;
+ struct compound_surface com2;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com1, client);
+ populate_compound_surface(&com2, client);
+
+ /* bad sibling */
+ wl_subsurface_place_below(com1.sub[0], com2.child[0]);
+
+ client_roundtrip(client);
+}
+
+TEST(test_subsurface_destroy_protocol)
+{
+ struct client *client;
+ struct compound_surface com;
+
+ client = client_create(100, 50, 123, 77);
+ assert(client);
+
+ populate_compound_surface(&com, client);
+
+ /* not needed anymore */
+ wl_subcompositor_destroy(com.subco);
+
+ /* detach child from parent */
+ wl_subsurface_destroy(com.sub[0]);
+
+ /* destroy: child, parent */
+ wl_surface_destroy(com.child[1]);
+ wl_surface_destroy(com.parent);
+
+ /* destroy: parent, child */
+ wl_surface_destroy(com.child[2]);
+
+ /* destroy: sub, child */
+ wl_surface_destroy(com.child[0]);
+
+ /* 2x destroy: child, sub */
+ wl_subsurface_destroy(com.sub[2]);
+ wl_subsurface_destroy(com.sub[1]);
+
+ client_roundtrip(client);
+}
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:44 UTC
Permalink
The shell needs to redirect some actions to the parent surface, when
they originally target a sub-surface. This patch implements the
following:

- Move, resize, and rotate bindings always target the parent surface.

- Opacity (full-surface alpha) binding targets the parent surface. This
is broken, because it should change the opacity of the whole compound
window, which is difficult to implement in the renderer.

- click_to_activate_binding() needs to check the shell surface type from
the main surface, because sub-surface would produce SHELL_SURFACE_NONE
and prevent activation.

- Also activate() needs to check the type from the main surface, and
restack the main surface. Keyboard focus is assigned to the original
(sub-)surface.

- focus_state_surface_destroy() needs to handle sub-surfaces: only the
main surface will be in a layer list. If the destroyed surface is
indeed a sub-surface, activate the main surface next. This way a
client that destroys a focused sub-surface still retains focus in the
same window.

- The workspace_manager.move_surface request can accept also
sub-surfaces, and it will move the corresponding main surface.

Changes in v2:
- do not special-case keyboard focus for sub-surfaces
- fix surface type checks for sub-surfaces in shell, fix restacking of
sub-surfaces in shell, fix focus_state_surface_destroy()

Changes in v3:
- Renamed weston_surface_get_parent() to
weston_surface_get_main_surface() to be more explicit that this is
about sub-surfaces
- Fixed move_surface_to_workspace() to handle keyboard focus on a
sub-surface.
- Used a temporary variable in several places to clarify code, instead
of reassigning a variable.
- Fixed workspace_manager_move_surface() to deal with sub-surfaces.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/compositor.c | 11 ++++++++++
src/compositor.h | 3 +++
src/shell.c | 67 +++++++++++++++++++++++++++++++++++++++++---------------
3 files changed, 63 insertions(+), 18 deletions(-)

diff --git a/src/compositor.c b/src/compositor.c
index 000f6bf..b9073f0 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -1963,6 +1963,17 @@ weston_surface_to_subsurface(struct weston_surface *surface)
return NULL;
}

+WL_EXPORT struct weston_surface *
+weston_surface_get_main_surface(struct weston_surface *surface)
+{
+ struct weston_subsurface *sub;
+
+ while (surface && (sub = weston_surface_to_subsurface(surface)))
+ surface = sub->parent;
+
+ return surface;
+}
+
static void
subsurface_set_position(struct wl_client *client,
struct wl_resource *resource, int32_t x, int32_t y)
diff --git a/src/compositor.h b/src/compositor.h
index f102a4a..53a8f91 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -789,6 +789,9 @@ weston_surface_move_to_plane(struct weston_surface *surface,
void
weston_surface_unmap(struct weston_surface *surface);

+struct weston_surface *
+weston_surface_get_main_surface(struct weston_surface *surface);
+
void
weston_buffer_reference(struct weston_buffer_reference *ref,
struct wl_buffer *buffer);
diff --git a/src/shell.c b/src/shell.c
index 9d0ae02..99949b7 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -427,17 +427,24 @@ focus_state_surface_destroy(struct wl_listener *listener, void *data)
struct focus_state,
surface_destroy_listener);
struct desktop_shell *shell;
+ struct weston_surface *main_surface;
struct weston_surface *surface, *next;

+ main_surface = weston_surface_get_main_surface(state->keyboard_focus);
+
next = NULL;
wl_list_for_each(surface, &state->ws->layer.surface_list, layer_link) {
- if (surface == state->keyboard_focus)
+ if (surface == main_surface)
continue;

next = surface;
break;
}

+ /* if the focus was a sub-surface, activate its main surface */
+ if (main_surface != state->keyboard_focus)
+ next = main_surface;
+
if (next) {
shell = state->seat->compositor->shell_interface.shell;
activate(shell, next, state->seat);
@@ -876,6 +883,9 @@ move_surface_to_workspace(struct desktop_shell *shell,
struct workspace *from;
struct workspace *to;
struct weston_seat *seat;
+ struct weston_surface *focus;
+
+ assert(weston_surface_get_main_surface(surface) == surface);

if (workspace == shell->workspaces.current)
return;
@@ -890,10 +900,14 @@ move_surface_to_workspace(struct desktop_shell *shell,
wl_list_insert(&to->layer.surface_list, &surface->layer_link);

drop_focus_state(shell, from, surface);
- wl_list_for_each(seat, &shell->compositor->seat_list, link)
- if (seat->has_keyboard &&
- seat->keyboard.keyboard.focus == &surface->surface)
+ wl_list_for_each(seat, &shell->compositor->seat_list, link) {
+ if (!seat->has_keyboard)
+ continue;
+
+ focus = (struct weston_surface *)seat->keyboard.keyboard.focus;
+ if (weston_surface_get_main_surface(focus) == surface)
wl_keyboard_set_focus(&seat->keyboard.keyboard, NULL);
+ }

weston_surface_damage_below(surface);
}
@@ -904,13 +918,15 @@ take_surface_to_workspace_by_seat(struct desktop_shell *shell,
unsigned int index)
{
struct weston_seat *seat = (struct weston_seat *) wl_seat;
- struct weston_surface *surface =
+ struct weston_surface *focus =
(struct weston_surface *) wl_seat->keyboard->focus;
+ struct weston_surface *surface;
struct shell_surface *shsurf;
struct workspace *from;
struct workspace *to;
struct focus_state *state;

+ surface = weston_surface_get_main_surface(focus);
if (surface == NULL ||
index == shell->workspaces.current)
return;
@@ -968,8 +984,10 @@ workspace_manager_move_surface(struct wl_client *client,
struct desktop_shell *shell = resource->data;
struct weston_surface *surface =
(struct weston_surface *) surface_resource;
+ struct weston_surface *main_surface;

- move_surface_to_workspace(shell, surface, workspace);
+ main_surface = weston_surface_get_main_surface(surface);
+ move_surface_to_workspace(shell, main_surface, workspace);
}

static const struct workspace_manager_interface workspace_manager_implementation = {
@@ -2511,10 +2529,12 @@ get_shell_surface_type(struct weston_surface *surface)
static void
move_binding(struct wl_seat *seat, uint32_t time, uint32_t button, void *data)
{
- struct weston_surface *surface =
+ struct weston_surface *focus =
(struct weston_surface *) seat->pointer->focus;
+ struct weston_surface *surface;
struct shell_surface *shsurf;

+ surface = weston_surface_get_main_surface(focus);
if (surface == NULL)
return;

@@ -2529,12 +2549,14 @@ move_binding(struct wl_seat *seat, uint32_t time, uint32_t button, void *data)
static void
resize_binding(struct wl_seat *seat, uint32_t time, uint32_t button, void *data)
{
- struct weston_surface *surface =
+ struct weston_surface *focus =
(struct weston_surface *) seat->pointer->focus;
+ struct weston_surface *surface;
uint32_t edges = 0;
int32_t x, y;
struct shell_surface *shsurf;

+ surface = weston_surface_get_main_surface(focus);
if (surface == NULL)
return;

@@ -2571,9 +2593,12 @@ surface_opacity_binding(struct wl_seat *seat, uint32_t time, uint32_t axis,
{
float step = 0.005;
struct shell_surface *shsurf;
- struct weston_surface *surface =
+ struct weston_surface *focus =
(struct weston_surface *) seat->pointer->focus;
+ struct weston_surface *surface;

+ /* XXX: broken for windows containing sub-surfaces */
+ surface = weston_surface_get_main_surface(focus);
if (surface == NULL)
return;

@@ -2791,10 +2816,12 @@ static void
rotate_binding(struct wl_seat *seat, uint32_t time, uint32_t button,
void *data)
{
- struct weston_surface *base_surface =
+ struct weston_surface *focus =
(struct weston_surface *) seat->pointer->focus;
+ struct weston_surface *base_surface;
struct shell_surface *surface;

+ base_surface = weston_surface_get_main_surface(focus);
if (base_surface == NULL)
return;

@@ -2823,9 +2850,12 @@ static void
activate(struct desktop_shell *shell, struct weston_surface *es,
struct weston_seat *seat)
{
+ struct weston_surface *main_surface;
struct focus_state *state;
struct workspace *ws;

+ main_surface = weston_surface_get_main_surface(es);
+
weston_surface_activate(es, seat);

state = ensure_focus_state(shell, seat);
@@ -2837,15 +2867,15 @@ activate(struct desktop_shell *shell, struct weston_surface *es,
wl_signal_add(&es->surface.resource.destroy_signal,
&state->surface_destroy_listener);

- switch (get_shell_surface_type(es)) {
+ switch (get_shell_surface_type(main_surface)) {
case SHELL_SURFACE_FULLSCREEN:
/* should on top of panels */
- shell_stack_fullscreen(get_shell_surface(es));
- shell_configure_fullscreen(get_shell_surface(es));
+ shell_stack_fullscreen(get_shell_surface(main_surface));
+ shell_configure_fullscreen(get_shell_surface(main_surface));
break;
default:
ws = get_current_workspace(shell);
- weston_surface_restack(es, &ws->layer.surface_list);
+ weston_surface_restack(main_surface, &ws->layer.surface_list);
break;
}
}
@@ -2874,16 +2904,17 @@ click_to_activate_binding(struct wl_seat *seat, uint32_t time, uint32_t button,
struct weston_seat *ws = (struct weston_seat *) seat;
struct desktop_shell *shell = data;
struct weston_surface *focus;
- struct weston_surface *upper;
+ struct weston_surface *main_surface;

focus = (struct weston_surface *) seat->pointer->focus;
if (!focus)
return;

- if (is_black_surface(focus, &upper))
- focus = upper;
+ if (is_black_surface(focus, &main_surface))
+ focus = main_surface;

- if (get_shell_surface_type(focus) == SHELL_SURFACE_NONE)
+ main_surface = weston_surface_get_main_surface(focus);
+ if (get_shell_surface_type(main_surface) == SHELL_SURFACE_NONE)
return;

if (seat->pointer->grab == &seat->pointer->default_grab)
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:45 UTC
Permalink
From: Giulio Camuffo <giuliocamuffo at gmail.com>

[pq: changed to weston_surface_get_main_surface(), and used a temporary
variable to clean up the expressions.]

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/shell.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/shell.c b/src/shell.c
index 99949b7..86bdd84 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -1108,10 +1108,16 @@ shell_surface_move(struct wl_client *client, struct wl_resource *resource,
{
struct weston_seat *ws = seat_resource->data;
struct shell_surface *shsurf = resource->data;
+ struct weston_surface *focus;
+ struct weston_surface *surface;
+
+ focus = container_of(ws->seat.pointer->focus,
+ struct weston_surface, surface);
+ surface = weston_surface_get_main_surface(focus);

if (ws->seat.pointer->button_count == 0 ||
ws->seat.pointer->grab_serial != serial ||
- ws->seat.pointer->focus != &shsurf->surface->surface)
+ surface != shsurf->surface)
return;

if (surface_move(shsurf, ws) < 0)
@@ -1232,13 +1238,19 @@ shell_surface_resize(struct wl_client *client, struct wl_resource *resource,
{
struct weston_seat *ws = seat_resource->data;
struct shell_surface *shsurf = resource->data;
+ struct weston_surface *focus;
+ struct weston_surface *surface;
+
+ focus = container_of(ws->seat.pointer->focus,
+ struct weston_surface, surface);
+ surface = weston_surface_get_main_surface(focus);

if (shsurf->type == SHELL_SURFACE_FULLSCREEN)
return;

if (ws->seat.pointer->button_count == 0 ||
ws->seat.pointer->grab_serial != serial ||
- ws->seat.pointer->focus != &shsurf->surface->surface)
+ surface != shsurf->surface)
return;

if (surface_resize(shsurf, ws, edges) < 0)
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:46 UTC
Permalink
From: Giulio Camuffo <giuliocamuffo at gmail.com>

We must calculate the bounding box of the surface + subsurfaces set and use
that when maximizing the window or going fullscreen.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/shell.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 68 insertions(+), 21 deletions(-)

diff --git a/src/shell.c b/src/shell.c
index 86bdd84..39df27a 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -1203,6 +1203,41 @@ static const struct wl_pointer_grab_interface resize_grab_interface = {
resize_grab_button,
};

+/*
+ * Returns the bounding box of a surface and all its sub-surfaces,
+ * in the surface coordinates system. */
+static void
+surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x,
+ int32_t *y, int32_t *w, int32_t *h) {
+ pixman_region32_t region;
+ pixman_box32_t *box;
+ struct weston_subsurface *subsurface;
+
+ pixman_region32_init_rect(&region, 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+
+ wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) {
+ pixman_region32_union_rect(&region, &region,
+ subsurface->position.x,
+ subsurface->position.y,
+ subsurface->surface->geometry.width,
+ subsurface->surface->geometry.height);
+ }
+
+ box = pixman_region32_extents(&region);
+ if (x)
+ *x = box->x1;
+ if (y)
+ *y = box->y1;
+ if (w)
+ *w = box->x2 - box->x1;
+ if (h)
+ *h = box->y2 - box->y1;
+
+ pixman_region32_fini(&region);
+}
+
static int
surface_resize(struct shell_surface *shsurf,
struct weston_seat *ws, uint32_t edges)
@@ -1222,8 +1257,8 @@ surface_resize(struct shell_surface *shsurf,
return -1;

resize->edges = edges;
- resize->width = shsurf->surface->geometry.width;
- resize->height = shsurf->surface->geometry.height;
+ surface_subsurfaces_boundingbox(shsurf->surface, NULL, NULL,
+ &resize->width, &resize->height);

shell_grab_start(&resize->base, &resize_grab_interface, shsurf,
ws->seat.pointer, edges);
@@ -1726,6 +1761,7 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
struct weston_surface *surface = shsurf->surface;
struct weston_matrix *matrix;
float scale, output_aspect, surface_aspect, x, y;
+ int32_t surf_x, surf_y, surf_width, surf_height;

if (!shsurf->fullscreen.black_surface)
shsurf->fullscreen.black_surface =
@@ -1740,6 +1776,9 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
&shsurf->fullscreen.black_surface->layer_link);
shsurf->fullscreen.black_surface->output = output;

+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y,
+ &surf_width, &surf_height);
+
switch (shsurf->fullscreen.type) {
case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT:
if (surface->buffer_ref.buffer)
@@ -1747,9 +1786,10 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
break;
case WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE:
/* 1:1 mapping between surface and output dimensions */
- if (output->width == surface->geometry.width &&
- output->height == surface->geometry.height) {
- weston_surface_set_position(surface, output->x, output->y);
+ if (output->width == surf_width &&
+ output->height == surf_height) {
+ weston_surface_set_position(surface, output->x - surf_x,
+ output->y - surf_y);
break;
}

@@ -1762,33 +1802,33 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
(float) surface->geometry.height;
if (output_aspect < surface_aspect)
scale = (float) output->width /
- (float) surface->geometry.width;
+ (float) surf_width;
else
scale = (float) output->height /
- (float) surface->geometry.height;
+ (float) surf_height;

weston_matrix_scale(matrix, scale, scale, 1);
wl_list_remove(&shsurf->fullscreen.transform.link);
wl_list_insert(&surface->geometry.transformation_list,
&shsurf->fullscreen.transform.link);
- x = output->x + (output->width - surface->geometry.width * scale) / 2;
- y = output->y + (output->height - surface->geometry.height * scale) / 2;
+ x = output->x + (output->width - surf_width * scale) / 2 - surf_x;
+ y = output->y + (output->height - surf_height * scale) / 2 - surf_y;
weston_surface_set_position(surface, x, y);

break;
case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER:
if (shell_surface_is_top_fullscreen(shsurf)) {
struct weston_mode mode = {0,
- surface->geometry.width,
- surface->geometry.height,
+ surf_width,
+ surf_height,
shsurf->fullscreen.framerate};

if (weston_output_switch_mode(output, &mode) == 0) {
weston_surface_configure(shsurf->fullscreen.black_surface,
- output->x, output->y,
+ output->x - surf_x,
+ output->y - surf_y,
output->width,
output->height);
- weston_surface_set_position(surface, output->x, output->y);
break;
}
}
@@ -3122,12 +3162,13 @@ hide_input_panels(struct wl_listener *listener, void *data)
static void
center_on_output(struct weston_surface *surface, struct weston_output *output)
{
- int32_t width = weston_surface_buffer_width(surface);
- int32_t height = weston_surface_buffer_height(surface);
+ int32_t surf_x, surf_y, width, height;
float x, y;

- x = output->x + (output->width - width) / 2;
- y = output->y + (output->height - height) / 2;
+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, &width, &height);
+
+ x = output->x + (output->width - width) / 2 - surf_x / 2;
+ y = output->y + (output->height - height) / 2 - surf_y / 2;

weston_surface_configure(surface, x, y, width, height);
}
@@ -3205,6 +3246,7 @@ map(struct desktop_shell *shell, struct weston_surface *surface,
struct weston_seat *seat;
struct workspace *ws;
int panel_height = 0;
+ int32_t surf_x, surf_y;

surface->geometry.width = width;
surface->geometry.height = height;
@@ -3222,8 +3264,10 @@ map(struct desktop_shell *shell, struct weston_surface *surface,
case SHELL_SURFACE_MAXIMIZED:
/* use surface configure to set the geometry */
panel_height = get_output_panel_height(shell,surface->output);
- weston_surface_set_position(surface, shsurf->output->x,
- shsurf->output->y + panel_height);
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+ NULL, NULL);
+ weston_surface_set_position(surface, shsurf->output->x - surf_x,
+ shsurf->output->y + panel_height - surf_y);
break;
case SHELL_SURFACE_POPUP:
shell_map_popup(shsurf);
@@ -3297,6 +3341,7 @@ configure(struct desktop_shell *shell, struct weston_surface *surface,
{
enum shell_surface_type surface_type = SHELL_SURFACE_NONE;
struct shell_surface *shsurf;
+ int32_t surf_x, surf_y;

shsurf = get_shell_surface(surface);
if (shsurf)
@@ -3311,9 +3356,11 @@ configure(struct desktop_shell *shell, struct weston_surface *surface,
break;
case SHELL_SURFACE_MAXIMIZED:
/* setting x, y and using configure to change that geometry */
- surface->geometry.x = surface->output->x;
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+ NULL, NULL);
+ surface->geometry.x = surface->output->x - surf_x;
surface->geometry.y = surface->output->y +
- get_output_panel_height(shell,surface->output);
+ get_output_panel_height(shell,surface->output) - surf_y;
break;
case SHELL_SURFACE_TOPLEVEL:
break;
--
1.8.1.5
Jonas Ådahl
2013-04-28 18:14:50 UTC
Permalink
Post by Pekka Paalanen
From: Giulio Camuffo <giuliocamuffo at gmail.com>
We must calculate the bounding box of the surface + subsurfaces set and use
that when maximizing the window or going fullscreen.
Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/shell.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 68 insertions(+), 21 deletions(-)
diff --git a/src/shell.c b/src/shell.c
index 86bdd84..39df27a 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -1203,6 +1203,41 @@ static const struct wl_pointer_grab_interface resize_grab_interface = {
resize_grab_button,
};
+/*
+ * Returns the bounding box of a surface and all its sub-surfaces,
+ * in the surface coordinates system. */
+static void
+surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x,
+ int32_t *y, int32_t *w, int32_t *h) {
Wouldn't it be better if we left sub-compositor details to the
compositor and instead use a generic "weston_surface_get_bounds()"
function to get the collective bounding box of the given surface and
associated sub-surfaces?
Post by Pekka Paalanen
+ pixman_region32_t region;
+ pixman_box32_t *box;
+ struct weston_subsurface *subsurface;
+
+ pixman_region32_init_rect(&region, 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+
+ wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) {
+ pixman_region32_union_rect(&region, &region,
+ subsurface->position.x,
+ subsurface->position.y,
+ subsurface->surface->geometry.width,
+ subsurface->surface->geometry.height);
+ }
+
+ box = pixman_region32_extents(&region);
+ if (x)
+ *x = box->x1;
+ if (y)
+ *y = box->y1;
+ if (w)
+ *w = box->x2 - box->x1;
+ if (h)
+ *h = box->y2 - box->y1;
+
+ pixman_region32_fini(&region);
+}
+
static int
surface_resize(struct shell_surface *shsurf,
struct weston_seat *ws, uint32_t edges)
@@ -1222,8 +1257,8 @@ surface_resize(struct shell_surface *shsurf,
return -1;
resize->edges = edges;
- resize->width = shsurf->surface->geometry.width;
- resize->height = shsurf->surface->geometry.height;
+ surface_subsurfaces_boundingbox(shsurf->surface, NULL, NULL,
+ &resize->width, &resize->height);
shell_grab_start(&resize->base, &resize_grab_interface, shsurf,
ws->seat.pointer, edges);
@@ -1726,6 +1761,7 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
struct weston_surface *surface = shsurf->surface;
struct weston_matrix *matrix;
float scale, output_aspect, surface_aspect, x, y;
+ int32_t surf_x, surf_y, surf_width, surf_height;
if (!shsurf->fullscreen.black_surface)
shsurf->fullscreen.black_surface =
@@ -1740,6 +1776,9 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
&shsurf->fullscreen.black_surface->layer_link);
shsurf->fullscreen.black_surface->output = output;
+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y,
+ &surf_width, &surf_height);
+
switch (shsurf->fullscreen.type) {
if (surface->buffer_ref.buffer)
@@ -1747,9 +1786,10 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
break;
/* 1:1 mapping between surface and output dimensions */
- if (output->width == surface->geometry.width &&
- output->height == surface->geometry.height) {
- weston_surface_set_position(surface, output->x, output->y);
+ if (output->width == surf_width &&
+ output->height == surf_height) {
+ weston_surface_set_position(surface, output->x - surf_x,
+ output->y - surf_y);
break;
}
@@ -1762,33 +1802,33 @@ shell_configure_fullscreen(struct shell_surface *shsurf)
(float) surface->geometry.height;
if (output_aspect < surface_aspect)
scale = (float) output->width /
- (float) surface->geometry.width;
+ (float) surf_width;
else
scale = (float) output->height /
- (float) surface->geometry.height;
+ (float) surf_height;
weston_matrix_scale(matrix, scale, scale, 1);
wl_list_remove(&shsurf->fullscreen.transform.link);
wl_list_insert(&surface->geometry.transformation_list,
&shsurf->fullscreen.transform.link);
- x = output->x + (output->width - surface->geometry.width * scale) / 2;
- y = output->y + (output->height - surface->geometry.height * scale) / 2;
+ x = output->x + (output->width - surf_width * scale) / 2 - surf_x;
+ y = output->y + (output->height - surf_height * scale) / 2 - surf_y;
weston_surface_set_position(surface, x, y);
break;
if (shell_surface_is_top_fullscreen(shsurf)) {
struct weston_mode mode = {0,
- surface->geometry.width,
- surface->geometry.height,
+ surf_width,
+ surf_height,
shsurf->fullscreen.framerate};
if (weston_output_switch_mode(output, &mode) == 0) {
weston_surface_configure(shsurf->fullscreen.black_surface,
- output->x, output->y,
+ output->x - surf_x,
+ output->y - surf_y,
output->width,
output->height);
- weston_surface_set_position(surface, output->x, output->y);
break;
}
}
@@ -3122,12 +3162,13 @@ hide_input_panels(struct wl_listener *listener, void *data)
static void
center_on_output(struct weston_surface *surface, struct weston_output *output)
{
- int32_t width = weston_surface_buffer_width(surface);
- int32_t height = weston_surface_buffer_height(surface);
+ int32_t surf_x, surf_y, width, height;
float x, y;
- x = output->x + (output->width - width) / 2;
- y = output->y + (output->height - height) / 2;
+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, &width, &height);
+
+ x = output->x + (output->width - width) / 2 - surf_x / 2;
+ y = output->y + (output->height - height) / 2 - surf_y / 2;
weston_surface_configure(surface, x, y, width, height);
}
@@ -3205,6 +3246,7 @@ map(struct desktop_shell *shell, struct weston_surface *surface,
struct weston_seat *seat;
struct workspace *ws;
int panel_height = 0;
+ int32_t surf_x, surf_y;
surface->geometry.width = width;
surface->geometry.height = height;
@@ -3222,8 +3264,10 @@ map(struct desktop_shell *shell, struct weston_surface *surface,
/* use surface configure to set the geometry */
panel_height = get_output_panel_height(shell,surface->output);
- weston_surface_set_position(surface, shsurf->output->x,
- shsurf->output->y + panel_height);
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+ NULL, NULL);
+ weston_surface_set_position(surface, shsurf->output->x - surf_x,
+ shsurf->output->y + panel_height - surf_y);
break;
shell_map_popup(shsurf);
@@ -3297,6 +3341,7 @@ configure(struct desktop_shell *shell, struct weston_surface *surface,
{
enum shell_surface_type surface_type = SHELL_SURFACE_NONE;
struct shell_surface *shsurf;
+ int32_t surf_x, surf_y;
shsurf = get_shell_surface(surface);
if (shsurf)
@@ -3311,9 +3356,11 @@ configure(struct desktop_shell *shell, struct weston_surface *surface,
break;
/* setting x, y and using configure to change that geometry */
- surface->geometry.x = surface->output->x;
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+ NULL, NULL);
+ surface->geometry.x = surface->output->x - surf_x;
surface->geometry.y = surface->output->y +
- get_output_panel_height(shell,surface->output);
+ get_output_panel_height(shell,surface->output) - surf_y;
break;
break;
--
1.8.1.5
Jonas
Pekka Paalanen
2013-04-29 07:19:20 UTC
Permalink
On Sun, 28 Apr 2013 20:14:50 +0200
On Thu, Apr 25, 2013 at 12:57 PM, Pekka Paalanen
Post by Pekka Paalanen
From: Giulio Camuffo <giuliocamuffo at gmail.com>
We must calculate the bounding box of the surface + subsurfaces set
and use that when maximizing the window or going fullscreen.
Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
src/shell.c | 89
++++++++++++++++++++++++++++++++++++++++++++++--------------- 1
file changed, 68 insertions(+), 21 deletions(-)
diff --git a/src/shell.c b/src/shell.c
index 86bdd84..39df27a 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -1203,6 +1203,41 @@ static const struct
wl_pointer_grab_interface resize_grab_interface =
{ resize_grab_button, };
+/*
+ * Returns the bounding box of a surface and all its sub-surfaces,
+ * in the surface coordinates system. */
+static void
+surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x,
+ int32_t *y, int32_t *w, int32_t *h) {
Wouldn't it be better if we left sub-compositor details to the
compositor and instead use a generic "weston_surface_get_bounds()"
function to get the collective bounding box of the given surface and
associated sub-surfaces?
Yeah, that's a good point. Another thing is, should this be computed
from the surface sizes like here, or from input regions, or from ...
regions?

We had some talk about adding a "window region" or something in the
past, that would be used for edge snapping and stuff, or was it deemed
that input region was enough; I can't recall.

If this should be computed from core features like the input region,
then we could indeed make this a Weston core function.

Oh and I just realized, this code does not recurse, so it does not
handle nested sub-surfaces.


Thanks,
pq
Post by Pekka Paalanen
+ pixman_region32_t region;
+ pixman_box32_t *box;
+ struct weston_subsurface *subsurface;
+
+ pixman_region32_init_rect(&region, 0, 0,
+ surface->geometry.width,
+ surface->geometry.height);
+
+ wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) {
+ pixman_region32_union_rect(&region, &region,
+ subsurface->position.x,
+ subsurface->position.y,
+
subsurface->surface->geometry.width,
+
subsurface->surface->geometry.height);
+ }
+
+ box = pixman_region32_extents(&region);
+ if (x)
+ *x = box->x1;
+ if (y)
+ *y = box->y1;
+ if (w)
+ *w = box->x2 - box->x1;
+ if (h)
+ *h = box->y2 - box->y1;
+
+ pixman_region32_fini(&region);
+}
+
static int
surface_resize(struct shell_surface *shsurf,
struct weston_seat *ws, uint32_t edges)
@@ -1222,8 +1257,8 @@ surface_resize(struct shell_surface *shsurf,
return -1;
resize->edges = edges;
- resize->width = shsurf->surface->geometry.width;
- resize->height = shsurf->surface->geometry.height;
+ surface_subsurfaces_boundingbox(shsurf->surface, NULL, NULL,
+ &resize->width,
&resize->height);
shell_grab_start(&resize->base, &resize_grab_interface,
shsurf, ws->seat.pointer, edges);
@@ -1726,6 +1761,7 @@ shell_configure_fullscreen(struct
shell_surface *shsurf) struct weston_surface *surface =
shsurf->surface; struct weston_matrix *matrix;
float scale, output_aspect, surface_aspect, x, y;
+ int32_t surf_x, surf_y, surf_width, surf_height;
if (!shsurf->fullscreen.black_surface)
shsurf->fullscreen.black_surface =
@@ -1740,6 +1776,9 @@ shell_configure_fullscreen(struct
shell_surface *shsurf)
&shsurf->fullscreen.black_surface->layer_link);
shsurf->fullscreen.black_surface->output = output;
+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y,
+ &surf_width, &surf_height);
+
switch (shsurf->fullscreen.type) {
if (surface->buffer_ref.buffer)
@@ -1747,9 +1786,10 @@ shell_configure_fullscreen(struct
shell_surface *shsurf) break;
/* 1:1 mapping between surface and output
dimensions */
- if (output->width == surface->geometry.width &&
- output->height == surface->geometry.height) {
- weston_surface_set_position(surface,
output->x, output->y);
+ if (output->width == surf_width &&
+ output->height == surf_height) {
+ weston_surface_set_position(surface,
output->x - surf_x,
+
output->y - surf_y); break;
}
@@ -1762,33 +1802,33 @@ shell_configure_fullscreen(struct
shell_surface *shsurf) (float) surface->geometry.height;
if (output_aspect < surface_aspect)
scale = (float) output->width /
- (float) surface->geometry.width;
+ (float) surf_width;
else
scale = (float) output->height /
- (float) surface->geometry.height;
+ (float) surf_height;
weston_matrix_scale(matrix, scale, scale, 1);
wl_list_remove(&shsurf->fullscreen.transform.link);
wl_list_insert(&surface->geometry.transformation_list,
&shsurf->fullscreen.transform.link);
- x = output->x + (output->width -
surface->geometry.width * scale) / 2;
- y = output->y + (output->height -
surface->geometry.height * scale) / 2;
+ x = output->x + (output->width - surf_width * scale) / 2 - surf_x;
+ y = output->y + (output->height - surf_height *
scale) / 2 - surf_y; weston_surface_set_position(surface, x, y);
break;
if (shell_surface_is_top_fullscreen(shsurf)) {
struct weston_mode mode = {0,
- surface->geometry.width,
- surface->geometry.height,
+ surf_width,
+ surf_height,
shsurf->fullscreen.framerate};
if (weston_output_switch_mode(output,
&mode) == 0)
{ weston_surface_configure(shsurf->fullscreen.black_surface,
- output->x, output->y,
+ output->x - surf_x,
+ output->y
- surf_y, output->width,
output->height);
-
weston_surface_set_position(surface, output->x, output->y); break;
}
}
@@ -3122,12 +3162,13 @@ hide_input_panels(struct wl_listener
*listener, void *data) static void
center_on_output(struct weston_surface *surface, struct
weston_output *output) {
- int32_t width = weston_surface_buffer_width(surface);
- int32_t height = weston_surface_buffer_height(surface);
+ int32_t surf_x, surf_y, width, height;
float x, y;
- x = output->x + (output->width - width) / 2;
- y = output->y + (output->height - height) / 2;
+ surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y,
&width, &height); +
+ x = output->x + (output->width - width) / 2 - surf_x / 2;
+ y = output->y + (output->height - height) / 2 - surf_y / 2;
weston_surface_configure(surface, x, y, width, height);
}
@@ -3205,6 +3246,7 @@ map(struct desktop_shell *shell, struct
weston_surface *surface, struct weston_seat *seat;
struct workspace *ws;
int panel_height = 0;
+ int32_t surf_x, surf_y;
surface->geometry.width = width;
surface->geometry.height = height;
@@ -3222,8 +3264,10 @@ map(struct desktop_shell *shell, struct
/* use surface configure to set the geometry */
panel_height =
get_output_panel_height(shell,surface->output);
- weston_surface_set_position(surface,
shsurf->output->x,
- shsurf->output->y + panel_height);
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+
NULL, NULL);
+ weston_surface_set_position(surface,
shsurf->output->x - surf_x,
+ shsurf->output->y +
panel_height - surf_y); break;
shell_map_popup(shsurf);
@@ -3297,6 +3341,7 @@ configure(struct desktop_shell *shell, struct
weston_surface *surface, {
enum shell_surface_type surface_type = SHELL_SURFACE_NONE;
struct shell_surface *shsurf;
+ int32_t surf_x, surf_y;
shsurf = get_shell_surface(surface);
if (shsurf)
@@ -3311,9 +3356,11 @@ configure(struct desktop_shell *shell,
struct weston_surface *surface, break;
/* setting x, y and using configure to change that geometry */
- surface->geometry.x = surface->output->x;
+ surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y,
+
NULL, NULL);
+ surface->geometry.x = surface->output->x - surf_x;
surface->geometry.y = surface->output->y +
-
get_output_panel_height(shell,surface->output);
+ get_output_panel_height(shell,surface->output) -
surf_y; break;
break;
--
1.8.1.5
Jonas
Bill Spitzak
2013-04-29 17:24:54 UTC
Permalink
Post by Pekka Paalanen
We had some talk about adding a "window region" or something in the
past, that would be used for edge snapping and stuff, or was it deemed
that input region was enough; I can't recall.
That was my proposal, though I think that is not relevant to this
discussion.

I'm not clear on what this merging of the bounding boxes is being used
for. I don't think it should be used by the compositor, as I see it the
compositor final step should be a stack of *all* surfaces from top to
bottom, no matter what clients or subsurface trees they belong to, and
all compositing algorithms are done on this stack. A bounding-box merge
of a particular subtree is not useful for such algorithms.

I would think mouse event delivery would be entirely controlled by the
input area set on the wl_shell_surface. The client would be allowed to
make it larger than the surface, so that it includes whatever portion of
subsurfaces it wants. This also means the events are delivered to the
main surface, rather than requiring the client to fix the events that
are delivered to various decorations.

(I also propose a smaller region be added to wl_shell_surface. The best
way to describe this is "the area that is left when you maximize the
surface on modern window managers". It has the "edges" removed, such as
the black 1-pixel border the toytoolkit is drawing. The purpose is so
that maximization against less than 4 edges can be done).
Pekka Paalanen
2013-04-30 08:26:24 UTC
Permalink
On Mon, 29 Apr 2013 10:24:54 -0700
Post by Bill Spitzak
Post by Pekka Paalanen
We had some talk about adding a "window region" or something in the
past, that would be used for edge snapping and stuff, or was it
deemed that input region was enough; I can't recall.
That was my proposal, though I think that is not relevant to this
discussion.
I'm not clear on what this merging of the bounding boxes is being
used for.
It is used for everything where you need to know the window size, as
opposed to the sizes of the individual surfaces that compose a window.

- pq
Pekka Paalanen
2013-04-25 10:57:47 UTC
Permalink
Increase the maximum number of shm "leaves" to three, and rewrite the
leaf release and pick algorithms. The new algorithms hopefully improve
on buffer re-use while freeing unused buffers.

The goal of the new release algorithm is to always leave one free leaf
with storage allocated, so that the next redraw could start straight on
it.

The new leaf picking algorithm will prefer a free leaf that already has
some storage allocated, instead of just picking the first free leaf that
may need to allocate a new buffer.

Triple-buffering is especially for sub-surfaces, where the compositor
may have one wl_buffer busy on screen, and another wl_buffer busy in the
sub-surface cached state due to the synchronized commit mode. To be
able to forcibly repaint at that situation for e.g. resize, we need a
third buffer.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/window.c | 66 +++++++++++++++++++++++++++++++++++---------------------
1 file changed, 41 insertions(+), 25 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index 2540cc9..695cf5d 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -820,6 +820,8 @@ shm_surface_leaf_release(struct shm_surface_leaf *leaf)
memset(leaf, 0, sizeof *leaf);
}

+#define MAX_LEAVES 3
+
struct shm_surface {
struct toysurface base;
struct display *display;
@@ -827,7 +829,7 @@ struct shm_surface {
uint32_t flags;
int dx, dy;

- struct shm_surface_leaf leaf[2];
+ struct shm_surface_leaf leaf[MAX_LEAVES];
struct shm_surface_leaf *current;
};

@@ -841,16 +843,32 @@ static void
shm_surface_buffer_release(void *data, struct wl_buffer *buffer)
{
struct shm_surface *surface = data;
+ struct shm_surface_leaf *leaf;
+ int i;
+ int free_found;

- if (surface->leaf[0].data->buffer == buffer)
- surface->leaf[0].busy = 0;
- else if (surface->leaf[1].data->buffer == buffer)
- surface->leaf[1].busy = 0;
- else
- assert(0 && "shm_surface_buffer_release: unknown buffer");
+ for (i = 0; i < MAX_LEAVES; i++) {
+ leaf = &surface->leaf[i];
+ if (leaf->data && leaf->data->buffer == buffer) {
+ leaf->busy = 0;
+ break;
+ }
+ }
+ assert(i < MAX_LEAVES && "unknown buffer released");
+
+ /* Leave one free leaf with storage, release others */
+ free_found = 0;
+ for (i = 0; i < MAX_LEAVES; i++) {
+ leaf = &surface->leaf[i];
+
+ if (!leaf->cairo_surface || leaf->busy)
+ continue;

- if (!surface->leaf[0].busy && !surface->leaf[1].busy)
- shm_surface_leaf_release(&surface->leaf[1]);
+ if (!free_found)
+ free_found = 1;
+ else
+ shm_surface_leaf_release(leaf);
+ }
}

static const struct wl_buffer_listener shm_surface_buffer_listener = {
@@ -864,25 +882,22 @@ shm_surface_prepare(struct toysurface *base, int dx, int dy,
int resize_hint = !!(flags & SURFACE_HINT_RESIZE);
struct shm_surface *surface = to_shm_surface(base);
struct rectangle rect = { 0, 0, width, height };
- struct shm_surface_leaf *leaf;
+ struct shm_surface_leaf *leaf = NULL;
+ int i;

surface->dx = dx;
surface->dy = dy;

- /* See shm_surface_buffer_release() */
- if (!surface->leaf[0].busy && !surface->leaf[1].busy &&
- surface->leaf[1].cairo_surface) {
- fprintf(stderr, "window.c:%s: TODO: release leaf[1]\n",
- __func__);
- }
+ /* pick a free buffer, preferrably one that already has storage */
+ for (i = 0; i < MAX_LEAVES; i++) {
+ if (surface->leaf[i].busy)
+ continue;

- /* pick a free buffer from the two */
- if (!surface->leaf[0].busy)
- leaf = &surface->leaf[0];
- else if (!surface->leaf[1].busy)
- leaf = &surface->leaf[1];
- else {
- fprintf(stderr, "%s: both buffers are held by the server.\n",
+ if (!leaf || surface->leaf[i].cairo_surface)
+ leaf = &surface->leaf[i];
+ }
+ if (!leaf) {
+ fprintf(stderr, "%s: all buffers are held by the server.\n",
__func__);
return NULL;
}
@@ -964,9 +979,10 @@ static void
shm_surface_destroy(struct toysurface *base)
{
struct shm_surface *surface = to_shm_surface(base);
+ int i;

- shm_surface_leaf_release(&surface->leaf[0]);
- shm_surface_leaf_release(&surface->leaf[1]);
+ for (i = 0; i < MAX_LEAVES; i++)
+ shm_surface_leaf_release(&surface->leaf[i]);

free(surface);
}
--
1.8.1.5
Bill Spitzak
2013-04-25 19:34:13 UTC
Permalink
Post by Pekka Paalanen
Triple-buffering is especially for sub-surfaces, where the compositor
may have one wl_buffer busy on screen, and another wl_buffer busy in the
sub-surface cached state due to the synchronized commit mode. To be
able to forcibly repaint at that situation for e.g. resize, we need a
third buffer.
I cannot see any difference between a subsurface waiting for a commit on
a parent and a normal surface that has not done a commit yet. There is
no need for triple buffering, or if there is it is not a subsurface
requirement.

What do you mean by "forcibly repaint for resize"? Resizes of windows
cannot happen until the client produces a new buffer with the resized
contents and does a commit. Otherwise it has to keep showing the old
buffer. Unless you really want to reproduce the biggest ugliness problem
with X?
Pekka Paalanen
2013-04-26 07:47:24 UTC
Permalink
On Thu, 25 Apr 2013 12:34:13 -0700
Post by Bill Spitzak
Post by Pekka Paalanen
Triple-buffering is especially for sub-surfaces, where the
compositor may have one wl_buffer busy on screen, and another
wl_buffer busy in the sub-surface cached state due to the
synchronized commit mode. To be able to forcibly repaint at that
situation for e.g. resize, we need a third buffer.
I cannot see any difference between a subsurface waiting for a commit
on a parent and a normal surface that has not done a commit yet.
There is no need for triple buffering, or if there is it is not a
subsurface requirement.
In the server, there are three slots per sub-surface, that may hold a
wl_buffer reference: the current buffer on display, the pending buffer
waiting for commit, and the cached buffer waiting for the parent commit.

Having a buffer in the pending slot does not actually count as the
buffer being reserved for the server. Instead, the current slot may
temporarily hold two buffers at the same time: before compositor
refresh cycle completes, the old current buffer may be kept by the
renderer and backend, and the new current buffer is just waiting to get
on screen.

When a sub-surface is committed (wl_surface.commit), the buffer in
pending is moved to either the current or the cache slot, depending on
the effective commit mode, and the buffer becomes reserved by the
server.

This should be clear in the protocol specification.

If the target is the cache slot, the previous buffer (if any) in the
cache slot is released. If the target is the current slot, the previous
buffer (if any) in the current slot will be released when the
compositor refresh cycle completes.

When the parent's current buffer gets replaced (that is, when the new
surface state is _applied_), each of its sub-surfaces' cached buffer is
moved to the current slot (new state is applied), depending on the
effective commit mode.

Why we should not need *four* buffers is because the application should
throttle its resizing to the server refresh cycle by using the frame
callback on one of the window's surfaces. This guarantees that the old
current buffer gets released, before the application repaints again.

Just to clarify, the four buffer case would be this:
- buffer A is currently on screen, the old current, because the
renderer or backend needs it continuously (e.g. directly scanned out)
- buffer B is going to screen, the new current
- buffer C is in the cache slot, because it is waiting for the parent
commit to apply state, and this sub-surface is in synchronized mode
- buffer D is being drawn into
This is a temporary state, where buffer A is soon released, but the
unthrottled client decided to draw before that, so it needs buffer D.
Post by Bill Spitzak
What do you mean by "forcibly repaint for resize"? Resizes of windows
cannot happen until the client produces a new buffer with the resized
contents and does a commit. Otherwise it has to keep showing the old
buffer. Unless you really want to reproduce the biggest ugliness
problem with X?
This is all *inside* the client. This is a toolkit patch.

The canonical use case for a sub-surface is an application, which has a
main surface, and a component in a sub-surface. The forcing is about the
application forcing the component to redraw in a new size.


Thanks,
pq
Bill Spitzak
2013-04-26 18:40:23 UTC
Permalink
Post by Pekka Paalanen
This is all *inside* the client. This is a toolkit patch.
You are right, the patch is in clients/window.c, I confused it with an
adjacent patch.

I agree the client has to throttle resizes but I think this can avoid
the triple buffer and removes the difference between main and subwindows.

For a single-surface client, it does not need a third, fourth, etc
buffer because it does not draw until it receives the buffer-release for
the old buffer. The same rule can be applied to subsurfaces. They should
not draw until they get their buffer-release. I don't see this as being
any more difficult than any kind of throttling, and removes the
difference between main and subsurfaces.

For the toytoolkit this means that draw is not called until all the old
buffers for all subsurfaces are released. Rather than the toytoolkit
having to manage this, it may be easier for the server to send the
release events for subsurfaces first, and the main surface last. Thus
the toolkit only has to wait for the main surface.

I cannot think of any plausible compositor that will not release all the
old buffers for an entire tree of subsurfaces at once so reordering the
events like this should not be a problem.

If some subsurface buffers were not released, I also do not see any way
that triple buffering is going to fix this, an infinite number of
buffers are going to be needed. Throttling will have to block until the
third buffer is released, so I see no reason not to have it block until
the second buffer is released instead.
Pekka Paalanen
2013-04-27 12:21:15 UTC
Permalink
On Fri, 26 Apr 2013 11:40:23 -0700
Post by Bill Spitzak
Post by Pekka Paalanen
This is all *inside* the client. This is a toolkit patch.
You are right, the patch is in clients/window.c, I confused it with an
adjacent patch.
I agree the client has to throttle resizes but I think this can avoid
the triple buffer and removes the difference between main and subwindows.
For a single-surface client, it does not need a third, fourth, etc
buffer because it does not draw until it receives the buffer-release for
the old buffer. The same rule can be applied to subsurfaces. They should
not draw until they get their buffer-release. I don't see this as being
any more difficult than any kind of throttling, and removes the
difference between main and subsurfaces.
But in resize scenario, a sub-surface will never get the buffer
release it would need to update its size, because that would need a
commit on the parent surface. Parent surface cannot commit,
until the sub-surface has committed its newly sized rendering,
otherwise there can be a glitch.

1. There is a window with a main surface and a sub-surface is in
desync mode. Sub-surface is running independently. Sub-surface has
buffer B1 reserved by the server on display.
2. Application decides the window must resize.
3. The sub-surface is set sync.
4. Meanwhile the sub-surface component keeps on running: it draws
and commits buffer B2.
5. Due to sync mode, B2 goes into cache instead. B1 is still on
display. Both B1 and B2 stay reserved indefinitely.
6. Application tells sub-surface component to resize, and waits for
it to do so.
7. Sub-surface component obviously does not get a frame callback
for B2, so it is still waiting on that.
8. Sub-surface component gets the command to resize, and needs to
redraw.
9. Sub-surface component draws and commits buffer B3, and acks the
resize.
10. The application gets the ack.
11. The application draws and commits the main surface in the new
size.
12. The application reverts the sub-surface mode to desync.
13. Due to step 9, the server releases buffer B2.
14. The main surface commit causes a server repaint cycle to start.
15. The main surface, and the sub-surface with buffer B3 are
updated atomically on display.
16. The server releases buffer B1.

It is a bit difficult to describe, since there are three
asyncronous lines of execution: the server, the application
controlling the main surface, and the component using the
sub-surface. Also, the Wayland connection acts as a serialising
mechanism for all requests coming from the two client threads. The
requests are processed in order by the server, but the client side
must take some care in ordering them.

Therefore the only way for glitch- and deadlock free resizing is to
use a third buffer, sans explicit synchronization of the
sub-surface thread or component.

Note, that a sub-surface is assumed to run independently. This
algorithm avoids the need to explicitly pause the sub-surface
thread for resizing. Of course, one is free to explicitly pause it,
and then resize in a completely synchronous way, but my example
does not do that. In my scenario, resizing does not need a full
stop of the sub-surface thread, just one roundtrip from the app to
the component, with the cost of temporarily using an extra buffer.
Post by Bill Spitzak
For the toytoolkit this means that draw is not called until all the old
buffers for all subsurfaces are released. Rather than the toytoolkit
having to manage this, it may be easier for the server to send the
release events for subsurfaces first, and the main surface last. Thus
the toolkit only has to wait for the main surface.
I cannot think of any plausible compositor that will not release all the
old buffers for an entire tree of subsurfaces at once so reordering the
events like this should not be a problem.
If some subsurface buffers were not released, I also do not see any way
that triple buffering is going to fix this, an infinite number of
buffers are going to be needed. Throttling will have to block until the
third buffer is released, so I see no reason not to have it block until
the second buffer is released instead.
There is only a finite number of slots where buffers can be held
reserved in the server for a sub-surface: three at most, which is a
transient state; otherwise two. For normal surfaces it is one less
in both cases, because normal surfaces do not have the cache slot.

Oh, and all this really assumes, that the server actually needs to
keep hold of the buffer, whose contents are on display. If instead
the server makes a copy, like for all wl_shm buffers in the
GL-renderer case, you get to subtract another one from both cases.


Thanks,
pq
Bill Spitzak
2013-04-28 01:41:48 UTC
Permalink
Post by Pekka Paalanen
1. There is a window with a main surface and a sub-surface is in
desync mode. Sub-surface is running independently. Sub-surface has
buffer B1 reserved by the server on display.
2. Application decides the window must resize.
3. The sub-surface is set sync.
4. Meanwhile the sub-surface component keeps on running: it draws
and commits buffer B2.
5. Due to sync mode, B2 goes into cache instead. B1 is still on
display. Both B1 and B2 stay reserved indefinitely.
6. Application tells sub-surface component to resize, and waits for
it to do so.
7. Sub-surface component obviously does not get a frame callback
for B2, so it is still waiting on that.
8. Sub-surface component gets the command to resize, and needs to
redraw.
9. Sub-surface component draws and commits buffer B3, and acks the
resize.
10. The application gets the ack.
11. The application draws and commits the main surface in the new
size.
12. The application reverts the sub-surface mode to desync.
13. Due to step 9, the server releases buffer B2.
14. The main surface commit causes a server repaint cycle to start.
15. The main surface, and the sub-surface with buffer B3 are
updated atomically on display.
16. The server releases buffer B1.
That does sound right.

If the server somehow knew that B2 was not in response to the resize, it
could composite the old buffers for the main surface with B2 and then
free B1. The client for the subsurface could then wait for B1 to be
freed before it starts working on the new resize surface.

I think this could be known by adding some kind of serial numbers in the
set-sync and buffer attach requests. It might be nice to fix this so if
the resize is slow to respond the video playback keeps happening.

However another problem is *after* the resize. The subsurface client may
want to update the frame several times after the resize even if the
parent has not done a commit yet. The compositor cannot use these
buffers because they don't match the main surface, so serial numbers
will not help. So the need for 3 buffers remains there.

In fact there may be a problem without subsurfaces. If a client wants to
update it's drawing 60 times a second, and the compositor for some
reason is not responding, that client probably wants the compositor to
show the *newest* frame when it finally does, not the oldest. So it does
not want to block waiting for the old buffer to be released, and wants
the triple buffering anyway with one buffer always containing the newest
image. I think it would be ok to require compositors to free one of two
outstanding buffers very soon after a third is attached so clients do
not need to track more than that.
Pekka Paalanen
2013-04-28 05:59:56 UTC
Permalink
On Sat, 27 Apr 2013 18:41:48 -0700
Post by Bill Spitzak
Post by Pekka Paalanen
1. There is a window with a main surface and a sub-surface is in
desync mode. Sub-surface is running independently. Sub-surface has
buffer B1 reserved by the server on display.
2. Application decides the window must resize.
3. The sub-surface is set sync.
4. Meanwhile the sub-surface component keeps on running: it draws
and commits buffer B2.
5. Due to sync mode, B2 goes into cache instead. B1 is still on
display. Both B1 and B2 stay reserved indefinitely.
6. Application tells sub-surface component to resize, and waits for
it to do so.
7. Sub-surface component obviously does not get a frame callback
for B2, so it is still waiting on that.
8. Sub-surface component gets the command to resize, and needs to
redraw.
9. Sub-surface component draws and commits buffer B3, and acks the
resize.
10. The application gets the ack.
11. The application draws and commits the main surface in the new
size.
12. The application reverts the sub-surface mode to desync.
13. Due to step 9, the server releases buffer B2.
14. The main surface commit causes a server repaint cycle to start.
15. The main surface, and the sub-surface with buffer B3 are
updated atomically on display.
16. The server releases buffer B1.
That does sound right.
If the server somehow knew that B2 was not in response to the resize, it
could composite the old buffers for the main surface with B2 and then
free B1. The client for the subsurface could then wait for B1 to be
freed before it starts working on the new resize surface.
I think this could be known by adding some kind of serial numbers in the
set-sync and buffer attach requests. It might be nice to fix this so if
the resize is slow to respond the video playback keeps happening.
That sounds complex. I'm not sure this is worth fixing.
Post by Bill Spitzak
However another problem is *after* the resize. The subsurface client may
want to update the frame several times after the resize even if the
parent has not done a commit yet. The compositor cannot use these
buffers because they don't match the main surface, so serial numbers
will not help. So the need for 3 buffers remains there.
In fact there may be a problem without subsurfaces. If a client wants to
update it's drawing 60 times a second, and the compositor for some
reason is not responding, that client probably wants the compositor to
show the *newest* frame when it finally does, not the oldest. So it does
not want to block waiting for the old buffer to be released, and wants
the triple buffering anyway with one buffer always containing the newest
image. I think it would be ok to require compositors to free one of two
outstanding buffers very soon after a third is attached so clients do
not need to track more than that.
That already happens. If you commit new buffers to a normal surface
faster than the server is displaying them, only the newest buffer
will be kept by the server (Weston), and the previous not yet
presented buffer will be released. The triple-buffering logic is
implicitly built-in.


Thanks,
pq
Bill Spitzak
2013-04-29 16:51:39 UTC
Permalink
Post by Pekka Paalanen
Post by Bill Spitzak
If the server somehow knew that B2 was not in response to the resize, it
could composite the old buffers for the main surface with B2 and then
free B1. The client for the subsurface could then wait for B1 to be
freed before it starts working on the new resize surface.
I think this could be known by adding some kind of serial numbers in the
set-sync and buffer attach requests. It might be nice to fix this so if
the resize is slow to respond the video playback keeps happening.
That sounds complex. I'm not sure this is worth fixing.
Probably not, though the display would be more correct with this fix.
Post by Pekka Paalanen
That already happens. If you commit new buffers to a normal surface
faster than the server is displaying them, only the newest buffer
will be kept by the server (Weston), and the previous not yet
presented buffer will be released. The triple-buffering logic is
implicitly built-in.
Yes but it sounds like the need for triple-buffering is not unique to
subsurfaces, a main surface can need it as well. That sounds more right
to me, I was bothered by the idea that subsurfaces somehow have
different requirements, though I came to the wrong conclusion that
somehow triple buffering was never needed.
Pekka Paalanen
2013-04-25 10:57:48 UTC
Permalink
The new application API window_add_subsurface() will create a plain
widget that is on a new sub-surface.

The sub-surface position is taken from the surface's root widget
allocation. This way widget allocations are always in the main surface
(i.e. window) coordinates. However, Cairo drawing coordinates will now
be different to widget coordinates for sub-surfaces. Cairo coordinates
are fixed by applying a translation in widget_cairo_create(), so that
widget drawing code can simply use the widget allocation as before.

Sub-surfaces are hooked up into resize, window flush, redraw, and
find_widget. Window maintains a list of sub-surfaces in top-first order.

Add a client settable default commit mode, and toggle the mode when
resizing to guarantee in-sync updates of a window and its sub-surfaces.

Changes in v3:
- replaced set_commit_mode with set_sync and set_desync

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/window.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
clients/window.h | 9 ++++
2 files changed, 155 insertions(+), 3 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index 695cf5d..30bb71e 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -85,6 +85,7 @@ struct display {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
+ struct wl_subcompositor *subcompositor;
struct wl_shell *shell;
struct wl_shm *shm;
struct wl_data_device_manager *data_device_manager;
@@ -189,6 +190,9 @@ struct surface {
struct window *window;

struct wl_surface *surface;
+ struct wl_subsurface *subsurface;
+ int synchronized;
+ int synchronized_default;
struct toysurface *toysurface;
struct widget *widget;

@@ -202,6 +206,8 @@ struct surface {
enum wl_output_transform buffer_transform;

cairo_surface_t *cairo_surface;
+
+ struct wl_list link;
};

struct window {
@@ -240,6 +246,9 @@ struct window {

struct frame *frame;

+ /* struct surface::link, contains also main_surface */
+ struct wl_list subsurface_list;
+
void *user_data;
struct wl_list link;
};
@@ -1197,12 +1206,21 @@ window_has_focus(struct window *window)
static void
window_flush(struct window *window)
{
+ struct surface *surface;
+
if (window->type == TYPE_NONE) {
window->type = TYPE_TOPLEVEL;
if (window->shell_surface)
wl_shell_surface_set_toplevel(window->shell_surface);
}

+ wl_list_for_each(surface, &window->subsurface_list, link) {
+ if (surface == window->main_surface)
+ continue;
+
+ surface_flush(surface);
+ }
+
surface_flush(window->main_surface);
}

@@ -1299,11 +1317,15 @@ surface_destroy(struct surface *surface)
if (surface->opaque_region)
wl_region_destroy(surface->opaque_region);

+ if (surface->subsurface)
+ wl_subsurface_destroy(surface->subsurface);
+
wl_surface_destroy(surface->surface);

if (surface->toysurface)
surface->toysurface->destroy(surface->toysurface);

+ wl_list_remove(&surface->link);
free(surface);
}

@@ -1373,7 +1395,16 @@ widget_find_widget(struct widget *widget, int32_t x, int32_t y)
static struct widget *
window_find_widget(struct window *window, int32_t x, int32_t y)
{
- return widget_find_widget(window->main_surface->widget, x, y);
+ struct surface *surface;
+ struct widget *widget;
+
+ wl_list_for_each(surface, &window->subsurface_list, link) {
+ widget = widget_find_widget(surface->widget, x, y);
+ if (widget)
+ return widget;
+ }
+
+ return NULL;
}

static struct widget *
@@ -1423,8 +1454,13 @@ void
widget_destroy(struct widget *widget)
{
struct display *display = widget->window->display;
+ struct surface *surface = widget->surface;
struct input *input;

+ /* Destroy the sub-surface along with the root widget */
+ if (surface->widget == widget && surface->subsurface)
+ surface_destroy(widget->surface);
+
if (widget->tooltip) {
free(widget->tooltip);
widget->tooltip = NULL;
@@ -1498,12 +1534,15 @@ widget_get_cairo_surface(struct widget *widget)
cairo_t *
widget_cairo_create(struct widget *widget)
{
+ struct surface *surface = widget->surface;
cairo_surface_t *cairo_surface;
cairo_t *cr;

cairo_surface = widget_get_cairo_surface(widget);
cr = cairo_create(cairo_surface);

+ cairo_translate(cr, -surface->allocation.x, -surface->allocation.y);
+
return cr;
}

@@ -3220,6 +3259,36 @@ window_move(struct window *window, struct input *input, uint32_t serial)
}

static void
+surface_set_synchronized(struct surface *surface)
+{
+ if (!surface->subsurface)
+ return;
+
+ if (surface->synchronized)
+ return;
+
+ wl_subsurface_set_sync(surface->subsurface);
+ surface->synchronized = 1;
+}
+
+static void
+surface_set_synchronized_default(struct surface *surface)
+{
+ if (!surface->subsurface)
+ return;
+
+ if (surface->synchronized == surface->synchronized_default)
+ return;
+
+ if (surface->synchronized_default)
+ wl_subsurface_set_sync(surface->subsurface);
+ else
+ wl_subsurface_set_desync(surface->subsurface);
+
+ surface->synchronized = surface->synchronized_default;
+}
+
+static void
surface_resize(struct surface *surface)
{
struct widget *widget = surface->widget;
@@ -3241,11 +3310,18 @@ surface_resize(struct surface *surface)
widget->allocation.height,
widget->user_data);

+ if (surface->subsurface &&
+ (surface->allocation.x != widget->allocation.x ||
+ surface->allocation.y != widget->allocation.y)) {
+ wl_subsurface_set_position(surface->subsurface,
+ widget->allocation.x,
+ widget->allocation.y);
+ }
if (surface->allocation.width != widget->allocation.width ||
surface->allocation.height != widget->allocation.height) {
- surface->allocation = widget->allocation;
window_schedule_redraw(widget->window);
}
+ surface->allocation = widget->allocation;

if (widget->opaque)
wl_region_add(surface->opaque_region, 0, 0,
@@ -3256,6 +3332,8 @@ surface_resize(struct surface *surface)
static void
idle_resize(struct window *window)
{
+ struct surface *surface;
+
window->resize_needed = 0;

widget_set_allocation(window->main_surface->widget,
@@ -3265,6 +3343,19 @@ idle_resize(struct window *window)
window->pending_allocation.height);

surface_resize(window->main_surface);
+
+ /* The main surface is in the list, too. Main surface's
+ * resize_handler is responsible for calling widget_set_allocation()
+ * on all sub-surface root widgets, so they will be resized
+ * properly.
+ */
+ wl_list_for_each(surface, &window->subsurface_list, link) {
+ if (surface == window->main_surface)
+ continue;
+
+ surface_set_synchronized(surface);
+ surface_resize(surface);
+ }
}

void
@@ -3377,17 +3468,23 @@ static void
idle_redraw(struct task *task, uint32_t events)
{
struct window *window = container_of(task, struct window, redraw_task);
+ struct surface *surface;

if (window->resize_needed)
idle_resize(window);

- widget_redraw(window->main_surface->widget);
+ wl_list_for_each(surface, &window->subsurface_list, link)
+ widget_redraw(surface->widget);
+
window->redraw_needed = 0;
wl_list_init(&window->redraw_task.link);

window->frame_cb = wl_surface_frame(window->main_surface->surface);
wl_callback_add_listener(window->frame_cb, &listener, window);
window_flush(window);
+
+ wl_list_for_each(surface, &window->subsurface_list, link)
+ surface_set_synchronized_default(surface);
}

void
@@ -3687,6 +3784,8 @@ surface_create(struct window *window)
surface->surface = wl_compositor_create_surface(display->compositor);
wl_surface_add_listener(surface->surface, &surface_listener, window);

+ wl_list_insert(&window->subsurface_list, &surface->link);
+
return surface;
}

@@ -3702,6 +3801,7 @@ window_create_internal(struct display *display,
return NULL;

memset(window, 0, sizeof *window);
+ wl_list_init(&window->subsurface_list);
window->display = display;
window->parent = parent;

@@ -3952,6 +4052,42 @@ window_set_buffer_type(struct window *window, enum window_buffer_type type)
window->main_surface->buffer_type = type;
}

+struct widget *
+window_add_subsurface(struct window *window, void *data,
+ enum subsurface_mode default_mode)
+{
+ struct widget *widget;
+ struct surface *surface;
+ struct wl_surface *parent;
+ struct wl_subcompositor *subcompo = window->display->subcompositor;
+
+ if (!subcompo)
+ return NULL;
+
+ surface = surface_create(window);
+ widget = widget_create(window, surface, data);
+ wl_list_init(&widget->link);
+ surface->widget = widget;
+
+ parent = window->main_surface->surface;
+ surface->subsurface = wl_subcompositor_get_subsurface(subcompo,
+ surface->surface,
+ parent);
+ surface->synchronized = 1;
+
+ switch (default_mode) {
+ case SUBSURFACE_SYNCHRONIZED:
+ surface->synchronized_default = 1;
+ break;
+ case SUBSURFACE_DESYNCHRONIZED:
+ surface->synchronized_default = 0;
+ break;
+ default:
+ assert(!"bad enum subsurface_mode");
+ }
+
+ return widget;
+}

static void
display_handle_geometry(void *data,
@@ -4223,6 +4359,10 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
&text_cursor_position_interface, 1);
} else if (strcmp(interface, "workspace_manager") == 0) {
init_workspace_manager(d, id);
+ } else if (strcmp(interface, "wl_subcompositor") == 0) {
+ d->subcompositor =
+ wl_registry_bind(registry, id,
+ &wl_subcompositor_interface, 1);
}

if (d->global_handler)
@@ -4516,6 +4656,9 @@ display_destroy(struct display *display)
fini_egl(display);
#endif

+ if (display->subcompositor)
+ wl_subcompositor_destroy(display->subcompositor);
+
if (display->shell)
wl_shell_destroy(display->shell);

diff --git a/clients/window.h b/clients/window.h
index 815b3f1..2f93bd4 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -259,6 +259,15 @@ window_destroy(struct window *window);
struct widget *
window_add_widget(struct window *window, void *data);

+enum subsurface_mode {
+ SUBSURFACE_SYNCHRONIZED,
+ SUBSURFACE_DESYNCHRONIZED
+};
+
+struct widget *
+window_add_subsurface(struct window *window, void *data,
+ enum subsurface_mode default_mode);
+
typedef void (*data_func_t)(void *data, size_t len,
int32_t x, int32_t y, void *user_data);
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:49 UTC
Permalink
Add redraw_needed flag to all surfaces, in addition to having one in
window. The window redraw_needed flag is changed to force a redraw of
the whole window, regardless of frame events.

widget_schedule_redraw() now schedules the redraw only for the surface,
where the widget is on. window_schedule_redraw() is equivalent to
scheduling a redraw for all (sub-)surfaces of the window.

We still use only one deferred task for all redraws.

surface_redraw() will skip the redraw, if the window does not force a
redraw and the surface does not need a redraw. It will also skip the
redraw, if the frame callback from the previous redraw has not triggered
yet. When the frame callback later arrives, the redraw task will be
scheduled, if the surface still needs a redraw.

If the window forces a redraw, the redraw is executed even if there is a
pending frame callback. This is for resizing: resizing should trigger a
window repaint, as it really wants to update all surfaces in one go, to
apply possible sub-surface size and position changes. Resizing is the
only thing that makes a window force a redraw.

With this change, subsurfaces demo can avoid repainting the cairo
sub-surface while still animating the GL sub-surface.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/window.c | 88 +++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 61 insertions(+), 27 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index 30bb71e..9bcf7ff 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -195,6 +195,8 @@ struct surface {
int synchronized_default;
struct toysurface *toysurface;
struct widget *widget;
+ int redraw_needed;
+ struct wl_callback *frame_cb;

struct rectangle allocation;
struct rectangle server_allocation;
@@ -220,8 +222,8 @@ struct window {
struct rectangle pending_allocation;
int x, y;
int resize_edges;
- int redraw_scheduled;
int redraw_needed;
+ int redraw_task_scheduled;
struct task redraw_task;
int resize_needed;
int saved_type;
@@ -242,7 +244,6 @@ struct window {

struct surface *main_surface;
struct wl_shell_surface *shell_surface;
- struct wl_callback *frame_cb;

struct frame *frame;

@@ -1311,6 +1312,9 @@ static void frame_destroy(struct frame *frame);
static void
surface_destroy(struct surface *surface)
{
+ if (surface->frame_cb)
+ wl_callback_destroy(surface->frame_cb);
+
if (surface->input_region)
wl_region_destroy(surface->input_region);

@@ -1337,8 +1341,7 @@ window_destroy(struct window *window)
struct window_output *window_output;
struct window_output *window_output_tmp;

- if (window->redraw_scheduled)
- wl_list_remove(&window->redraw_task.link);
+ wl_list_remove(&window->redraw_task.link);

wl_list_for_each(input, &display->input_list, link) {
if (input->pointer_focus == window)
@@ -1365,8 +1368,6 @@ window_destroy(struct window *window)

wl_list_remove(&window->link);

- if (window->frame_cb)
- wl_callback_destroy(window->frame_cb);
free(window->title);
free(window);
}
@@ -1593,10 +1594,14 @@ widget_set_axis_handler(struct widget *widget,
widget->axis_handler = handler;
}

+static void
+window_schedule_redraw_task(struct window *window);
+
void
widget_schedule_redraw(struct widget *widget)
{
- window_schedule_redraw(widget->window);
+ widget->surface->redraw_needed = 1;
+ window_schedule_redraw_task(widget->window);
}

cairo_surface_t *
@@ -3335,6 +3340,7 @@ idle_resize(struct window *window)
struct surface *surface;

window->resize_needed = 0;
+ window->redraw_needed = 1;

widget_set_allocation(window->main_surface->widget,
window->pending_allocation.x,
@@ -3450,14 +3456,14 @@ widget_redraw(struct widget *widget)
static void
frame_callback(void *data, struct wl_callback *callback, uint32_t time)
{
- struct window *window = data;
+ struct surface *surface = data;

- assert(callback == window->frame_cb);
+ assert(callback == surface->frame_cb);
wl_callback_destroy(callback);
- window->frame_cb = 0;
- window->redraw_scheduled = 0;
- if (window->redraw_needed)
- window_schedule_redraw(window);
+ surface->frame_cb = NULL;
+
+ if (surface->redraw_needed || surface->window->redraw_needed)
+ window_schedule_redraw_task(surface->window);
}

static const struct wl_callback_listener listener = {
@@ -3465,6 +3471,29 @@ static const struct wl_callback_listener listener = {
};

static void
+surface_redraw(struct surface *surface)
+{
+ if (!surface->window->redraw_needed && !surface->redraw_needed)
+ return;
+
+ /* Whole-window redraw forces a redraw even if the previous has
+ * not yet hit the screen.
+ */
+ if (surface->frame_cb) {
+ if (!surface->window->redraw_needed)
+ return;
+
+ wl_callback_destroy(surface->frame_cb);
+ }
+
+ surface->frame_cb = wl_surface_frame(surface->surface);
+ wl_callback_add_listener(surface->frame_cb, &listener, surface);
+
+ surface->redraw_needed = 0;
+ widget_redraw(surface->widget);
+}
+
+static void
idle_redraw(struct task *task, uint32_t events)
{
struct window *window = container_of(task, struct window, redraw_task);
@@ -3474,32 +3503,41 @@ idle_redraw(struct task *task, uint32_t events)
idle_resize(window);

wl_list_for_each(surface, &window->subsurface_list, link)
- widget_redraw(surface->widget);
+ surface_redraw(surface);

window->redraw_needed = 0;
wl_list_init(&window->redraw_task.link);
+ window->redraw_task_scheduled = 0;

- window->frame_cb = wl_surface_frame(window->main_surface->surface);
- wl_callback_add_listener(window->frame_cb, &listener, window);
window_flush(window);

wl_list_for_each(surface, &window->subsurface_list, link)
surface_set_synchronized_default(surface);
}

-void
-window_schedule_redraw(struct window *window)
+static void
+window_schedule_redraw_task(struct window *window)
{
- window->redraw_needed = 1;
if (window->configure_requests)
return;
- if (!window->redraw_scheduled) {
+ if (!window->redraw_task_scheduled) {
window->redraw_task.run = idle_redraw;
display_defer(window->display, &window->redraw_task);
- window->redraw_scheduled = 1;
+ window->redraw_task_scheduled = 1;
}
}

+void
+window_schedule_redraw(struct window *window)
+{
+ struct surface *surface;
+
+ wl_list_for_each(surface, &window->subsurface_list, link)
+ surface->redraw_needed = 1;
+
+ window_schedule_redraw_task(window);
+}
+
int
window_is_fullscreen(struct window *window)
{
@@ -3527,13 +3565,9 @@ window_defer_redraw_until_configure(struct window* window)
{
struct wl_callback *callback;

- if (window->redraw_scheduled) {
+ if (window->redraw_task_scheduled) {
wl_list_remove(&window->redraw_task.link);
- window->redraw_scheduled = 0;
- }
- if (window->frame_cb) {
- wl_callback_destroy(window->frame_cb);
- window->frame_cb = 0;
+ window->redraw_task_scheduled = 0;
}

callback = wl_display_sync(window->display->display);
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:50 UTC
Permalink
Add a demo program with:
- a main surface (green)
- a Cairo-image sub-surface (red)
- a raw GLESv2 widget (triangle)

Sub-surface input region is set empty to avoid problems in toytoolkit.

If Cairo links to libGL, then we will end up with also libGLESv2 linked
to subsurfaces program, and both libs getting really used, which leads
to disaster.

Do not build subsurfaces demo, if Cairo links to libGL and cairo-egl is
usable.

The GL rendering loop is not tied to the toytoolkit or the widget, but
runs directly from its own frame callback. Therefore it runs
independent of the rest of the application. This also relies on one of
two things:
- eglSwapInterval(0) is implemented, and therefore eglSwapBuffers never
blocks indefinitely, or
- toytoolkit has a workaround, that guarantees that eglSwapBuffers will
return soon, when we force a repaint on resize.
Otherwise the demo will deadlock.

The code is separated into three sections:

1. The library component, using only EGL, GLESv2, and libwayland-client
APIs, and not aware of any toolkit details of the parent application.
This runs independently until the parent application tells otherwise.

2. The glue code: a toytoolkit application widget, who has its own
rendering machinery.

3. The application written in toytoolkit.

This patch also adds new toytoolkit interfaces:
- widget_get_wl_surface()
- widget_get_last_time()
- widget_input_region_add()

Toytoolkit applications have not had a possibility to change the input
region. The frame widget (decorations) set the input region on its own
when used, otherwise the default input region of everything has been
used. If a window does not have a frame widget, it can now use
widget_input_region_add() to set a custom input region.

These are not window methods, because a widget may lie on a different
wl_surface (sub-surface) than the window.

Changes in v3:
- replace set_commit_mode with set_sync and set_desync

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/.gitignore | 1 +
clients/Makefile.am | 8 +
clients/subsurfaces.c | 792 ++++++++++++++++++++++++++++++++++++++++++++++++++
clients/window.c | 30 ++
clients/window.h | 9 +
configure.ac | 3 +
6 files changed, 843 insertions(+)
create mode 100644 clients/subsurfaces.c

diff --git a/clients/.gitignore b/clients/.gitignore
index 16088e8..f925ce6 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -22,6 +22,7 @@ simple-touch
smoke
subsurface-client-protocol.h
subsurface-protocol.c
+subsurfaces
tablet-shell-client-protocol.h
tablet-shell-protocol.c
text-client-protocol.h
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 5f83acd..d360174 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -64,6 +64,7 @@ clients_programs = \
clickdot \
transformed \
calibrator \
+ $(subsurfaces) \
$(full_gl_client_programs)

desktop_shell = weston-desktop-shell
@@ -131,6 +132,13 @@ transformed_LDADD = libtoytoolkit.la
calibrator_SOURCES = calibrator.c ../shared/matrix.c ../shared/matrix.h
calibrator_LDADD = libtoytoolkit.la

+if BUILD_SUBSURFACES_CLIENT
+subsurfaces = subsurfaces
+subsurfaces_SOURCES = subsurfaces.c
+subsurfaces_CPPFLAGS = $(AM_CPPFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS)
+subsurfaces_LDADD = libtoytoolkit.la $(SIMPLE_EGL_CLIENT_LIBS) -lm
+endif
+
if HAVE_PANGO
pango_programs = editor
editor_SOURCES = \
diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c
new file mode 100644
index 0000000..7fa8abb
--- /dev/null
+++ b/clients/subsurfaces.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright ? 2010 Intel Corporation
+ * Copyright ? 2011 Benjamin Franzke
+ * Copyright ? 2012-2013 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no representations
+ * about the suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cairo.h>
+#include <math.h>
+#include <assert.h>
+
+#include <linux/input.h>
+#include <wayland-client.h>
+
+#include <wayland-egl.h>
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+
+#include "window.h"
+
+#if 0
+#define DBG(fmt, ...) \
+ fprintf(stderr, "%d:%s " fmt, __LINE__, __func__, ##__VA_ARGS__)
+#else
+#define DBG(...) do {} while (0)
+#endif
+
+static int32_t option_red_mode;
+static int32_t option_triangle_mode;
+static int32_t option_no_triangle;
+static int32_t option_help;
+
+static const struct weston_option options[] = {
+ { WESTON_OPTION_INTEGER, "red-mode", 'r', &option_red_mode },
+ { WESTON_OPTION_INTEGER, "triangle-mode", 't', &option_triangle_mode },
+ { WESTON_OPTION_BOOLEAN, "no-triangle", 'n', &option_no_triangle },
+ { WESTON_OPTION_BOOLEAN, "help", 'h', &option_help },
+};
+
+static enum subsurface_mode
+int_to_mode(int32_t i)
+{
+ switch (i) {
+ case 0:
+ return SUBSURFACE_DESYNCHRONIZED;
+ case 1:
+ return SUBSURFACE_SYNCHRONIZED;
+ default:
+ fprintf(stderr, "error: %d is not a valid commit mode.\n", i);
+ exit(1);
+ }
+}
+
+static const char help_text[] =
+"Usage: %s [options]\n"
+"\n"
+" -r, --red-mode=MODE\t\tthe commit mode for the red sub-surface (0)\n"
+" -t, --triangle-mode=MODE\tthe commit mode for the GL sub-surface (0)\n"
+" -n, --no-triangle\t\tDo not create the GL sub-surface.\n"
+"\n"
+"The MODE is the wl_subsurface commit mode used by default for the\n"
+"given sub-surface. Valid values are the integers:\n"
+" 0\tfor desynchronized, i.e. free-running\n"
+" 1\tfor synchronized\n"
+"\n"
+"This program demonstrates sub-surfaces with the toytoolkit.\n"
+"The main surface contains the decorations, a green canvas, and a\n"
+"green spinner. One sub-surface is red with a red spinner. These\n"
+"are rendered with Cairo. The other sub-surface contains a spinning\n"
+"triangle rendered in EGL/GLESv2, without Cairo, i.e. it is a raw GL\n"
+"widget.\n"
+"\n"
+"The GL widget animates on its own. The spinners follow wall clock\n"
+"time and update only when their surface is repainted, so you see\n"
+"which surfaces get redrawn. The red sub-surface animates on its own,\n"
+"but can be toggled with the spacebar.\n"
+"\n"
+"Even though the sub-surfaces attempt to animate on their own, they\n"
+"are subject to the commit mode. If commit mode is synchronized,\n"
+"they will need a commit on the main surface to actually display.\n"
+"You can trigger a main surface repaint, without a resize, by\n"
+"hovering the pointer over the title bar buttons.\n"
+"\n"
+"Resizing will temporarily toggle the commit mode of all sub-surfaces\n"
+"to guarantee synchronized rendering on size changes. It also forces\n"
+"a repaint of all surfaces.\n"
+"\n"
+"Using -t1 -r1 is especially useful for trying to catch inconsistent\n"
+"rendering and deadlocks, since free-running sub-surfaces would\n"
+"immediately hide the problem.\n"
+"\n"
+"Key controls:\n"
+" space - toggle red sub-surface animation loop\n"
+" up - step window size shorter\n"
+" down - step window size taller\n"
+"\n";
+
+struct egl_state {
+ EGLDisplay dpy;
+ EGLContext ctx;
+ EGLConfig conf;
+};
+
+struct triangle_gl_state {
+ GLuint rotation_uniform;
+ GLuint pos;
+ GLuint col;
+};
+
+struct triangle {
+ struct egl_state *egl;
+
+ struct wl_surface *wl_surface;
+ struct wl_egl_window *egl_window;
+ EGLSurface egl_surface;
+ int width;
+ int height;
+
+ struct triangle_gl_state gl;
+
+ struct widget *widget;
+ uint32_t time;
+ struct wl_callback *frame_cb;
+};
+
+/******** Pure EGL/GLESv2/libwayland-client component: ***************/
+
+static const char *vert_shader_text =
+ "uniform mat4 rotation;\n"
+ "attribute vec4 pos;\n"
+ "attribute vec4 color;\n"
+ "varying vec4 v_color;\n"
+ "void main() {\n"
+ " gl_Position = rotation * pos;\n"
+ " v_color = color;\n"
+ "}\n";
+
+static const char *frag_shader_text =
+ "precision mediump float;\n"
+ "varying vec4 v_color;\n"
+ "void main() {\n"
+ " gl_FragColor = v_color;\n"
+ "}\n";
+
+static void
+egl_print_config_info(struct egl_state *egl)
+{
+ EGLint r, g, b, a;
+
+ printf("Chosen EGL config details:\n");
+
+ printf("\tRGBA bits");
+ if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_RED_SIZE, &r) &&
+ eglGetConfigAttrib(egl->dpy, egl->conf, EGL_GREEN_SIZE, &g) &&
+ eglGetConfigAttrib(egl->dpy, egl->conf, EGL_BLUE_SIZE, &b) &&
+ eglGetConfigAttrib(egl->dpy, egl->conf, EGL_ALPHA_SIZE, &a))
+ printf(": %d %d %d %d\n", r, g, b, a);
+ else
+ printf(" unknown\n");
+
+ printf("\tswap interval range");
+ if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MIN_SWAP_INTERVAL, &a) &&
+ eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MAX_SWAP_INTERVAL, &b))
+ printf(": %d - %d\n", a, b);
+ else
+ printf(" unknown\n");
+}
+
+static struct egl_state *
+egl_state_create(struct wl_display *display)
+{
+ struct egl_state *egl;
+
+ static const EGLint context_attribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+
+ EGLint config_attribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 1,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_NONE
+ };
+
+ EGLint major, minor, n;
+ EGLBoolean ret;
+
+ egl = calloc(1, sizeof *egl);
+ assert(egl);
+
+ egl->dpy = eglGetDisplay(display);
+ assert(egl->dpy);
+
+ ret = eglInitialize(egl->dpy, &major, &minor);
+ assert(ret == EGL_TRUE);
+ ret = eglBindAPI(EGL_OPENGL_ES_API);
+ assert(ret == EGL_TRUE);
+
+ ret = eglChooseConfig(egl->dpy, config_attribs, &egl->conf, 1, &n);
+ assert(ret && n == 1);
+
+ egl->ctx = eglCreateContext(egl->dpy, egl->conf,
+ EGL_NO_CONTEXT, context_attribs);
+ assert(egl->ctx);
+ egl_print_config_info(egl);
+
+ return egl;
+}
+
+static void
+egl_state_destroy(struct egl_state *egl)
+{
+ /* Required, otherwise segfault in egl_dri2.c: dri2_make_current()
+ * on eglReleaseThread(). */
+ eglMakeCurrent(egl->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+
+ eglTerminate(egl->dpy);
+ eglReleaseThread();
+ free(egl);
+}
+
+static void
+egl_make_swapbuffers_nonblock(struct egl_state *egl)
+{
+ EGLint a = EGL_MIN_SWAP_INTERVAL;
+ EGLint b = EGL_MAX_SWAP_INTERVAL;
+
+ if (!eglGetConfigAttrib(egl->dpy, egl->conf, a, &a) ||
+ !eglGetConfigAttrib(egl->dpy, egl->conf, b, &b)) {
+ fprintf(stderr, "warning: swap interval range unknown\n");
+ } else
+ if (a > 0) {
+ fprintf(stderr, "warning: minimum swap interval is %d, "
+ "while 0 is required to not deadlock on resize.\n", a);
+ }
+
+ /*
+ * We rely on the Wayland compositor to sync to vblank anyway.
+ * We just need to be able to call eglSwapBuffers() without the
+ * risk of waiting for a frame callback in it.
+ */
+ if (!eglSwapInterval(egl->dpy, 0)) {
+ fprintf(stderr, "error: eglSwapInterval() failed.\n");
+ }
+}
+
+static GLuint
+create_shader(const char *source, GLenum shader_type)
+{
+ GLuint shader;
+ GLint status;
+
+ shader = glCreateShader(shader_type);
+ assert(shader != 0);
+
+ glShaderSource(shader, 1, (const char **) &source, NULL);
+ glCompileShader(shader);
+
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ char log[1000];
+ GLsizei len;
+ glGetShaderInfoLog(shader, 1000, &len, log);
+ fprintf(stderr, "Error: compiling %s: %*s\n",
+ shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment",
+ len, log);
+ exit(1);
+ }
+
+ return shader;
+}
+
+static void
+triangle_init_gl(struct triangle_gl_state *trigl)
+{
+ GLuint frag, vert;
+ GLuint program;
+ GLint status;
+
+ frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER);
+ vert = create_shader(vert_shader_text, GL_VERTEX_SHADER);
+
+ program = glCreateProgram();
+ glAttachShader(program, frag);
+ glAttachShader(program, vert);
+ glLinkProgram(program);
+
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (!status) {
+ char log[1000];
+ GLsizei len;
+ glGetProgramInfoLog(program, 1000, &len, log);
+ fprintf(stderr, "Error: linking:\n%*s\n", len, log);
+ exit(1);
+ }
+
+ glUseProgram(program);
+
+ trigl->pos = 0;
+ trigl->col = 1;
+
+ glBindAttribLocation(program, trigl->pos, "pos");
+ glBindAttribLocation(program, trigl->col, "color");
+ glLinkProgram(program);
+
+ trigl->rotation_uniform = glGetUniformLocation(program, "rotation");
+}
+
+static void
+triangle_draw(const struct triangle_gl_state *trigl, uint32_t time)
+{
+ static const GLfloat verts[3][2] = {
+ { -0.5, -0.5 },
+ { 0.5, -0.5 },
+ { 0, 0.5 }
+ };
+ static const GLfloat colors[3][3] = {
+ { 1, 0, 0 },
+ { 0, 1, 0 },
+ { 0, 0, 1 }
+ };
+ GLfloat angle;
+ GLfloat rotation[4][4] = {
+ { 1, 0, 0, 0 },
+ { 0, 1, 0, 0 },
+ { 0, 0, 1, 0 },
+ { 0, 0, 0, 1 }
+ };
+ static const int32_t speed_div = 5;
+
+ angle = (time / speed_div) % 360 * M_PI / 180.0;
+ rotation[0][0] = cos(angle);
+ rotation[0][2] = sin(angle);
+ rotation[2][0] = -sin(angle);
+ rotation[2][2] = cos(angle);
+
+ glUniformMatrix4fv(trigl->rotation_uniform, 1, GL_FALSE,
+ (GLfloat *) rotation);
+
+ glClearColor(0.0, 0.0, 0.0, 0.5);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glVertexAttribPointer(trigl->pos, 2, GL_FLOAT, GL_FALSE, 0, verts);
+ glVertexAttribPointer(trigl->col, 3, GL_FLOAT, GL_FALSE, 0, colors);
+ glEnableVertexAttribArray(trigl->pos);
+ glEnableVertexAttribArray(trigl->col);
+
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ glDisableVertexAttribArray(trigl->pos);
+ glDisableVertexAttribArray(trigl->col);
+}
+
+static void
+triangle_frame_callback(void *data, struct wl_callback *callback,
+ uint32_t time);
+
+static const struct wl_callback_listener triangle_frame_listener = {
+ triangle_frame_callback
+};
+
+static void
+triangle_frame_callback(void *data, struct wl_callback *callback,
+ uint32_t time)
+{
+ struct triangle *tri = data;
+
+ DBG("%stime %u\n", callback ? "" : "artificial ", time);
+ assert(callback == tri->frame_cb);
+ tri->time = time;
+
+ if (callback)
+ wl_callback_destroy(callback);
+
+ glViewport(0, 0, tri->width, tri->height);
+
+ triangle_draw(&tri->gl, tri->time);
+
+ tri->frame_cb = wl_surface_frame(tri->wl_surface);
+ wl_callback_add_listener(tri->frame_cb, &triangle_frame_listener, tri);
+
+ eglSwapBuffers(tri->egl->dpy, tri->egl_surface);
+}
+
+static void
+triangle_create_egl_surface(struct triangle *tri, int width, int height)
+{
+ EGLBoolean ret;
+
+ tri->wl_surface = widget_get_wl_surface(tri->widget);
+ tri->egl_window = wl_egl_window_create(tri->wl_surface, width, height);
+ tri->egl_surface = eglCreateWindowSurface(tri->egl->dpy,
+ tri->egl->conf,
+ tri->egl_window, NULL);
+
+ ret = eglMakeCurrent(tri->egl->dpy, tri->egl_surface,
+ tri->egl_surface, tri->egl->ctx);
+ assert(ret == EGL_TRUE);
+
+ egl_make_swapbuffers_nonblock(tri->egl);
+ triangle_init_gl(&tri->gl);
+}
+
+/********* The widget code interfacing the toolkit agnostic code: **********/
+
+static void
+triangle_resize_handler(struct widget *widget,
+ int32_t width, int32_t height, void *data)
+{
+ struct triangle *tri = data;
+
+ DBG("to %dx%d\n", width, height);
+ tri->width = width;
+ tri->height = height;
+
+ if (tri->egl_surface) {
+ wl_egl_window_resize(tri->egl_window, width, height, 0, 0);
+ } else {
+ triangle_create_egl_surface(tri, width, height);
+ triangle_frame_callback(tri, NULL, 0);
+ }
+}
+
+static void
+triangle_redraw_handler(struct widget *widget, void *data)
+{
+ struct triangle *tri = data;
+ int w, h;
+
+ wl_egl_window_get_attached_size(tri->egl_window, &w, &h);
+
+ DBG("previous %dx%d, new %dx%d\n", w, h, tri->width, tri->height);
+
+ /* If size is not changing, do not redraw ahead of time.
+ * That would risk blocking in eglSwapbuffers().
+ */
+ if (w == tri->width && h == tri->height)
+ return;
+
+ if (tri->frame_cb) {
+ wl_callback_destroy(tri->frame_cb);
+ tri->frame_cb = NULL;
+ }
+ triangle_frame_callback(tri, NULL, tri->time);
+}
+
+static void
+set_empty_input_region(struct widget *widget, struct display *display)
+{
+ struct wl_compositor *compositor;
+ struct wl_surface *surface;
+ struct wl_region *region;
+
+ compositor = display_get_compositor(display);
+ surface = widget_get_wl_surface(widget);
+ region = wl_compositor_create_region(compositor);
+ wl_surface_set_input_region(surface, region);
+ wl_region_destroy(region);
+}
+
+static struct triangle *
+triangle_create(struct window *window, struct egl_state *egl)
+{
+ struct triangle *tri;
+
+ tri = calloc(1, sizeof *tri);
+
+ tri->egl = egl;
+ tri->widget = window_add_subsurface(window, tri,
+ int_to_mode(option_triangle_mode));
+ widget_set_resize_handler(tri->widget, triangle_resize_handler);
+ widget_set_redraw_handler(tri->widget, triangle_redraw_handler);
+
+ set_empty_input_region(tri->widget, window_get_display(window));
+
+ return tri;
+}
+
+static void
+triangle_destroy(struct triangle *tri)
+{
+ if (tri->egl_surface)
+ eglDestroySurface(tri->egl->dpy, tri->egl_surface);
+
+ if (tri->egl_window)
+ wl_egl_window_destroy(tri->egl_window);
+
+ widget_destroy(tri->widget);
+ free(tri);
+}
+
+/************** The toytoolkit application code: *********************/
+
+struct demoapp {
+ struct display *display;
+ struct window *window;
+ struct widget *widget;
+ struct widget *subsurface;
+
+ struct egl_state *egl;
+ struct triangle *triangle;
+
+ int animate;
+};
+
+static void
+draw_spinner(cairo_t *cr, const struct rectangle *rect, uint32_t time)
+{
+ double cx, cy, r, angle;
+ unsigned t;
+
+ cx = rect->x + rect->width / 2;
+ cy = rect->y + rect->height / 2;
+ r = (rect->width < rect->height ? rect->width : rect->height) * 0.3;
+ t = time % 2000;
+ angle = t * (M_PI / 500.0);
+
+ cairo_set_line_width(cr, 4.0);
+
+ if (t < 1000)
+ cairo_arc(cr, cx, cy, r, 0.0, angle);
+ else
+ cairo_arc(cr, cx, cy, r, angle, 0.0);
+
+ cairo_stroke(cr);
+}
+
+static void
+sub_redraw_handler(struct widget *widget, void *data)
+{
+ struct demoapp *app = data;
+ cairo_t *cr;
+ struct rectangle allocation;
+ uint32_t time;
+
+ widget_get_allocation(app->subsurface, &allocation);
+
+ cr = widget_cairo_create(widget);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+
+ /* debug: paint whole surface magenta; no magenta should show */
+ cairo_set_source_rgba(cr, 0.9, 0.0, 0.9, 1.0);
+ cairo_paint(cr);
+
+ cairo_rectangle(cr,
+ allocation.x,
+ allocation.y,
+ allocation.width,
+ allocation.height);
+ cairo_clip(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0, 0, 0.8);
+ cairo_paint(cr);
+
+ time = widget_get_last_time(widget);
+ cairo_set_source_rgba(cr, 1.0, 0.5, 0.5, 1.0);
+ draw_spinner(cr, &allocation, time);
+
+ cairo_destroy(cr);
+
+ if (app->animate)
+ widget_schedule_redraw(app->subsurface);
+ DBG("%dx%d @ %d,%d, last time %u\n",
+ allocation.width, allocation.height,
+ allocation.x, allocation.y, time);
+}
+
+static void
+sub_resize_handler(struct widget *widget,
+ int32_t width, int32_t height, void *data)
+{
+ DBG("%dx%d\n", width, height);
+ widget_input_region_add(widget, NULL);
+}
+
+static void
+redraw_handler(struct widget *widget, void *data)
+{
+ struct demoapp *app = data;
+ cairo_t *cr;
+ struct rectangle allocation;
+ uint32_t time;
+
+ widget_get_allocation(app->widget, &allocation);
+
+ cr = widget_cairo_create(widget);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_rectangle(cr,
+ allocation.x,
+ allocation.y,
+ allocation.width,
+ allocation.height);
+ cairo_set_source_rgba(cr, 0, 0.8, 0, 0.8);
+ cairo_fill(cr);
+
+ time = widget_get_last_time(widget);
+ cairo_set_source_rgba(cr, 0.5, 1.0, 0.5, 1.0);
+ draw_spinner(cr, &allocation, time);
+
+ cairo_destroy(cr);
+
+ DBG("%dx%d @ %d,%d, last time %u\n",
+ allocation.width, allocation.height,
+ allocation.x, allocation.y, time);
+}
+
+static void
+resize_handler(struct widget *widget,
+ int32_t width, int32_t height, void *data)
+{
+ struct demoapp *app = data;
+ struct rectangle area;
+ int side, h;
+
+ widget_get_allocation(widget, &area);
+
+ side = area.width < area.height ? area.width / 2 : area.height / 2;
+ h = area.height - side;
+
+ widget_set_allocation(app->subsurface,
+ area.x + area.width - side,
+ area.y,
+ side, h);
+
+ if (app->triangle) {
+ widget_set_allocation(app->triangle->widget,
+ area.x + area.width - side,
+ area.y + h,
+ side, side);
+ }
+
+ DBG("green %dx%d, red %dx%d, GL %dx%d\n",
+ area.width, area.height, side, h, side, side);
+}
+
+static void
+keyboard_focus_handler(struct window *window,
+ struct input *device, void *data)
+{
+ struct demoapp *app = data;
+
+ window_schedule_redraw(app->window);
+}
+
+static void
+key_handler(struct window *window, struct input *input, uint32_t time,
+ uint32_t key, uint32_t sym,
+ enum wl_keyboard_key_state state, void *data)
+{
+ struct demoapp *app = data;
+ struct rectangle winrect;
+
+ if (state == WL_KEYBOARD_KEY_STATE_RELEASED)
+ return;
+
+ switch (sym) {
+ case XKB_KEY_space:
+ app->animate = !app->animate;
+ window_schedule_redraw(window);
+ break;
+ case XKB_KEY_Up:
+ window_get_allocation(window, &winrect);
+ winrect.height -= 100;
+ if (winrect.height < 150)
+ winrect.height = 150;
+ window_schedule_resize(window, winrect.width, winrect.height);
+ break;
+ case XKB_KEY_Down:
+ window_get_allocation(window, &winrect);
+ winrect.height += 100;
+ if (winrect.height > 600)
+ winrect.height = 600;
+ window_schedule_resize(window, winrect.width, winrect.height);
+ break;
+ case XKB_KEY_Escape:
+ display_exit(app->display);
+ break;
+ }
+}
+
+static struct demoapp *
+demoapp_create(struct display *display)
+{
+ struct demoapp *app;
+
+ app = calloc(1, sizeof *app);
+ if (!app)
+ return NULL;
+
+ app->egl = egl_state_create(display_get_display(display));
+
+ app->display = display;
+ display_set_user_data(app->display, app);
+
+ app->window = window_create(app->display);
+ app->widget = frame_create(app->window, app);
+ window_set_title(app->window, "Wayland Sub-surface Demo");
+
+ window_set_key_handler(app->window, key_handler);
+ window_set_user_data(app->window, app);
+ window_set_keyboard_focus_handler(app->window, keyboard_focus_handler);
+
+ widget_set_redraw_handler(app->widget, redraw_handler);
+ widget_set_resize_handler(app->widget, resize_handler);
+
+ app->subsurface = window_add_subsurface(app->window, app,
+ int_to_mode(option_red_mode));
+ widget_set_redraw_handler(app->subsurface, sub_redraw_handler);
+ widget_set_resize_handler(app->subsurface, sub_resize_handler);
+
+ if (app->egl && !option_no_triangle)
+ app->triangle = triangle_create(app->window, app->egl);
+
+ /* minimum size */
+ widget_schedule_resize(app->widget, 100, 100);
+
+ /* initial size */
+ widget_schedule_resize(app->widget, 400, 300);
+
+ app->animate = 1;
+
+ return app;
+}
+
+static void
+demoapp_destroy(struct demoapp *app)
+{
+ if (app->triangle)
+ triangle_destroy(app->triangle);
+
+ if (app->egl)
+ egl_state_destroy(app->egl);
+
+ widget_destroy(app->subsurface);
+ widget_destroy(app->widget);
+ window_destroy(app->window);
+ free(app);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct display *display;
+ struct demoapp *app;
+
+ parse_options(options, ARRAY_LENGTH(options), &argc, argv);
+ if (option_help) {
+ printf(help_text, argv[0]);
+ return 0;
+ }
+
+ display = display_create(&argc, argv);
+ if (display == NULL) {
+ fprintf(stderr, "failed to create display: %m\n");
+ return -1;
+ }
+
+ app = demoapp_create(display);
+
+ display_run(display);
+
+ demoapp_destroy(app);
+ display_destroy(display);
+
+ return 0;
+}
diff --git a/clients/window.c b/clients/window.c
index 9bcf7ff..b12c21c 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -197,6 +197,7 @@ struct surface {
struct widget *widget;
int redraw_needed;
struct wl_callback *frame_cb;
+ uint32_t last_time;

struct rectangle allocation;
struct rectangle server_allocation;
@@ -1547,6 +1548,33 @@ widget_cairo_create(struct widget *widget)
return cr;
}

+struct wl_surface *
+widget_get_wl_surface(struct widget *widget)
+{
+ return widget->surface->surface;
+}
+
+uint32_t
+widget_get_last_time(struct widget *widget)
+{
+ return widget->surface->last_time;
+}
+
+void
+widget_input_region_add(struct widget *widget, const struct rectangle *rect)
+{
+ struct wl_compositor *comp = widget->window->display->compositor;
+ struct surface *surface = widget->surface;
+
+ if (!surface->input_region)
+ surface->input_region = wl_compositor_create_region(comp);
+
+ if (rect) {
+ wl_region_add(surface->input_region,
+ rect->x, rect->y, rect->width, rect->height);
+ }
+}
+
void
widget_set_resize_handler(struct widget *widget,
widget_resize_handler_t handler)
@@ -3462,6 +3490,8 @@ frame_callback(void *data, struct wl_callback *callback, uint32_t time)
wl_callback_destroy(callback);
surface->frame_cb = NULL;

+ surface->last_time = time;
+
if (surface->redraw_needed || surface->window->redraw_needed)
window_schedule_redraw_task(surface->window);
}
diff --git a/clients/window.h b/clients/window.h
index 2f93bd4..7db9c63 100644
--- a/clients/window.h
+++ b/clients/window.h
@@ -395,6 +395,15 @@ widget_get_user_data(struct widget *widget);
cairo_t *
widget_cairo_create(struct widget *widget);

+struct wl_surface *
+widget_get_wl_surface(struct widget *widget);
+
+uint32_t
+widget_get_last_time(struct widget *widget);
+
+void
+widget_input_region_add(struct widget *widget, const struct rectangle *rect);
+
void
widget_set_redraw_handler(struct widget *widget,
widget_redraw_handler_t handler);
diff --git a/configure.ac b/configure.ac
index d5fea9d..c3b4b7c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -268,6 +268,9 @@ AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes")
AM_CONDITIONAL(BUILD_FULL_GL_CLIENTS,
test x$cairo_modules = "xcairo-gl" -a "x$have_cairo_egl" = "xyes" -a "x$enable_egl" = "xyes")

+AM_CONDITIONAL(BUILD_SUBSURFACES_CLIENT,
+ [test '(' "x$have_cairo_egl" != "xyes" -o "x$cairo_modules" = "xcairo-glesv2" ')' -a "x$enable_simple_egl_clients" = "xyes"])
+
AM_CONDITIONAL(ENABLE_DESKTOP_SHELL, true)

AC_ARG_ENABLE(tablet-shell,
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:51 UTC
Permalink
Mesa's eglSwapBuffers() waits for the frame event from the previous
swapBuffers, before it returns. Apparently eglSwapInterval(), which
should be able to disable the wait, is unimplemented for now.

When a sub-surface contains an EGL widget, and the commit mode is
synchronized, the frame events will not be delivered to EGL until the
parent surface gets committed. Therefore rendering the EGL widget twice
would lead to a deadlock.

When the window is being resized, we need to force a repaint of the EGL
widget, too, to make the whole window consistent. For that, we need to
make sure the frame event from the previous eglSwapBuffers() actually
arrives.

This patch adds an extra wl_surface.commit(parent), when the window is
being resized, which should guarantee, that the previous eglSwapBuffers
gets its event.

To properly handle an EGL widget in a sub-surface, running in its own
thread, the EGL widget's automatic updates should be paused before
sending the extra wl_surface.commit(parent). A natural place for the
pause would be in the widget's resize hook. However, wl_surface.commit
cannot be called right after resize hooks, because it would commit new,
incomplete surface state. Therefore this patch is not enough for
threaded toytoolkit applications. Luckily those do not exist yet.

When eglSwapInterval() gets implemented, this patch should be reverted.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/window.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)

diff --git a/clients/window.c b/clients/window.c
index b12c21c..ab9a36e 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -3363,6 +3363,36 @@ surface_resize(struct surface *surface)
}

static void
+hack_prevent_EGL_sub_surface_deadlock(struct window *window)
+{
+ /*
+ * This hack should be removed, when EGL respects
+ * eglSwapInterval(0).
+ *
+ * If this window has sub-surfaces, especially a free-running
+ * EGL-widget, we need to post the parent surface once with
+ * all the old state to guarantee, that the EGL-widget will
+ * receive its frame callback soon. Otherwise, a forced call
+ * to eglSwapBuffers may end up blocking, waiting for a frame
+ * event that will never come, because we will commit the parent
+ * surface with all new state only after eglSwapBuffers returns.
+ *
+ * This assumes, that:
+ * 1. When the EGL widget's resize hook is called, it pauses.
+ * 2. When the EGL widget's redraw hook is called, it forces a
+ * repaint and a call to eglSwapBuffers(), and maybe resumes.
+ * In a single threaded application condition 1 is a no-op.
+ *
+ * XXX: This should actually be after the surface_resize() calls,
+ * but cannot, because then it would commit the incomplete state
+ * accumulated from the widget resize hooks.
+ */
+ if (window->subsurface_list.next != &window->main_surface->link ||
+ window->subsurface_list.prev != &window->main_surface->link)
+ wl_surface_commit(window->main_surface->surface);
+}
+
+static void
idle_resize(struct window *window)
{
struct surface *surface;
@@ -3370,6 +3400,8 @@ idle_resize(struct window *window)
window->resize_needed = 0;
window->redraw_needed = 1;

+ hack_prevent_EGL_sub_surface_deadlock(window);
+
widget_set_allocation(window->main_surface->widget,
window->pending_allocation.x,
window->pending_allocation.y,
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:52 UTC
Permalink
In case a toytoolkit application manages to schedule resizes constantly,
throttle them to the main surface display.

When resizing, all surfaces are updated synchronously, so it also makes
sense to synchronize on the main surface's frame callback particularly.
Rendering any faster will not make sense.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>
---
clients/window.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index ab9a36e..b72c2ca 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -3561,16 +3561,21 @@ idle_redraw(struct task *task, uint32_t events)
struct window *window = container_of(task, struct window, redraw_task);
struct surface *surface;

- if (window->resize_needed)
+ wl_list_init(&window->redraw_task.link);
+ window->redraw_task_scheduled = 0;
+
+ if (window->resize_needed) {
+ /* throttle resizing to the main surface display */
+ if (window->main_surface->frame_cb)
+ return;
+
idle_resize(window);
+ }

wl_list_for_each(surface, &window->subsurface_list, link)
surface_redraw(surface);

window->redraw_needed = 0;
- wl_list_init(&window->redraw_task.link);
- window->redraw_task_scheduled = 0;
-
window_flush(window);

wl_list_for_each(surface, &window->subsurface_list, link)
--
1.8.1.5
Pekka Paalanen
2013-04-25 10:57:53 UTC
Permalink
Aids for debugging and inspecting the algorithms.

Signed-off-by: Pekka Paalanen <ppaalanen at gmail.com>

---
This patch is not essential, but probably useful if anyone needs to
debug that stuff.
---
clients/window.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 91 insertions(+), 3 deletions(-)

diff --git a/clients/window.c b/clients/window.c
index b72c2ca..1562957 100644
--- a/clients/window.c
+++ b/clients/window.c
@@ -28,6 +28,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
@@ -419,6 +420,47 @@ enum window_location {

static const cairo_user_data_key_t shm_surface_data_key;

+#if 0
+
+static void
+debug_print(void *proxy, int line, const char *func, const char *fmt, ...)
+__attribute__ ((format (printf, 4, 5)));
+
+static void
+debug_print(void *proxy, int line, const char *func, const char *fmt, ...)
+{
+ va_list ap;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ fprintf(stderr, "%8ld.%03ld ",
+ (long)tv.tv_sec & 0xffff, (long)tv.tv_usec / 1000);
+
+ if (proxy)
+ fprintf(stderr, "%s@%d ",
+ wl_proxy_get_class(proxy), wl_proxy_get_id(proxy));
+
+ /*fprintf(stderr, __FILE__ ":%d:%s ", line, func);*/
+ fprintf(stderr, "%s ", func);
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+#define DBG(fmt, ...) \
+ debug_print(NULL, __LINE__, __func__, fmt, ##__VA_ARGS__)
+
+#define DBG_OBJ(obj, fmt, ...) \
+ debug_print(obj, __LINE__, __func__, fmt, ##__VA_ARGS__)
+
+#else
+
+#define DBG(...) do {} while (0)
+#define DBG_OBJ(...) do {} while (0)
+
+#endif
+
#ifdef HAVE_CAIRO_EGL

struct egl_window_surface {
@@ -857,11 +899,14 @@ shm_surface_buffer_release(void *data, struct wl_buffer *buffer)
struct shm_surface_leaf *leaf;
int i;
int free_found;
+ int available = MAX_LEAVES;
+ char bufs[MAX_LEAVES + 1];

for (i = 0; i < MAX_LEAVES; i++) {
leaf = &surface->leaf[i];
if (leaf->data && leaf->data->buffer == buffer) {
leaf->busy = 0;
+ available = i;
break;
}
}
@@ -872,14 +917,27 @@ shm_surface_buffer_release(void *data, struct wl_buffer *buffer)
for (i = 0; i < MAX_LEAVES; i++) {
leaf = &surface->leaf[i];

+ if (leaf->busy)
+ bufs[i] = 'b';
+ else if (leaf->cairo_surface)
+ bufs[i] = 'a';
+ else
+ bufs[i] = ' ';
+
if (!leaf->cairo_surface || leaf->busy)
continue;

if (!free_found)
free_found = 1;
- else
+ else {
shm_surface_leaf_release(leaf);
+ bufs[i] = '*';
+ }
}
+
+ bufs[MAX_LEAVES] = '\0';
+ DBG_OBJ(surface->surface, "leaf %d released, leaves [%s]\n",
+ available, bufs);
}

static const struct wl_buffer_listener shm_surface_buffer_listener = {
@@ -907,9 +965,13 @@ shm_surface_prepare(struct toysurface *base, int dx, int dy,
if (!leaf || surface->leaf[i].cairo_surface)
leaf = &surface->leaf[i];
}
+ DBG_OBJ(surface->surface, "pick leaf %d\n",
+ (int)(leaf - &surface->leaf[0]));
+
if (!leaf) {
fprintf(stderr, "%s: all buffers are held by the server.\n",
__func__);
+ exit(1);
return NULL;
}

@@ -971,6 +1033,9 @@ shm_surface_swap(struct toysurface *base,
server_allocation->width, server_allocation->height);
wl_surface_commit(surface->surface);

+ DBG_OBJ(surface->surface, "leaf %d busy\n",
+ (int)(leaf - &surface->leaf[0]));
+
leaf->busy = 1;
surface->current = NULL;
}
@@ -1003,6 +1068,7 @@ shm_surface_create(struct display *display, struct wl_surface *wl_surface,
uint32_t flags, struct rectangle *rectangle)
{
struct shm_surface *surface;
+ DBG_OBJ(wl_surface, "\n");

surface = calloc(1, sizeof *surface);
if (!surface)
@@ -1628,6 +1694,7 @@ window_schedule_redraw_task(struct window *window);
void
widget_schedule_redraw(struct widget *widget)
{
+ DBG_OBJ(widget->surface->surface, "widget %p\n", widget);
widget->surface->redraw_needed = 1;
window_schedule_redraw_task(widget->window);
}
@@ -3400,6 +3467,12 @@ idle_resize(struct window *window)
window->resize_needed = 0;
window->redraw_needed = 1;

+ DBG("from %dx%d to %dx%d\n",
+ window->main_surface->server_allocation.width,
+ window->main_surface->server_allocation.height,
+ window->pending_allocation.width,
+ window->pending_allocation.height);
+
hack_prevent_EGL_sub_surface_deadlock(window);

widget_set_allocation(window->main_surface->widget,
@@ -3519,13 +3592,16 @@ frame_callback(void *data, struct wl_callback *callback, uint32_t time)
struct surface *surface = data;

assert(callback == surface->frame_cb);
+ DBG_OBJ(callback, "done\n");
wl_callback_destroy(callback);
surface->frame_cb = NULL;

surface->last_time = time;

- if (surface->redraw_needed || surface->window->redraw_needed)
+ if (surface->redraw_needed || surface->window->redraw_needed) {
+ DBG_OBJ(surface->surface, "window_schedule_redraw_task\n");
window_schedule_redraw_task(surface->window);
+ }
}

static const struct wl_callback_listener listener = {
@@ -3535,6 +3611,8 @@ static const struct wl_callback_listener listener = {
static void
surface_redraw(struct surface *surface)
{
+ DBG_OBJ(surface->surface, "begin\n");
+
if (!surface->window->redraw_needed && !surface->redraw_needed)
return;

@@ -3545,14 +3623,18 @@ surface_redraw(struct surface *surface)
if (!surface->window->redraw_needed)
return;

+ DBG_OBJ(surface->frame_cb, "cancelled\n");
wl_callback_destroy(surface->frame_cb);
}

surface->frame_cb = wl_surface_frame(surface->surface);
wl_callback_add_listener(surface->frame_cb, &listener, surface);
+ DBG_OBJ(surface->frame_cb, "new\n");

surface->redraw_needed = 0;
+ DBG_OBJ(surface->surface, "-> widget_redraw\n");
widget_redraw(surface->widget);
+ DBG_OBJ(surface->surface, "done\n");
}

static void
@@ -3561,13 +3643,17 @@ idle_redraw(struct task *task, uint32_t events)
struct window *window = container_of(task, struct window, redraw_task);
struct surface *surface;

+ DBG(" --------- \n");
+
wl_list_init(&window->redraw_task.link);
window->redraw_task_scheduled = 0;

if (window->resize_needed) {
/* throttle resizing to the main surface display */
- if (window->main_surface->frame_cb)
+ if (window->main_surface->frame_cb) {
+ DBG_OBJ(window->main_surface->frame_cb, "pending\n");
return;
+ }

idle_resize(window);
}
@@ -3599,6 +3685,8 @@ window_schedule_redraw(struct window *window)
{
struct surface *surface;

+ DBG_OBJ(window->main_surface->surface, "window %p\n", window);
+
wl_list_for_each(surface, &window->subsurface_list, link)
surface->redraw_needed = 1;
--
1.8.1.5
Kristian Høgsberg
2013-05-11 00:25:45 UTC
Permalink
Post by Pekka Paalanen
Hi all,
this is the v3 of the sub-surfaces patch series, and it is no longer
an RFC. This means that I consider this series ready for merging into
Weston. The server side sub-surface protocol implementation is feature
complete. Once the protocol is deemed stable, we will move it into
Wayland core. Until then, you need to copy protocol/subsurface.xml if
you want to use it in other projects.
Pekka,

Great work, I've merged it to master. I reviewed the protocol and
weston implementation and skimmed the toytoolkit stuff, and it all
looks good. We still have quirks to work out, but that'll be easier
with the core functionality merged. I think I saw a crash and there's
the missing unlock dialog, but we'll figure that out.

On the protocol level, I'm not sure about the eglSwapInterval
requirement, I feel like there's a better solution - maybe commit on
the async (is 'async' better than 'desync'?) subsurface can still send
a frame event even if the parent surface hasn't committed yet?

Thanks for driving this work,
Kristian
Post by Pekka Paalanen
The previous series, v2, was announced in
http://lists.freedesktop.org/archives/wayland-devel/2013-February/007590.html
Personally, I believe that all the previously open issues are now
sufficiently explored, that there should not be any big surprises.
- Improved protocol object destruction rules: wl_subsurface simply
becomes inert, if the corresponding wl_surface is destroyed.
- Added support for nested sub-surfaces. You can now have
sub-sub-surfaces, and so on.
- Renamed the commit modes, and adjusted their behaviour to support
nested sub-surfaces better. If a sub-surface is synchronized, all
its children will act synchronized, too.
- Completed the server side protocol implementation with sub-surface
nesting support, and double-buffered sub-surface z-order updates.
And of course bug fixes, and a rebase on top of the current upstream
master branch.
window: Add a log handler for window.c clients (2013-04-16 20:46:59 -0400)
git://git.collabora.co.uk/git/user/pq/weston.git subsurface-v3
window: add DBG code for leaf management and redraws (2013-04-25 12:10:18 +0300)
shell: enable moving and resizing of a surface when clicking on a
subsurface
shell: account for the subsurfaces when going fullscreen or maximizing
protocol: add sub-surfaces
compositor: introduce sub-surfaces
tests: add sub-surface protocol tests
shell: keyboard focus and restacking fixes for sub-surfaces
window: implement shm triple-buffering
window: create sub-surfaces
window: implement per-surface redraws
clients: add subsurfaces demo
window: prevent EGL sub-surface deadlock
window: throttle resizing to the main surface
window: add DBG code for leaf management and redraws
clients/.gitignore | 3 +
clients/Makefile.am | 12 +
clients/subsurfaces.c | 792 ++++++++++++++++++++++++++++++++++++++++++++++++
clients/window.c | 460 ++++++++++++++++++++++++----
clients/window.h | 19 ++
configure.ac | 3 +
protocol/subsurface.xml | 236 +++++++++++++++
src/.gitignore | 3 +
src/Makefile.am | 4 +
src/compositor.c | 747 ++++++++++++++++++++++++++++++++++++++++++++-
src/compositor.h | 58 ++++
src/shell.c | 172 ++++++++---
tests/.gitignore | 3 +
tests/Makefile.am | 9 +-
tests/subsurface-test.c | 325 ++++++++++++++++++++
15 files changed, 2732 insertions(+), 114 deletions(-)
create mode 100644 clients/subsurfaces.c
create mode 100644 protocol/subsurface.xml
create mode 100644 tests/subsurface-test.c
This patch series applies on top of the current upstream master branch
http://cgit.collabora.com/git/user/pq/weston.git/log/?h=subsurface-v3
- A Weston demo client with window decorations stitched from 4
sub-surfaces, handling input in all sub-surface. (Giulio already has
an example in Qt.)
- Nested sub-surface tests and a demo. Nesting is currently untested.
- Fix full-surface alpha for windows that have sub-surfaces.
- Investigate if the shell "black surfaces" could be implemented as
server-side sub-surfaces more easily than they are now.
I have not forgotten about the clipping & scaling protocol extension,
and I have some ideas for it, but it is free for taking, too. If
you're interested in taking a shot at any of these items, let me know
so we can avoid overlapping work.
When you build this series, remember --with-cairo-glesv2, or the
subsurfaces demo application may not get built.
Thanks,
pq
--
1.8.1.5
Pekka Paalanen
2013-05-11 10:25:52 UTC
Permalink
On Fri, 10 May 2013 20:25:45 -0400
Post by Kristian Høgsberg
Post by Pekka Paalanen
Hi all,
this is the v3 of the sub-surfaces patch series, and it is no longer
an RFC. This means that I consider this series ready for merging into
Weston. The server side sub-surface protocol implementation is feature
complete. Once the protocol is deemed stable, we will move it into
Wayland core. Until then, you need to copy protocol/subsurface.xml if
you want to use it in other projects.
Pekka,
Great work, I've merged it to master. I reviewed the protocol and
weston implementation and skimmed the toytoolkit stuff, and it all
looks good. We still have quirks to work out, but that'll be easier
with the core functionality merged. I think I saw a crash and there's
the missing unlock dialog, but we'll figure that out.
Thank you :-)
Post by Kristian Høgsberg
On the protocol level, I'm not sure about the eglSwapInterval
requirement, I feel like there's a better solution - maybe commit on
the async (is 'async' better than 'desync'?) subsurface can still send
a frame event even if the parent surface hasn't committed yet?
At least we cannot send the frame callback straight on commit, it
has to be throttled.

I imagine there will be two kinds of continuously updating
sub-surface components:
- running completely on their own clock, possibly regardless of the
frame callback, which will then be essentially triple- or
quadruple-buffered; e.g. video
- running completely based on the frame callback, like your usual
animation

The latter case depends on the frame callback to throttle the
rendering, otherwise it will become busyloop roundtripping.

How should we fake the frame event? What would we throttle it on?

I would prefer the swap interval solution, since that is solving a
client problem in the client (EGL problem in EGL, actually),
rather than adding a trick in the server. The swap interval is not
mandatory, if you synchronize the component more tightly to the
main application for resizes.


Cheers,
pq

Loading...