Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gskgpurenderpass.c

  Sprache: C
 

#include "config.h"

#include "gskgpurenderpassprivate.h"

#include "gskglimageprivate.h"
#include "gskgpublendopprivate.h"
#include "gskgpuborderopprivate.h"
#include "gskgpucoloropprivate.h"
#include "gskgpudeviceprivate.h"
#include "gskgpuframeprivate.h"
#include "gskgpuglobalsopprivate.h"
#include "gskgpuimageprivate.h"
#include "gskgpurenderpassopprivate.h"
#include "gskgpuroundedcoloropprivate.h"
#include "gskgpuscissoropprivate.h"
#include "gskgputextureopprivate.h"

#include "gskdebugprivate.h"
#include "gskrectprivate.h"
#include "gsktransform.h"

#include "gdk/gdkcolorprivate.h"

/* the epsilon we allow pixels to be off due to rounding errors.
 * Chosen rather randomly.
 */

#define EPSILON 0.001

/* the minimum scale required. If it's smaller than this, we treat it like 0 and
 * skip it.
 *
 * We set this to 1/100,000 because then 100,000 units would make up 1 pixel. And
 * it's unlikely that something useful results from scaling this far, considering
 * max texture size is usually 16,384 and FLOAT16 can't represent numbers this big.
 */

#define GSK_GPU_MIN_SCALE 0.00001

gboolean
gsk_gpu_render_pass_device_to_user (GskGpuRenderPass            *self,
                                    const cairo_rectangle_int_t *device,
                                    graphene_rect_t             *user)
{
  GskTransform *inverse;
  graphene_rect_t tmp;

  if (gsk_transform_get_fine_category (self->modelview) < GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
    return FALSE;

  inverse = gsk_transform_invert (gsk_transform_ref (self->modelview));
  gsk_transform_transform_bounds (inverse, &GSK_RECT_INIT_CAIRO (device), &tmp);
  gsk_transform_unref (inverse);

  *user = GRAPHENE_RECT_INIT (tmp.origin.x / self->scale.width - self->offset.x,
                              tmp.origin.y / self->scale.height - self->offset.y,
                              tmp.size.width / self->scale.width,
                              tmp.size.height / self->scale.height);

  return TRUE;
}

static gboolean
gsk_gpu_render_pass_user_to_device (GskGpuRenderPass      *self,
                                    const graphene_rect_t *user,
                                    graphene_rect_t       *device)
{
  graphene_rect_t tmp;

  if (gsk_transform_get_fine_category (self->modelview) < GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
    return FALSE;

  tmp = GRAPHENE_RECT_INIT ((user->origin.x + self->offset.x) * self->scale.width,
                            (user->origin.y + self->offset.y) * self->scale.height,
                            user->size.width * self->scale.width,
                            user->size.height * self->scale.height);

  gsk_transform_transform_bounds (self->modelview, &tmp, device);

  return TRUE;
}

static gboolean
gsk_gpu_render_pass_user_to_device_grow (GskGpuRenderPass      *self,
                                         const graphene_rect_t *user,
                                         cairo_rectangle_int_t *device)
{
  graphene_rect_t tmp;

  if (!gsk_gpu_render_pass_user_to_device (self, user, &tmp))
    return FALSE;

  gsk_rect_to_cairo_grow (&tmp, device);

  return TRUE;
}

gboolean
gsk_gpu_render_pass_user_to_device_shrink (GskGpuRenderPass      *self,
                                           const graphene_rect_t *user,
                                           cairo_rectangle_int_t *device)
{
  graphene_rect_t tmp;

  if (!gsk_gpu_render_pass_user_to_device (self, user, &tmp))
    return FALSE;

  if (!gsk_rect_to_cairo_shrink (&tmp, device))
    return FALSE;

  return TRUE;
}

gboolean
gsk_gpu_render_pass_user_to_device_exact (GskGpuRenderPass      *self,
                                          const graphene_rect_t *user,
                                          cairo_rectangle_int_t *device)
{
  graphene_rect_t tmp;

  if (!gsk_gpu_render_pass_user_to_device (self, user, &tmp))
    return FALSE;

  if (!gsk_rect_to_cairo_exact (&tmp, device))
    return FALSE;

  return TRUE;
}

/*<private>
 * gsk_gpu_render_pass_snap_rect:
 * @self: the renderpass
 * @rect: the rectangle to snap
 * @snap: how to snap the rectangle
 * @result: (out caller-allocates)
 *
 * Snaps the given rectangle to the device pixel grid
 * of the renderpass.
 * 
 * If the renderpass is transformed in a way that isn't axis-aligned,
 * then the result will be set to the input and no snapping will be
 * applied.
 *
 * When shrinking the rectangle or when using very small
 * rectangles it can happen that the resulting rectangle
 * is empty. In that case this function will return false.
 *
 * Returns: true if the result was not empty
 **/

gboolean
gsk_gpu_render_pass_snap_rect (GskGpuRenderPass      *self,
                               const graphene_rect_t *rect,
                               GskRectSnap            snap,
                               graphene_rect_t       *result)
{
  if (gsk_transform_get_fine_category (self->modelview) < GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
    {
      if (rect != result)
        *result = *rect;
      return TRUE;
    }

  return gsk_rect_snap_to_grid (rect,
                                snap,
                                &self->scale,
                                &self->offset,
                                result);
}

static void
gsk_gpu_render_pass_compute_mvp (GskGpuRenderPass  *self,
                                 graphene_matrix_t *out_mvp)
{
  if (self->modelview)
    {
      gsk_transform_to_matrix (self->modelview, out_mvp);
      graphene_matrix_multiply (out_mvp, &self->projection, out_mvp);
    }
  else
    graphene_matrix_init_from_matrix (out_mvp, &self->projection);
}

static void
gsk_gpu_render_pass_user_to_device_bounds (GskGpuRenderPass      *self,
                                           const graphene_rect_t *user,
                                           graphene_rect_t       *device)
{
  graphene_rect_t tmp;
  graphene_matrix_t mvp;
  float scale_x, scale_y;

  /* 1. transform to ccordinate space of MVP */
  tmp = GRAPHENE_RECT_INIT ((user->origin.x + self->offset.x) * self->scale.width,
                            (user->origin.y + self->offset.y) * self->scale.height,
                             user->size.width * self->scale.width,
                             user->size.height * self->scale.height);

  /* 2. apply MVP to move to normalized device coordinates (NDC) and project
   *    back into 2D space */

  gsk_gpu_render_pass_compute_mvp (self, &mvp);
  graphene_matrix_transform_bounds (&mvp, &tmp, &tmp);

  /* 3. go back from NDC into device space
   *    This is a little hack that's faster than multiplying by the matrix) */

  scale_x = graphene_matrix_get_x_scale (&self->projection);
  scale_y = graphene_matrix_get_y_scale (&self->projection);
  *device = GRAPHENE_RECT_INIT ((tmp.origin.x - graphene_matrix_get_x_translation (&self->projection)) / scale_x,
                                (tmp.origin.y - graphene_matrix_get_y_translation (&self->projection)) / scale_y,
                                tmp.size.width / scale_x,
                                tmp.size.height / scale_y);
  gsk_rect_normalize (device);
}

GskGpuRenderPass *
gsk_gpu_render_pass_new (GskGpuFrame                 *frame,
                         GskGpuImage                 *target,
                         GdkColorState               *ccs,
                         GskRenderPassType            pass_type,
                         GskGpuLoadOp                 load_op,
                         float                        clear_color[4],
                         const cairo_rectangle_int_t *clip,
                         const graphene_rect_t       *viewport)
{
  GskGpuRenderPass *self;
  gsize width, height;

  self = g_new0 (GskGpuRenderPass, 1);

  width = gsk_gpu_image_get_width (target);
  height = gsk_gpu_image_get_height (target);

  self->frame = frame;
  self->target = target;
  self->pass_type = pass_type;
  self->ccs = ccs;

  self->scissor = *clip;
  self->blend = GSK_GPU_BLEND_OVER;
  self->offset = GRAPHENE_POINT_INIT (-viewport->origin.x,
                                      -viewport->origin.y);
  if (clip->x == 0 && clip->y == 0 && clip->width == width && clip->height == height)
    {
      gsk_gpu_clip_init_empty (&self->clip, &self->offset, viewport);
    }
  else
    {
      float scale_x = viewport->size.width / width;
      float scale_y = viewport->size.height / height;
      gsk_gpu_clip_init_empty (&self->clip,
                               graphene_point_zero (),
                               &GRAPHENE_RECT_INIT (
                                   scale_x * clip->x,
                                   scale_y * clip->y,
                                   scale_x * clip->width,
                                   scale_y * clip->height
                               ));
    }

  self->clip_mask = NULL;
  self->clip_mask_rect = GRAPHENE_RECT_INIT (0000);
  self->clip_mask_has_opacity = FALSE;
  self->modelview = NULL;
  gsk_gpu_image_get_projection_matrix (target, &self->projection);
  self->scale = GRAPHENE_SIZE_INIT (width / viewport->size.width,
                                    height / viewport->size.height);
  self->opacity = 1.0;
  self->pending_globals = GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_SCISSOR | GSK_GPU_GLOBAL_BLEND;

  gsk_gpu_render_pass_begin_op (frame,
                                target,
                                clip,
                                load_op,
                                clear_color,
                                pass_type);

  return self;
}

void
gsk_gpu_render_pass_free (GskGpuRenderPass *self)
{
  gsk_gpu_render_pass_end_op (self->frame,
                              self->target,
                              self->pass_type);

  g_clear_pointer (&self->modelview, gsk_transform_unref);

  g_free (self);
}

void
gsk_gpu_render_pass_prepare_shader (GskGpuRenderPass *self)
{
  if (self->pending_globals == 0)
    return;

  if (self->pending_globals & (GSK_GPU_GLOBAL_MATRIX | GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_MASK))
    {
      graphene_matrix_t mvp;

      gsk_gpu_render_pass_compute_mvp (self, &mvp);

      /* gl_FragCoord is origin bottom left and no way to change in GLES,
       * so we have to mess up the variable */

      if (GSK_IS_GL_IMAGE (self->target) &&
          gsk_gl_image_is_flipped (GSK_GL_IMAGE (self->target)) &&
          self->clip_mask_rect.size.height > 0)
        {
          float size = gsk_gpu_image_get_height (self->target);
          gsk_gpu_globals_op (self->frame,
                              &self->scale,
                              &mvp,
                              &GRAPHENE_RECT_INIT (self->clip_mask_rect.origin.x,
                                                   size - self->clip_mask_rect.origin.y,
                                                   self->clip_mask_rect.size.width,
                                                   - self->clip_mask_rect.size.height),
                              &self->clip.rect);
        }
      else
        {
          gsk_gpu_globals_op (self->frame,
                              &self->scale,
                              &mvp,
                              &self->clip_mask_rect,
                              &self->clip.rect);
        }
    }

  if (self->pending_globals & GSK_GPU_GLOBAL_SCISSOR)
    {
      gsk_gpu_scissor_op (self->frame,
                          &self->scissor);
    }

  if (self->pending_globals & GSK_GPU_GLOBAL_BLEND)
    {
      gsk_gpu_blend_op (self->frame, self->blend);
    }

  self->pending_globals = 0;
}

void
gsk_gpu_render_pass_push_blend (GskGpuRenderPass             *self,
                                GskGpuBlend                   blend,
                                GskGpuRenderPassBlendStorage *storage)
{
  storage->blend = self->blend;
  
  self->blend = blend;

  if (storage->blend != self->blend)
    self->pending_globals |= GSK_GPU_GLOBAL_BLEND;
}

void
gsk_gpu_render_pass_pop_blend (GskGpuRenderPass             *self,
                               GskGpuRenderPassBlendStorage *storage)
{
  if (storage->blend != self->blend)
    self->pending_globals |= GSK_GPU_GLOBAL_BLEND;

  self->blend = storage->blend;
}

void
gsk_gpu_render_pass_push_opacity (GskGpuRenderPass               *self,
                                  float                           opacity,
                                  GskGpuRenderPassOpacityStorage *storage)
{
  storage->opacity = self->opacity;
  self->opacity *= opacity;
}

void
gsk_gpu_render_pass_pop_opacity (GskGpuRenderPass               *self,
                                 GskGpuRenderPassOpacityStorage *storage)
{
  self->opacity = storage->opacity;
}

gboolean
gsk_gpu_render_pass_has_opacity (GskGpuRenderPass *self)
{
  return self->opacity < 1.0 || self->clip_mask_has_opacity;
}

void
gsk_gpu_render_pass_set_transform (GskGpuRenderPass *self,
                                   GskGpuTransform  *transform)
{
  gsk_gpu_clip_init_empty (&self->clip, graphene_point_zero (), &GSK_RECT_INIT_CAIRO (&self->scissor));
  gsk_gpu_clip_scale (&self->clip,
                      &self->clip,
                      gdk_dihedral_invert (transform->dihedral),
                      transform->scale.width,
                      transform->scale.height);
  self->offset = transform->offset;
  self->scale = GRAPHENE_SIZE_INIT (transform->scale.width, transform->scale.height);
  self->pending_globals |= GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
  if (self->modelview || transform->dihedral != GDK_DIHEDRAL_NORMAL)
    {
      g_clear_pointer (&self->modelview, gsk_transform_unref);
      self->pending_globals |= GSK_GPU_GLOBAL_MATRIX;
      self->modelview = gsk_transform_dihedral (NULL, transform->dihedral);
    }
}

static void
extract_scale_from_transform (GskTransform *transform,
                              float        *out_scale_x,
                              float        *out_scale_y)
{
  switch (gsk_transform_get_fine_category (transform))
    {
    default:
      g_assert_not_reached ();
    case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
    case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
      *out_scale_x = 1.0f;
      *out_scale_y = 1.0f;
      return;

    case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
      {
        float scale_x, scale_y, dx, dy;
        gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
        *out_scale_x = scale_x;
        *out_scale_y = scale_y;
      }
      return;

    case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
    case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
    case GSK_FINE_TRANSFORM_CATEGORY_2D:
      {
        float skew_x, skew_y, scale_x, scale_y, angle, dx, dy;
        gsk_transform_to_2d_components (transform,
                                        &skew_x, &skew_y,
                                        &scale_x, &scale_y,
                                        &angle,
                                        &dx, &dy);
        *out_scale_x = fabs (scale_x);
        *out_scale_y = fabs (scale_y);
      }
      return;

    case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
    case GSK_FINE_TRANSFORM_CATEGORY_ANY:
    case GSK_FINE_TRANSFORM_CATEGORY_3D:
      {
        graphene_quaternion_t rotation;
        graphene_matrix_t matrix;
        graphene_vec4_t perspective;
        graphene_vec3_t translation;
        graphene_vec3_t matrix_scale;
        graphene_vec3_t shear;

        gsk_transform_to_matrix (transform, &matrix);
        if (!graphene_matrix_decompose (&matrix,
                                        &translation,
                                        &matrix_scale,
                                        &rotation,
                                        &shear,
                                        &perspective))
          {
            *out_scale_x = 0;
            *out_scale_y = 0;
            return;
          }

        *out_scale_x = fabs (graphene_vec3_get_x (&matrix_scale));
        *out_scale_y = fabs (graphene_vec3_get_y (&matrix_scale));
      }
      return;
    }
}

static gboolean
gsk_gpu_render_pass_push_transform_check_scale (GskGpuRenderPass                 *self,
                                                float                             scale_x,
                                                float                             scale_y,
                                                GskGpuRenderPassTransformStorage *storage)
{
  if (scale_x >= GSK_GPU_MIN_SCALE &&
      scale_y >= GSK_GPU_MIN_SCALE)
    return TRUE;

  gsk_gpu_clip_init_all_clipped (&self->clip);
  self->modelview = gsk_transform_ref (storage->modelview);
  storage->clip.modified |= GSK_GPU_GLOBAL_CLIP;
  self->pending_globals |= storage->clip.modified;

  return FALSE;
}

static void
gsk_gpu_render_pass_draw_clip_mask (GskGpuRenderPass            *self,
                                    const graphene_rect_t       *new_clip_rect,
                                    const GskRoundedRect        *new_clip_rounded,
                                    GskGpuImage                 *new_clip_mask,
                                    const graphene_rect_t       *new_clip_mask_rect,
                                    GskGpuRenderPassClipStorage *storage);

void
gsk_gpu_render_pass_push_transform (GskGpuRenderPass                 *self,
                                    GskTransform                     *transform,
                                    const graphene_rect_t            *bounds,
                                    const graphene_rect_t            *child_bounds,
                                    GskGpuRenderPassTransformStorage *storage)
{
  storage->modelview = self->modelview;
  storage->scale = self->scale;
  storage->offset = self->offset;
  gsk_gpu_clip_init_copy (&storage->clip.clip, &self->clip);
  storage->clip.modified = 0;

  switch (gsk_transform_get_fine_category (transform))
    {
    case GSK_FINE_TRANSFORM_CATEGORY_IDENTITY:
    case GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE:
      /* We assume these 2 to be special-cased by the caller, so we just */
      G_GNUC_FALLTHROUGH;

    case GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE:
      {
        float dx, dy, scale_x, scale_y;

        gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &dy);
        if (!gsk_gpu_render_pass_push_transform_check_scale (self,
                                                             scale_x * storage->scale.width,
                                                             scale_y * storage->scale.height,
                                                             storage))
          return;

        gsk_gpu_clip_scale (&self->clip, &storage->clip.clip, GDK_DIHEDRAL_NORMAL, scale_x, scale_y);
        self->offset.x = (self->offset.x + dx) / scale_x;
        self->offset.y = (self->offset.y + dy) / scale_y;
        self->scale = GRAPHENE_SIZE_INIT (scale_x * storage->scale.width,
                                          scale_y * storage->scale.height);
        self->modelview = gsk_transform_ref (storage->modelview);
        storage->clip.modified = GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP;
      }
      break;

    case GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE:
    case GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL:
      {
        GdkDihedral dihedral, inverted;
        float xx, xy, yx, yy, dx, dy, scale_x, scale_y, old_scale_x, old_scale_y;

        gsk_transform_to_dihedral (transform, &dihedral, &scale_x, &scale_y, &dx, &dy);
        inverted = gdk_dihedral_invert (dihedral);
        gdk_dihedral_get_mat2 (inverted, &xx, &xy, &yx, &yy);
        old_scale_x = storage->scale.width;
        old_scale_y = storage->scale.height;
        if (!gsk_gpu_render_pass_push_transform_check_scale (self,
                                                             fabs (scale_x * (old_scale_x * xx + old_scale_y * yx)),
                                                             fabs (scale_y * (old_scale_x * xy + old_scale_y * yy)),
                                                             storage))
          return;
        gsk_gpu_clip_scale (&self->clip, &storage->clip.clip, inverted, scale_x, scale_y);
        self->offset.x = (self->offset.x + dx) / scale_x;
        self->offset.y = (self->offset.y + dy) / scale_y;
        self->offset = GRAPHENE_POINT_INIT (xx * self->offset.x + xy * self->offset.y,
                                            yx * self->offset.x + yy * self->offset.y);
        self->scale = GRAPHENE_SIZE_INIT (fabs (scale_x * (old_scale_x * xx + old_scale_y * yx)),
                                          fabs (scale_y * (old_scale_x * xy + old_scale_y * yy)));
        self->modelview = gsk_transform_dihedral (gsk_transform_ref (storage->modelview), dihedral);
        storage->clip.modified = GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_MATRIX;
      }
      break;

    case GSK_FINE_TRANSFORM_CATEGORY_2D:
    case GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN:
    case GSK_FINE_TRANSFORM_CATEGORY_ANY:
    case GSK_FINE_TRANSFORM_CATEGORY_3D:
      {
        GskTransform *clip_transform;

        clip_transform = gsk_transform_transform (gsk_transform_translate (NULL, &self->offset), transform);

        if (child_bounds && gsk_gpu_clip_contains_rect (&self->clip, &self->offset, bounds))
          {
            gsk_gpu_clip_init_contained (&self->clip, child_bounds);
          }
        else 
          {
            GskTransform *inverse;
            graphene_rect_t new_bounds;

            if (storage->clip.clip.type != GSK_GPU_CLIP_NONE)
              {
                gsk_gpu_render_pass_draw_clip_mask (self, NULL, NULL, NULL, NULL, &storage->clip);
              }
            if (!gsk_gpu_clip_is_all_clipped (&self->clip))
              {
                /* after drawing a clip mask, the clip is either empty or everything is clipped */
                g_assert (self->clip.type == GSK_GPU_CLIP_NONE);
                inverse = gsk_transform_invert (gsk_transform_ref (clip_transform));
                gsk_transform_transform_bounds (inverse, &storage->clip.clip.rect.bounds, &new_bounds);
                gsk_transform_unref (inverse);
                gsk_gpu_clip_init_empty (&self->clip, graphene_point_zero (), &new_bounds);
              }
          }
        gsk_transform_unref (clip_transform);

        self->modelview = gsk_transform_scale (gsk_transform_ref (storage->modelview),
                                               self->scale.width,
                                               self->scale.height);
        self->modelview = gsk_transform_translate (self->modelview, &self->offset);
        self->modelview = gsk_transform_transform (self->modelview, transform);

        if (gsk_transform_get_fine_category (self->modelview) >= GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
          {
            float scale_x, scale_y, dx, dy, xx, xy, yx, yy;
            graphene_rect_t scissor_rect;
            GdkDihedral dihedral, inverted;
            GskGpuClip scissored_clip;

            /* we magically transformed back into a simple transform */
            gsk_transform_to_dihedral (self->modelview, &dihedral, &scale_x, &scale_y, &dx, &dy);
            inverted = gdk_dihedral_invert (dihedral);
            gdk_dihedral_get_mat2 (inverted, &xx, &xy, &yx, &yy);
            if (!gsk_gpu_render_pass_push_transform_check_scale (self,
                                                                 fabs (scale_x * xx + scale_y * yx),
                                                                 fabs (scale_x * xy + scale_y * yy),
                                                                 storage))
              return;
            dx /= scale_x;
            dy /= scale_y;
            self->offset = GRAPHENE_POINT_INIT (xx * dx + xy * dy,
                                                yx * dx + yy * dy);
            self->scale = GRAPHENE_SIZE_INIT (fabs (scale_x * xx + scale_y * yx),
                                              fabs (scale_x * xy + scale_y * yy));
            g_clear_pointer (&self->modelview, gsk_transform_unref);
            self->modelview = gsk_transform_dihedral (NULL, dihedral);
            if (!gsk_gpu_clip_is_all_clipped (&self->clip))
              {
                self->clip.rect.bounds.origin.x += self->offset.x;
                self->clip.rect.bounds.origin.y += self->offset.y;

                /* We can now check the clip against the scissor rect again */
                if (!gsk_gpu_render_pass_device_to_user (self, &self->scissor, &scissor_rect))
                  {
                    /* Only happens with more complex transforms */
                    g_assert_not_reached ();
                  }
                if (gsk_gpu_clip_intersect_rect (&scissored_clip, &self->clip, &self->offset, &scissor_rect))
                  gsk_gpu_clip_init_copy (&self->clip, &scissored_clip);
              }
          }
        else
          {
            float scale_x, scale_y, old_pixels, new_pixels;

            extract_scale_from_transform (self->modelview, &scale_x, &scale_y);

            old_pixels = MAX (storage->scale.width * storage->clip.clip.rect.bounds.size.width,
                              storage->scale.height * storage->clip.clip.rect.bounds.size.height);
            new_pixels = MAX (scale_x * self->clip.rect.bounds.size.width,
                              scale_y * self->clip.rect.bounds.size.height);
            /* Check that our offscreen doesn't get too big.  1.5 ~ sqrt(2) */
            if (new_pixels > 1.5 * old_pixels)
              {
                float forced_downscale = 2 * old_pixels / new_pixels;
                scale_x *= forced_downscale;
                scale_y *= forced_downscale;
              }

            if (!gsk_gpu_render_pass_push_transform_check_scale (self,
                                                                 scale_x,
                                                                 scale_y,
                                                                 storage))
              return;

            self->modelview = gsk_transform_scale (self->modelview, 1 / scale_x, 1 / scale_y);
            self->scale = GRAPHENE_SIZE_INIT (scale_x, scale_y);
            self->offset = GRAPHENE_POINT_INIT (00);
          }
        storage->clip.modified |= GSK_GPU_GLOBAL_SCALE | GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_MATRIX;
      }
      break;

    default:
      g_assert_not_reached ();
      break;
    }

  self->pending_globals |= storage->clip.modified;
}

void
gsk_gpu_render_pass_pop_transform (GskGpuRenderPass                 *self,
                                   GskGpuRenderPassTransformStorage *storage)
{
  gsk_gpu_render_pass_pop_clip_rect (self, &storage->clip);
  g_clear_pointer (&self->modelview, gsk_transform_unref);
  self->modelview = storage->modelview;
  self->scale = storage->scale;
  self->offset = storage->offset;
}

void
gsk_gpu_render_pass_push_translate (GskGpuRenderPass                 *self,
                                    const graphene_point_t           *offset,
                                    GskGpuRenderPassTranslateStorage *storage)
{
  storage->offset = self->offset;
  self->offset.x += offset->x;
  self->offset.y += offset->y;
}

void
gsk_gpu_render_pass_pop_translate (GskGpuRenderPass                 *self,
                                   GskGpuRenderPassTranslateStorage *storage)
{
  self->offset = storage->offset;
}

gboolean
gsk_gpu_render_pass_is_all_clipped (GskGpuRenderPass *self)
{
  return gsk_gpu_clip_is_all_clipped (&self->clip);
}

gboolean
gsk_gpu_render_pass_get_clip_bounds (GskGpuRenderPass *self,
                                     graphene_rect_t  *out_bounds)
{
  graphene_rect_t scissor;

  if (gsk_gpu_clip_is_all_clipped (&self->clip))
    return FALSE;

  if (gsk_gpu_render_pass_device_to_user (self,
                                          &self->scissor,
                                          &scissor))
    {
      graphene_rect_t tmp;

      gsk_rect_init_offset (&tmp,
                            &self->clip.rect.bounds,
                            &GRAPHENE_POINT_INIT (-self->offset.x, -self->offset.y));

      if (!gsk_rect_intersection (&scissor, &tmp, out_bounds))
        {
          g_warning ("Clipping is broken, everything is clipped, but we didn't set all-clipped.");
          return FALSE;
        }

      return TRUE;
    }
  else
    {
      gsk_rect_init_offset (out_bounds,
                            &self->clip.rect.bounds,
                            &GRAPHENE_POINT_INIT (-self->offset.x, -self->offset.y));
      return TRUE;
    }
}

/*<private>
 * gsk_gpu_render_pass_in_clip_fast:
 * @self: the renderpass
 * @rect: a rectangle to check
 *
 * Does a quick check to see if the rectangle may be inside the clip.
 *
 * This function is written for performance and quick rejection, so it
 * may return true even though the rectangle is clipped entirely.
 * However, it will never return false if the rect is still visible.
 *
 * This function takes into account that the rectangle may be snapped,
 * so it will check with pixel boundary accuracy.
 *
 * Returns: false if it can guarantee the rectangle will be clipped away
 **/

gboolean
gsk_gpu_render_pass_in_clip_fast (GskGpuRenderPass      *self,
                                  const graphene_rect_t *rect)
{
  cairo_rectangle_int_t device;

  if (gsk_gpu_render_pass_user_to_device_grow (self, rect, &device))
    {
      /* conversion worked, so check against scissor rect */
      return gdk_rectangle_intersect (&self->scissor, &device, NULL);
    }
  else
    {
      return gsk_gpu_clip_may_intersect_rect (&self->clip, &self->offset, rect);
    }
}

void
gsk_gpu_render_pass_push_clip_device_rect (GskGpuRenderPass            *self,
                                           const cairo_rectangle_int_t *clip,
                                           GskGpuRenderPassClipStorage *storage)
{
  graphene_rect_t scissor;

  gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
  storage->scissor = self->scissor;

  if (!gdk_rectangle_intersect (&self->scissor, clip, &self->scissor))
    {
      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
      gsk_gpu_clip_init_all_clipped (&self->clip);
      storage->modified = GSK_GPU_GLOBAL_CLIP;
      self->pending_globals |= storage->modified;
      return;
    }

  storage->modified = GSK_GPU_GLOBAL_SCISSOR;
  if (gsk_gpu_render_pass_device_to_user (self, &self->scissor, &scissor) &&
      gsk_gpu_clip_intersect_rect (&self->clip, &storage->clip, &self->offset, &scissor))
    {
      /* if scissoring does all the work, we can pretend the clip is empty */
      if (gsk_gpu_clip_contains_rect (&self->clip, &self->offset, &scissor))
        {
          gsk_gpu_clip_init_empty (&self->clip, &self->offset, &scissor);
        }
      storage->modified |= GSK_GPU_GLOBAL_CLIP;
    }
  else
    {
      gsk_gpu_clip_init_copy (&self->clip, &storage->clip);
    }
}

void
gsk_gpu_render_pass_pop_clip_device_rect (GskGpuRenderPass            *self,
                                          GskGpuRenderPassClipStorage *storage)
{
  /* They're identical currently */
  gsk_gpu_render_pass_pop_clip_rect (self, storage);
}

static gboolean
gsk_gpu_render_pass_try_push_clip_rect (GskGpuRenderPass            *self,
                                        const graphene_rect_t       *clip,
                                        GskGpuRenderPassClipStorage *storage)
{
  cairo_rectangle_int_t scissor;
  graphene_rect_t scissored;

  if (gsk_gpu_clip_is_all_clipped (&self->clip))
    {
      storage->modified = 0;
      return TRUE;
    }

  if (gsk_gpu_render_pass_device_to_user (self,
                                          &self->scissor,
                                          &scissored))
    {
      if (!gsk_rect_intersection (&scissored, clip, &scissored))
        {
          gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
          gsk_gpu_clip_init_all_clipped (&self->clip);
          storage->modified = GSK_GPU_GLOBAL_CLIP;
          self->pending_globals |= storage->modified;
          return TRUE;
        }
    }
  else
    scissored = *clip;

  /* Check if we can use scissoring for the clip */
  /* We could check scissoring for each edge individually, but that's a lot
   * more code */

  if (gsk_gpu_render_pass_user_to_device_exact (self, &scissored, &scissor))
    {
      storage->scissor = self->scissor;
      storage->modified = GSK_GPU_GLOBAL_SCISSOR;
      self->scissor = scissor;

      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);

      if (gsk_gpu_clip_intersect_rect (&self->clip, &storage->clip, &self->offset, &scissored))
        {
          /* if scissoring does all the work, we can pretend the clip is empty */
          if (gsk_gpu_clip_contains_rect (&self->clip, &self->offset, &scissored))
            {
              gsk_gpu_clip_init_empty (&self->clip, &self->offset, &scissored);
            }
          storage->modified |= GSK_GPU_GLOBAL_CLIP;
        }
      else
        {
          gsk_gpu_clip_init_copy (&self->clip, &storage->clip);
        }
    }
  else
    {
      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);

      if (gsk_gpu_clip_intersect_rect (&self->clip, &storage->clip, &self->offset, &scissored))
        {
          storage->modified = GSK_GPU_GLOBAL_CLIP;
        }
      else
        {
          gsk_gpu_clip_init_copy (&self->clip, &storage->clip);
          return FALSE;
        }

    }

  self->pending_globals |= storage->modified;
  return TRUE;
}

static void
gsk_gpu_render_pass_clear_all (GskGpuRenderPass      *self,
                               const graphene_rect_t *rect)
{
  GdkColor white;
  GskGpuRenderPassBlendStorage storage;

  gdk_color_init (&white, self->ccs, (float[4]) { 1111 });
  gsk_gpu_render_pass_push_blend (self, GSK_GPU_BLEND_CLEAR, &storage);

  gsk_gpu_color_op (self,
                    self->ccs,
                    self->ccs,
                    rect,
                    &white);

  gsk_gpu_render_pass_pop_blend (self, &storage);
  gdk_color_finish (&white);
}

static void
gsk_gpu_render_pass_clear_rect (GskGpuRenderPass      *self,
                                const graphene_rect_t *outer,
                                const graphene_rect_t *inner)
{
  graphene_vec4_t widths;
  float borders[4];
  GdkColor white;
  GskGpuRenderPassBlendStorage storage;

  borders[0] = MAX (inner->origin.y - outer->origin.y, 0);
  borders[1] = MAX (outer->origin.x + outer->size.width - inner->origin.x - inner->size.width, 0);
  borders[2] = MAX (outer->origin.y + outer->size.height - inner->origin.y - inner->size.height0);
  borders[3] = MAX (inner->origin.x - outer->origin.x, 0);
  if (borders[0] <= 0 && borders[1] <= 0 &&
      borders[2] <= 0 && borders[3] <= 0)
    return;

  gdk_color_init (&white, self->ccs, (float[4]) { 1111 });
  gsk_gpu_render_pass_push_blend (self, GSK_GPU_BLEND_CLEAR, &storage);

  graphene_vec4_init_from_float (&widths, borders);
  gsk_gpu_border_op (self,
                     self->ccs,
                     self->ccs,
                     outer,
                     &GSK_ROUNDED_RECT_INIT_FROM_RECT (*outer),
                     &white,
                     &white,
                     &white,
                     &white,
                     &widths,
                     &GRAPHENE_SIZE_INIT (00));

  gsk_gpu_render_pass_pop_blend (self, &storage);
  gdk_color_finish (&white);
}

static void
gsk_gpu_render_pass_clear_rounded (GskGpuRenderPass      *self,
                                   const graphene_rect_t *outer,
                                   const GskRoundedRect  *inner)
{
  graphene_vec4_t widths;
  float borders[4];
  GskRoundedRect r;
  GdkColor white;
  GskGpuRenderPassBlendStorage storage;

  r = *inner;

  borders[0] = MAX (r.bounds.origin.y - outer->origin.y, 0);
  borders[0] += MAX (r.corner[GSK_CORNER_TOP_LEFT].height,
                     r.corner[GSK_CORNER_TOP_RIGHT].height);
  borders[1] = MAX (outer->origin.x + outer->size.width - r.bounds.origin.x - r.bounds.size.width, 0);
  borders[1] += MAX (r.corner[GSK_CORNER_TOP_RIGHT].width,
                     r.corner[GSK_CORNER_BOTTOM_RIGHT].width);
  borders[2] = MAX (outer->origin.y + outer->size.height - r.bounds.origin.y - r.bounds.size.height, 0);
  borders[2] += MAX (r.corner[GSK_CORNER_BOTTOM_LEFT].height,
                     r.corner[GSK_CORNER_BOTTOM_RIGHT].height);
  borders[3] = MAX (r.bounds.origin.x - outer->origin.x, 0);
  borders[3] += MAX (r.corner[GSK_CORNER_TOP_LEFT].width,
                     r.corner[GSK_CORNER_BOTTOM_LEFT].width);
  if (borders[0] <= 0 && borders[1] <= 0 &&
      borders[2] <= 0 && borders[3] <= 0)
    return;

  gdk_color_init (&white, self->ccs, (float[4]) { 1111 });
  gsk_gpu_render_pass_push_blend (self, GSK_GPU_BLEND_CLEAR, &storage);

  graphene_vec4_init_from_float (&widths, borders);
  gsk_rounded_rect_shrink (&r, -borders[0], -borders[1], -borders[2], -borders[3]);
  gsk_gpu_border_op (self,
                     self->ccs,
                     self->ccs,
                     outer,
                     &r,
                     &white,
                     &white,
                     &white,
                     &white,
                     &widths,
                     &GRAPHENE_SIZE_INIT (00));

  gsk_gpu_render_pass_pop_blend (self, &storage);
  gdk_color_finish (&white);
}

static void
gsk_gpu_render_pass_clear_clip (GskGpuRenderPass *self,
                                const GskGpuClip *clip)
{
  graphene_rect_t bounds;

  if (!gsk_gpu_render_pass_get_clip_bounds (self, &bounds))
    return;

  switch (clip->type)
    {
      case GSK_GPU_CLIP_ROUNDED:
        gsk_gpu_render_pass_clear_rounded (self,
                                           &bounds,
                                           &clip->rect);
        break;

      case GSK_GPU_CLIP_RECT:
      case GSK_GPU_CLIP_CONTAINED:
        gsk_gpu_render_pass_clear_rect (self, 
                                        &bounds,
                                        &clip->rect.bounds);
        break;

      case GSK_GPU_CLIP_NONE:
        break;

      case GSK_GPU_CLIP_ALL_CLIPPED:
        gsk_gpu_render_pass_clear_all (self, &bounds);
        break;

      default:
        g_assert_not_reached ();
        break;
    }
}

static void
gsk_gpu_render_pass_draw_clip_mask (GskGpuRenderPass            *self,
                                    const graphene_rect_t       *new_clip_rect,
                                    const GskRoundedRect        *new_clip_rounded,
                                    GskGpuImage                 *new_clip_mask,
                                    const graphene_rect_t       *new_clip_mask_rect,
                                    GskGpuRenderPassClipStorage *storage)
{
  GskGpuRenderPass *other;
  graphene_rect_t bounds, device;
  cairo_rectangle_int_t area;
  GskGpuImage *image;
  GskDebugProfile *profile;
  GdkMemoryFormat format;
  GskGpuRenderPassBlendStorage blend_storage;
  GskGpuRenderPassTransformStorage transform_storage;
  GskGpuRenderPassTranslateStorage translate_storage;
  GskTransform *transform;
  GskGpuClip old_clip;

  if (!gsk_gpu_render_pass_get_clip_bounds (self, &bounds) ||
      (new_clip_rect && !gsk_rect_intersection (&bounds, new_clip_rect, &bounds)) ||
      (new_clip_rounded && !gsk_rect_intersection (&bounds, &new_clip_rounded->bounds, &bounds)) ||
      (new_clip_mask_rect && !gsk_rect_intersection (&bounds, new_clip_mask_rect, &bounds)))
    {
      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
      gsk_gpu_clip_init_all_clipped (&self->clip);
      storage->modified = GSK_GPU_GLOBAL_CLIP;
      self->pending_globals |= storage->modified;
      return;
    }

  gsk_gpu_render_pass_user_to_device_bounds (self, &bounds, &device);

  gsk_rect_to_cairo_grow (&device, &area);
  if (!gdk_rectangle_intersect (&area,
                                &self->scissor,
                                &area))
    {
      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
      gsk_gpu_clip_init_all_clipped (&self->clip);
      storage->modified = GSK_GPU_GLOBAL_CLIP;
      self->pending_globals |= storage->modified;
      return;
    }

  format = gdk_memory_depth_get_alpha_format (gdk_memory_format_get_depth (gsk_gpu_image_get_format (self->target)));
  image = gsk_gpu_device_create_offscreen_image (gsk_gpu_frame_get_device (self->frame),
                                                 FALSE,
                                                 format,
                                                 FALSE,
                                                 area.width,
                                                 area.height);
  if (image == NULL)
    {
      gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
      gsk_gpu_clip_init_all_clipped (&self->clip);
      storage->modified = GSK_GPU_GLOBAL_CLIP;
      self->pending_globals |= storage->modified;
      return;
    }

  profile = gsk_gpu_frame_get_profile (self->frame);
  if (profile)
    {
      profile->self.n_offscreens++;
      profile->self.offscreen_pixels += area.width * area.height;
    }

  other = gsk_gpu_render_pass_new (self->frame,
                                   image,
                                   self->ccs,
                                   GSK_RENDER_PASS_OFFSCREEN,
                                   GSK_GPU_LOAD_OP_CLEAR,
                                   (float[4]) { self->opacity, self->opacity, self->opacity, self->opacity },
                                   &(cairo_rectangle_int_t) {
                                     00,
                                     area.width, area.height
                                   },
                                   &GSK_RECT_INIT_CAIRO (&area));

  gsk_gpu_render_pass_push_blend (other, GSK_GPU_BLEND_MASK, &blend_storage);

  if (self->clip_mask)
    {
      gsk_gpu_texture_op (other,
                          self->ccs,
                          &GSK_RECT_INIT_CAIRO (&area),
                          self->clip_mask,
                          GSK_GPU_SAMPLER_TRANSPARENT,
                          &self->clip_mask_rect);
    }

  transform = gsk_transform_ref (self->modelview);
  transform = gsk_transform_scale (transform,
                                   self->scale.width,
                                   self->scale.height);
  if (transform)
    gsk_gpu_render_pass_push_transform (other, transform, NULL, NULL, &transform_storage);

  gsk_gpu_render_pass_clear_clip (other, &self->clip);

  gsk_gpu_render_pass_push_translate (other, &self->offset, &translate_storage);

  if (!gsk_gpu_render_pass_get_clip_bounds (other, &bounds))
    {
      g_assert_not_reached ();
    }

  if (new_clip_mask)
    {
      /* we rely on the mask being transparent outside of the clip
       * rect here as bounds may be larger than new_clip_rect */

      gsk_gpu_texture_op (other,
                          self->ccs,
                          &bounds,
                          new_clip_mask,
                          GSK_GPU_SAMPLER_TRANSPARENT,
                          new_clip_mask_rect);
    }
  else if (new_clip_rect)
    {
      gsk_gpu_render_pass_clear_rect (other,
                                      &bounds,
                                      new_clip_rect);
    }

  if (new_clip_rounded)
    {
      gsk_gpu_render_pass_clear_rounded (other,
                                         &bounds,
                                         new_clip_rounded);
    }

  gsk_gpu_render_pass_pop_translate (other, &translate_storage);
  if (transform)
    {
      gsk_gpu_render_pass_pop_transform (other, &transform_storage);
      gsk_transform_unref (transform);
    }
  gsk_gpu_render_pass_pop_blend (other, &blend_storage);
  gsk_gpu_render_pass_free (other);

  /* We can reset things now, the mask does it all */
  gsk_gpu_clip_init_copy (&old_clip, &self->clip);
  gsk_gpu_clip_init_empty (&self->clip, &self->offset, &bounds);
  gsk_gpu_render_pass_push_clip_device_rect (self, &area, storage);
  gsk_gpu_clip_init_copy (&storage->clip, &old_clip);
  storage->clip_mask = self->clip_mask;
  storage->clip_mask_rect = self->clip_mask_rect;
  storage->clip_mask_has_opacity = self->clip_mask_has_opacity;
  storage->opacity = self->opacity;
  storage->modified |= GSK_GPU_GLOBAL_CLIP | GSK_GPU_GLOBAL_MASK;

  self->clip_mask = image;
  self->clip_mask_has_opacity |= self->opacity < 1.0;
  self->opacity = 1.0;
  self->clip_mask_rect = GSK_RECT_INIT_CAIRO (&area);
  /* We can reset things now, the mask does it all */
  gsk_gpu_clip_init_empty (&self->clip, &self->offset, &bounds);
  self->pending_globals |= storage->modified;
}

void
gsk_gpu_render_pass_push_clip_rect (GskGpuRenderPass            *self,
                                    const graphene_rect_t       *clip,
                                    GskGpuRenderPassClipStorage *storage)
{
  if (gsk_gpu_render_pass_try_push_clip_rect (self, clip, storage))
    return;

  GSK_DEBUG (FALLBACK, "push_clip_rect() needs clip mask");
  gsk_gpu_render_pass_draw_clip_mask (self, clip, NULL, NULL, NULL, storage);
}

void
gsk_gpu_render_pass_pop_clip_rect (GskGpuRenderPass            *self,
                                   GskGpuRenderPassClipStorage *storage)
{
  if (storage->modified & GSK_GPU_GLOBAL_SCISSOR)
    self->scissor = storage->scissor;

  if (storage->modified & GSK_GPU_GLOBAL_CLIP)
    gsk_gpu_clip_init_copy (&self->clip, &storage->clip);

  if (storage->modified & GSK_GPU_GLOBAL_MASK)
    {
      g_clear_object (&self->clip_mask);
      self->clip_mask = storage->clip_mask;
      self->clip_mask_rect = storage->clip_mask_rect;
      self->clip_mask_has_opacity = storage->clip_mask_has_opacity;
      self->opacity = storage->opacity;
    }

  self->pending_globals |= storage->modified;
}

void
gsk_gpu_render_pass_push_clip_rounded (GskGpuRenderPass            *self,
                                       const GskRoundedRect        *clip,
                                       GskGpuRenderPassClipStorage *storage)
{
  graphene_rect_t scissor;

  gsk_gpu_clip_init_copy (&storage->clip, &self->clip);
  storage->modified = GSK_GPU_GLOBAL_CLIP;

  if (!gsk_gpu_clip_intersect_rounded_rect (&self->clip, &storage->clip, &self->offset, clip))
    {
      gsk_gpu_clip_init_copy (&self->clip, &storage->clip);
      GSK_DEBUG (FALLBACK, "push_clip_rounded() needs clip mask");
      gsk_gpu_render_pass_draw_clip_mask (self, NULL, clip, NULL, NULL, storage);
      return;
    }

  if (gsk_gpu_render_pass_device_to_user (self,
                                          &self->scissor,
                                          &scissor))
    {
      GskGpuClip scissored_clip;
      if (gsk_gpu_clip_intersect_rect (&scissored_clip, &self->clip, &self->offset, &scissor))
        gsk_gpu_clip_init_copy (&self->clip, &scissored_clip);
    }

  self->pending_globals |= storage->modified;
}

void
gsk_gpu_render_pass_pop_clip_rounded (GskGpuRenderPass            *self,
                                      GskGpuRenderPassClipStorage *storage)
{
  /* They're identical currently */
  gsk_gpu_render_pass_pop_clip_rect (self, storage);
}

void
gsk_gpu_render_pass_push_clip_mask (GskGpuRenderPass            *self,
                                    const graphene_rect_t       *clip,
                                    GskGpuImage                 *clip_mask,
                                    const graphene_rect_t       *clip_mask_rect,
                                    gboolean                     has_opacity,
                                    GskGpuRenderPassClipStorage *storage)
{
  graphene_rect_t device;

  if (self->clip_mask != NULL ||
      gsk_transform_get_fine_category (self->modelview) <= GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE ||
      !gsk_gpu_render_pass_try_push_clip_rect (self, clip, storage) ||
      !gsk_gpu_render_pass_user_to_device (self, clip_mask_rect, &device))
    {
      GSK_DEBUG (FALLBACK, "push_clip_mask() needs to draw clip mask");
      gsk_gpu_render_pass_draw_clip_mask (self, clip, NULL, clip_mask, clip_mask_rect, storage);
      self->clip_mask_has_opacity |= has_opacity;
      return;
    }

  g_assert (gsk_gpu_image_get_shader_op (clip_mask) == GDK_SHADER_DEFAULT);

  storage->clip_mask = self->clip_mask;
  storage->clip_mask_rect = self->clip_mask_rect;
  storage->clip_mask_has_opacity = self->clip_mask_has_opacity;
  storage->opacity = self->opacity;
  storage->modified |= GSK_GPU_GLOBAL_MASK;

  self->clip_mask = g_object_ref (clip_mask);
  self->clip_mask_rect = device;
  self->clip_mask_has_opacity = has_opacity;

  self->pending_globals |= storage->modified;
}

void
gsk_gpu_render_pass_pop_clip_mask (GskGpuRenderPass            *self,
                                   GskGpuRenderPassClipStorage *storage)
{
  /* They're identical currently */
  gsk_gpu_render_pass_pop_clip_rect (self, storage);
}

Messung V0.5 in Prozent
C=98 H=95 G=96

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet am  2026-07-02) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik