/*
 * Copyright © 2023 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/>.
 *
 * Authors: Matthias Clasen <mclasen@redhat.com>
 */

#include "config.h"

#include "gdkdmabuftexturebuilder.h"

#include "gdkdebugprivate.h"
#include "gdkdisplay.h"
#include "gdkenumtypes.h"
#include "gdkcolorstate.h"
#include "gdkdmabuftextureprivate.h"
#include "gdkdmabuftexturebuilderprivate.h"

#include <cairo-gobject.h>


struct _GdkDmabufTextureBuilder
{
  GObject parent_instance;

  GdkDisplay *display;
  unsigned int width;
  unsigned int height;
  gboolean premultiplied;

  GdkDmabuf dmabuf;

  GdkColorState *color_state;

  GdkTexture *update_texture;
  cairo_region_t *update_region;
};

struct _GdkDmabufTextureBuilderClass
{
  GObjectClass parent_class;
};

/**
 * GdkDmabufTextureBuilder:
 *
 * Constructs [class@Gdk.Texture] objects from DMA buffers.
 *
 * DMA buffers are commonly called **_dma-bufs_**.
 *
 * DMA buffers are a feature of the Linux kernel to enable efficient buffer and
 * memory sharing between hardware such as codecs, GPUs, displays, cameras and the
 * kernel drivers controlling them. For example, a decoder may want its output to
 * be directly shared with the display server for rendering without a copy.
 *
 * Any device driver which participates in DMA buffer sharing, can do so as either
 * the exporter or importer of buffers (or both).
 *
 * The memory that is shared via DMA buffers is usually stored in non-system memory
 * (maybe in device's local memory or something else not directly accessible by the
 * CPU), and accessing this memory from the CPU may have higher-than-usual overhead.
 *
 * In particular for graphics data, it is not uncommon that data consists of multiple
 * separate blocks of memory, for example one block for each of the red, green and
 * blue channels. These blocks are called **_planes_**. DMA buffers can have up to
 * four planes. Even if the memory is a single block, the data can be organized in
 * multiple planes, by specifying offsets from the beginning of the data.
 *
 * DMA buffers are exposed to user-space as file descriptors allowing to pass them
 * between processes. If a DMA buffer has multiple planes, there is one file
 * descriptor per plane.
 *
 * The format of the data (for graphics data, essentially its colorspace) is described
 * by a 32-bit integer. These format identifiers are defined in the header file `drm_fourcc.h`
 * and commonly referred to as **_fourcc_** values, since they are identified by 4 ASCII
 * characters. Additionally, each DMA buffer has a **_modifier_**, which is a 64-bit integer
 * that describes driver-specific details of the memory layout, such as tiling or compression.
 *
 * For historical reasons, some producers of dma-bufs don't provide an explicit modifier, but
 * instead return `DMA_FORMAT_MOD_INVALID` to indicate that their modifier is **_implicit_**.
 * GTK tries to accommodate this situation by accepting `DMA_FORMAT_MOD_INVALID` as modifier.
 *
 * The operation of `GdkDmabufTextureBuilder` is quite simple: Create a texture builder,
 * set all the necessary properties, and then call [method@Gdk.DmabufTextureBuilder.build]
 * to create the new texture.
 *
 * The required properties for a dma-buf texture are
 *
 *  * The width and height in pixels
 *
 *  * The `fourcc` code and `modifier` which identify the format and memory layout of the dma-buf
 *
 *  * The file descriptor, offset and stride for each of the planes
 *
 * `GdkDmabufTextureBuilder` can be used for quick one-shot construction of
 * textures as well as kept around and reused to construct multiple textures.
 *
 * For further information, see
 *
 * * The Linux kernel [documentation](https://docs.kernel.org/driver-api/dma-buf.html)
 *
 * * The header file [drm_fourcc.h](https://gitlab.freedesktop.org/mesa/drm/-/blob/main/include/drm/drm_fourcc.h)
 *
 * Since: 4.14
 */

enum
{
  PROP_0,
  PROP_DISPLAY,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_FOURCC,
  PROP_MODIFIER,
  PROP_PREMULTIPLIED,
  PROP_N_PLANES,
  PROP_COLOR_STATE,
  PROP_UPDATE_REGION,
  PROP_UPDATE_TEXTURE,

  N_PROPS
};

G_DEFINE_TYPE (GdkDmabufTextureBuilder, gdk_dmabuf_texture_builder, G_TYPE_OBJECT)

static GParamSpec *properties[N_PROPS] = { NULL, };

static void
gdk_dmabuf_texture_builder_dispose (GObject *object)
{
  GdkDmabufTextureBuilder *self = GDK_DMABUF_TEXTURE_BUILDER (object);

  g_clear_object (&self->update_texture);
  g_clear_pointer (&self->update_region, cairo_region_destroy);
  g_clear_pointer (&self->color_state, gdk_color_state_unref);

  G_OBJECT_CLASS (gdk_dmabuf_texture_builder_parent_class)->dispose (object);
}

static void
gdk_dmabuf_texture_builder_get_property (GObject    *object,
                                         guint       property_id,
                                         GValue     *value,
                                         GParamSpec *pspec)
{
  GdkDmabufTextureBuilder *self = GDK_DMABUF_TEXTURE_BUILDER (object);

  switch (property_id)
    {
    case PROP_DISPLAY:
      g_value_set_object (value, self->display);
      break;

    case PROP_WIDTH:
      g_value_set_uint (value, self->width);
      break;

    case PROP_HEIGHT:
      g_value_set_uint (value, self->height);
      break;

    case PROP_FOURCC:
      g_value_set_uint (value, self->dmabuf.fourcc);
      break;

    case PROP_MODIFIER:
      g_value_set_uint64 (value, self->dmabuf.modifier);
      break;

    case PROP_PREMULTIPLIED:
      g_value_set_boolean (value, self->premultiplied);
      break;

    case PROP_N_PLANES:
      g_value_set_uint (value, self->dmabuf.n_planes);
      break;

    case PROP_COLOR_STATE:
      g_value_set_boxed (value, self->color_state);
      break;

    case PROP_UPDATE_REGION:
      g_value_set_boxed (value, self->update_region);
      break;

    case PROP_UPDATE_TEXTURE:
      g_value_set_object (value, self->update_texture);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gdk_dmabuf_texture_builder_set_property (GObject      *object,
                                         guint         property_id,
                                         const GValue *value,
                                         GParamSpec   *pspec)
{
  GdkDmabufTextureBuilder *self = GDK_DMABUF_TEXTURE_BUILDER (object);

  switch (property_id)
    {
    case PROP_DISPLAY:
      gdk_dmabuf_texture_builder_set_display (self, g_value_get_object (value));
      break;

    case PROP_WIDTH:
      gdk_dmabuf_texture_builder_set_width (self, g_value_get_uint (value));
      break;

    case PROP_HEIGHT:
      gdk_dmabuf_texture_builder_set_height (self, g_value_get_uint (value));
      break;

    case PROP_FOURCC:
      gdk_dmabuf_texture_builder_set_fourcc (self, g_value_get_uint (value));
      break;

    case PROP_MODIFIER:
      gdk_dmabuf_texture_builder_set_modifier (self, g_value_get_uint64 (value));
      break;

    case PROP_PREMULTIPLIED:
      gdk_dmabuf_texture_builder_set_premultiplied (self, g_value_get_boolean (value));
      break;

    case PROP_N_PLANES:
      gdk_dmabuf_texture_builder_set_n_planes (self, g_value_get_uint (value));
      break;

    case PROP_COLOR_STATE:
      gdk_dmabuf_texture_builder_set_color_state (self, g_value_get_boxed (value));
      break;

    case PROP_UPDATE_REGION:
      gdk_dmabuf_texture_builder_set_update_region (self, g_value_get_boxed (value));
      break;

    case PROP_UPDATE_TEXTURE:
      gdk_dmabuf_texture_builder_set_update_texture (self, g_value_get_object (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gdk_dmabuf_texture_builder_class_init (GdkDmabufTextureBuilderClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = gdk_dmabuf_texture_builder_dispose;
  gobject_class->get_property = gdk_dmabuf_texture_builder_get_property;
  gobject_class->set_property = gdk_dmabuf_texture_builder_set_property;

  /**
   * GdkDmabufTextureBuilder:display:
   *
   * The display that this texture will be used on.
   *
   * Since: 4.14
   */
  properties[PROP_DISPLAY] =
    g_param_spec_object ("display", NULL, NULL,
                         GDK_TYPE_DISPLAY,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:width:
   *
   * The width of the texture.
   *
   * Since: 4.14
   */
  properties[PROP_WIDTH] =
    g_param_spec_uint ("width", NULL, NULL,
                       0, G_MAXUINT, 0,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:height:
   *
   * The height of the texture.
   *
   * Since: 4.14
   */
  properties[PROP_HEIGHT] =
    g_param_spec_uint ("height", NULL, NULL,
                       0, G_MAXUINT, 0,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:fourcc:
   *
   * The format of the texture, as a fourcc value.
   *
   * Since: 4.14
   */
  properties[PROP_FOURCC] =
    g_param_spec_uint ("fourcc", NULL, NULL,
                       0, 0xffffffff, 0,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:modifier:
   *
   * The modifier.
   *
   * Since: 4.14
   */
  properties[PROP_MODIFIER] =
    g_param_spec_uint64 ("modifier", NULL, NULL,
                         0, G_MAXUINT, 0,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:premultiplied:
   *
   * Whether the alpha channel is premultiplied into the others.
   *
   * Only relevant if the format has alpha.
   *
   * Since: 4.14
   */
  properties[PROP_PREMULTIPLIED] =
    g_param_spec_boolean ("premultiplied", NULL, NULL,
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:n-planes:
   *
   * The number of planes of the texture.
   *
   * Note that you can set properties for other planes,
   * but they will be ignored when constructing the texture.
   *
   * Since: 4.14
   */
  properties[PROP_N_PLANES] =
    g_param_spec_uint ("n-planes", NULL, NULL,
                       1, GDK_DMABUF_MAX_PLANES, 1,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:color-state:
   *
   * The color state of the texture.
   *
   * Since: 4.16
   */
  properties[PROP_COLOR_STATE] =
    g_param_spec_boxed ("color-state", NULL, NULL,
                        GDK_TYPE_COLOR_STATE,
                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:update-region:
   *
   * The update region for [property@Gdk.DmabufTextureBuilder:update-texture].
   *
   * Since: 4.14
   */
  properties[PROP_UPDATE_REGION] =
    g_param_spec_boxed ("update-region", NULL, NULL,
                        CAIRO_GOBJECT_TYPE_REGION,
                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * GdkDmabufTextureBuilder:update-texture:
   *
   * The texture [property@Gdk.DmabufTextureBuilder:update-region] is an update for.
   *
   * Since: 4.14
   */
  properties[PROP_UPDATE_TEXTURE] =
    g_param_spec_object ("update-texture", NULL, NULL,
                         GDK_TYPE_TEXTURE,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (gobject_class, N_PROPS, properties);
}

static void
gdk_dmabuf_texture_builder_init (GdkDmabufTextureBuilder *self)
{
  self->premultiplied = TRUE;
  self->display = gdk_display_get_default ();
  self->dmabuf.n_planes = 1;

  for (int i = 0; i < GDK_DMABUF_MAX_PLANES; i++)
    self->dmabuf.planes[i].fd = -1;

  self->color_state = NULL;
}

/**
 * gdk_dmabuf_texture_builder_new: (constructor):
 *
 * Creates a new texture builder.
 *
 * Returns: the new `GdkTextureBuilder`
 *
 * Since: 4.14
 **/
GdkDmabufTextureBuilder *
gdk_dmabuf_texture_builder_new (void)
{
  return g_object_new (GDK_TYPE_DMABUF_TEXTURE_BUILDER, NULL);
}

/**
 * gdk_dmabuf_texture_builder_get_display:
 * @self: a `GdkDmabufTextureBuilder
 *
 * Returns the display that this texture builder is
 * associated with.
 *
 * Returns: (transfer none): the display
 *
 * Since: 4.14
 */
GdkDisplay *
gdk_dmabuf_texture_builder_get_display (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), NULL);

  return self->display;
}

/**
 * gdk_dmabuf_texture_builder_set_display:
 * @self: a `GdkDmabufTextureBuilder
 * @display: the display
 *
 * Sets the display that this texture builder is
 * associated with.
 *
 * The display is used to determine the supported
 * dma-buf formats.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_display (GdkDmabufTextureBuilder *self,
                                        GdkDisplay              *display)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (GDK_IS_DISPLAY (display));

  if (g_set_object (&self->display, display))
    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DISPLAY]);
}

/**
 * gdk_dmabuf_texture_builder_get_width:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the width previously set via gdk_dmabuf_texture_builder_set_width() or
 * 0 if the width wasn't set.
 *
 * Returns: The width
 *
 * Since: 4.14
 */
unsigned int
gdk_dmabuf_texture_builder_get_width (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);

  return self->width;
}

/**
 * gdk_dmabuf_texture_builder_set_width:
 * @self: a `GdkDmabufTextureBuilder`
 * @width: The texture's width or 0 to unset
 *
 * Sets the width of the texture.
 *
 * The width must be set before calling [method@Gdk.DmabufTextureBuilder.build].
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_width (GdkDmabufTextureBuilder *self,
                                      unsigned int             width)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->width == width)
    return;

  self->width = width;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDTH]);
}

/**
 * gdk_dmabuf_texture_builder_get_height:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the height previously set via gdk_dmabuf_texture_builder_set_height() or
 * 0 if the height wasn't set.
 *
 * Returns: The height
 *
 * Since: 4.14
 */
unsigned int
gdk_dmabuf_texture_builder_get_height (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);

  return self->height;
}

/**
 * gdk_dmabuf_texture_builder_set_height:
 * @self: a `GdkDmabufTextureBuilder`
 * @height: the texture's height or 0 to unset
 *
 * Sets the height of the texture.
 *
 * The height must be set before calling [method@Gdk.DmabufTextureBuilder.build].
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_height (GdkDmabufTextureBuilder *self,
                                       unsigned int             height)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->height == height)
    return;

  self->height = height;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEIGHT]);
}

/**
 * gdk_dmabuf_texture_builder_get_fourcc:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the format previously set via gdk_dmabuf_texture_builder_set_fourcc()
 * or 0 if the format wasn't set.
 *
 * The format is specified as a fourcc code.
 *
 * Returns: The format
 *
 * Since: 4.14
 */
guint32
gdk_dmabuf_texture_builder_get_fourcc (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);

  return self->dmabuf.fourcc;
}

/**
 * gdk_dmabuf_texture_builder_set_fourcc:
 * @self: a `GdkDmabufTextureBuilder`
 * @fourcc: the texture's format or 0 to unset
 *
 * Sets the format of the texture.
 *
 * The format is specified as a fourcc code.
 *
 * The format must be set before calling [method@Gdk.DmabufTextureBuilder.build].
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_fourcc (GdkDmabufTextureBuilder *self,
                                       guint32                  fourcc)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->dmabuf.fourcc == fourcc)
    return;

  self->dmabuf.fourcc = fourcc;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FOURCC]);
}

/**
 * gdk_dmabuf_texture_builder_get_modifier:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the modifier value.
 *
 * Returns: the modifier
 *
 * Since: 4.14
 */
guint64
gdk_dmabuf_texture_builder_get_modifier (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);

  return self->dmabuf.modifier;
}

/**
 * gdk_dmabuf_texture_builder_set_modifier:
 * @self: a `GdkDmabufTextureBuilder`
 * @modifier: the modifier value
 *
 * Sets the modifier.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_modifier (GdkDmabufTextureBuilder *self,
                                         guint64                  modifier)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->dmabuf.modifier == modifier)
    return;

  self->dmabuf.modifier = modifier;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODIFIER]);
}

/**
 * gdk_dmabuf_texture_builder_get_n_planes:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the number of planes.
 *
 * Returns: The number of planes
 *
 * Since: 4.14
 */
unsigned int
gdk_dmabuf_texture_builder_get_n_planes (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);

  return self->dmabuf.n_planes;
}

/**
 * gdk_dmabuf_texture_builder_get_premultiplied:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Whether the data is premultiplied.
 *
 * Returns: whether the data is premultiplied
 *
 * Since: 4.14
 */
gboolean
gdk_dmabuf_texture_builder_get_premultiplied (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), TRUE);

  return self->premultiplied;
}

/**
 * gdk_dmabuf_texture_builder_set_premultiplied:
 * @self: a `GdkDmabufTextureBuilder`
 * @premultiplied: whether the data is premultiplied
 *
 * Sets whether the data is premultiplied.
 *
 * Unless otherwise specified, all formats including alpha channels are assumed
 * to be premultiplied.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_premultiplied (GdkDmabufTextureBuilder *self,
                                              gboolean                 premultiplied)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->premultiplied == premultiplied)
    return;

  self->premultiplied = premultiplied;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREMULTIPLIED]);
}

/**
 * gdk_dmabuf_texture_builder_set_n_planes:
 * @self: a `GdkDmabufTextureBuilder`
 * @n_planes: the number of planes
 *
 * Sets the number of planes of the texture.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_n_planes (GdkDmabufTextureBuilder *self,
                                         unsigned int             n_planes)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (n_planes > 0 && n_planes <= GDK_DMABUF_MAX_PLANES);

  if (self->dmabuf.n_planes == n_planes)
    return;

  self->dmabuf.n_planes = n_planes;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_PLANES]);
}

/**
 * gdk_dmabuf_texture_builder_get_fd:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to get the fd for
 *
 * Gets the file descriptor for a plane.
 *
 * Returns: the file descriptor
 *
 * Since: 4.14
 */
int
gdk_dmabuf_texture_builder_get_fd (GdkDmabufTextureBuilder *self,
                                   unsigned int             plane)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);
  g_return_val_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES, 0);

  return self->dmabuf.planes[plane].fd;
}

/**
 * gdk_dmabuf_texture_builder_set_fd:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to set the fd for
 * @fd: the file descriptor
 *
 * Sets the file descriptor for a plane.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_fd (GdkDmabufTextureBuilder *self,
                                   unsigned int             plane,
                                   int                      fd)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES);

  if (self->dmabuf.planes[plane].fd == fd)
    return;

  self->dmabuf.planes[plane].fd = fd;
}

/**
 * gdk_dmabuf_texture_builder_get_stride:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to get the stride for
 *
 * Gets the stride value for a plane.
 *
 * Returns: the stride
 *
 * Since: 4.14
 */
unsigned int
gdk_dmabuf_texture_builder_get_stride (GdkDmabufTextureBuilder *self,
                                       unsigned int             plane)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);
  g_return_val_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES, 0);

  return self->dmabuf.planes[plane].stride;
}

/**
 * gdk_dmabuf_texture_builder_set_stride:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to set the stride for
 * @stride: the stride value
 *
 * Sets the stride for a plane.
 *
 * The stride must be set for all planes before calling [method@Gdk.DmabufTextureBuilder.build].
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_stride (GdkDmabufTextureBuilder *self,
                                       unsigned int             plane,
                                       unsigned int             stride)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES);

  if (self->dmabuf.planes[plane].stride == stride)
    return;

  self->dmabuf.planes[plane].stride = stride;
}

/**
 * gdk_dmabuf_texture_builder_get_offset:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to get the offset for
 *
 * Gets the offset value for a plane.
 *
 * Returns: the offset
 *
 * Since: 4.14
 */
unsigned int
gdk_dmabuf_texture_builder_get_offset (GdkDmabufTextureBuilder *self,
                                       unsigned int             plane)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), 0);
  g_return_val_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES, 0);

  return self->dmabuf.planes[plane].offset;
}

/**
 * gdk_dmabuf_texture_builder_set_offset:
 * @self: a `GdkDmabufTextureBuilder`
 * @plane: the plane to set the offset for
 * @offset: the offset value
 *
 * Sets the offset for a plane.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_offset (GdkDmabufTextureBuilder *self,
                                       unsigned int             plane,
                                       unsigned int             offset)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (0 <= plane && plane < GDK_DMABUF_MAX_PLANES);

  if (self->dmabuf.planes[plane].offset == offset)
    return;

  self->dmabuf.planes[plane].offset = offset;
}

/**
 * gdk_dmabuf_texture_builder_get_color_state:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the color state previously set via gdk_dmabuf_texture_builder_set_color_state().
 *
 * Returns: (nullable) (transfer none): the color state
 *
 * Since: 4.16
 */
GdkColorState *
gdk_dmabuf_texture_builder_get_color_state (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), NULL);

  return self->color_state;
}

/**
 * gdk_dmabuf_texture_builder_set_color_state:
 * @self: a `GdkDmabufTextureBuilder`
 * @color_state: (nullable): a `GdkColorState` or `NULL` to unset the colorstate.
 *
 * Sets the color state for the texture.
 *
 * By default, the colorstate is `NULL`. In that case, GTK will choose the
 * correct colorstate based on the format.
 * If you don't know what colorstates are, this is probably the right thing.
 *
 * Since: 4.16
 */
void
gdk_dmabuf_texture_builder_set_color_state (GdkDmabufTextureBuilder *self,
                                            GdkColorState           *color_state)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->color_state == color_state ||
      (self->color_state != NULL && color_state != NULL && gdk_color_state_equal (self->color_state, color_state)))
    return;

  g_clear_pointer (&self->color_state, gdk_color_state_unref);
  self->color_state = color_state;
  if (color_state)
    gdk_color_state_ref (color_state);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR_STATE]);
}

/**
 * gdk_dmabuf_texture_builder_get_update_texture:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the texture previously set via gdk_dmabuf_texture_builder_set_update_texture() or
 * %NULL if none was set.
 *
 * Returns: (transfer none) (nullable): The texture
 *
 * Since: 4.14
 */
GdkTexture *
gdk_dmabuf_texture_builder_get_update_texture (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), NULL);

  return self->update_texture;
}

/**
 * gdk_dmabuf_texture_builder_set_update_texture:
 * @self: a `GdkDmabufTextureBuilder`
 * @texture: (nullable): the texture to update
 *
 * Sets the texture to be updated by this texture. See
 * [method@Gdk.DmabufTextureBuilder.set_update_region] for an explanation.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_update_texture (GdkDmabufTextureBuilder *self,
                                               GdkTexture          *texture)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));
  g_return_if_fail (texture == NULL || GDK_IS_TEXTURE (texture));

  if (!g_set_object (&self->update_texture, texture))
    return;

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPDATE_TEXTURE]);
}

/**
 * gdk_dmabuf_texture_builder_get_update_region:
 * @self: a `GdkDmabufTextureBuilder`
 *
 * Gets the region previously set via gdk_dmabuf_texture_builder_set_update_region() or
 * %NULL if none was set.
 *
 * Returns: (transfer none) (nullable): The region
 *
 * Since: 4.14
 */
cairo_region_t *
gdk_dmabuf_texture_builder_get_update_region (GdkDmabufTextureBuilder *self)
{
  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), NULL);

  return self->update_region;
}

/**
 * gdk_dmabuf_texture_builder_set_update_region:
 * @self: a `GdkDmabufTextureBuilder`
 * @region: (nullable): the region to update
 *
 * Sets the region to be updated by this texture. Together with
 * [property@Gdk.DmabufTextureBuilder:update-texture] this describes an
 * update of a previous texture.
 *
 * When rendering animations of large textures, it is possible that
 * consecutive textures are only updating contents in parts of the texture.
 * It is then possible to describe this update via these two properties,
 * so that GTK can avoid rerendering parts that did not change.
 *
 * An example would be a screen recording where only the mouse pointer moves.
 *
 * Since: 4.14
 */
void
gdk_dmabuf_texture_builder_set_update_region (GdkDmabufTextureBuilder *self,
                                              cairo_region_t      *region)
{
  g_return_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self));

  if (self->update_region == region)
    return;

  g_clear_pointer (&self->update_region, cairo_region_destroy);

  if (region)
    self->update_region = cairo_region_reference (region);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPDATE_REGION]);
}

/**
 * gdk_dmabuf_texture_builder_build:
 * @self: a `GdkDmabufTextureBuilder`
 * @destroy: (nullable): destroy function to be called when the texture is
 *   released
 * @data: user data to pass to the destroy function
 * @error: Return location for an error
 *
 * Builds a new `GdkTexture` with the values set up in the builder.
 *
 * It is a programming error to call this function if any mandatory property has not been set.
 *
 * Not all formats defined in the `drm_fourcc.h` header are supported. You can use
 * [method@Gdk.Display.get_dmabuf_formats] to get a list of supported formats. If the
 * format is not supported by GTK, %NULL will be returned and @error will be set.
 *
 * The `destroy` function gets called when the returned texture gets released.
 *
 * It is the responsibility of the caller to keep the file descriptors for the planes
 * open until the created texture is no longer used, and close them afterwards (possibly
 * using the @destroy notify).
 *
 * It is possible to call this function multiple times to create multiple textures,
 * possibly with changing properties in between.
 *
 * Returns: (transfer full) (nullable): a newly built `GdkTexture` or `NULL`
 *   if the format is not supported
 *
 * Since: 4.14
 */
GdkTexture *
gdk_dmabuf_texture_builder_build (GdkDmabufTextureBuilder *self,
                                  GDestroyNotify           destroy,
                                  gpointer                 data,
                                  GError                 **error)
{
  unsigned i;

  g_return_val_if_fail (GDK_IS_DMABUF_TEXTURE_BUILDER (self), NULL);
  g_return_val_if_fail (destroy == NULL || data != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
  g_return_val_if_fail (self->width > 0, NULL);
  g_return_val_if_fail (self->height > 0, NULL);
  g_return_val_if_fail (self->dmabuf.fourcc != 0, NULL);

  for (i = 0; i < self->dmabuf.n_planes; i++)
    g_return_val_if_fail (self->dmabuf.planes[i].fd != -1, NULL);

  if (!gdk_has_feature (GDK_FEATURE_DMABUF))
    {
      g_set_error_literal (error,
                           GDK_DMABUF_ERROR, GDK_DMABUF_ERROR_NOT_AVAILABLE,
                           "dmabuf support disabled via GDK_DISABLE environment variable");
      return NULL;
    }

  return gdk_dmabuf_texture_new_from_builder (self, destroy, data, error);
}

const GdkDmabuf *
gdk_dmabuf_texture_builder_get_dmabuf (GdkDmabufTextureBuilder *self)
{
  return &self->dmabuf;
}

void
gdk_dmabuf_texture_builder_set_dmabuf (GdkDmabufTextureBuilder *self,
                                       const GdkDmabuf         *dmabuf)
{
  gdk_dmabuf_texture_builder_set_fourcc (self, dmabuf->fourcc);
  gdk_dmabuf_texture_builder_set_modifier (self, dmabuf->modifier);
  gdk_dmabuf_texture_builder_set_n_planes (self, dmabuf->n_planes);

  for (unsigned int i = 0; i < dmabuf->n_planes; i++)
    {
      gdk_dmabuf_texture_builder_set_fd (self, i, dmabuf->planes[i].fd);
      gdk_dmabuf_texture_builder_set_stride (self, i, dmabuf->planes[i].stride);
      gdk_dmabuf_texture_builder_set_offset (self, i, dmabuf->planes[i].offset);
    }
}
