Quelle GdkMacosLayer.c
Sprache: C
/* GdkMacosLayer.c
*
* Copyright © 2022 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 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"
#import "GdkMacosLayer.h"
#import "GdkMacosTile.h"
@protocol CanSetContentsOpaque
- (void )setContentsOpaque:(BOOL )mask;
@end
@implementation GdkMacosLayer
#define TILE_MAX_SIZE 128
#define TILE_EDGE_MAX_SIZE 512
static CGAffineTransform flipTransform;
static gboolean hasFlipTransform;
typedef struct
{
GdkMacosTile *tile;
cairo_rectangle_int_t cr_area;
CGRect area;
guint opaque : 1 ;
} TileInfo;
typedef struct
{
const cairo_region_t *region;
guint n_rects;
guint iter;
cairo_rectangle_int_t rect;
cairo_rectangle_int_t stash;
guint finished : 1 ;
} Tiler;
static void
tiler_init (Tiler *tiler,
const cairo_region_t *region)
{
memset (tiler, 0 , sizeof *tiler);
if (region == NULL)
{
tiler->finished = TRUE ;
return ;
}
tiler->region = region;
tiler->n_rects = cairo_region_num_rectangles (region);
if (tiler->n_rects > 0 )
cairo_region_get_rectangle (region, 0 , &tiler->rect);
else
tiler->finished = TRUE ;
}
static gboolean
tiler_next (Tiler *tiler,
cairo_rectangle_int_t *tile,
int max_size)
{
if (tiler->finished)
return FALSE ;
if (tiler->rect.width == 0 || tiler->rect.height == 0 )
{
tiler->iter++;
if (tiler->iter >= tiler->n_rects)
{
tiler->finished = TRUE ;
return FALSE ;
}
cairo_region_get_rectangle (tiler->region, tiler->iter, &tiler->rect);
}
/* If the next rectangle is too tall, slice the bottom off to
* leave just the height we want into tiler - > stash .
*/
if (tiler->rect.height > max_size)
{
tiler->stash = tiler->rect;
tiler->stash.y += max_size;
tiler->stash.height -= max_size;
tiler->rect.height = max_size;
}
/* Now we can take the next horizontal slice */
tile->x = tiler->rect.x;
tile->y = tiler->rect.y;
tile->height = tiler->rect.height;
tile->width = MIN (max_size, tiler->rect.width);
tiler->rect.x += tile->width;
tiler->rect.width -= tile->width;
if (tiler->rect.width == 0 )
{
tiler->rect = tiler->stash;
tiler->stash.width = tiler->stash.height = 0 ;
}
return TRUE ;
}
static inline cairo_rectangle_int_t
fromCGRect (const CGRect rect)
{
return (cairo_rectangle_int_t) {
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height
};
}
-(id)init
{
if (!hasFlipTransform)
{
hasFlipTransform = TRUE ;
flipTransform = CGAffineTransformMakeScale (1 , -1 );
}
self = [super init];
if (self == NULL)
return NULL;
self->_layoutInvalid = TRUE ;
[self setContentsGravity:kCAGravityCenter];
[self setGeometryFlipped:YES];
return self;
}
-(BOOL )isOpaque
{
return NO;
}
-(void )_applyLayout:(GArray *)tiles
{
CGAffineTransform transform;
GArray *prev;
gboolean exhausted;
guint j = 0 ;
if (self->_isFlipped)
transform = flipTransform;
else
transform = CGAffineTransformIdentity;
prev = g_steal_pointer (&self->_tiles);
self->_tiles = tiles;
exhausted = prev == NULL;
/* Try to use existing CALayer to avoid creating new layers
* as that can be rather expensive .
*/
for (guint i = 0 ; i < tiles->len; i++)
{
TileInfo *info = &g_array_index (tiles, TileInfo, i);
if (!exhausted)
{
TileInfo *other = NULL;
for (; j < prev->len; j++)
{
other = &g_array_index (prev, TileInfo, j);
if (other->opaque == info->opaque)
{
j++;
break ;
}
other = NULL;
}
if (other != NULL)
{
info->tile = g_steal_pointer (&other->tile);
[info->tile setFrame:info->area];
[info->tile setAffineTransform:transform];
continue ;
}
}
info->tile = [GdkMacosTile layer];
[info->tile setAffineTransform:transform];
[info->tile setOpaque:info->opaque];
[(id<CanSetContentsOpaque>)info->tile setContentsOpaque:info->opaque];
[info->tile setFrame:info->area];
[self addSublayer:info->tile];
}
/* Release all of our old layers */
if (prev != NULL)
{
for (guint i = 0 ; i < prev->len; i++)
{
TileInfo *info = &g_array_index (prev, TileInfo, i);
if (info->tile != NULL)
[info->tile removeFromSuperlayer];
}
g_array_unref (prev);
}
}
-(void )layoutSublayers
{
Tiler tiler;
GArray *ar;
cairo_region_t *transparent;
cairo_rectangle_int_t rect;
int max_size;
if (!self->_inSwapBuffer)
return ;
self->_layoutInvalid = FALSE ;
ar = g_array_sized_new (FALSE , FALSE , sizeof (TileInfo), 32 );
rect = fromCGRect ([self bounds]);
rect.x = rect.y = 0 ;
/* Calculate the transparent region (edges usually) */
transparent = cairo_region_create_rectangle (&rect);
if (self->_opaqueRegion)
cairo_region_subtract (transparent, self->_opaqueRegion);
self->_opaque = cairo_region_is_empty (transparent);
/* If we have transparent borders around the opaque region, then
* we are okay with a bit larger tiles since they don ' t change
* all that much and are generally small in width .
*/
if (!self->_opaque &&
self->_opaqueRegion &&
!cairo_region_is_empty (self->_opaqueRegion))
max_size = TILE_EDGE_MAX_SIZE;
else
max_size = TILE_MAX_SIZE;
/* Track transparent children */
tiler_init (&tiler, transparent);
while (tiler_next (&tiler, &rect, max_size))
{
TileInfo *info;
g_array_set_size (ar, ar->len+1 );
info = &g_array_index (ar, TileInfo, ar->len-1 );
info->tile = NULL;
info->opaque = FALSE ;
info->cr_area = rect;
info->area = CGRectMake (rect.x, rect.y, rect.width, rect.height);
}
/* Track opaque children */
tiler_init (&tiler, self->_opaqueRegion);
while (tiler_next (&tiler, &rect, TILE_MAX_SIZE))
{
TileInfo *info;
g_array_set_size (ar, ar->len+1 );
info = &g_array_index (ar, TileInfo, ar->len-1 );
info->tile = NULL;
info->opaque = TRUE ;
info->cr_area = rect;
info->area = CGRectMake (rect.x, rect.y, rect.width, rect.height);
}
cairo_region_destroy (transparent);
[self _applyLayout:g_steal_pointer (&ar)];
[super layoutSublayers];
}
-(void )setFrame:(NSRect)frame
{
if (frame.size.width != self.bounds.size.width ||
frame.size.height != self.bounds.size.height)
{
self->_layoutInvalid = TRUE ;
[self setNeedsLayout];
}
[super setFrame:frame];
}
-(void )setOpaqueRegion:(const cairo_region_t *)opaqueRegion
{
g_clear_pointer (&self->_opaqueRegion, cairo_region_destroy);
self->_opaqueRegion = cairo_region_copy (opaqueRegion);
self->_layoutInvalid = TRUE ;
[self setNeedsLayout];
}
-(void )swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage
{
IOSurfaceRef ioSurface = _gdk_macos_buffer_get_native (buffer);
gboolean flipped = _gdk_macos_buffer_get_flipped (buffer);
double scale = _gdk_macos_buffer_get_device_scale (buffer);
double width = _gdk_macos_buffer_get_width (buffer) / scale;
double height = _gdk_macos_buffer_get_height (buffer) / scale;
if (flipped != self->_isFlipped)
{
self->_isFlipped = flipped;
self->_layoutInvalid = TRUE ;
}
if (self->_layoutInvalid)
{
self->_inSwapBuffer = TRUE ;
[self layoutSublayers];
self->_inSwapBuffer = FALSE ;
}
if (self->_tiles == NULL)
return ;
for (guint i = 0 ; i < self->_tiles->len; i++)
{
const TileInfo *info = &g_array_index (self->_tiles, TileInfo, i);
cairo_region_overlap_t overlap;
CGRect area;
cairo_rectangle_int_t cr_area_scaled = {
info->cr_area.x * scale,
info->cr_area.y * scale,
info->cr_area.width * scale,
info->cr_area.height * scale
};
overlap = cairo_region_contains_rectangle (damage, &cr_area_scaled);
if (overlap == CAIRO_REGION_OVERLAP_OUT)
continue ;
area.origin.x = info->area.origin.x / width;
area.size.width = info->area.size.width / width;
area.size.height = info->area.size.height / height;
if (flipped)
area.origin.y = (height - info->area.origin.y - info->area.size.height) / height;
else
area.origin.y = info->area.origin.y / height;
[info->tile swapBuffer:ioSurface withRect:area];
}
}
@end
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland
2026-07-09