2020-04-23 23:36:46 +00:00
|
|
|
/*
|
|
|
|
* Copyright © 2020 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <AppKit/AppKit.h>
|
|
|
|
#include <float.h>
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
#import "GdkMacosView.h"
|
2020-04-23 23:36:46 +00:00
|
|
|
|
2021-09-24 19:04:47 +00:00
|
|
|
#include "gdkmacossurface-private.h"
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
#include "gdkdeviceprivate.h"
|
|
|
|
#include "gdkdisplay.h"
|
2021-09-24 19:04:47 +00:00
|
|
|
#include "gdkeventsprivate.h"
|
2020-04-23 23:36:46 +00:00
|
|
|
#include "gdkframeclockidleprivate.h"
|
2021-09-24 19:04:47 +00:00
|
|
|
#include "gdkseatprivate.h"
|
2020-04-23 23:36:46 +00:00
|
|
|
#include "gdksurfaceprivate.h"
|
|
|
|
|
|
|
|
#include "gdkmacosdevice.h"
|
2020-08-26 19:33:37 +00:00
|
|
|
#include "gdkmacosdevice-private.h"
|
2020-04-23 23:36:46 +00:00
|
|
|
#include "gdkmacosdisplay-private.h"
|
|
|
|
#include "gdkmacosdrag-private.h"
|
|
|
|
#include "gdkmacosdragsurface-private.h"
|
|
|
|
#include "gdkmacosglcontext-private.h"
|
|
|
|
#include "gdkmacosmonitor-private.h"
|
|
|
|
#include "gdkmacospopupsurface-private.h"
|
|
|
|
#include "gdkmacostoplevelsurface-private.h"
|
|
|
|
#include "gdkmacosutils-private.h"
|
|
|
|
|
|
|
|
G_DEFINE_ABSTRACT_TYPE (GdkMacosSurface, gdk_macos_surface, GDK_TYPE_SURFACE)
|
|
|
|
|
|
|
|
enum {
|
|
|
|
PROP_0,
|
|
|
|
PROP_NATIVE,
|
|
|
|
LAST_PROP
|
|
|
|
};
|
|
|
|
|
|
|
|
static GParamSpec *properties [LAST_PROP];
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
window_is_fullscreen (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0;
|
|
|
|
}
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
void
|
|
|
|
_gdk_macos_surface_reposition_children (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
|
2022-02-25 00:29:53 +00:00
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
2020-04-23 23:36:46 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
for (const GList *iter = GDK_SURFACE (self)->children;
|
|
|
|
iter != NULL;
|
|
|
|
iter = iter->next)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *child = iter->data;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (child));
|
|
|
|
|
|
|
|
if (GDK_IS_MACOS_POPUP_SURFACE (child))
|
|
|
|
_gdk_macos_popup_surface_reposition (GDK_MACOS_POPUP_SURFACE (child));
|
|
|
|
}
|
|
|
|
|
2020-10-14 23:26:52 +00:00
|
|
|
if (GDK_IS_POPUP (self) && self->did_initial_present)
|
2020-12-03 22:20:31 +00:00
|
|
|
gdk_surface_request_layout (GDK_SURFACE (self));
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_set_input_region (GdkSurface *surface,
|
|
|
|
cairo_region_t *region)
|
|
|
|
{
|
2022-02-05 01:24:13 +00:00
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
cairo_rectangle_int_t rect;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (self->window == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cairo_region_get_extents (region, &rect);
|
|
|
|
|
|
|
|
[(GdkMacosBaseView *)[self->window contentView] setInputArea:&rect];
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_set_opaque_region (GdkSurface *surface,
|
|
|
|
cairo_region_t *region)
|
|
|
|
{
|
2021-02-19 21:18:42 +00:00
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
2020-04-23 23:36:46 +00:00
|
|
|
NSView *nsview;
|
|
|
|
|
2021-02-19 21:18:42 +00:00
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface))))
|
|
|
|
[(GdkMacosView *)nsview setOpaqueRegion:region];
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_hide (GdkSurface *surface)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
GdkSeat *seat;
|
2020-12-02 21:10:23 +00:00
|
|
|
gboolean was_mapped;
|
2022-02-17 02:26:59 +00:00
|
|
|
gboolean was_key;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
self->show_on_next_swap = FALSE;
|
|
|
|
|
2022-02-02 00:06:08 +00:00
|
|
|
_gdk_macos_display_remove_frame_callback (GDK_MACOS_DISPLAY (surface->display), self);
|
|
|
|
|
2020-12-02 21:10:23 +00:00
|
|
|
was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
|
2022-02-17 02:26:59 +00:00
|
|
|
was_key = [self->window isKeyWindow];
|
2020-12-02 21:10:23 +00:00
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
seat = gdk_display_get_default_seat (surface->display);
|
|
|
|
gdk_seat_ungrab (seat);
|
|
|
|
|
|
|
|
[self->window hide];
|
|
|
|
|
|
|
|
_gdk_surface_clear_update_area (surface);
|
2020-12-02 21:10:23 +00:00
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
g_clear_object (&self->buffer);
|
2022-02-22 07:53:55 +00:00
|
|
|
g_clear_object (&self->front);
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
|
2022-02-17 02:26:59 +00:00
|
|
|
if (was_key)
|
|
|
|
{
|
|
|
|
/* Return key input to the parent window if necessary */
|
|
|
|
if (surface->parent != NULL && GDK_SURFACE_IS_MAPPED (surface->parent))
|
|
|
|
{
|
|
|
|
GdkMacosWindow *parentWindow = GDK_MACOS_SURFACE (surface->parent)->window;
|
|
|
|
|
|
|
|
[parentWindow showAndMakeKey:YES];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-02 21:10:23 +00:00
|
|
|
if (was_mapped)
|
|
|
|
gdk_surface_freeze_updates (GDK_SURFACE (self));
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
2020-07-24 13:54:49 +00:00
|
|
|
static int
|
2020-04-23 23:36:46 +00:00
|
|
|
gdk_macos_surface_get_scale_factor (GdkSurface *surface)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
return [self->window backingScaleFactor];
|
|
|
|
}
|
|
|
|
|
2020-12-02 13:58:45 +00:00
|
|
|
void
|
2020-12-08 19:29:31 +00:00
|
|
|
_gdk_macos_surface_set_shadow (GdkMacosSurface *surface,
|
|
|
|
int top,
|
|
|
|
int right,
|
|
|
|
int bottom,
|
|
|
|
int left)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
2021-01-01 21:09:57 +00:00
|
|
|
if (self->shadow_top == top &&
|
|
|
|
self->shadow_right == right &&
|
|
|
|
self->shadow_bottom == bottom &&
|
|
|
|
self->shadow_left == left)
|
|
|
|
return;
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
self->shadow_top = top;
|
|
|
|
self->shadow_right = right;
|
|
|
|
self->shadow_bottom = bottom;
|
|
|
|
self->shadow_left = left;
|
|
|
|
|
|
|
|
if (top || right || bottom || left)
|
|
|
|
[self->window setHasShadow:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_begin_frame (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_end_frame (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
GdkFrameTimings *timings;
|
|
|
|
GdkFrameClock *frame_clock;
|
|
|
|
GdkDisplay *display;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
2022-01-31 21:02:58 +00:00
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
|
|
|
return;
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
display = gdk_surface_get_display (GDK_SURFACE (self));
|
|
|
|
frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self));
|
|
|
|
|
|
|
|
if ((timings = gdk_frame_clock_get_current_timings (frame_clock)))
|
|
|
|
self->pending_frame_counter = timings->frame_counter;
|
|
|
|
|
2022-02-02 00:06:08 +00:00
|
|
|
if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)))
|
|
|
|
{
|
|
|
|
_gdk_macos_display_add_frame_callback (GDK_MACOS_DISPLAY (display), self);
|
|
|
|
gdk_surface_freeze_updates (GDK_SURFACE (self));
|
|
|
|
}
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_before_paint (GdkMacosSurface *self,
|
|
|
|
GdkFrameClock *frame_clock)
|
|
|
|
{
|
|
|
|
GdkSurface *surface = (GdkSurface *)self;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
|
|
|
|
|
2022-01-31 21:02:58 +00:00
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
2020-04-23 23:36:46 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-31 21:02:58 +00:00
|
|
|
if (surface->update_freeze_count == 0)
|
|
|
|
gdk_macos_surface_begin_frame (self);
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_after_paint (GdkMacosSurface *self,
|
|
|
|
GdkFrameClock *frame_clock)
|
|
|
|
{
|
|
|
|
GdkSurface *surface = (GdkSurface *)self;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
|
|
|
|
|
2022-01-31 21:02:58 +00:00
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
2020-04-23 23:36:46 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-31 21:02:58 +00:00
|
|
|
if (surface->update_freeze_count == 0)
|
|
|
|
gdk_macos_surface_end_frame (self);
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_get_root_coords (GdkSurface *surface,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int *root_x,
|
|
|
|
int *root_y)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (root_x)
|
|
|
|
*root_x = self->root_x + x;
|
|
|
|
|
|
|
|
if (root_y)
|
|
|
|
*root_y = self->root_y + y;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gdk_macos_surface_get_device_state (GdkSurface *surface,
|
|
|
|
GdkDevice *device,
|
2020-07-24 20:32:16 +00:00
|
|
|
double *x,
|
|
|
|
double *y,
|
2020-04-23 23:36:46 +00:00
|
|
|
GdkModifierType *mask)
|
|
|
|
{
|
|
|
|
GdkDisplay *display;
|
|
|
|
NSWindow *nswindow;
|
|
|
|
NSPoint point;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (surface));
|
|
|
|
g_assert (GDK_IS_MACOS_DEVICE (device));
|
|
|
|
g_assert (x != NULL);
|
|
|
|
g_assert (y != NULL);
|
|
|
|
g_assert (mask != NULL);
|
|
|
|
|
2020-10-14 18:32:20 +00:00
|
|
|
if (GDK_SURFACE_DESTROYED (surface))
|
|
|
|
return FALSE;
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
display = gdk_surface_get_display (surface);
|
|
|
|
nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface));
|
|
|
|
point = [nswindow mouseLocationOutsideOfEventStream];
|
|
|
|
|
2020-10-14 18:32:20 +00:00
|
|
|
*mask = _gdk_macos_display_get_current_keyboard_modifiers (GDK_MACOS_DISPLAY (display))
|
|
|
|
| _gdk_macos_display_get_current_mouse_modifiers (GDK_MACOS_DISPLAY (display));
|
2020-04-23 23:36:46 +00:00
|
|
|
|
2020-10-14 18:32:20 +00:00
|
|
|
*x = point.x;
|
2020-11-05 02:02:04 +00:00
|
|
|
*y = surface->height - point.y;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
2020-10-14 18:32:20 +00:00
|
|
|
return *x >= 0 && *y >= 0 && *x < surface->width && *y < surface->height;
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_get_geometry (GdkSurface *surface,
|
|
|
|
int *x,
|
|
|
|
int *y,
|
|
|
|
int *width,
|
|
|
|
int *height)
|
|
|
|
{
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (surface));
|
|
|
|
|
|
|
|
if (x != NULL)
|
|
|
|
*x = surface->x;
|
|
|
|
|
|
|
|
if (y != NULL)
|
|
|
|
*y = surface->y;
|
|
|
|
|
|
|
|
if (width != NULL)
|
|
|
|
*width = surface->width;
|
|
|
|
|
|
|
|
if (height != NULL)
|
|
|
|
*height = surface->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GdkDrag *
|
|
|
|
gdk_macos_surface_drag_begin (GdkSurface *surface,
|
|
|
|
GdkDevice *device,
|
|
|
|
GdkContentProvider *content,
|
|
|
|
GdkDragAction actions,
|
|
|
|
double dx,
|
|
|
|
double dy)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
GdkMacosSurface *drag_surface;
|
|
|
|
GdkMacosDrag *drag;
|
|
|
|
GdkCursor *cursor;
|
|
|
|
GdkSeat *seat;
|
|
|
|
double px;
|
|
|
|
double py;
|
|
|
|
int sx;
|
|
|
|
int sy;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_assert (GDK_IS_MACOS_TOPLEVEL_SURFACE (self) ||
|
|
|
|
GDK_IS_MACOS_POPUP_SURFACE (self));
|
|
|
|
g_assert (GDK_IS_MACOS_DEVICE (device));
|
|
|
|
g_assert (GDK_IS_CONTENT_PROVIDER (content));
|
|
|
|
|
|
|
|
seat = gdk_device_get_seat (device);
|
2020-08-26 19:33:37 +00:00
|
|
|
gdk_macos_device_query_state (device, surface, NULL, &px, &py, NULL);
|
2020-04-23 23:36:46 +00:00
|
|
|
_gdk_macos_surface_get_root_coords (GDK_MACOS_SURFACE (surface), &sx, &sy);
|
|
|
|
drag_surface = _gdk_macos_surface_new (GDK_MACOS_DISPLAY (surface->display),
|
|
|
|
GDK_SURFACE_TEMP,
|
|
|
|
surface,
|
2021-06-17 20:23:38 +00:00
|
|
|
sx, sy, 1, 1);
|
2020-04-23 23:36:46 +00:00
|
|
|
drag = g_object_new (GDK_TYPE_MACOS_DRAG,
|
|
|
|
"drag-surface", drag_surface,
|
|
|
|
"surface", surface,
|
|
|
|
"device", device,
|
|
|
|
"content", content,
|
|
|
|
"actions", actions,
|
|
|
|
NULL);
|
|
|
|
g_clear_object (&drag_surface);
|
|
|
|
|
|
|
|
cursor = gdk_drag_get_cursor (GDK_DRAG (drag),
|
|
|
|
gdk_drag_get_selected_action (GDK_DRAG (drag)));
|
|
|
|
gdk_drag_set_cursor (GDK_DRAG (drag), cursor);
|
|
|
|
|
|
|
|
if (!_gdk_macos_drag_begin (drag))
|
|
|
|
{
|
|
|
|
g_object_unref (drag);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hold a reference until drop_done is called */
|
|
|
|
g_object_ref (drag);
|
|
|
|
|
|
|
|
return GDK_DRAG (g_steal_pointer (&drag));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_destroy (GdkSurface *surface,
|
|
|
|
gboolean foreign_destroy)
|
|
|
|
{
|
|
|
|
GDK_BEGIN_MACOS_ALLOC_POOL;
|
|
|
|
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)surface;
|
|
|
|
GdkMacosWindow *window = g_steal_pointer (&self->window);
|
2020-12-02 19:35:02 +00:00
|
|
|
GdkFrameClock *frame_clock;
|
|
|
|
|
2022-02-25 21:52:08 +00:00
|
|
|
g_clear_object (&self->best_monitor);
|
|
|
|
|
2020-12-02 19:35:02 +00:00
|
|
|
if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
|
|
|
|
{
|
|
|
|
g_signal_handlers_disconnect_by_func (frame_clock,
|
|
|
|
G_CALLBACK (gdk_macos_surface_before_paint),
|
|
|
|
self);
|
|
|
|
g_signal_handlers_disconnect_by_func (frame_clock,
|
2022-02-16 09:46:25 +00:00
|
|
|
G_CALLBACK (gdk_macos_surface_after_paint),
|
2020-12-02 19:35:02 +00:00
|
|
|
self);
|
|
|
|
}
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
g_clear_pointer (&self->title, g_free);
|
|
|
|
|
|
|
|
if (window != NULL)
|
|
|
|
[window close];
|
|
|
|
|
|
|
|
_gdk_macos_display_surface_removed (GDK_MACOS_DISPLAY (surface->display), self);
|
|
|
|
|
|
|
|
g_clear_pointer (&self->monitors, g_ptr_array_unref);
|
|
|
|
|
2022-02-22 07:53:55 +00:00
|
|
|
g_clear_object (&self->buffer);
|
|
|
|
g_clear_object (&self->front);
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
g_assert (self->sorted.prev == NULL);
|
|
|
|
g_assert (self->sorted.next == NULL);
|
|
|
|
g_assert (self->frame.prev == NULL);
|
|
|
|
g_assert (self->frame.next == NULL);
|
|
|
|
g_assert (self->main.prev == NULL);
|
|
|
|
g_assert (self->main.next == NULL);
|
|
|
|
|
|
|
|
GDK_END_MACOS_ALLOC_POOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_constructed (GObject *object)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = (GdkMacosSurface *)object;
|
|
|
|
GdkFrameClock *frame_clock;
|
|
|
|
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (gdk_macos_surface_parent_class)->constructed (object);
|
|
|
|
|
|
|
|
if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
|
|
|
|
{
|
|
|
|
g_signal_connect_object (frame_clock,
|
|
|
|
"before-paint",
|
|
|
|
G_CALLBACK (gdk_macos_surface_before_paint),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
g_signal_connect_object (frame_clock,
|
|
|
|
"after-paint",
|
|
|
|
G_CALLBACK (gdk_macos_surface_after_paint),
|
|
|
|
self,
|
|
|
|
G_CONNECT_SWAPPED);
|
|
|
|
}
|
2022-02-16 10:50:51 +00:00
|
|
|
|
|
|
|
if (self->window != NULL)
|
|
|
|
_gdk_macos_surface_configure (self);
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_get_property (GObject *object,
|
|
|
|
guint prop_id,
|
|
|
|
GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
{
|
|
|
|
case PROP_NATIVE:
|
|
|
|
g_value_set_pointer (value, self->window);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_set_property (GObject *object,
|
|
|
|
guint prop_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GdkMacosSurface *self = GDK_MACOS_SURFACE (object);
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
{
|
|
|
|
case PROP_NATIVE:
|
|
|
|
self->window = g_value_get_pointer (value);
|
|
|
|
[self->window setGdkSurface:self];
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_class_init (GdkMacosSurfaceClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (klass);
|
|
|
|
|
|
|
|
object_class->constructed = gdk_macos_surface_constructed;
|
|
|
|
object_class->get_property = gdk_macos_surface_get_property;
|
|
|
|
object_class->set_property = gdk_macos_surface_set_property;
|
|
|
|
|
|
|
|
surface_class->destroy = gdk_macos_surface_destroy;
|
|
|
|
surface_class->drag_begin = gdk_macos_surface_drag_begin;
|
|
|
|
surface_class->get_device_state = gdk_macos_surface_get_device_state;
|
|
|
|
surface_class->get_geometry = gdk_macos_surface_get_geometry;
|
|
|
|
surface_class->get_root_coords = gdk_macos_surface_get_root_coords;
|
|
|
|
surface_class->get_scale_factor = gdk_macos_surface_get_scale_factor;
|
|
|
|
surface_class->hide = gdk_macos_surface_hide;
|
|
|
|
surface_class->set_input_region = gdk_macos_surface_set_input_region;
|
|
|
|
surface_class->set_opaque_region = gdk_macos_surface_set_opaque_region;
|
|
|
|
|
|
|
|
properties [PROP_NATIVE] =
|
|
|
|
g_param_spec_pointer ("native",
|
|
|
|
"Native",
|
|
|
|
"The native NSWindow",
|
|
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
|
|
|
|
|
|
|
g_object_class_install_properties (object_class, LAST_PROP, properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gdk_macos_surface_init (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
self->frame.data = self;
|
|
|
|
self->main.data = self;
|
|
|
|
self->sorted.data = self;
|
|
|
|
self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
}
|
|
|
|
|
|
|
|
GdkMacosSurface *
|
|
|
|
_gdk_macos_surface_new (GdkMacosDisplay *display,
|
|
|
|
GdkSurfaceType surface_type,
|
|
|
|
GdkSurface *parent,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int width,
|
|
|
|
int height)
|
|
|
|
{
|
|
|
|
GdkFrameClock *frame_clock;
|
|
|
|
GdkMacosSurface *ret;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL);
|
|
|
|
|
|
|
|
if (parent != NULL)
|
|
|
|
frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent));
|
|
|
|
else
|
|
|
|
frame_clock = _gdk_frame_clock_idle_new ();
|
|
|
|
|
|
|
|
switch (surface_type)
|
|
|
|
{
|
|
|
|
case GDK_SURFACE_TOPLEVEL:
|
|
|
|
ret = _gdk_macos_toplevel_surface_new (display, parent, frame_clock, x, y, width, height);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_SURFACE_POPUP:
|
|
|
|
ret = _gdk_macos_popup_surface_new (display, parent, frame_clock, x, y, width, height);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case GDK_SURFACE_TEMP:
|
|
|
|
ret = _gdk_macos_drag_surface_new (display, frame_clock, x, y, width, height);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
g_warn_if_reached ();
|
|
|
|
ret = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != NULL)
|
2020-12-02 21:10:23 +00:00
|
|
|
{
|
|
|
|
gdk_surface_freeze_updates (GDK_SURFACE (ret));
|
|
|
|
_gdk_macos_surface_monitor_changed (ret);
|
|
|
|
}
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
g_object_unref (frame_clock);
|
|
|
|
|
|
|
|
return g_steal_pointer (&ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_get_shadow (GdkMacosSurface *self,
|
2020-07-24 13:54:49 +00:00
|
|
|
int *top,
|
|
|
|
int *right,
|
|
|
|
int *bottom,
|
|
|
|
int *left)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (top)
|
|
|
|
*top = self->shadow_top;
|
|
|
|
|
|
|
|
if (left)
|
|
|
|
*left = self->shadow_left;
|
|
|
|
|
|
|
|
if (bottom)
|
|
|
|
*bottom = self->shadow_bottom;
|
|
|
|
|
|
|
|
if (right)
|
|
|
|
*right = self->shadow_right;
|
|
|
|
}
|
|
|
|
|
2022-02-14 10:18:51 +00:00
|
|
|
gboolean
|
|
|
|
_gdk_macos_surface_is_opaque (GdkMacosSurface *self)
|
|
|
|
{
|
2022-02-25 21:14:22 +00:00
|
|
|
GdkSurface *surface = (GdkSurface *)self;
|
|
|
|
|
2022-02-14 10:18:51 +00:00
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE);
|
|
|
|
|
2022-02-25 21:14:22 +00:00
|
|
|
if (surface->opaque_region != NULL &&
|
|
|
|
cairo_region_num_rectangles (surface->opaque_region) == 1)
|
2022-02-14 10:18:51 +00:00
|
|
|
{
|
|
|
|
cairo_rectangle_int_t extents;
|
|
|
|
|
2022-02-25 21:14:22 +00:00
|
|
|
cairo_region_get_extents (surface->opaque_region, &extents);
|
2022-02-14 10:18:51 +00:00
|
|
|
|
|
|
|
return (extents.x == 0 &&
|
|
|
|
extents.y == 0 &&
|
|
|
|
extents.width == GDK_SURFACE (self)->width &&
|
|
|
|
extents.height == GDK_SURFACE (self)->height);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2020-04-23 23:36:46 +00:00
|
|
|
const char *
|
|
|
|
_gdk_macos_surface_get_title (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
|
|
|
|
return self->title;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_set_title (GdkMacosSurface *self,
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *title)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (title == NULL)
|
|
|
|
title = "";
|
|
|
|
|
|
|
|
if (g_strcmp0 (self->title, title) != 0)
|
|
|
|
{
|
|
|
|
g_free (self->title);
|
|
|
|
self->title = g_strdup (title);
|
|
|
|
|
|
|
|
GDK_BEGIN_MACOS_ALLOC_POOL;
|
|
|
|
[self->window setTitle:[NSString stringWithUTF8String:title]];
|
|
|
|
GDK_END_MACOS_ALLOC_POOL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CGDirectDisplayID
|
|
|
|
_gdk_macos_surface_get_screen_id (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), (CGDirectDisplayID)-1);
|
|
|
|
|
|
|
|
if (self->window != NULL)
|
|
|
|
{
|
|
|
|
NSScreen *screen = [self->window screen];
|
|
|
|
return [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
return (CGDirectDisplayID)-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSWindow *
|
|
|
|
_gdk_macos_surface_get_native (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
|
|
|
|
|
|
|
|
return (NSWindow *)self->window;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_set_geometry_hints (GdkMacosSurface *self,
|
|
|
|
const GdkGeometry *geometry,
|
|
|
|
GdkSurfaceHints geom_mask)
|
|
|
|
{
|
|
|
|
NSSize max_size;
|
|
|
|
NSSize min_size;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_return_if_fail (geometry != NULL);
|
|
|
|
g_return_if_fail (self->window != NULL);
|
|
|
|
|
|
|
|
if (geom_mask & GDK_HINT_MAX_SIZE)
|
|
|
|
max_size = NSMakeSize (geometry->max_width, geometry->max_height);
|
|
|
|
else
|
|
|
|
max_size = NSMakeSize (FLT_MAX, FLT_MAX);
|
|
|
|
[self->window setContentMaxSize:max_size];
|
|
|
|
|
|
|
|
if (geom_mask & GDK_HINT_MIN_SIZE)
|
|
|
|
min_size = NSMakeSize (geometry->min_width, geometry->min_height);
|
|
|
|
else
|
|
|
|
min_size = NSMakeSize (1, 1);
|
|
|
|
[self->window setContentMinSize:min_size];
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_resize (GdkMacosSurface *self,
|
|
|
|
int width,
|
|
|
|
int height)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
_gdk_macos_surface_move_resize (self, -1, -1, width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self)
|
|
|
|
{
|
2020-09-10 04:39:03 +00:00
|
|
|
GdkToplevelState state;
|
2020-04-23 23:36:46 +00:00
|
|
|
gboolean is_fullscreen;
|
|
|
|
gboolean was_fullscreen;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
state = GDK_SURFACE (self)->state;
|
|
|
|
is_fullscreen = window_is_fullscreen (self);
|
2020-09-10 04:39:03 +00:00
|
|
|
was_fullscreen = (state & GDK_TOPLEVEL_STATE_FULLSCREEN) != 0;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
if (is_fullscreen != was_fullscreen)
|
|
|
|
{
|
|
|
|
if (is_fullscreen)
|
2020-09-10 04:39:03 +00:00
|
|
|
gdk_synthesize_surface_state (GDK_SURFACE (self), 0, GDK_TOPLEVEL_STATE_FULLSCREEN);
|
2020-04-23 23:36:46 +00:00
|
|
|
else
|
2020-09-10 04:39:03 +00:00
|
|
|
gdk_synthesize_surface_state (GDK_SURFACE (self), GDK_TOPLEVEL_STATE_FULLSCREEN, 0);
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-02-16 10:50:51 +00:00
|
|
|
_gdk_macos_surface_configure (GdkMacosSurface *self)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
2022-02-16 10:50:51 +00:00
|
|
|
GdkMacosDisplay *display;
|
|
|
|
GdkSurface *surface = (GdkSurface *)self;
|
|
|
|
NSRect frame_rect;
|
|
|
|
NSRect content_rect;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
|
|
|
return;
|
|
|
|
|
|
|
|
display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
|
|
|
|
frame_rect = [self->window frame];
|
|
|
|
content_rect = [self->window contentRectForFrameRect:frame_rect];
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
_gdk_macos_display_from_display_coords (GDK_MACOS_DISPLAY (display),
|
|
|
|
content_rect.origin.x,
|
|
|
|
content_rect.origin.y + content_rect.size.height,
|
|
|
|
&self->root_x, &self->root_y);
|
|
|
|
|
2022-02-25 00:29:53 +00:00
|
|
|
if (surface->parent != NULL)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
2022-02-25 00:29:53 +00:00
|
|
|
surface->x = self->root_x - GDK_MACOS_SURFACE (surface->parent)->root_x;
|
|
|
|
surface->y = self->root_y - GDK_MACOS_SURFACE (surface->parent)->root_y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
surface->x = self->root_x;
|
|
|
|
surface->y = self->root_y;
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
2022-02-16 10:50:51 +00:00
|
|
|
|
|
|
|
if (surface->width != content_rect.size.width ||
|
|
|
|
surface->height != content_rect.size.height)
|
|
|
|
{
|
|
|
|
surface->width = content_rect.size.width;
|
|
|
|
surface->height = content_rect.size.height;
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
g_clear_object (&self->buffer);
|
2022-02-22 07:53:55 +00:00
|
|
|
g_clear_object (&self->front);
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
|
2022-02-16 10:50:51 +00:00
|
|
|
_gdk_surface_update_size (surface);
|
|
|
|
gdk_surface_request_layout (surface);
|
|
|
|
gdk_surface_invalidate_rect (surface, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
_gdk_macos_surface_reposition_children (self);
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-02-02 00:06:08 +00:00
|
|
|
_gdk_macos_surface_publish_timings (GdkMacosSurface *self,
|
|
|
|
gint64 presentation_time,
|
|
|
|
gint64 refresh_interval)
|
2020-04-23 23:36:46 +00:00
|
|
|
{
|
|
|
|
GdkFrameTimings *timings;
|
|
|
|
GdkFrameClock *frame_clock;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
2022-01-30 00:19:57 +00:00
|
|
|
if (!(frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self))))
|
|
|
|
return;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
if (self->pending_frame_counter)
|
|
|
|
{
|
|
|
|
timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter);
|
|
|
|
|
|
|
|
if (timings != NULL)
|
2020-10-16 04:04:51 +00:00
|
|
|
{
|
|
|
|
timings->presentation_time = presentation_time - refresh_interval;
|
|
|
|
timings->complete = TRUE;
|
|
|
|
}
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
self->pending_frame_counter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
timings = gdk_frame_clock_get_current_timings (frame_clock);
|
|
|
|
|
|
|
|
if (timings != NULL)
|
|
|
|
{
|
|
|
|
timings->refresh_interval = refresh_interval;
|
|
|
|
timings->predicted_presentation_time = presentation_time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_show (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
gboolean was_mapped;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
|
|
|
return;
|
|
|
|
|
|
|
|
was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self));
|
|
|
|
|
|
|
|
if (!was_mapped)
|
gdk: Replace 'WITHDRAWN' state with async 'is-mapped' boolean
It was used by all surfaces to track 'is-mapped', but still part of the
GdkToplevelState, and is now replaced with a separate boolean in the
GdkSurface structure.
It also caused issues when a widget was unmapped, and due to that
unmapped a popover which hid its corresponding surface. When this
surface was hidden, it emitted a state change event, which would then go
back into GTK and queue a resize on popover widget, which would travel
back down to the widget that was originally unmapped, causing confusino
when doing future allocations.
To summarize, one should not hide widgets during allocation, and to
avoid this, make this new is-mapped boolean asynchronous when hiding a
surface, meaning the notification event for the changed mapped state
will be emitted in an idle callback. This avoids the above described
reentry issue.
2020-12-07 17:18:38 +00:00
|
|
|
gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE);
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
_gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display));
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
self->show_on_next_swap = TRUE;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
if (!was_mapped)
|
|
|
|
{
|
|
|
|
if (gdk_surface_get_mapped (GDK_SURFACE (self)))
|
|
|
|
{
|
2022-02-16 10:50:51 +00:00
|
|
|
_gdk_macos_surface_configure (self);
|
2020-12-02 21:10:23 +00:00
|
|
|
gdk_surface_thaw_updates (GDK_SURFACE (self));
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
GdkTranslatedKey translated = {0};
|
|
|
|
GdkTranslatedKey no_lock = {0};
|
|
|
|
GdkDisplay *display;
|
|
|
|
GdkEvent *event;
|
|
|
|
GdkSeat *seat;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
translated.keyval = GDK_KEY_VoidSymbol;
|
|
|
|
no_lock.keyval = GDK_KEY_VoidSymbol;
|
|
|
|
|
|
|
|
display = gdk_surface_get_display (GDK_SURFACE (self));
|
|
|
|
seat = gdk_display_get_default_seat (display);
|
|
|
|
event = gdk_key_event_new (GDK_KEY_PRESS,
|
|
|
|
GDK_SURFACE (self),
|
|
|
|
gdk_seat_get_keyboard (seat),
|
|
|
|
GDK_CURRENT_TIME,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
FALSE,
|
|
|
|
&translated,
|
|
|
|
&no_lock);
|
|
|
|
_gdk_event_queue_append (display, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_move (GdkMacosSurface *self,
|
|
|
|
int x,
|
|
|
|
int y)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
_gdk_macos_surface_move_resize (self, x, y, -1, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_move_resize (GdkMacosSurface *self,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int width,
|
|
|
|
int height)
|
|
|
|
{
|
|
|
|
GdkSurface *surface = (GdkSurface *)self;
|
|
|
|
GdkDisplay *display;
|
|
|
|
NSRect content_rect;
|
|
|
|
NSRect frame_rect;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if ((x == -1 || (x == self->root_x)) &&
|
|
|
|
(y == -1 || (y == self->root_y)) &&
|
|
|
|
(width == -1 || (width == surface->width)) &&
|
|
|
|
(height == -1 || (height == surface->height)))
|
|
|
|
return;
|
|
|
|
|
|
|
|
display = gdk_surface_get_display (surface);
|
|
|
|
|
|
|
|
if (width == -1)
|
|
|
|
width = surface->width;
|
|
|
|
|
|
|
|
if (height == -1)
|
|
|
|
height = surface->height;
|
|
|
|
|
|
|
|
if (x == -1)
|
|
|
|
x = self->root_x;
|
|
|
|
|
|
|
|
if (y == -1)
|
|
|
|
y = self->root_y;
|
|
|
|
|
|
|
|
_gdk_macos_display_to_display_coords (GDK_MACOS_DISPLAY (display),
|
2022-02-16 10:50:51 +00:00
|
|
|
x, y + height,
|
|
|
|
&x, &y);
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
content_rect = NSMakeRect (x, y, width, height);
|
|
|
|
frame_rect = [self->window frameRectForContentRect:content_rect];
|
|
|
|
[self->window setFrame:frame_rect display:YES];
|
2022-02-16 10:50:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_user_resize (GdkMacosSurface *self,
|
|
|
|
CGRect new_frame)
|
|
|
|
{
|
|
|
|
GdkMacosDisplay *display;
|
|
|
|
CGRect content_rect;
|
|
|
|
int root_x, root_y;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_return_if_fail (GDK_IS_TOPLEVEL (self));
|
|
|
|
|
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
|
|
|
return;
|
|
|
|
|
|
|
|
display = GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display);
|
|
|
|
content_rect = [self->window contentRectForFrameRect:new_frame];
|
|
|
|
|
|
|
|
_gdk_macos_display_from_display_coords (display,
|
|
|
|
new_frame.origin.x,
|
|
|
|
new_frame.origin.y + new_frame.size.height,
|
|
|
|
&root_x, &root_y);
|
2020-04-23 23:36:46 +00:00
|
|
|
|
2022-02-16 10:50:51 +00:00
|
|
|
self->next_layout.root_x = root_x;
|
|
|
|
self->next_layout.root_y = root_y;
|
|
|
|
self->next_layout.width = content_rect.size.width;
|
|
|
|
self->next_layout.height = content_rect.size.height;
|
|
|
|
|
|
|
|
gdk_surface_request_layout (GDK_SURFACE (self));
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
_gdk_macos_surface_is_tracking (GdkMacosSurface *self,
|
|
|
|
NSTrackingArea *area)
|
|
|
|
{
|
|
|
|
GdkMacosBaseView *view;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE);
|
|
|
|
|
|
|
|
if (self->window == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
view = (GdkMacosBaseView *)[self->window contentView];
|
|
|
|
if (view == NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return [view trackingArea] == area;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_monitor_changed (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
GListModel *monitors;
|
2022-02-25 21:52:08 +00:00
|
|
|
GdkMonitor *best = NULL;
|
2020-04-23 23:36:46 +00:00
|
|
|
GdkRectangle rect;
|
|
|
|
GdkRectangle intersect;
|
|
|
|
GdkDisplay *display;
|
|
|
|
GdkMonitor *monitor;
|
|
|
|
guint n_monitors;
|
2022-02-25 21:52:08 +00:00
|
|
|
int best_area = 0;
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
rect.x = self->root_x;
|
|
|
|
rect.y = self->root_y;
|
|
|
|
rect.width = GDK_SURFACE (self)->width;
|
|
|
|
rect.height = GDK_SURFACE (self)->height;
|
|
|
|
|
|
|
|
for (guint i = self->monitors->len; i > 0; i--)
|
|
|
|
{
|
|
|
|
monitor = g_ptr_array_index (self->monitors, i-1);
|
|
|
|
|
|
|
|
if (!gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
|
|
|
|
{
|
|
|
|
g_object_ref (monitor);
|
|
|
|
g_ptr_array_remove_index (self->monitors, i-1);
|
|
|
|
gdk_surface_leave_monitor (GDK_SURFACE (self), monitor);
|
|
|
|
g_object_unref (monitor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
display = gdk_surface_get_display (GDK_SURFACE (self));
|
|
|
|
monitors = gdk_display_get_monitors (display);
|
|
|
|
n_monitors = g_list_model_get_n_items (monitors);
|
|
|
|
|
|
|
|
for (guint i = 0; i < n_monitors; i++)
|
|
|
|
{
|
|
|
|
monitor = g_list_model_get_item (monitors, i);
|
|
|
|
|
|
|
|
if (!g_ptr_array_find (self->monitors, monitor, NULL))
|
|
|
|
{
|
|
|
|
gdk_surface_enter_monitor (GDK_SURFACE (self), monitor);
|
|
|
|
g_ptr_array_add (self->monitors, g_object_ref (monitor));
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_unref (monitor);
|
|
|
|
}
|
2022-02-04 20:29:57 +00:00
|
|
|
|
2022-02-17 01:09:48 +00:00
|
|
|
/* We need to create a new IOSurface for this monitor */
|
|
|
|
g_clear_object (&self->buffer);
|
2022-02-22 07:53:55 +00:00
|
|
|
g_clear_object (&self->front);
|
2022-02-17 01:09:48 +00:00
|
|
|
|
2022-02-25 21:52:08 +00:00
|
|
|
/* Determine the best-fit monitor */
|
2020-04-23 23:36:46 +00:00
|
|
|
for (guint i = 0; i < self->monitors->len; i++)
|
|
|
|
{
|
2022-02-25 21:52:08 +00:00
|
|
|
monitor = g_ptr_array_index (self->monitors, i);
|
2020-04-23 23:36:46 +00:00
|
|
|
|
|
|
|
if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect))
|
|
|
|
{
|
|
|
|
int area = intersect.width * intersect.height;
|
|
|
|
|
|
|
|
if (area > best_area)
|
|
|
|
{
|
|
|
|
best_area = area;
|
2022-02-25 21:52:08 +00:00
|
|
|
best = monitor;
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 21:52:08 +00:00
|
|
|
if (g_set_object (&self->best_monitor, best))
|
|
|
|
{
|
|
|
|
/* TODO: change frame clock to new monitor */
|
|
|
|
}
|
|
|
|
|
|
|
|
_gdk_macos_surface_configure (self);
|
|
|
|
|
|
|
|
gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
GdkMonitor *
|
|
|
|
_gdk_macos_surface_get_best_monitor (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
|
|
|
|
|
|
|
|
return self->best_monitor;
|
2020-04-23 23:36:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
NSView *
|
|
|
|
_gdk_macos_surface_get_view (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
|
|
|
|
|
|
|
|
if (self->window == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return [self->window contentView];
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_set_opacity (GdkMacosSurface *self,
|
|
|
|
double opacity)
|
|
|
|
{
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
if (self->window != NULL)
|
|
|
|
[self->window setAlphaValue:opacity];
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_get_root_coords (GdkMacosSurface *self,
|
|
|
|
int *x,
|
|
|
|
int *y)
|
|
|
|
{
|
|
|
|
GdkSurface *surface;
|
|
|
|
int out_x = 0;
|
|
|
|
int out_y = 0;
|
|
|
|
|
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
for (surface = GDK_SURFACE (self); surface; surface = surface->parent)
|
|
|
|
{
|
|
|
|
out_x += surface->x;
|
|
|
|
out_y += surface->y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x)
|
|
|
|
*x = out_x;
|
|
|
|
|
|
|
|
if (y)
|
|
|
|
*y = out_y;
|
|
|
|
}
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
|
|
|
|
GdkMacosBuffer *
|
|
|
|
_gdk_macos_surface_get_buffer (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL);
|
|
|
|
|
|
|
|
if (GDK_SURFACE_DESTROYED (self))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (self->buffer == NULL)
|
|
|
|
{
|
|
|
|
/* Create replacement buffer. We always use 4-byte and 32-bit BGRA for
|
|
|
|
* our surface as that can work with both Cairo and GL. The GdkMacosTile
|
|
|
|
* handles opaque regions for the compositor, so using 3-byte/24-bit is
|
|
|
|
* not a necessary optimization.
|
|
|
|
*/
|
|
|
|
double scale = gdk_surface_get_scale_factor (GDK_SURFACE (self));
|
|
|
|
guint width = GDK_SURFACE (self)->width * scale;
|
|
|
|
guint height = GDK_SURFACE (self)->height * scale;
|
|
|
|
|
|
|
|
self->buffer = _gdk_macos_buffer_new (width, height, scale, 4, 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
return self->buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
_gdk_macos_surface_do_delayed_show (GdkMacosSurface *self)
|
|
|
|
{
|
|
|
|
g_assert (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
|
|
|
|
self->show_on_next_swap = FALSE;
|
|
|
|
[self->window showAndMakeKey:YES];
|
|
|
|
gdk_surface_request_motion (GDK_SURFACE (self));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
_gdk_macos_surface_swap_buffers (GdkMacosSurface *self,
|
|
|
|
const cairo_region_t *damage)
|
|
|
|
{
|
2022-02-22 07:53:55 +00:00
|
|
|
GdkMacosBuffer *swap;
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
|
|
|
g_return_if_fail (damage != NULL);
|
|
|
|
|
2022-02-22 07:53:55 +00:00
|
|
|
swap = self->buffer;
|
|
|
|
self->buffer = self->front;
|
|
|
|
self->front = swap;
|
|
|
|
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
/* This code looks like it swaps buffers, but since the IOSurfaceRef
|
|
|
|
* appears to be retained on the other side, we really just ask all
|
|
|
|
* of the GdkMacosTile CALayer's to update their contents.
|
|
|
|
*/
|
2022-02-22 07:53:55 +00:00
|
|
|
[self->window swapBuffer:swap withDamage:damage];
|
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL
as well as software rendering with Cairo. In short, it uses tiles of Core
Animation's CALayer to display contents from an OpenGL or Cairo rendering
so that the window can provide partial damage updates. Partial damage is
not generally available when using OpenGL as the whole buffer is flipped
even if you only submitted a small change using a scissor rect.
Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to
upload contents to the display server. We use the tiling system we do for
OpenGL which reduces overall complexity and differences between them.
A New Buffer
============
GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was
used because 1) surface is already used and 2) it loosely maps to a
front/back buffer semantic.
However, it appears that IOSurfaceRef contents are being retained in
some fashion (likely in the compositor result) so we can update the same
IOSurfaceRef without flipping as long as we're fast. This appears to be
what Chromium does as well, but Firefox uses two IOSurfaceRef and flips
between them. We would like to avoid two surfaces because it doubles the
GPU VRAM requirements of the application.
Changes to Windows
==================
Previously, the NSWindow would dynamically change between different
types of NSView based on the renderer being used. This is no longer
necessary as we just have a single NSView type, GdkMacosView, which
inherits from GdkMacosBaseView just to keep the tedius stuff separate
from the machinery of GdkMacosView. We can merge those someday if we
are okay with that.
Changes to Views
================
GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been
removed and replaced with GdkMacosView. This new view has a single
CALayer (GdkMacosLayer) attached to it which itself has sublayers.
The contents of the CALayer is populated with an IOSurfaceRef which
we allocated with the GdkMacosSurface. The surface is replaced when
the NSWindow resizes.
Changes to Layers
=================
We now have a dedicated GdkMacosLayer which contains sublayers of
GdkMacosTile. The tile has a maximum size of 128x128 pixels in device
units.
The GdkMacosTile is partitioned by splitting both the transparent
region (window bounds minus opaque area) and then by splitting the
opaque area.
A tile has either translucent contents (and therefore is not opaque) or
has opaque contents (and therefore is opaque). An opaque tile never
contains transparent contents. As such, the opaque tiles contain a black
background so that Core Animation will consider the tile's bounds as
opaque. This can be verified with "Quartz Debug -> Show opaque regions".
Changes to Cairo
================
GTK 4 cannot currently use cairo-quartz because of how CSS borders are
rendered. It simply causes errors in the cairo_quartz_surface_t backend.
Since we are restricted to using cairo_image_surface_t (which happens to
be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a
mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit
with alpha channel even if we will discard the alpha channel as that is
necessary to hit the fast paths in other parts of the platform. Note
that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on
little-endian as we expect.
OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo
renders with 0,O in the top-left. We could use cairo_translate() and
cairo_scale() to reverse this, but it looks like some cairo things may
not look quite as right if we do so. To reduce the chances of one-off
bugs this continues to draw as Cairo would normally, but instead uses
an CGAffineTransform in the tiles and some CGRect translation when
swapping buffers to get the same effect.
Changes to OpenGL
=================
To simplify things, removal of all NSOpenGL* related components have
been removed and we strictly use the Core GL (CGL*) API. This probably
should have been done long ago anyay.
Most examples found in the browsers to use IOSurfaceRef with OpenGL are
using Legacy GL and there is still work underway to make this fit in
with the rest of how the GSK GL renderer works.
Since IOSurfaceRef bound to a texture/framebuffer will not have a
default framebuffer ID of 0, we needed to add a default framebuffer id
to the GdkGLContext. GskGLRenderer can use this to setup the command
queue in such a way that our IOSurface destination has been
glBindFramebuffer() as if it were the default drawable.
This stuff is pretty slight-of-hand, so where things are and what needs
flushing when and where has been a bit of an experiment to see what
actually works to get synchronization across subsystems.
Efficient Damages
=================
After we draw with Cairo, we unlock the IOSurfaceRef and the contents
are uploaded to the GPU. To make the contents visible to the app,
we must clear the tiles contents with `layer.contents=nil;` and then
re-apply the IOSurfaceRef. Since the buffer has likely not changed, we
only do this if the tile overlaps the damage region.
This gives the effect of having more tightly controlled damage regions
even though updating the layer would damage be the whole window (as it
is with OpenGL/Metal today with the exception of scissor-rect).
This too can be verified usign "Quartz Debug -> Flash screen udpates".
Frame Synchronized Resize
=========================
In GTK 4, we have the ability to perform sizing changes from compute-size
during the layout phase. Since the macOS backend already tracks window
resizes manually, we can avoid doing the setFrame: immediately and instead
do it within the frame clock's layout phase.
Doing so gives us vastly better resize experience as we're more likely to
get the size-change and updated-contents in the same frame on screen. It
makes things feel "connected" in a way they weren't before.
Some additional effort to tweak gravity during the process is also
necessary but we were already doing that in the GTK 4 backend.
Backporting
===========
The design here has made an attempt to make it possible to backport by
keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly
independent. There may be an opportunity to integrate this into GTK 3's
quartz backend with a fair bit of work. Doing so could improve the
situation for applications which are damage-rich such as The GIMP.
2022-02-14 10:20:19 +00:00
|
|
|
|
|
|
|
/* We might have delayed actually showing the window until the buffer
|
|
|
|
* contents are ready to be displayed. Doing so ensures that we don't
|
|
|
|
* get a point where we might have invalid buffer contents before we
|
|
|
|
* have content to display to the user.
|
|
|
|
*/
|
|
|
|
if G_UNLIKELY (self->show_on_next_swap)
|
|
|
|
_gdk_macos_surface_do_delayed_show (self);
|
|
|
|
}
|