/* GSK - The GTK Scene Kit
 *
 * Copyright 2016  Endless
 *
 * 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/>.
 */

/**
 * GskRenderNode: (ref-func gsk_render_node_ref) (unref-func gsk_render_node_unref) (set-value-func gsk_value_set_render_node) (get-value-func gsk_value_get_render_node)
 *
 * `GskRenderNode` is the basic block in a scene graph to be
 * rendered using [class@Gsk.Renderer].
 *
 * Each node has a parent, except the top-level node; each node may have
 * children nodes.
 *
 * Each node has an associated drawing surface, which has the size of
 * the rectangle set when creating it.
 *
 * Render nodes are meant to be transient; once they have been associated
 * to a [class@Gsk.Renderer] it's safe to release any reference you have on
 * them. All [class@Gsk.RenderNode]s are immutable, you can only specify their
 * properties during construction.
 */

#include "config.h"

#include "gskrendernodeprivate.h"

#include "gskdebugprivate.h"
#include "gskrendererprivate.h"
#include "gskrendernodeparserprivate.h"

#include "gdk/gdkcairoprivate.h"
#include "gdk/gdkcolorstateprivate.h"

#include <graphene-gobject.h>

#include <math.h>

#include <gobject/gvaluecollector.h>

/**
 * gsk_serialization_error_quark:
 *
 * Registers an error quark for [class@Gsk.RenderNode] errors.
 *
 * Returns: the error quark
 **/
G_DEFINE_QUARK (gsk-serialization-error-quark, gsk_serialization_error)

#define GSK_RENDER_NODE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_RENDER_NODE, GskRenderNodeClass))


static void
value_render_node_init (GValue *value)
{
  value->data[0].v_pointer = NULL;
}

static void
value_render_node_free_value (GValue *value)
{
  if (value->data[0].v_pointer != NULL)
    gsk_render_node_unref (value->data[0].v_pointer);
}

static void
value_render_node_copy_value (const GValue *src,
                              GValue       *dst)
{
  if (src->data[0].v_pointer != NULL)
    dst->data[0].v_pointer = gsk_render_node_ref (src->data[0].v_pointer);
  else
    dst->data[0].v_pointer = NULL;
}

static gpointer
value_render_node_peek_pointer (const GValue *value)
{
  return value->data[0].v_pointer;
}

static char *
value_render_node_collect_value (GValue      *value,
                                 guint        n_collect_values,
                                 GTypeCValue *collect_values,
                                 guint        collect_flags)
{
  GskRenderNode *node = collect_values[0].v_pointer;

  if (node == NULL)
    {
      value->data[0].v_pointer = NULL;
      return NULL;
    }

  if (node->parent_instance.g_class == NULL)
    return g_strconcat ("invalid unclassed GskRenderNode pointer for "
                        "value type '",
                        G_VALUE_TYPE_NAME (value),
                        "'",
                        NULL);

  value->data[0].v_pointer = gsk_render_node_ref (node);

  return NULL;
}

static char *
value_render_node_lcopy_value (const GValue *value,
                               guint         n_collect_values,
                               GTypeCValue  *collect_values,
                               guint         collect_flags)
{
  GskRenderNode **node_p = collect_values[0].v_pointer;

  if (node_p == NULL)
    return g_strconcat ("value location for '",
                        G_VALUE_TYPE_NAME (value),
                        "' passed as NULL",
                        NULL);

  if (value->data[0].v_pointer == NULL)
    *node_p = NULL;
  else if (collect_flags & G_VALUE_NOCOPY_CONTENTS)
    *node_p = value->data[0].v_pointer;
  else
    *node_p = gsk_render_node_ref (value->data[0].v_pointer);

  return NULL;
}

static void
gsk_render_node_finalize (GskRenderNode *self)
{
  g_type_free_instance ((GTypeInstance *) self);
}

static gboolean
gsk_render_node_real_can_diff (const GskRenderNode *node1,
                               const GskRenderNode *node2)
{
  return TRUE;
}

static void
gsk_render_node_real_diff (GskRenderNode  *node1,
                           GskRenderNode  *node2,
                           GskDiffData    *data)
{
  gsk_render_node_diff_impossible (node1, node2, data);
}

static gboolean
gsk_render_node_real_get_opaque_rect (GskRenderNode   *node,
                                      graphene_rect_t *out_opaque)
{
  return FALSE;
}

static void
gsk_render_node_class_init (GskRenderNodeClass *klass)
{
  klass->node_type = GSK_NOT_A_RENDER_NODE;
  klass->finalize = gsk_render_node_finalize;
  klass->can_diff = gsk_render_node_real_can_diff;
  klass->diff = gsk_render_node_real_diff;
  klass->get_opaque_rect = gsk_render_node_real_get_opaque_rect;
}

static void
gsk_render_node_init (GskRenderNode *self)
{
  g_atomic_ref_count_init (&self->ref_count);
  self->preferred_depth = GDK_N_DEPTHS; /* illegal value */
}

GType
gsk_render_node_get_type (void)
{
  static gsize render_node_type__volatile;

  if (g_once_init_enter (&render_node_type__volatile))
    {
      static const GTypeFundamentalInfo finfo = {
        (G_TYPE_FLAG_CLASSED |
         G_TYPE_FLAG_INSTANTIATABLE |
         G_TYPE_FLAG_DERIVABLE |
         G_TYPE_FLAG_DEEP_DERIVABLE),
      };

      static const GTypeValueTable value_table = {
        value_render_node_init,
        value_render_node_free_value,
        value_render_node_copy_value,
        value_render_node_peek_pointer,
        "p",
        value_render_node_collect_value,
        "p",
        value_render_node_lcopy_value,
      };

      const GTypeInfo node_info = {
        /* Class */
        sizeof (GskRenderNodeClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) gsk_render_node_class_init,
        (GClassFinalizeFunc) NULL,
        NULL,

        /* Instance */
        sizeof (GskRenderNode),
        0,
        (GInstanceInitFunc) gsk_render_node_init,

        /* GValue */
        &value_table,
      };

      GType render_node_type =
        g_type_register_fundamental (g_type_fundamental_next (),
                                     g_intern_static_string ("GskRenderNode"),
                                     &node_info, &finfo,
                                     G_TYPE_FLAG_ABSTRACT);

      g_once_init_leave (&render_node_type__volatile, render_node_type);
    }

  return render_node_type__volatile;
}

/*< private >
 * gsk_render_node_type_register_static:
 * @node_name: the name of the node
 * @node_info: type information of the node
 *
 * Registers a new `GskRenderNode` type for the given @node_name using
 * the type information in @node_info.
 *
 * Returns: the newly registered GType
 */
GType
gsk_render_node_type_register_static (const char     *node_name,
                                      gsize           instance_size,
                                      GClassInitFunc  class_init)
{
  GTypeInfo info;

  info.class_size = sizeof (GskRenderNodeClass);
  info.base_init = NULL;
  info.base_finalize = NULL;
  info.class_init = class_init;
  info.class_finalize = NULL;
  info.instance_size = instance_size;
  info.n_preallocs = 0;
  info.instance_init = NULL;
  info.value_table = NULL;

  return g_type_register_static (GSK_TYPE_RENDER_NODE, node_name, &info, 0);
}

/*< private >
 * gsk_render_node_alloc:
 * @node_type: the `GskRenderNode`Type to instantiate
 *
 * Instantiates a new `GskRenderNode` for the given @node_type.
 *
 * Returns: (transfer full) (type GskRenderNode): the newly created `GskRenderNode`
 */
gpointer
gsk_render_node_alloc (GskRenderNodeType node_type)
{
  g_return_val_if_fail (node_type > GSK_NOT_A_RENDER_NODE, NULL);
  g_return_val_if_fail (node_type < GSK_RENDER_NODE_TYPE_N_TYPES, NULL);

  g_assert (gsk_render_node_types[node_type] != G_TYPE_INVALID);
  return g_type_create_instance (gsk_render_node_types[node_type]);
}

/**
 * gsk_render_node_ref:
 * @node: a `GskRenderNode`
 *
 * Acquires a reference on the given `GskRenderNode`.
 *
 * Returns: (transfer full): the `GskRenderNode` with an additional reference
 */
GskRenderNode *
(gsk_render_node_ref) (GskRenderNode *node)
{
  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), NULL);

  return _gsk_render_node_ref (node);
}

void
_gsk_render_node_unref (GskRenderNode *node)
{
  if G_UNLIKELY (g_atomic_ref_count_dec (&node->ref_count))
    GSK_RENDER_NODE_GET_CLASS (node)->finalize (node);
}


/**
 * gsk_render_node_unref:
 * @node: (transfer full): a `GskRenderNode`
 *
 * Releases a reference on the given `GskRenderNode`.
 *
 * If the reference was the last, the resources associated to the @node are
 * freed.
 */
void
(gsk_render_node_unref) (GskRenderNode *node)
{
  g_return_if_fail (GSK_IS_RENDER_NODE (node));

  _gsk_render_node_unref (node);
}


/**
 * gsk_render_node_get_node_type:
 * @node: a `GskRenderNode`
 *
 * Returns the type of the @node.
 *
 * Returns: the type of the `GskRenderNode`
 */
GskRenderNodeType
gsk_render_node_get_node_type (const GskRenderNode *node)
{
  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), GSK_NOT_A_RENDER_NODE);

  return GSK_RENDER_NODE_GET_CLASS (node)->node_type;
}

G_GNUC_PURE static inline
GskRenderNodeType
_gsk_render_node_get_node_type (const GskRenderNode *node)
{
  return GSK_RENDER_NODE_GET_CLASS (node)->node_type;
}

/**
 * gsk_render_node_get_bounds:
 * @node: a `GskRenderNode`
 * @bounds: (out caller-allocates): return location for the boundaries
 *
 * Retrieves the boundaries of the @node.
 *
 * The node will not draw outside of its boundaries.
 */
void
gsk_render_node_get_bounds (GskRenderNode   *node,
                            graphene_rect_t *bounds)
{
  g_return_if_fail (GSK_IS_RENDER_NODE (node));
  g_return_if_fail (bounds != NULL);

  graphene_rect_init_from_rect (bounds, &node->bounds);
}

void
gsk_render_node_draw_ccs (GskRenderNode *node,
                          cairo_t       *cr,
                          GdkColorState *ccs)
{
  /* Check that the calling function did pass a correct color state */
  g_assert (ccs == gdk_color_state_get_rendering_color_state (ccs));

  cairo_save (cr);

  GSK_RENDER_NODE_GET_CLASS (node)->draw (node, cr, ccs);

  if (GSK_DEBUG_CHECK (GEOMETRY))
    {
      cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
      cairo_rectangle (cr, node->bounds.origin.x - 1, node->bounds.origin.y - 1,
                       node->bounds.size.width + 2, node->bounds.size.height + 2);
      cairo_set_line_width (cr, 2);
      cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
      cairo_stroke (cr);
    }

  cairo_restore (cr);

  if (cairo_status (cr))
    {
      g_warning ("drawing failure for render node %s: %s",
                 g_type_name_from_instance ((GTypeInstance *) node),
                 cairo_status_to_string (cairo_status (cr)));
    }
}

void
gsk_render_node_draw_with_color_state (GskRenderNode *node,
                                       cairo_t       *cr,
                                       GdkColorState *color_state)
{
  GdkColorState *ccs;

  ccs = gdk_color_state_get_rendering_color_state (color_state);

  if (gdk_color_state_equal (color_state, ccs))
    {
      gsk_render_node_draw_ccs (node, cr, ccs);
    }
  else
    {
      cairo_save (cr);
      gdk_cairo_rect (cr, &node->bounds);
      cairo_clip (cr);
      cairo_push_group (cr);

      gsk_render_node_draw_ccs (node, cr, ccs);
      gdk_cairo_surface_convert_color_state (cairo_get_group_target (cr),
                                             ccs,
                                             color_state);
      cairo_pop_group_to_source (cr);
      cairo_paint (cr);
      cairo_restore (cr);
    }
}

/**
 * gsk_render_node_draw:
 * @node: a `GskRenderNode`
 * @cr: cairo context to draw to
 *
 * Draw the contents of @node to the given cairo context.
 *
 * Typically, you'll use this function to implement fallback rendering
 * of `GskRenderNode`s on an intermediate Cairo context, instead of using
 * the drawing context associated to a [class@Gdk.Surface]'s rendering buffer.
 *
 * For advanced nodes that cannot be supported using Cairo, in particular
 * for nodes doing 3D operations, this function may fail.
 **/
void
gsk_render_node_draw (GskRenderNode *node,
                      cairo_t       *cr)
{
  g_return_if_fail (GSK_IS_RENDER_NODE (node));
  g_return_if_fail (cr != NULL);
  g_return_if_fail (cairo_status (cr) == CAIRO_STATUS_SUCCESS);

  gsk_render_node_draw_with_color_state (node, cr, GDK_COLOR_STATE_SRGB);
}

/*
 * gsk_render_node_draw_fallback:
 * @node: a `GskRenderNode`
 * @cr: cairo context to draw to
 *
 * Like gsk_render_node_draw(), but will overlay an error pattern if
 * GSK_DEBUG=cairo is enabled.
 *
 * This has 2 purposes:
 * 1. It allows detecting fallbacks in GPU renderers.
 * 2. Application code can use it to detect where it is using Cairo
 *    drawing.
 *
 * So use this function whenever either of those cases should be detected.
 */
void
gsk_render_node_draw_fallback (GskRenderNode *node,
                               cairo_t       *cr)
{
  gsk_render_node_draw (node, cr);

  if (GSK_DEBUG_CHECK (CAIRO))
    {
      /* pink, black
       * black, pink
       */
      static const guint32 fallback_pixels[] = { 0xFFFF00CC, 0xFF000000,
                                                 0xFF000000, 0xFFFF00CC };
      static const guint32 cairo_pixels[] = { 0xFF9900FF, 0xFF000000,
                                              0xFF000000, 0xFF9900FF };
      const guint32 *pixels;
      cairo_surface_t *surface;

      cairo_save (cr);
      if (GSK_RENDER_NODE_TYPE (node) == GSK_CAIRO_NODE)
        pixels = cairo_pixels;
      else
        pixels = fallback_pixels;
      surface = cairo_image_surface_create_for_data ((guchar *) pixels,
                                                     CAIRO_FORMAT_ARGB32,
                                                     2, 2,
                                                     2 * 4);
      cairo_scale (cr, 10, 10);
      cairo_set_source_surface (cr, surface, 0, 0);
      cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_NEAREST);
      cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
      cairo_paint_with_alpha (cr, 0.6);
      cairo_surface_destroy (surface);
      cairo_restore (cr);
    }
}

/*
 * gsk_render_node_can_diff:
 * @node1: a `GskRenderNode`
 * @node2: the `GskRenderNode` to compare with
 *
 * Checks if two render nodes can be expected to be compared via
 * gsk_render_node_diff().
 *
 * The node diffing algorithm uses this function to match up similar
 * nodes to compare when trying to minimize the resulting region.
 *
 * Nodes of different type always return %FALSE here.
 *
 * Returns: %TRUE if @node1 and @node2 can be expected to be compared
 **/
gboolean
gsk_render_node_can_diff (const GskRenderNode *node1,
                          const GskRenderNode *node2)
{
  if (node1 == node2)
    return TRUE;

  if (_gsk_render_node_get_node_type (node1) == _gsk_render_node_get_node_type (node2))
    return GSK_RENDER_NODE_GET_CLASS (node1)->can_diff (node1, node2);

  if (_gsk_render_node_get_node_type (node1) == GSK_CONTAINER_NODE ||
      _gsk_render_node_get_node_type (node2) == GSK_CONTAINER_NODE)
    return TRUE;

  return FALSE;
}

static void
rectangle_init_from_graphene (cairo_rectangle_int_t *cairo,
                              const graphene_rect_t *graphene)
{
  cairo->x = floorf (graphene->origin.x);
  cairo->y = floorf (graphene->origin.y);
  cairo->width = ceilf (graphene->origin.x + graphene->size.width) - cairo->x;
  cairo->height = ceilf (graphene->origin.y + graphene->size.height) - cairo->y;
}

void
gsk_render_node_diff_impossible (GskRenderNode  *node1,
                                 GskRenderNode  *node2,
                                 GskDiffData    *data)
{
  cairo_rectangle_int_t rect;

  rectangle_init_from_graphene (&rect, &node1->bounds);
  cairo_region_union_rectangle (data->region, &rect);
  rectangle_init_from_graphene (&rect, &node2->bounds);
  cairo_region_union_rectangle (data->region, &rect);
}

/**
 * gsk_render_node_diff:
 * @node1: a `GskRenderNode`
 * @node2: the `GskRenderNode` to compare with
 * @data: the diff data to use
 *
 * Compares @node1 and @node2 trying to compute the minimal region of changes.
 *
 * In the worst case, this is the union of the bounds of @node1 and @node2.
 *
 * This function is used to compute the area that needs to be redrawn when
 * the previous contents were drawn by @node1 and the new contents should
 * correspond to @node2. As such, it is important that this comparison is
 * faster than the time it takes to actually do the redraw.
 *
 * Note that the passed in @region may already contain previous results from
 * previous node comparisons, so this function call will only add to it.
 */
void
gsk_render_node_diff (GskRenderNode  *node1,
                      GskRenderNode  *node2,
                      GskDiffData    *data)
{
  if (node1 == node2)
    return;

  if (_gsk_render_node_get_node_type (node1) == _gsk_render_node_get_node_type (node2))
    {
      GSK_RENDER_NODE_GET_CLASS (node1)->diff (node1, node2, data);
    }
  else if (_gsk_render_node_get_node_type (node1) == GSK_CONTAINER_NODE)
    {
      gsk_container_node_diff_with (node1, node2, data);
    }
  else if (_gsk_render_node_get_node_type (node2) == GSK_CONTAINER_NODE)
    {
      gsk_container_node_diff_with (node2, node1, data);
    }
  else
    {
      gsk_render_node_diff_impossible (node1, node2, data);
    }
}

/**
 * gsk_render_node_get_opaque_rect:
 * @self: a `GskRenderNode`
 * @out_opaque: (out): return location for the opaque rect
 *
 * Gets an opaque rectangle inside the node that GTK can determine to
 * be fully opaque.
 *
 * There is no guarantee that this is indeed the largest opaque rectangle or
 * that regions outside the rectangle are not opaque. This function is a best
 * effort with that goal.
 *
 * The rectangle will be fully contained in the bounds of the node.
 *
 * Returns: %TRUE if part or all of the rendernode is opaque, %FALSE if no
 *   opaque region could be found.
 *
 * Since: 4.16
 **/
gboolean
gsk_render_node_get_opaque_rect (GskRenderNode   *self,
                                 graphene_rect_t *out_opaque)
{
  g_return_val_if_fail (GSK_IS_RENDER_NODE (self), FALSE);
  g_return_val_if_fail (out_opaque != NULL, FALSE);

  if (self->fully_opaque)
    {
      *out_opaque = self->bounds;
      return TRUE;
    }

  return GSK_RENDER_NODE_GET_CLASS (self)->get_opaque_rect (self, out_opaque);
}

/**
 * gsk_render_node_write_to_file:
 * @node: a `GskRenderNode`
 * @filename: (type filename): the file to save it to.
 * @error: Return location for a potential error
 *
 * This function is equivalent to calling [method@Gsk.RenderNode.serialize]
 * followed by [func@GLib.file_set_contents].
 *
 * See those two functions for details on the arguments.
 *
 * It is mostly intended for use inside a debugger to quickly dump a render
 * node to a file for later inspection.
 *
 * Returns: %TRUE if saving was successful
 **/
gboolean
gsk_render_node_write_to_file (GskRenderNode *node,
                               const char    *filename,
                               GError       **error)
{
  GBytes *bytes;
  gboolean result;

  g_return_val_if_fail (GSK_IS_RENDER_NODE (node), FALSE);
  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  bytes = gsk_render_node_serialize (node);
  result = g_file_set_contents (filename,
                                g_bytes_get_data (bytes, NULL),
                                g_bytes_get_size (bytes),
                                error);
  g_bytes_unref (bytes);

  return result;
}

/**
 * gsk_render_node_deserialize:
 * @bytes: the bytes containing the data
 * @error_func: (nullable) (scope call) (closure user_data): Callback on parsing errors
 * @user_data: user_data for @error_func
 *
 * Loads data previously created via [method@Gsk.RenderNode.serialize].
 *
 * For a discussion of the supported format, see that function.
 *
 * Returns: (nullable) (transfer full): a new `GskRenderNode`
 */
GskRenderNode *
gsk_render_node_deserialize (GBytes            *bytes,
                             GskParseErrorFunc  error_func,
                             gpointer           user_data)
{
  GskRenderNode *node = NULL;

  node = gsk_render_node_deserialize_from_bytes (bytes, error_func, user_data);

  return node;
}

/**
 * gsk_value_set_render_node:
 * @value: a [struct@GObject.Value] initialized with type `GSK_TYPE_RENDER_NODE`
 * @node: a `GskRenderNode`
 *
 * Stores the given `GskRenderNode` inside `value`.
 *
 * The [struct@GObject.Value] will acquire a reference to the `node`.
 *
 * Since: 4.6
 */
void
gsk_value_set_render_node (GValue        *value,
                           GskRenderNode *node)
{
  GskRenderNode *old_node;

  g_return_if_fail (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE));

  old_node = value->data[0].v_pointer;

  if (node != NULL)
    {
      g_return_if_fail (GSK_IS_RENDER_NODE (node));

      value->data[0].v_pointer = gsk_render_node_ref (node);
    }
  else
    {
      value->data[0].v_pointer = NULL;
    }

  if (old_node != NULL)
    gsk_render_node_unref (old_node);
}

/**
 * gsk_value_take_render_node:
 * @value: a [struct@GObject.Value] initialized with type `GSK_TYPE_RENDER_NODE`
 * @node: (transfer full) (nullable): a `GskRenderNode`
 *
 * Stores the given `GskRenderNode` inside `value`.
 *
 * This function transfers the ownership of the `node` to the `GValue`.
 *
 * Since: 4.6
 */
void
gsk_value_take_render_node (GValue        *value,
                            GskRenderNode *node)
{
  GskRenderNode *old_node;

  g_return_if_fail (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE));

  old_node = value->data[0].v_pointer;

  if (node != NULL)
    {
      g_return_if_fail (GSK_IS_RENDER_NODE (node));

      value->data[0].v_pointer = node;
    }
  else
    {
      value->data[0].v_pointer = NULL;
    }

  if (old_node != NULL)
    gsk_render_node_unref (old_node);
}

/**
 * gsk_value_get_render_node:
 * @value: a `GValue` initialized with type `GSK_TYPE_RENDER_NODE`
 *
 * Retrieves the `GskRenderNode` stored inside the given `value`.
 *
 * Returns: (transfer none) (nullable): a `GskRenderNode`
 *
 * Since: 4.6
 */
GskRenderNode *
gsk_value_get_render_node (const GValue *value)
{
  g_return_val_if_fail (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE), NULL);

  return value->data[0].v_pointer;
}

/**
 * gsk_value_dup_render_node:
 * @value: a [struct@GObject.Value] initialized with type `GSK_TYPE_RENDER_NODE`
 *
 * Retrieves the `GskRenderNode` stored inside the given `value`, and acquires
 * a reference to it.
 *
 * Returns: (transfer full) (nullable): a `GskRenderNode`
 *
 * Since: 4.6
 */
GskRenderNode *
gsk_value_dup_render_node (const GValue *value)
{
  g_return_val_if_fail (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE), NULL);

  if (value->data[0].v_pointer == NULL)
    return NULL;

  return gsk_render_node_ref (value->data[0].v_pointer);
}

GdkMemoryDepth
gsk_render_node_get_preferred_depth (const GskRenderNode *node)
{
  return node->preferred_depth;
}

gboolean
gsk_render_node_is_hdr (const GskRenderNode *node)
{
  return node->is_hdr;
}

/* Whether we need an offscreen to handle opacity correctly for this node.
 * We don't if there is only one drawing node inside (could be child
 * node, or grandchild, or...).
 *
 * For containers with multiple children, we can avoid the offscreen if
 * the children are known not to overlap.
 */
gboolean
gsk_render_node_use_offscreen_for_opacity (const GskRenderNode *node)
{
  return node->offscreen_for_opacity;
}
