using System.Buffers;
using System.Collections;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System;
using System.Reflection;
using System.Xml.Linq;

/*! \mainpage Afterwarp Framework
 *
 * Afterwarp Framework &reg; is a cross-platform framework for development of scientific, industrial and
 * interactive real-time applications. The framework includes both low-level API with full hardware
 * abstraction, allowing development of custom graphics engine using programmable pipeline and shaders,
 * including compute shaders for GPGPU work, and a high-level framework that can be used to quickly develop
 * 3D applications without knowledge of shaders.
 */

namespace Afterwarp
{
  public partial struct API
  {
    /// Exception type raised by Afterwarp classes.
    public class Exception : System.Exception
    {
      public Exception() {}
      public Exception(string message) : base(message) {}
      public Exception(string message, System.Exception innerException) : base(message, innerException) {}
    }
  }

  /// A high-level class that wraps one of Afterwarp's objects.
  public abstract class CustomObject : CriticalFinalizerObject, IDisposable, IEquatable<CustomObject>
  {
    /// Delegate type that matches API functions for destroying object's handle.
    internal delegate void DestroyDelegate(IntPtr handle);

    /// Creates new instance of wrapper object owning the given handle.
    internal CustomObject(DestroyDelegate destroy, IntPtr handle)
    {
      _handle = handle;
      _destroy = destroy;
    }

    /// Releases current object instance.
    ~CustomObject() => Dispose(disposing: false);

    /// Relinquishes control of the wrapped handle.
    internal IntPtr Relinquish()
    {
      IntPtr handle = _handle;
      _handle = IntPtr.Zero;
      return handle;
    }

    /// Releases the object's resources.
    protected virtual void Dispose(bool disposing)
    {
      if (_handle != IntPtr.Zero)
      {
        _destroy?.Invoke(_handle);
        _handle = IntPtr.Zero;
        _destroy = null;
      }
    }

    /// Wrapped object's handle.
    public IntPtr Handle => _handle;

    #region IDisposable

    public void Dispose()
    {
      // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
      Dispose(disposing: true);
      GC.SuppressFinalize(this);
    }

    #endregion
    #region IEquatable

    public bool Equals(CustomObject other)
    {
      return other is not null && _handle == other._handle;
    }

    public override bool Equals(object obj)
    {
      return obj is not null && Equals(obj as CustomObject);
    }

    public override int GetHashCode()
    {
      return _handle.GetHashCode();
    }

    #endregion

    /// Wrapped object's handle.
    protected IntPtr _handle;

    /// Delegate that will destroy object's handle.
    internal DestroyDelegate _destroy;
  }

  /// Device class that handles initialization, rendering, state management and other common tasks.
  public sealed class Device : CustomObject
  {
    /// Device attributes that define its behavior.
    public struct Attribute
    {
      /// Debug-type context that helps to resolve issues in rendering code.
      public const uint Debug = 0x01;

      /// Software-based solution is used for rendering instead of GPU acceleration.
      public const uint Software = 0x02;

      /// Compatibility device that is meant for older technologies.
      public const uint Legacy = 0x04;

      /// Device should only enable extensions determined by their appropriate level
      /// (deviceAttributeExtensions), even if a higher core version is available that inherently supports
      /// higher level extensions.
      public const uint LimitedExtensions = 0x80;
    }

    /// Cumulative memory barrier bit flags.
    struct Barrier
    {
      /// Shader data sourced from buffers after the barrier will reflect data written by shaders prior
      /// to the barrier.
      public const uint ShaderBuffer = 0x00000001;

      /// Texture fetches from shaders, including fetches from typed buffers, after the barrier will reflect
      /// data written by shaders prior to the barrier.
      public const uint TextureFetch = 0x00000002;

      /// Memory accesses using shader image load, store, and atomic built-in functions issued after the
      /// barrier will reflect data written by shaders prior to the barrier. Additionally, image stores and
      /// atomics issued after the barrier will not execute until all memory accesses (e.g., loads, stores,
      /// texture fetches, vertex fetches) initiated prior to the barrier complete.
      public const uint ShaderImageAccess = 0x00000004;

      /// Writes to a texture by calling Update and Copy functions, and reads via Retrieve after the barrier
      /// will reflect data written by shaders prior to the barrier. Additionally, texture writes from these
      /// commands issued after the barrier will not execute until all shader writes initiated prior to the
      /// barrier complete.
      public const uint TextureUpdate = 0x00000008;

      /// Buffer reads or writes bu calling Update and Copy functions after the barrier will reflect data
      /// written by shaders prior to the barrier. Additionally, writes via these commands issued after the
      /// barrier will wait on the completion of any shader writes to the same memory initiated prior
      /// to the barrier.
      public const uint BufferUpdate = 0x00000010;

      /// Reads and writes to drawable textures after the barrier will reflect data written by shaders prior
      /// to the barrier. Additionally, framebuffer writes issued after the barrier will wait on the
      /// completion of all shader writes issued prior to the barrier.
      public const uint Drawables = 0x00000020;

      /// Accesses to atomic counters after the barrier will reflect writes prior to the barrier.
      public const uint AtomicCounter = 0x00000040;

      /// Accesses to structured buffers after the barrier will reflect writes prior to the barrier.
      public const uint Structured = 0x00000080;
    }

    /// Creates a wrapped or uninitialized device.
    internal Device(IntPtr handle) : base(null, handle) { }

    /// Creates a new graphics device of the given technology and attributes.
    public Device(DeviceTechnology technology, uint attributes = 0)
    : base(new DestroyDelegate(API.DeviceDestroy), IntPtr.Zero)
    {
      _handle = API.DeviceCreate(IntPtr.Zero, technology, attributes);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a graphics device");
    }

    /// Releases any cached device resources.
    public void ResetCache() => API.DeviceResetCache(Handle);

    /// Clears rendering surface that is currently active.
    public void Clear(byte layers, FloatColor color, float depth = 1.0f, uint stencil = 0)
    {
      if (!API.DeviceClear(Handle, layers, ref color, depth, stencil))
        throw new API.Exception("Failed clearing rendering surface");
    }

    /// Issues a memory barrier according to the specified bit flags.
    public void MemoryBarrier(uint bits) => API.DeviceMemoryBarrier(Handle, bits);

    /// Returns attributes with which the device has been created.
    public uint Attributes => API.DeviceGetAttributes(Handle);

    /// Returns device behavior attributes.
    public uint Behavior => API.DeviceGetBehavior(Handle);

    /// Returns legacy feature bits that define older hardware features.
    public uint LegacyBits => API.DeviceGetLegacyBits(Handle);

    /// Returns graphics technology type that is currently being used.
    public DeviceTechnology Technology => API.DeviceGetTechnology(Handle);

    /// Returns OS platform the device application is currently running on.
    public DevicePlatform Platform => API.DeviceGetPlatform(Handle);

    /// Returns the version of technology that is currently being used. The values are specified in
    /// hexadecimal format. That is, a value of 0x100 indicates version 1.0, while a value of 0x247 would
    /// indicate version 2.4.7. The value is used in combination with device technology, so if technology
    /// is Direct3D, and this function returns 0xA10, it means that Direct3D 10.1 is being used.
    public uint TechVersion => API.DeviceGetTechVersion(Handle);

    /// Returns the feature level version of technology that is currently being used. The difference between
    /// this and technology version is that the last one indicates type of technology being used (for example,
    /// DirectX 3D), while this one indicates the level of features available (for example, Direct3D 9.0c).
    /// The values here are specified in hexadecimal format. That is, a value of 0x213 would indicate
    /// version 2.1.3.
    public uint TechFeatureVersion => API.DeviceGetTechFeatureVersion(Handle);

    /// Rendering parameters and operation characteristics of the device.
    public RenderingState RenderingState
    {
      get
      {
        API.DeviceGetRenderingState(Handle, out RenderingState state);
        return state;
      }

      set
      {
        if (!API.DeviceSetRenderingState(Handle, ref value))
          throw new API.Exception("Could not update device's rendering state");
      }
    }

    /// Current viewport parameters.
    public Rect Viewport
    {
      get
      {
        API.DeviceGetViewport(Handle, out Rect viewport);
        return viewport;
      }

      set
      {
        if (!API.DeviceSetViewport(Handle, ref value))
          throw new API.Exception("Could not update device's viewport rectangle");
      }
    }

    /// Current scissor parameters.
    public Rect Scissor
    {
      get
      {
        API.DeviceGetScissor(Handle, out Rect scissor);
        return scissor;
      }

      set
      {
        if (!API.DeviceSetScissor(Handle, ref value))
          throw new API.Exception("Could not update device's scissor rectangle");
      }
    }
  }

  /// Swap-chain that enables double-buffered rendering to a particular window.
  public sealed class SwapChain : CustomObject
  {
    /// Creates a wrapped or uninitialized swap-chain.
    internal SwapChain(IntPtr handle) : base(null, handle) { }

    /// Creates a new rendering swap-chain associated with the given device and window handle. Once the
    /// swap-chain has been created for a particular window under OpenGL-based backends, it would not be
    /// possible to remove this association without re-creating the window.
    public SwapChain(Device device, IntPtr windowHandle, Point? size = null,
      PixelFormat format = PixelFormat.Unknown, PixelFormat depthStencil = PixelFormat.Unknown,
      int multisamples = 0, bool vsync = false)
    : base(new DestroyDelegate(API.SwapChainDestroy), IntPtr.Zero)
    {
      Point size1 = size ?? Point.Zero;

      _handle = API.SwapChainCreate(device?.Handle ?? IntPtr.Zero, windowHandle, ref size1, format,
        depthStencil, multisamples, vsync);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a rendering swap chain");
    }

    /// Resizes the swap-chain.
    public void Resize(Point size)
    {
      if (!API.SwapChainResize(Handle, ref size))
        throw new API.Exception("Could not resize rendering swap chain");
    }

    /// Begins rendering on the swap-chain.
    public void Begin()
    {
      if (!API.SwapChainBegin(Handle))
        throw new API.Exception("Could not start rendering to swap chain");
    }

    /// Finishes rendering on the swap-chain and flips the buffers.
    public void End() => API.SwapChainEnd(Handle);

    /// Returns device associated with this swap-chain.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SwapChainGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Returns handle of the associated window.
    public IntPtr WindowHandle => API.SwapChainGetWindowHandle(Handle);

    /// Returns swap-chain width.
    public int Width => API.SwapChainGetWidth(Handle);

    /// Returns swap-chain height.
    public int Height => API.SwapChainGetHeight(Handle);

    /// Returns pixel format of swap-chain rendering surface.
    public PixelFormat Format => API.SwapChainGetFormat(Handle);

    /// Returns pixel format of swap-chain depth/stencil surface.
    public PixelFormat DepthStencil => API.SwapChainGetDepthStencil(Handle);

    /// Returns number of samples of swap-chain surfaces.
    public int Multisamples => API.SwapChainGetMultisamples(Handle);

    /// Returns whether to wait for a vertical retrace.
    public bool VSync => API.SwapChainGetVSync(Handle);
  }

  /// Generic device buffer object that can contain vertices, indices or shader data.
  public sealed class Buffer : CustomObject
  {
    /// Creates a wrapped or uninitialized buffer.
    internal Buffer(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of hardware buffer associated with a particular device with the given parameters
    /// and (optional) initial data.
    public Buffer(Device device, BufferDataType dataType, BufferAccessType accessType, uint size,
      uint pitch = 16u, PixelFormat format = PixelFormat.Unknown, Array initialData = null)
    : base(new DestroyDelegate(API.BufferDestroy), IntPtr.Zero)
    {
      GCHandle? pinnedInitialData = initialData != null && initialData.Length > 0 ?
        GCHandle.Alloc(initialData, GCHandleType.Pinned) : null;
      try
      {
        _handle = API.BufferCreate(device?.Handle ?? IntPtr.Zero, dataType, accessType, size, pitch, format,
          pinnedInitialData?.AddrOfPinnedObject() ?? IntPtr.Zero);
      }
      finally
      {
        pinnedInitialData?.Free();
      }
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a graphics buffer");
    }

    /// Updates the contents of the buffer with a new data at a particular offset. Note that if buffer has
    /// been created with an access type that is non-default, then typically a whole buffer should be
    /// updated, attempting to update a portion of it may be implementation-dependent.
    public void Update(Array data, uint offset = 0u)
    {
      GCHandle pinned = GCHandle.Alloc(data, GCHandleType.Pinned);
      try
      {
        if (!API.BufferUpdate(Handle, pinned.AddrOfPinnedObject(), offset, (uint)data.Length))
          throw new API.Exception("Could not update the contents of a graphics buffer");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Retrieves the contents of the buffer from a particular offset.
    public void Retrieve(Array data, uint offset = 0)
    {
      GCHandle pinned = GCHandle.Alloc(data, GCHandleType.Pinned);
      try
      {
        if (!API.BufferRetrieve(Handle, pinned.AddrOfPinnedObject(), offset, (uint)data.Length))
          throw new API.Exception("Could not retrieve the contents of a graphics buffer");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Issues a memory barrier to make sure that data written by shaders to the buffer is valid before it is
    /// accessed in another dispatch call.
    public void Copy(Buffer source, uint offsetDest = 0, uint offsetSource = 0, uint size = 0)
    {
      if (!API.BufferCopy(Handle, source?.Handle ?? IntPtr.Zero, offsetDest, offsetSource, size))
        throw new API.Exception("Could not copy the contents from another graphics buffer");
    }

    /// Returns device associated with this buffer.
    public Device Device
    {
      get
      {
        IntPtr handle = API.BufferGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Returns platform-specific buffer handle.
    public IntPtr PlatformHandle => API.BufferGetPlatformHandle(Handle);

    /// Returns the type of data that buffer contains.
    public BufferDataType DataType => API.BufferGetDataType(Handle);

    /// Returns the access type of for buffer data.
    public BufferAccessType AccessType => API.BufferGetAccessType(Handle);

    /// Returns buffer size in bytes.
    public uint Size => API.BufferGetSize(Handle);

    /// Returns the number of bytes for offset to move for advancing the pointer from one element to another.
    public uint Pitch => API.BufferGetPitch(Handle);

    /// Returns pixel format that represents the buffer contents.
    public PixelFormat Format => API.BufferGetFormat(Handle);
  }

  /// Shader program that is typically executed on graphics hardware.
  public sealed class Program : CustomObject
  {
    /// Creates a wrapped or uninitialized program.
    internal Program(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of shader program.
    public Program(Device device, VertexElement[] vertexElements, ProgramElement[] programElements,
      ProgramVariable[] programVariables, byte[] shaderVertex, byte[] shaderHull, byte[] shaderDomain,
      byte[] shaderGeometry, byte[] shaderPixel)
    : base(new DestroyDelegate(API.ProgramDestroy), IntPtr.Zero)
    {
      _handle = API.ProgramCreate(device?.Handle ?? IntPtr.Zero, vertexElements,
        vertexElements?.Length ?? 0, programElements, programElements?.Length ?? 0, programVariables,
        programVariables?.Length ?? 0, shaderVertex, shaderVertex?.Length ?? 0, shaderHull,
        shaderHull?.Length ?? 0, shaderDomain, shaderDomain?.Length ?? 0, shaderGeometry,
        shaderGeometry?.Length ?? 0, shaderPixel, shaderPixel?.Length ?? 0);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a shader program");
    }

    /// Updates a portion of a specific variable that corresponds to the given index. This function should
    /// only be used inside Begin() and End() call block.
    public void Update(int variableIndex, Array variableData)
    {
      GCHandle pinned = GCHandle.Alloc(variableData, GCHandleType.Pinned);
      try
      {
        if (!API.ProgramUpdateByIndex(Handle, variableIndex, pinned.AddrOfPinnedObject(),
          (uint)variableData.Length))
          throw new API.Exception(
            $"Could not update the contents of a shader variable by index ({variableIndex})");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Updates a portion of a specific variable identified by its name (case-sensitive). This function
    /// should only be used inside Begin() and End() call block.
    public void Update(string variableName, Array variableData)
    {
      GCHandle pinned = GCHandle.Alloc(variableData, GCHandleType.Pinned);
      try
      {
        if (!API.ProgramUpdateByName(Handle, Encoding.UTF8.GetBytes(variableName + '\0'),
          pinned.AddrOfPinnedObject(), (uint)variableData.Length))
          throw new API.Exception(
            $"Could not update the contents of a shader variable by variable name ({variableName})");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Assigns a generic buffer (or a portion of starting from the given offset in bytes) to a particular
    /// channel. Vertex and index buffers are associated with input stream channels, whereas constant
    /// buffers are associated with shader program channels - each of these being unrelated and dependent on
    /// shader vertex and program element declarations. The current bindings can be considered valid until
    /// End(), ResetBindings() or ResetCache() is called.
    public void Bind(Buffer buffer, uint channel = 0u, uint offset = 0u)
    {
      if (!API.ProgramBind(Handle, buffer?.Handle ?? IntPtr.Zero, channel, offset))
        throw new API.Exception(
          $"Could not bind buffer to a shader program at channel ({channel}), offset ({offset})");
    }

    /// Removes association that was previously made with Bind() between the buffer and the given channel.
    public void Unbind(Buffer buffer, uint channel = 0)
    {
      API.ProgramUnbind(Handle, buffer?.Handle ?? IntPtr.Zero, channel);
    }

    /// Resets any bindings that were previously made as if Unbind() would be called for each of them.
    public void ResetBindings() => API.ProgramResetBindings(Handle);

    /// Resets any cache associated with vertex buffers. In case of OpenGL, this resets internal
    /// Vertex Array Object (VAO) cache.
    public void ResetCache() => API.ProgramResetCache(Handle);

    /// Applies any binding changes to constant and/or vertex buffers, which usually occur when a draw call
    /// is issued, to occur immediately.
    public void Commit()
    {
      if (!API.ProgramCommit(Handle))
        throw new API.Exception("Could not commit changes to a shader program");
    }

    /// Activates the shader program and prepares for rendering.
    public void Begin()
    {
      if (!API.ProgramBegin(Handle))
        throw new API.Exception("Could not begin rendering with a shader program");
    }

    /// Deactivates the shader program, resets previously active input streams and bindings.
    public void End() => API.ProgramEnd(Handle);

    /// Renders primitives with the given number of vertices.
    public void Draw(PrimitiveTopology topology, int vertexCount, int baseVertex = 0)
    {
      if (!API.ProgramDraw(Handle, topology, vertexCount, baseVertex))
        throw new API.Exception($"Failed issuing a draw call with topology ({topology}), " +
          $"vertex count ({vertexCount}) and base vertex ({baseVertex})");
    }

    /// Renders indexed primitives with the given number of indices.
    public void DrawIndexed(PrimitiveTopology topology, int indexCount, int firstIndex = 0,
      int baseVertex = 0)
    {
      if (!API.ProgramDrawIndexed(Handle, topology, indexCount, firstIndex, baseVertex))
        throw new API.Exception($"Failed issuing an indexed draw call with topology ({topology}), " +
          $"index count ({indexCount}), first index ({firstIndex}) and base vertex ({baseVertex})");
    }

    /// Renders multiple number of instances of the given primitives.
    public void DrawInstances(PrimitiveTopology topology, int vertexCount, int instanceCount,
      int baseVertex = 0)
    {
      if (!API.ProgramDrawInstances(Handle, topology, vertexCount, instanceCount, baseVertex))
        throw new API.Exception($"Failed issuing an instanced draw call with topology ({topology}), " +
          $"instance count ({instanceCount}), vertex count ({vertexCount}) and " +
          $"base vertex ({baseVertex})");
    }

    /// Renders multiple number of instances of the given indexed primitives.
    public void DrawInstancesIndexed(PrimitiveTopology topology, int indexCount, int instanceCount,
      int firstIndex = 0, int baseVertex = 0)
    {
      if (!API.ProgramDrawInstancesIndexed(Handle, topology, indexCount, instanceCount, firstIndex,
        baseVertex))
        throw new API.Exception($"Failed issuing an instanced draw call with topology ({topology}), " +
          $"index count ({indexCount}), instance count ({instanceCount}), first index ({firstIndex}) and " +
          $"base vertex ({baseVertex})");
    }

    /// Specifies number of vertices in each patch. This only applies when rendering patch topology.
    public void SetPatchVertices(int patchVertices)
    {
      if (!API.ProgramSetPatchVertices(Handle, patchVertices))
        throw new API.Exception($"Could not set ({patchVertices}) patch vertices to a shader program");
    }

    /// Returns device associated with this program.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ProgramGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }
  }

  /// Compute shader program for general processing on GPU (GPGPU).
  public sealed class ComputeProgram : CustomObject
  {
    /// Creates a wrapped or uninitialized program.
    internal ComputeProgram(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of compute shader program.
    public ComputeProgram(Device device, ProgramElement[] programElements, byte[] shader)
    : base(new DestroyDelegate(API.ComputeProgramDestroy), IntPtr.Zero)
    {
      _handle = API.ComputeProgramCreate(device?.Handle ?? IntPtr.Zero, programElements,
        programElements?.Length ?? 0, shader, shader?.Length ?? 0);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a compute shader program");
    }

    /// Assigns a generic buffer (or a portion of starting from the given offset in bytes) to a particular
    /// channel. The current bindings can be considered valid until End() or ResetBindings() is called.
    public void Bind(Buffer buffer, uint channel = 0u, uint offset = 0u)
    {
      if (!API.ComputeProgramBindBuffer(Handle, buffer?.Handle ?? IntPtr.Zero, channel, offset))
        throw new API.Exception(
          $"Could not bind buffer to a compute shader program at channel ({channel}), offset ({offset})");
    }

    /// Removes association that was previously made with Bind() between the buffer and the given channel.
    public void Unbind(Buffer buffer, uint channel = 0)
    {
      API.ComputeProgramUnbindBuffer(Handle, buffer?.Handle ?? IntPtr.Zero, channel);
    }

    /// Assigns texture to a particular channel.
    public void Bind(Texture texture, ComputeBindTextureFormat bindFormat)
    {
      if (!API.ComputeProgramBindTexture(Handle, texture?.Handle ?? IntPtr.Zero, ref bindFormat))
        throw new API.Exception("Could not bind texture to a compute shader program");
    }

    /// Removes existing texture association with the particular channel.
    public void Unbind(Texture texture, ComputeBindTextureFormat bindFormat)
    {
      API.ComputeProgramUnbindTexture(Handle, texture?.Handle ?? IntPtr.Zero, ref bindFormat);
    }

    /// Resets any bindings that were previously made as if Unbind() would be called for each of them.
    public void ResetBindings() => API.ComputeProgramResetBindings(Handle);

    /// Applies any binding changes to constant buffers, which usually occur when a dispatch call is issued,
    /// to occur immediately.
    public void Commit()
    {
      if (!API.ComputeProgramCommit(Handle))
        throw new API.Exception("Could not commit changes to a compute shader program");
    }

    /// Activates the compute shader program and prepares for rendering.
    public void Begin()
    {
      if (!API.ComputeProgramBegin(Handle))
        throw new API.Exception("Could not begin rendering with a compute shader program");
    }

    /// Deactivates the compute shader program, resets previously active input streams and bindings.
    public void End() => API.ComputeProgramEnd(Handle);

    /// Launches one or more compute shader work groups.
    public void Dispatch(int groupsX, int groupsY, int groupsZ)
    {
      if (!API.ComputeProgramDispatch(Handle, groupsX, groupsY, groupsZ))
        throw new API.Exception("Could not execute a compute shader program with groups " +
          $"({groupsX}, {groupsY}, {groupsZ})");
    }

    /// Returns device associated with this compute program.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ComputeProgramGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }
  }

  /// Sampler object that defines how texture is read by the shaders.
  public sealed class Sampler : CustomObject
  {
    /// Creates a wrapped or uninitialized texture.
    internal Sampler(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of sampler object.
    public Sampler(Device device, SamplerState? samplerState = null)
    : base(new DestroyDelegate(API.SamplerDestroy), IntPtr.Zero)
    {
      SamplerState samplerState1 = samplerState ?? SamplerState.Default;

      _handle = API.SamplerCreate(device?.Handle ?? IntPtr.Zero, ref samplerState1);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a texture sampler object");
    }

    /// Binds sampler object to a particular channel.
    public void Bind(uint channel)
    {
      if (!API.SamplerBind(Handle, channel))
        throw new API.Exception($"Could not bind a texture sampler object to channel ({channel})");
    }

    /// Unbinds sampler object from the particular channel.
    public void Unbind(uint channel) => API.SamplerUnbind(Handle, channel);

    /// Returns device associated with this sampler object.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ComputeProgramGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Current sampler object parameters.
    public SamplerState State
    {
      get
      {
        API.SamplerGetState(Handle, out SamplerState state);
        return state;
      }

      set
      {
        if (!API.SamplerSetState(Handle, ref value))
          throw new API.Exception("Could not update texture sampler object's state");
      }
    }
  }

  /// Surface that contains image in memory for pixel manipulation.
  public sealed class Surface : CustomObject
  {
    /// Creates a wrapped or uninitialized surface.
    internal Surface(IntPtr handle) : base(null, handle) { }

    /// Creates surface with the given width, height and pixel format. If PixelFormat.Unknown is specified,
    /// then default pixel format would be picked that best represents the current platform.
    public Surface(int width, int height, PixelFormat format = PixelFormat.Unknown)
    : base(new DestroyDelegate(API.SurfaceDestroy), IntPtr.Zero)
    {
      _handle = API.SurfaceCreate(width, height, format);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a raster surface");
    }

    /// Creates a new instance of raster surface containing image loaded from external file. The preference
    /// for premultiplied or non-premultiplied alpha-channel is merely a suggestion, which can influence how
    /// the image is loaded (depending on underlying implementation). This function typically supports most
    /// common file formats such as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF.
    public Surface(string fileName, AlphaFormatRequest formatRequest = AlphaFormatRequest.DontCare)
    : base(new DestroyDelegate(API.SurfaceDestroy), IntPtr.Zero)
    {
      _handle = API.SurfaceCreateFromFile(Encoding.UTF8.GetBytes(fileName + '\0'), formatRequest);
      if (_handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a raster surface from external file ({fileName})");
    }

    /// Creates a new instance of raster surface containing image loaded from file that has been preloaded
    /// into memory. The internal file format is determined by "extension" parameter, which should contain
    /// a validfile extension such as ".png" (case-insensitive). The preference for premultiplied or
    /// non-premultiplied alpha-channel is merely a suggestion, which can influence how the image is loaded
    /// (depending on underlying implementation). This function typically supports most common file formats
    /// such as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF.
    public Surface(Array fileContents, string extension,
      AlphaFormatRequest formatRequest = AlphaFormatRequest.DontCare)
    : base(new DestroyDelegate(API.SurfaceDestroy), IntPtr.Zero)
    {
      GCHandle pinned = GCHandle.Alloc(fileContents, GCHandleType.Pinned);
      try
      {
        _handle = API.SurfaceCreateFromFileInMemory(pinned.AddrOfPinnedObject(), (uint)fileContents.Length,
          Encoding.UTF8.GetBytes(extension + '\0'), formatRequest);
        if (_handle == IntPtr.Zero)
          throw new API.Exception("Could not create a raster surface from memory");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Provides unmanaged pointer to a first pixel at the given scanline index (that is, row number).
    public IntPtr GetScanline(int index) => API.SurfaceGetScanline(Handle, index);

    /// Provides unmanaged pointer to the pixel data at the given coordinates.
    public IntPtr GetPixelPtr(int x, int y) => API.SurfaceGetPixelPtr(Handle, x, y);

    /// Redefines surface size to the specified width, height and pixel format, discarding previous contents.
    public void Resize(int width, int height, PixelFormat format = PixelFormat.Unknown)
    {
      if (!API.SurfaceResize(Handle, width, height, format))
        throw new API.Exception("Could not resize a raster surface");
    }

    /// Converts surface from its current format to another one.
    public void ConvertFormat(PixelFormat format)
    {
      if (!API.SurfaceConvertFormat(Handle, format))
        throw new API.Exception("Could not convert format of a raster surface");
    }

    /// Reads a single pixel from the surface.
    public uint GetPixel(int x, int y) => API.SurfaceGetPixel(Handle, x, y);

    /// Writes a single pixel to the surface.
    public void SetPixel(int x, int y, uint color) { API.SurfaceSetPixel(Handle, x, y, color); }

    /// Retrieves pixel from floating-point coordinates, interpolating linearly between four neighbor pixels
    /// as necessary to get an accurate match. This can be used for limitless stretching, to get color values
    /// that lie between individual pixels and slowly change from one pixel to another.
    public uint GetPixelBilinear(float x, float y) => API.SurfaceGetPixelBilinear(Handle, x, y);

    /// Copies a portion of source surface to the current one according to specified source rectangle and
    /// destination position. If source rectangle is empty (or null), then the entire source surface will be
    /// copied. This function does the appropriate clipping and pixel format conversion. It does not change
    /// current surface size or pixel format. Note that "premultiplied alpha" flag of either current or
    /// source surfaces is completely ignored.
    public void Copy(Surface source, Rect? sourceRect = null, Point? destPos = null)
    {
      Rect sourceRect1 = sourceRect ?? default;
      Point destPos1 = destPos ?? default;

      if (!API.SurfaceCopyRect(Handle, source?.Handle ?? IntPtr.Zero, ref sourceRect1, ref destPos1))
        throw new API.Exception("Could not copy a portion of source raster surface");
    }

    /// Copies entire contents from a source surface to the current one. If the current surface has size
    /// and/or pixel format not specified, these will be copied from the source surface as well. If current
    /// surface is not empty, then its pixel format will not be modified - in this case, pixel format
    /// conversion may occur. This function will try to ensure that current surface size matches the source
    /// surface and if if this cannot be achieved, it will fail.
    public void Copy(Surface source)
    {
      if (!API.SurfaceCopyFrom(Handle, source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not make a copy of source surface");
    }

    /// This function works similarly to Copy(), but provides stretching and/or shrinking.
    /// That is, it copies source surface rectangle onto destination rectangle with point filtering.
    /// Clipping and pixel format conversion is done as necessary.
    public void Stretch(Surface source, RectF? destRect = null, RectF? sourceRect = null)
    {
      RectF destRect1 = destRect ?? default;
      RectF sourceRect1 = sourceRect ?? default;

      if (!API.SurfaceStretchRect(Handle, source?.Handle ?? IntPtr.Zero, ref destRect1, ref sourceRect1))
        throw new API.Exception("Could not copy a portion of source raster surface with stretching");
    }

    /// This function works similarly to Copy(), but provides bilinear stretching. That is, it copies source
    /// surface rectangle onto destination rectangle with linear filtering. Clipping and pixel format
    /// conversion is done as necessary. Note that this function is meant for stretching only; shrinking
    /// although will also work, but result in inaccurate results as shrinking requires calculating average
    /// of variable number of pixels depending on shrink ratio.
    public void StretchBilinear(Surface source, RectF? destRect = null, RectF? sourceRect = null)
    {
      RectF destRect1 = destRect ?? default;
      RectF sourceRect1 = sourceRect ?? default;

      if (!API.SurfaceStretchRectBilinear(Handle, source?.Handle ?? IntPtr.Zero, ref destRect1,
        ref sourceRect1))
        throw new API.Exception(
          "Could not copy a portion of source raster surface with bilinear stretching");
    }

    /// Clears the entire surface with zeros.
    public void Clear() => API.SurfaceClear(Handle);

    /// Clears the entire surface with a given color. This does pixel format conversion when appropriate.
    public void Clear(uint color)
    {
      if (!API.SurfaceClearWith(Handle, color))
        throw new API.Exception("Could not clear a raster surface");
    }

    /// Processes surface pixels, setting alpha-channel to either fully translucent or fully opaque.
    public void ResetAlpha(bool opaque = true)
    {
      if (!API.SurfaceResetAlpha(Handle, opaque))
        throw new API.Exception("Could not reset alpha-channel of a raster surface");
    }

    /// Processes the whole surface, premultiplying each pixel's red, green and blue values by the
    /// corresponding alpha-channel value, resulting in image with premultiplied alpha. Note that this is
    /// an irreversible process, during which some color information is lost permanently (smaller alpha
    /// values contribute to bigger information loss). This is generally useful to prepare the image for
    /// generating mipmaps and/or alpha-blending, to get more accurate visual results.
    public void PremultiplyAlpha()
    {
      if (!API.SurfacePremultiplyAlpha(Handle))
        throw new API.Exception("Could not premultiply alpha-channel of a raster surface");
    }

    /// Processes the whole surface, dividing each pixel by its alpha-value, resulting in image with
    /// non-premultiplied alpha. During this process, some color information may be lost due to precision
    /// issues. This can be useful to obtain original pixel information from image that has been previously
    /// premultiplied; however, this does not recover lost information during premultiplication process.
    /// For instance, pixels that had alpha value of zero and were premultiplied, lose all information and
    /// cannot be recovered; pixels with alpha value of 128 (that is, 50% opaque) lose half of their
    /// precision and after "unpremultiply" process will have values multiples of 2.
    public void UnpremultiplyAlpha()
    {
      if (!API.SurfaceUnpremultiplyAlpha(Handle))
        throw new API.Exception("Could not un-premultiply alpha-channel of a raster surface");
    }

    /// Mirrors the visible image on surface horizontally.
    public void Mirror()
    {
      if (!API.SurfaceMirror(Handle))
        throw new API.Exception("Could not mirror a raster surface");
    }

    /// Flips the visible image on surface vertically.
    public void Flip()
    {
      if (!API.SurfaceFlip(Handle))
        throw new API.Exception("Could not flip a raster surface");
    }

    /// Inverts colors in the surface.
    public void Invert()
    {
      if (!API.SurfaceInvert(Handle))
        throw new API.Exception("Could not invert a raster surface");
    }

    /// This function works similarly to Copy(), except that it produces image with half of size, averaging
    /// each four pixels to one. This is specifically useful to generate mipmaps.
    public void ShrinkFrom(Surface source)
    {
      if (!API.SurfaceShrinkFrom(Handle, source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not shrink to half a raster surface");
    }

    /// Saves the contents of the surface to external file. This function typically supports most common
    /// file formats such as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF. Quality
    /// parameter, in case of formats such as JPEG, determines the compression ratio (between 0 and 100).
    public void SaveToFile(string fileName, int quality = 50)
    {
      if (!API.SurfaceSaveToFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), quality))
        throw new API.Exception($"Could not save a raster surface to external file ({fileName})");
    }

    /// Saves the contents of surface to a file in memory and returns the size of resulting file.
    /// This function will save up to the size in bytes of given array, even if it means that not a complete
    /// file will be saved. If "fileContents" is null, then this function will provide an estimate size
    /// required to store the file in memory. This function typically supports most common file formats such
    /// as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF. Quality parameter, in case
    /// of formats such as JPEG, determines the compression ratio (between 0 and 100).
    public uint SaveToFileInMemory(byte[] fileContents, string extension, int quality = 50)
    {
      GCHandle? pinnedFileContents = fileContents is not null && fileContents.Length > 0 ?
        GCHandle.Alloc(fileContents, GCHandleType.Pinned) : null;
      try
      {
        return API.SurfaceSaveToFileInMemory(Handle, pinnedFileContents?.AddrOfPinnedObject() ?? IntPtr.Zero,
          (uint)(fileContents?.Length ?? 0), Encoding.UTF8.GetBytes(extension + '\0'), quality);
      }
      finally
      {
        pinnedFileContents?.Free();
      }
    }

    /// Calculates signed distance field from alpha values of the source.
    public void MakeSignedDistanceField(Surface source, float spread = 1.0f, Point? destPos = null,
      Rect? sourceRect = null)
    {
      Point destPos1 = destPos ?? default;
      Rect sourceRect1 = sourceRect ?? default;

      if (!API.SurfaceMakeSignedDistanceField(Handle, source?.Handle ?? IntPtr.Zero, spread, ref destPos1,
        ref sourceRect1))
        throw new API.Exception("Could not calculate signed distance field for a raster surface");
    }

    /// Returns unmanaged pointer to top/left corner of pixel data contained by this surface, with
    /// horizontal rows arranged linearly from top to bottom.
    public IntPtr Bits => API.SurfaceGetBits(Handle);

    /// Returns number of bytes each horizontal row of pixels occupies. This may differ from an actual
    /// calculated number and may include unused or even protected memory locations, which should be
    /// considered read-only and be skipped.
    public uint Pitch => API.SurfaceGetPitch(Handle);

    /// Returns number of bytes each pixel occupies.
    public int BytesPerPixel => API.SurfaceGetBytesPerPixel(Handle);

    /// Returns surface width in pixels.
    public int Width => API.SurfaceGetWidth(Handle);

    /// Returns surface height in pixels.
    public int Height => API.SurfaceGetHeight(Handle);

    /// Returns surface size in bytes.
    public nuint ByteSize => API.SurfaceGetByteSize(Handle);

    /// Returns pixel format in which pixels are stored.
    public PixelFormat Format => API.SurfaceGetFormat(Handle);

    /// Indicates whether the pixels in this surface have their alpha premultiplied or not.
    /// This is just an informative parameter; to actually convert pixels from one mode to another,
    /// use PremultiplyAlpha() and UnpremultiplyAlpha() methods.
    public bool PremultipliedAlpha
    {
      get => API.SurfaceGetPremultipliedAlpha(Handle);
      set => API.SurfaceSetPremultipliedAlpha(Handle, value);
    }

    /// Checks whether the surface has width or height set to zero, has undefined pixel format, unknown
    /// pitch, unknown bytes per pixel, or access bits set to null.
    /// If this returns false, then the surface can be considered valid.
    public bool Empty => API.SurfaceEmpty(Handle);

    /// Processes the whole surface to determine whether it has meaningful alpha-channel. A surface that
    /// has all its pixels with alpha-channel set to fully translucent or fully opaque (but not mixed) is
    /// considered lacking alpha-channel. On the other hand, a surface that has at least one pixel with
    /// alpha-channel value different than any other pixel, is considered to have alpha-channel. This is
    /// useful to determine whether the surface can be stored in one of pixel formats lacking alpha-channel,
    /// to avoid losing any transparency information.
    public bool HasAlphaChannel => API.SurfaceHasAlphaChannel(Handle);
  }

  /// Texture that contains image data typically stored in GPU memory.
  public sealed class Texture : CustomObject
  {
    /// Attribute that defines special behavior and/or unique characteristics of the texture.
    public struct Attribute
    {
      /// Texture has a full mipmap surface chain attached. MipMapping technique can improve visual quality
      /// when the texture is drawn in different sizes, especially in smaller ones.
      public const uint MipMapping = 0x01u;

      /// Texture contains a special type of surface that can be rendered to. In other words, the texture
      /// can be considered a render target (Direct3D) or render buffer object (OpenGL).
      public const uint Drawable = 0x02u;

      /// Texture is meant for frequent write-only access. Dynamic textures cannot ontain mipmap surface
      /// chain attached and cannot be drawable either.
      public const uint Dynamic = 0x04u;

      /// Texture has its color components premultiplied by alpha-channel. This implies permanent loss of
      /// information as the components are multiplied by alpha value and stored (so, for example, pixels
      /// with alpha value of zero permanently lose all color information), however this can improve visual
      /// quality on mipmaps with translucent pixels. The parameter is merely a hint for rendering system,
      /// it does not change the actual pixels - an action that should be performed separately.
      public const uint PremultipliedAlpha = 0x08u;

      /// Texture will be used for Compute shader access (Direct3D-only, makes texture UAV-capable).
      public const uint Compute = 0x40u;

      /// Texture is of "scratch" type. That is, it uses system memory for storage and, when used in context
      /// of Direct3D and/or OpenGL, it cannot be used directly.
      public const uint Scratch = 0x8000u;
    }

    internal Texture(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of texture.
    public Texture(Device device, TextureParameters parameters)
    : base(new DestroyDelegate(API.TextureDestroy), IntPtr.Zero)
    {
      _handle = API.TextureCreate(device?.Handle ?? IntPtr.Zero, ref parameters);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a graphics texture");
    }

    /// Creates a new instance of texture object loading its contents from an external file. The preference
    /// for premultiplied or non-premultiplied alpha-channel is merely a suggestion, which can influence how the
    /// image is loaded (depending on underlying implementation). This function typically supports most common
    /// file formats such as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF.
    public Texture(Device device, string fileName, PixelFormat format = PixelFormat.Unknown,
      uint attributes = 0)
    : base(new DestroyDelegate(API.TextureDestroy), IntPtr.Zero)
    {
      _handle = API.TextureCreateFromFile(device?.Handle ?? IntPtr.Zero,
        Encoding.UTF8.GetBytes(fileName + '\0'), format, attributes);
      if (_handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a texture from external file ({fileName})");
    }

    /// Creates a new instance of texture object loading its contents from an image file that has been
    /// preloaded into memory. The internal file format is determined by extension parameter, which should
    /// contain a valid file extension such as ".png" (case-insensitive). This function typically supports
    /// most common file formats such as BMP, PNG, JPEG, but may also support other formats like GIF and TIFF.
    public Texture(Device device, Array fileContents, string extension,
      PixelFormat format = PixelFormat.Unknown, uint attributes = 0)
    : base(new DestroyDelegate(API.TextureDestroy), IntPtr.Zero)
    {
      GCHandle pinned = GCHandle.Alloc(fileContents, GCHandleType.Pinned);
      try
      {
        _handle = API.TextureCreateFromFileInMemory(device?.Handle ?? IntPtr.Zero,
          pinned.AddrOfPinnedObject(), (uint)fileContents.Length, Encoding.UTF8.GetBytes(extension + '\0'),
          format, attributes);
        if (_handle == IntPtr.Zero)
          throw new API.Exception("Could not create a graphics texture from file in memory");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Creates a new instance of texture object loading its contents from an existing surface.
    public Texture(Device device, Surface surface, uint attributes = 0)
    : base(new DestroyDelegate(API.TextureDestroy), IntPtr.Zero)
    {
      _handle = API.TextureCreateFromSurface(device?.Handle ?? IntPtr.Zero, surface?.Handle ?? IntPtr.Zero,
        attributes);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a graphics texture from the given surface");
    }

    /// Creates texture containing displacement map for Parallax Mapping technique from an external file
    /// on disk.
    public static Texture CreateParallax(Device device, string fileName,
      PixelFormat format = PixelFormat.Unknown, uint attributes = 0)
    {
      IntPtr handle = API.TextureCreateFromFileParallax(device?.Handle ?? IntPtr.Zero,
        Encoding.UTF8.GetBytes(fileName + '\0'), format, attributes);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a displacement mapping texture from file ({fileName})");

      return new Texture(handle);
    }

    /// Creates texture containing normal map and occlusion map from external files on disk. Both normal and
    /// occlusion map must have equal sizes.
    public static Texture CreateNormalsAndOcclusion(Device device, string fileNameNormalMap,
      string fileNameOcclusionMap, uint attributes = 0)
    {
      IntPtr handle = API.TextureCreateFromFileNormalsAndOcclusion(device?.Handle ?? IntPtr.Zero,
        Encoding.UTF8.GetBytes(fileNameNormalMap + '\0'),
        Encoding.UTF8.GetBytes(fileNameOcclusionMap + '\0'), attributes);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not create a normal map and occlusion map texture from files " +
          $"({fileNameNormalMap}), ({fileNameOcclusionMap})");

      return new Texture(handle);
    }

    /// Copies content from user pointer to texture's owned memory at the given location. The data is
    /// assumed to match texture's pixel format and destination rectangle, if specified, must be within
    /// valid bounds (no clipping is performed).
    public void Update(Array content, uint pitch = 0, int layer = 0, Rect? rect = null, int mipLevel = 0)
    {
      Rect rect1 = rect ?? default;
      GCHandle pinned = GCHandle.Alloc(content, GCHandleType.Pinned);
      try
      {
        if (!API.TextureUpdate(Handle, pinned.AddrOfPinnedObject(), pitch, layer, ref rect1, mipLevel))
          throw new API.Exception("Could not update a portion of texture");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Retrieves content from the given location of texture's owned memory to the user pointer. The data is
    /// copied in its original format and source rectangle, if specified, must be within valid bounds
    /// (no clipping is performed).
    public void Retrieve(Array content, uint pitch = 0, int layer = 0, Rect? rect = null, int mipLevel = 0)
    {
      Rect rect1 = rect ?? default;
      GCHandle pinned = GCHandle.Alloc(content, GCHandleType.Pinned);
      try
      {
        if (!API.TextureRetrieve(Handle, pinned.AddrOfPinnedObject(), pitch, layer, ref rect1, mipLevel))
          throw new API.Exception("Could not retrieve a portion of texture");
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Copies a portion of current texture to the destination surface according to specified source
    /// rectangle and destination position. If source rectangle is empty, then the entire texture surface
    /// will be copied. This function does the appropriate clipping and pixel format conversion. Note that
    /// "premultiplied alpha" attribute of either current or source surface is completely ignored.
    public void Retrieve(Surface surface, int layer = 0, Point? destPos = null, Rect? sourceRect = null,
      int mipLevel = 0)
    {
      Point destPos1 = destPos ?? default;
      Rect sourceRect1 = sourceRect ?? default;

      if (!API.TextureCopyToSurface(Handle, surface?.Handle ?? IntPtr.Zero, layer, ref destPos1,
        ref sourceRect1, mipLevel))
        throw new API.Exception("Could not copy a portion of texture to raster surface");
    }

    /// Copies a portion of source texture to the current one according to specified source rectangle and
    /// destination position. If source rectangle is empty, then the entire source texture will be copied.
    /// This function does the appropriate clipping and pixel format conversion (albeit at significant
    /// performance cost). Note that "premultiplied alpha" attribute of either current or source texture is
    /// completely ignored.
    public void Copy(Texture source, int destLayer = 0, Point? destPos = null, int srcLayer = 0,
      Rect? sourceRect = null, int destMipLevel = 0, int srcMipLevel = 0)
    {
      Point destPos1 = destPos ?? default;
      Rect sourceRect1 = sourceRect ?? default;

      if (!API.TextureCopy(Handle, source?.Handle ?? IntPtr.Zero, destLayer, ref destPos1, srcLayer,
        ref sourceRect1, destMipLevel, srcMipLevel))
        throw new API.Exception("Could not copy a portion of one texture to another");
    }

    /// Copies a portion of source surface to the current texture according to specified source rectangle and
    /// destination position. If source rectangle is empty, then the entire source surface will be copied.
    /// This function does the appropriate clipping and pixel format conversion. Note that \c
    /// premultipliedAlpha attribute of either current or source surface is completely ignored.
    public void Copy(Surface source, int layer = 0, Point? destPos = null, Rect? sourceRect = null,
      int mipLevel = 0)
    {
      Point destPos1 = destPos ?? default;
      Rect sourceRect1 = sourceRect ?? default;

      if (!API.TextureCopyFromSurface(Handle, source?.Handle ?? IntPtr.Zero, layer, ref destPos1,
        ref sourceRect1, mipLevel))
        throw new API.Exception("Could not copy a portion of raster surface to texture");
    }

    /// Loads image from disk and copies it to the specified location and parameters.
    public void LoadFromFile(string fileName, int layer = 0, Point? destPos = null, Rect? sourceRect = null,
      int mipLevel = 0)
    {
      Point destPos1 = destPos ?? default;
      Rect sourceRect1 = sourceRect ?? default;

      if (!API.TextureLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), layer, ref destPos1,
        ref sourceRect1, mipLevel))
        throw new API.Exception("Could not load a portion of texture from external file");
    }

    /// Saves the appropriate portion of texture to external file.
    public void SaveToFile(string fileName, int quality = 50, int layer = 0, Rect? sourceRect = null,
      int mipLevel = 0)
    {
      Rect sourceRect1 = sourceRect ?? default;
      if (!API.TextureSaveToFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), quality, layer,
        ref sourceRect1, mipLevel))
        throw new API.Exception("Could not save a portion of texture to external file");
    }

    /// Clears entire texture surface and fills pixels with zeros.
    public void Clear()
    {
      if (!API.TextureClear(Handle))
        throw new API.Exception("Failed clearing the texture");
    }

    /// Clears texture surface with the given color.
    /// This must be called outside of device and texture Begin() / End() blocks.
    public void Clear(FloatColor color, int layer = 0, int mipLevel = 0)
    {
      if (!API.TextureClearWith(Handle, ref color, layer, mipLevel))
        throw new API.Exception("Failed clearing the texture with a given color");
    }

    /// Binds texture to the specified rendering channel.
    public void Bind(uint channel = 0)
    {
      if (!API.TextureBind(Handle, channel))
        throw new API.Exception($"Failed binding texture to a rendering channel ({channel})");
    }

    /// Unbinds texture from the specified rendering channel.
    public void Unbind(uint channel = 0)
    {
      if (!API.TextureUnbind(Handle, channel))
        throw new API.Exception($"Failed unbinding texture from a rendering channel ({channel})");
    }

    /// Attaches another drawable texture with the current texture into a common MRT rendering stack.
    /// Drawable textures will be available to shaders in the same order of attachment.
    public void Attach(Texture attachment, int layer = 0, int mipLevel = 0)
    {
      if (!API.TextureAttach(Handle, attachment?.Handle ?? IntPtr.Zero, layer, mipLevel))
        throw new API.Exception("Could not attach one texture to another");
    }

    /// Removes all previous drawable texture attachments.
    public void Detach() => API.TextureDetach(Handle);

    /// Starts rendering on the drawable texture.
    public void Begin(int layer = 0, int mipLevel = 0)
    {
      if (!API.TextureBegin(Handle, layer, mipLevel))
        throw new API.Exception("Could not start rendering to the texture");
    }

    /// Finishes rendering on the drawable texture.
    public void End()
    {
      if (!API.TextureEnd(Handle))
        throw new API.Exception("Could not finish rendering to the texture");
    }

    /// Generates texture's mipmaps from its base (level = 0) image.
    public void GenerateMipMaps()
    {
      if (!API.TextureGenerateMipMaps(Handle))
        throw new API.Exception("Could not generate mipmaps for the texture");
    }

    /// Resets any cache associated with the texture, which means purging any temporary resources.
    /// This also removes all existing texture attachments.
    public void ResetCache() => API.TextureResetCache(Handle);

    /// Returns device associated with this texture.
    public Device Device
    {
      get
      {
        IntPtr handle = API.TextureGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Returns platform-specific texture handle.
    public IntPtr PlatformHandle => API.TextureGetPlatformHandle(Handle);

    /// Retrieves current texture parameters.
    public TextureParameters Parameters
    {
      get
      {
        API.TextureGetParameters(Handle, out TextureParameters parameters);
        return parameters;
      }
    }
  }

  /// A container for vertices and indices defining one or more triangular meshes that can be suitable for
  /// rendering with a 2D canvas.
  public sealed class CanvasBuffer : CustomObject
  {
    /// Creates a wrapped or uninitialized canvas buffer.
    internal CanvasBuffer(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of canvas buffer.
    public CanvasBuffer()
    : base(new DestroyDelegate(API.CanvasBufferDestroy), IntPtr.Zero)
    {
      _handle = API.CanvasBufferCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a canvas buffer");
    }

    /// Clears all buffers.
    public void Clear() => API.CanvasBufferClear(Handle);

    /// Clears all buffers and releases their memory.
    public void ClearAndShrink() => API.CanvasBufferClearAndShrink(Handle);

    /// Total number of vertices in the buffer.
    public int VertexCount
    {
      get => API.CanvasBufferGetVertexCount(Handle);
      set
      {
        if (!API.CanvasBufferSetVertexCount(Handle, value))
          throw new API.Exception($"Could not set vertex count of canvas buffer to ({value})");
      }
    }

    /// Total number of indices in the buffer.
    public int IndexCount
    {
      get => API.CanvasBufferGetIndexCount(Handle);
      set
      {
        if (!API.CanvasBufferSetIndexCount(Handle, value))
          throw new API.Exception($"Could not set index count of canvas buffer to ({value})");
      }
    }

    /// Returns or changes existing vertices in the buffer.
    public Vector2[] Vertices
    {
      get
      {
        int count = API.CanvasBufferGetVertexCount(Handle);
        Vector2[] vertices = new Vector2[count];
        long offset = API.CanvasBufferGetVertices(Handle).ToInt64();

        for (int i = 0; i < count; ++i)
        {
          vertices[i] = Marshal.PtrToStructure<Vector2>(new IntPtr(offset));
          offset += Marshal.SizeOf(typeof(Vector2));
        }
        return vertices;
      }

      set
      {
        VertexCount = value.Length;
        long offset = API.CanvasBufferGetVertices(Handle).ToInt64();

        for (int i = 0; i < value.Length; ++i)
        {
          Marshal.StructureToPtr(value[i], new IntPtr(offset), false);
          offset += Marshal.SizeOf(typeof(Vector2));
        }
      }
    }

    /// Returns or changes existing colors in the buffer.
    public uint[] Colors
    {
      get
      {
        int[] values = new int[VertexCount];
        Marshal.Copy(API.CanvasBufferGetColors(Handle), values, 0, values.Length);

        uint[] colors = new uint[values.Length];
        System.Buffer.BlockCopy(values, 0, colors, 0, values.Length * sizeof(uint));

        return colors;
      }

      set
      {
        int[] values = new int[value.Length];
        System.Buffer.BlockCopy(value, 0, values, 0, value.Length * sizeof(uint));

        VertexCount = values.Length;
        Marshal.Copy(values, 0, API.CanvasBufferGetIndices(Handle), value.Length);
      }
    }

    /// Returns or changes existing indices in the buffer.
    public int[] Indices
    {
      get
      {
        int[] indices = new int[IndexCount];
        Marshal.Copy(API.CanvasBufferGetIndices(Handle), indices, 0, indices.Length);
        return indices;
      }

      set
      {
        IndexCount = value.Length;
        Marshal.Copy(value, 0, API.CanvasBufferGetIndices(Handle), value.Length);
      }
    }

    /// Calculates boundaries for the existing vertices.
    public RectF Extents
    {
      get
      {
        API.CanvasBufferGetExtents(Handle, out RectF extents);
        return extents;
      }
    }
  }

  /// A container for vertices and indices defining one or more triangular meshes that can be suitable for
  /// rendering with a 2D canvas.
  public sealed class PathBroker : CustomObject
  {
    /// Creates a wrapped or uninitialized canvas buffer.
    internal PathBroker(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of canvas buffer.
    public PathBroker()
    : base(new DestroyDelegate(API.PathBrokerDestroy), IntPtr.Zero)
    {
      _handle = API.PathBrokerCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a path broker module");
    }

    /// Pre-renders a fill for the given path. "quality" parameter determines how the resulting contours are
    /// triangulated.
    public void Fill(CanvasBuffer buffer, PathElement[] path, ColorRect colors, bool quality = true)
    {
      if (!API.PathBrokerFill(Handle, buffer?.Handle ?? IntPtr.Zero, path, path.Length, ref colors,
        quality))
        throw new API.Exception("Could not pre-render a fill for the given path");
    }

    /// Pre-renders a stroke for the given path.
    public void Stroke(CanvasBuffer buffer, PathElement[] path, float thickness, uint color,
      PathJoint joints = PathJoint.None, LineCaps caps = LineCaps.Butt, float miterLimit = 1.0f,
      float smoothStep = 1.0f)
    {
      if (!API.PathBrokerStroke(Handle, buffer?.Handle ?? IntPtr.Zero, path, path.Length, thickness, color,
        joints, caps, miterLimit, smoothStep))
        throw new API.Exception("Could not pre-render a stroke for the given path");
    }

    /// Resets internal cache and releases any allocated memory.
    public void Reset() => API.PathBrokerReset(Handle);
  }

  /// Canvas interface that provides utility functions for rendering a variety of 2D shapes filled with
  /// solid color gradients and/or textures.
  public sealed class Canvas : CustomObject
  {
    /// Distance between hexagon center and each of its vertices (used for drawing hexagons).
    public const float HexagonDelta = 1.154700538f;

    /// Canvas attribute flags that can be combined together.
    public struct Attribute
    {
      /// Transform 2D coordinates as 3D vectors (with Z = 0) by the appropriate 4x4 matrix as if drawing to
      /// an arbitrary plane in 3D.
      public const uint Projected = 0x01u;

      /// Preprocess the texture by using its alpha-channel as a signed distance field.
      public const uint SDF = 0x02u;

      /// Include outline when rendering signed distance field (implies \c SDF).
      public const uint Outline = 0x04u;

      /// Sample texture using bicubic interpolation for higher-quality results.
      public const uint Cubic = 0x08u;

      /// Adjusts texture color using YCH instead of RGB (only works for textures).
      public const uint ColorAdjust = 0x10u;

      /// Applies 3x2 world transformation matrix to non-holographic primitives.
      public const uint Transform = 0x20u;
    }

    /// Creates a wrapped or uninitialized 2D canvas.
    internal Canvas(IntPtr handle) : base(null, handle) { }

    /// Creates new 2D rendering canvas.
    public Canvas(Device device)
    : base(new DestroyDelegate(API.CanvasDestroy), IntPtr.Zero)
    {
      _handle = API.CanvasCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 2D rendering canvas");
    }

    /// Starts rendering with the canvas.
    public void Begin()
    {
      if (!API.CanvasBegin(Handle))
        throw new API.Exception("Could not start rendering with the canvas");
    }

    /// Finishes rendering with the canvas.
    public void End() => API.CanvasEnd(Handle);

    /// Draws pixels with the given positions and colors.
    public void Pixels(Vector2[] positions, uint[] colors, int elementCount,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasPixels(Handle, positions, colors, elementCount, effect);
    }

    /// Draws lines with the given vertices, colors and indices.
    public void Lines(Vector2[] vertices, uint[] colors, int[] indices, int vertexCount, int primitiveCount,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLines(Handle, vertices, colors, indices, vertexCount, primitiveCount, effect);
    }

    /// Draws triangles with the given vertices, colors and indices.
    public void Triangles(Vector2[] vertices, uint[] colors, int[] indices, int vertexCount,
      int primitiveCount, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTriangles(Handle, vertices, colors, indices, vertexCount, primitiveCount, effect);
    }

    /// Draws triangles with the given vertices, texture coordinates, colors and indices.
    public void TexturedTriangles(Texture texture, Vector2[] vertices, Vector2[] texCoords, uint[] colors,
      int[] indices, int vertexCount, int primitiveCount, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTexturedTriangles(Handle, texture?.Handle ?? IntPtr.Zero, vertices, texCoords, colors,
        indices, vertexCount, primitiveCount, effect);
    }

    /// Draws a pixel with the given position and color.
    public void Pixel(Vector2 position, uint color, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasPixel(Handle, ref position, color, effect);
    }

    /// Draws a line with the given positions and colors.
    public void Line(Vector2 srcPos, Vector2 destPos, ColorPair colors,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLine(Handle, ref srcPos, ref destPos, ref colors, effect);
    }

    /// Draws a line of varying thickness with the given positions and colors.
    public void ThickLine(Vector2 srcPos, Vector2 destPos, ColorPair colors, float thickness,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLine(Handle, ref srcPos, ref destPos, ref colors, thickness, effect);
    }

    /// Draws ellipse with the given origin, radiuses, color and steps using "line" primitive.
    public void LineEllipse(Vector2 origin, Vector2 radius, int steps, uint color,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineEllipse(Handle, ref origin, ref radius, steps, color, effect);
    }

    /// Draws circle with the given origin, radius, color and steps using "line" primitive.
    public void LineCircle(Vector2 origin, float radius, int steps, uint color,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineCircle(Handle, ref origin, radius, steps, color, effect);
    }

    /// Draws ellipse of varying thickness with the given origin, radiuses, color and steps.
    public void ThickLineEllipse(Vector2 origin, Vector2 radius, int steps, uint color, float thickness,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineEllipse(Handle, ref origin, ref radius, steps, color, thickness, effect);
    }

    /// Draws circle of varying thickness with the given origin, radius, color and steps.
    public void ThickLineCircle(Vector2 origin, float radius, int steps, uint color, float thickness,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineCircle(Handle, ref origin, radius, steps, color, thickness, effect);
    }

    /// Draws lines between specified triangle vertices and colors.
    public void LineTriangle(Vector2 vertex1, Vector2 vertex2, Vector2 vertex3, uint color1, uint color2,
      uint color3, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineTriangle(Handle, ref vertex1, ref vertex2, ref vertex3, color1, color2, color3, effect);
    }

    /// Draws line path of varying thickness between specified triangle vertices and colors.
    public void ThickLineTriangle(Vector2 vertex1, Vector2 vertex2, Vector2 vertex3, uint color1,
      uint color2, uint color3, float thickness, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineTriangle(Handle, ref vertex1, ref vertex2, ref vertex3, color1, color2, color3,
        thickness, effect);
    }

    /// Draws a wireframe quadrilateral with the given vertices and colors using "line" primitive.
    public void LineQuad(Quad vertices, ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineQuad(Handle, ref vertices, ref colors, effect);
    }

    /// Draws a wireframe quadrilateral of varying thickness with the given vertices and colors.
    public void ThickLineQuad(Quad vertices, ColorRect colors, float thickness,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineQuad(Handle, ref vertices, ref colors, thickness, effect);
    }

    /// Draws lines between each vertex in a hexagon of unity size centered along axis origin and drawn with
    /// gradient of six colors at the corresponding vertices. The bounding width of hexagon is exactly one,
    /// which simplifies placement of multiple hexagons. The final size, position and rotation of hexagon can
    /// be given using one or a combination of several 3x2 transformation matrices multiplied together.
    /// This method uses line primitive.
    public void LineHexagon(uint color1, uint color2, uint color3, uint color4, uint color5, uint color6,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineHexagon(Handle, color1, color2, color3, color4, color5, color6, effect);
    }

    /// Draws lines between each vertex in a hexagon of unity size centered along axis origin and filled with
    /// four-color gradient interpolated to the corresponding vertices. The bounding width of hexagon is
    /// exactly one, which simplifies placement of multiple hexagons. The final size, position and rotation of
    /// hexagon can be given using one or a combination of several 3x2 transformation matrices multiplied
    /// together. This method uses \c line primitive.
    public void LineHexagon(ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasLineHexagonGrad(Handle, ref colors, effect);
    }

    /// Draws lines of varying thickness between each vertex in a hexagon of unity size centered along axis
    /// origin and drawn with gradient of six colors at the corresponding corners. The final size, position
    /// and rotation of hexagon can be given using one or a combination of several 3x2 transformation matrices
    /// multiplied together.
    public void ThickLineHexagon(uint color1, uint color2, uint color3, uint color4, uint color5,
      uint color6, float thickness, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineHexagon(Handle, color1, color2, color3, color4, color5, color6, thickness, effect);
    }

    /// Draws lines of varying thickness between each vertex in a hexagon of unity size centered along axis
    /// origin and filled with four-color gradient interpolated to the corresponding vertices. The final size,
    /// position and rotation of hexagon can be given using one or a combination of several 3x2 transformation
    /// matrices multiplied together.
    public void ThickLineHexagon(ColorRect colors, float thickness,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasThickLineHexagonGrad(Handle, ref colors, thickness, effect);
    }

    /// Draws triangle filled with color gradient specified by given positions and colors.
    public void Triangle(Vector2 vertex1, Vector2 vertex2, Vector2 vertex3, uint color1, uint color2,
      uint color3, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTriangle(Handle, ref vertex1, ref vertex2, ref vertex3, color1, color2, color3, effect);
    }

    /// Draws a filled quadrilateral with the given vertices and colors.
    public void Quad(Quad vertices, ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasQuad(Handle, ref vertices, ref colors, effect);
    }

    /// Draws a filled rectangle with the given vertices and colors.
    public void FillRect(RectF rect, ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFillRect(Handle, ref rect, ref colors, effect);
    }

    /// Draws a filled rectangle with the given vertices and colors.
    public void FillRect(float left, float top, float width, float height, ColorRect colors,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      RectF rect = new (left, top, width, height);
      API.CanvasFillRect(Handle, ref rect, ref colors, effect);
    }

    /// Draws a rectangle of varying thickness with the given vertices and colors.
    public void FrameRect(RectF rect, ColorRect colors, float thickness = 1.0f,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFrameRect(Handle, ref rect, ref colors, thickness, effect);
    }

    /// Draws a rectangle of varying thickness with the given vertices and colors.
    public void FrameRect(float left, float top, float width, float height, ColorRect colors,
      float thickness = 1.0f, BlendingEffect effect = BlendingEffect.Normal)
    {
      RectF rect = new (left, top, width, height);
      API.CanvasFrameRect(Handle, ref rect, ref colors, thickness, effect);
    }

    /// Draws a filled rectangle with the given gradient and round corners.
    public void FillRoundRect(RectF rect, ColorRect colors, float radius, int steps,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFillRoundRect(Handle, ref rect, ref colors, radius, steps, effect);
    }

    /// Draws a round frame for the given rectangle area with varying thickness and gradient.
    public void FrameRoundRect(RectF rect, ColorRect colors, float radius, float thickness, int steps,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFrameRoundRect(Handle, ref rect, ref colors, radius, thickness, steps, effect);
    }

    /// Draws a filled rectangle with the given gradient and round corners on top.
    public void FillRoundRectTop(RectF rect, ColorRect colors, float radius, int steps,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFillRoundRectTop(Handle, ref rect, ref colors, radius, steps, effect);
    }

    /// Draws a filled rectangle with the given gradient and round corners on bottom.
    public void FillRoundRectBottom(RectF rect, ColorRect colors, float radius, int steps,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFillRoundRectBottom(Handle, ref rect, ref colors, radius, steps, effect);
    }

    /// Draws a filled rectangle with the given gradient and round corners, but top is reverted.
    /// This can be used to render rounded window background behind caption.
    public void FillRoundRectTopInverse(RectF rect, ColorRect colors, float radius, int steps,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasFillRoundRectTopInverse(Handle, ref rect, ref colors, radius, steps, effect);
    }

    /// Draws either a highlight or a shadow around the specified rectangle with the given parameters,
    /// mimicking effect of gaussian highlight.
    public void Highlight(RectF rect, float cornerRadius, float luma, float distance, int steps)
    {
      API.CanvasHighlight(Handle, ref rect, cornerRadius, luma, distance, steps);
    }

    /// Draws hexagon of unity size centered along axis origin and filled with gradient of six colors at the
    /// corresponding vertices. The bounding width of hexagon is exactly one, which simplifies placement of
    /// multiple hexagons. The final size, position and rotation of hexagon can be given using one or a
    /// combination of several 3x2 transformation matrices multiplied together.
    public void Hexagon(uint color1, uint color2, uint color3, uint color4, uint color5, uint color6,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasHexagon(Handle, color1, color2, color3, color4, color5, color6, effect);
    }

    /// Draws hexagon of unity size centered along axis origin and filled with four-color gradient interpolated
    /// to the corresponding vertices. The bounding width of hexagon is exactly one, which simplifies placement
    /// of multiple hexagons. The final size, position and rotation of hexagon can be given using one or a
    /// combination of several 3x2 transformation matrices multiplied together.
    public void Hexagon(ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasHexagonGrad(Handle, ref colors, effect);
    }

    /// Draws a filled arc with the given origin, radiuses, angles, steps and color gradient going away from
    /// the center of the arc.
    public void Arc(Vector2 origin, Vector2 radius, float initAngle, float endAngle, int steps,
      uint colorInside, uint colorOutside, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasArc(Handle, ref origin, ref radius, initAngle, endAngle, steps, colorInside, colorOutside,
        effect);
    }

    /// Draws a filled arc with the given coordinates, radius, angles, steps and color gradient going away
    /// from the center of the arc.
    public void Arc(float x, float y, float radius, float initAngle, float endAngle, int steps,
      uint colorInside, uint colorOutside, BlendingEffect effect = BlendingEffect.Normal)
    {
      Arc(new Vector2(x, y), new Vector2(radius, radius), initAngle, endAngle, steps, colorInside,
        colorOutside, effect);
    }

    /// Draws a filled arc with the given origin, radiuses, angles, steps and colors.
    public void Arc(Vector2 origin, Vector2 radius, float initAngle, float endAngle, int steps,
      ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasArcGrad(Handle, ref origin, ref radius, initAngle, endAngle, steps, ref colors, effect);
    }

    /// Draws a filled arc with the given coordinates, radius, angles, steps and colors.
    public void Arc(float x, float y, float radius, float initAngle, float endAngle, int steps,
      ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      Arc(new Vector2(x, y), new Vector2(radius, radius), initAngle, endAngle, steps, colors, effect);
    }

    /// Draws filled ellipse at given position and radius. The ellipse is subdivided into a number of
    /// triangles specified in steps. The shape of ellipse is filled with four-color gradient.
    public void Ellipse(Vector2 origin, Vector2 radius, int steps, ColorRect colors,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasEllipse(Handle, ref origin, ref radius, steps, ref colors, effect);
    }

    /// Draws filled circle at given position and radius. The circle is subdivided into a number of
    /// triangles specified in steps. The shape of circle is filled with four-color gradient.
    public void Circle(float x, float y, float radius, int steps, ColorRect colors,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      Ellipse(new Vector2(x, y), new Vector2(radius, radius), steps, colors, effect);
    }

    /// Draws a filled ribbon between inner and outer radiuses filled with four-color gradient.
    public void Ribbon(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, int steps,
      ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasRibbonGrad(Handle, ref origin, ref insideRadius, ref outsideRadius, steps, ref colors,
        effect);
    }

    /// Draws a filled ribbon between inner and outer radiuses filled with continuous three-pair gradient.
    public void Ribbon(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, int steps,
      ColorPair color1, ColorPair color2, ColorPair color3, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasRibbonTri(Handle, ref origin, ref insideRadius, ref outsideRadius, steps, ref color1,
        ref color2, ref color3, effect);
    }

    /// Draws a filled ribbon between inner and outer radiuses filled with a single color.
    public void Ribbon(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, int steps, uint color,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasRibbon(Handle, ref origin, ref insideRadius, ref outsideRadius, steps, color, effect);
    }

    /// Draws a filled tape between the given radiuses and angles, filled with four-color gradient.
    public void Tape(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, float initAngle,
      float endAngle, int steps, ColorRect colors, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTapeGrad(Handle, ref origin, ref insideRadius, ref outsideRadius, initAngle, endAngle,
        steps, ref colors, effect);
    }

    /// Draws a filled tape between the given radiuses and angles, filled with continuous three-pair gradient.
    public void Tape(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, float initAngle,
      float endAngle, int steps, ColorPair color1, ColorPair color2, ColorPair color3,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTapeTri(Handle, ref origin, ref insideRadius, ref outsideRadius, initAngle, endAngle, steps,
        ref color1, ref color2, ref color3, effect);
    }

    /// Draws a filled tape between the given radiuses and angles, filled with a single color.
    public void Tape(Vector2 origin, Vector2 insideRadius, Vector2 outsideRadius, float initAngle,
      float endAngle, int steps, uint color, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTape(Handle, ref origin, ref insideRadius, ref outsideRadius, initAngle, endAngle, steps,
        color, effect);
    }

    /// Draws a filled rectangle with a hole inside of it, filled with the given colors.
    public void RectWithHole(RectF rect, Vector2 holeOrigin, Vector2 holeRadius, ColorPair colors,
      int steps, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasRectWithHole(Handle, ref rect, ref holeOrigin, ref holeRadius, ref colors, steps, effect);
    }

    /// Draws triangles from an existing buffer.
    public void Draw(CanvasBuffer buffer, BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasDrawBuffer(Handle, buffer?.Handle ?? IntPtr.Zero, effect);
    }

    /// Draws a textured triangle multiplied with a color gradient specified by given positions, colors and
    /// texture coordinates (in range from 0 to 1).
    public void Triangle(Texture texture, Vector2 vertex1, Vector2 vertex2, Vector2 vertex3,
      Vector2 texCoord1, Vector2 texCoord2, Vector2 texCoord3, uint color1, uint color2, uint color3,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTexturedTriangle(Handle, texture?.Handle ?? IntPtr.Zero, ref vertex1, ref vertex2,
        ref vertex3, ref texCoord1, ref texCoord2, ref texCoord3, color1, color2, color3, effect);
    }

    /// Draws a textured triangle multiplied with a color gradient specified by given positions, colors and
    /// texture coordinates (in pixels).
    public void TriangleRegion(Texture texture, Vector2 vertex1, Vector2 vertex2, Vector2 vertex3,
      Vector2 texCoord1, Vector2 texCoord2, Vector2 texCoord3, uint color1, uint color2, uint color3,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      API.CanvasTexturedTriangleRegion(Handle, texture?.Handle ?? IntPtr.Zero, ref vertex1, ref vertex2,
        ref vertex3, ref texCoord1, ref texCoord2, ref texCoord3, color1, color2, color3, effect);
    }

    /// Draws a textured quadrilateral multiplied with a color gradient specified by the given vertices,
    /// colors and texture coordinates (in range from 0 to 1).
    public void Quad(Texture texture, Quad vertices, ColorRect? colors = null, Quad? texCoords = null,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      ColorRect colors1 = colors ?? ColorRect.White;
      Quad texCoords1 = texCoords ?? Afterwarp.Quad.Unity;
      API.CanvasTexturedQuad(Handle, texture?.Handle ?? IntPtr.Zero, ref vertices, ref texCoords1,
        ref colors1, effect);
    }

    /// Draws a textured quadrilateral multiplied with a color gradient specified by the given vertices,
    /// colors and texture coordinates (in pixels).
    public void QuadRegion(Texture texture, Quad vertices, ColorRect? colors, Quad texCoords,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      ColorRect colors1 = colors ?? ColorRect.White;
      API.CanvasTexturedQuadRegion(Handle, texture?.Handle ?? IntPtr.Zero, ref vertices, ref texCoords,
        ref colors1, effect);
    }

    /// Draws a textured rectangle with round corners and the given gradient and texture coordinates.
    public void RoundRect(Texture texture, RectF rect, Quad texCoords, ColorRect? colors, float radius,
      int steps, BlendingEffect effect = BlendingEffect.Normal)
    {
      ColorRect colors1 = colors ?? ColorRect.White;
      API.CanvasTexturedRoundRect(Handle, texture?.Handle ?? IntPtr.Zero, ref rect, ref texCoords,
        ref colors1, radius, steps, effect);
    }

    /// Draws a textured rectangle with round corners and the given gradient and texture
    /// coordinates (in pixels).
    public void RoundRectRegion(Texture texture, RectF rect, Quad texCoords, ColorRect? colors,
      float radius, int steps, BlendingEffect effect = BlendingEffect.Normal)
    {
      ColorRect colors1 = colors ?? ColorRect.White;
      API.CanvasTexturedRoundRectRegion(Handle, texture?.Handle ?? IntPtr.Zero, ref rect, ref texCoords,
        ref colors1, radius, steps, effect);
    }

    /// Draws a textured quadrilateral from source portion of image atlas, multiplied by a color gradient.
    public void QuadImage(ImageAtlas atlas, Quad vertices, int regionIndex, ColorRect? colors = null,
      RectF? sourceRect = null, uint modifiers = 0, BlendingEffect effect = BlendingEffect.Normal)
    {
      ColorRect colors1 = colors ?? ColorRect.White;
      RectF sourceRect1 = sourceRect ?? default;
      API.CanvasQuadImage(Handle, atlas?.Handle ?? IntPtr.Zero, ref vertices, regionIndex, ref colors1,
        ref sourceRect1, modifiers, effect);
    }

    /// Resets any canvas sampler state that is currently set.
    public void ResetSamplerState() => API.CanvasResetSamplerState(Handle);

    /// Flushes the canvas by rendering primitives that are still in batch buffers.
    public void Flush() => API.CanvasFlush(Handle);

    /// Resets canvas internal resources and releases any non-critical memory buffers.
    public void Reset() => API.CanvasReset(Handle);

    /// Returns device associated with this canvas.
    public Device Device
    {
      get
      {
        IntPtr handle = API.CanvasGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// 3D world transformation matrix (applies only "Projected" rendering mode).
    public Matrix4x4 World
    {
      get
      {
        API.CanvasGetWorld(Handle, out Matrix4x4 world);
        return world;
      }

      set => API.CanvasSetWorld(Handle, ref value);
    }

    /// Combined 3D view/projection transformation matrix (applies only "Projected" rendering mode).
    public Matrix4x4 ViewProjection
    {
      get
      {
        API.CanvasGetViewProjection(Handle, out Matrix4x4 viewProjection);
        return viewProjection;
      }

      set => API.CanvasSetViewProjection(Handle, ref value);
    }

    /// 2D transformation matrix.
    public Matrix3x2 Transform
    {
      get
      {
        API.CanvasGetTransform(Handle, out Matrix3x2 transform);
        return transform;
      }

      set => API.CanvasSetTransform(Handle, ref value);
    }

    /// Signed Distance Field (SDF) parameters. The actual SDF rendering needs to be enabled by specifying
    /// one of the appropriate canvas attributes.
    public SignedDistanceField SignedDistanceField
    {
      get
      {
        API.CanvasGetSignedDistanceField(Handle, out SignedDistanceField signedDistanceField);
        return signedDistanceField;
      }

      set { API.CanvasSetSignedDistanceField(Handle, ref value); }
    }

    /// Device rendering state pre-configured to one of high-level canvas states.
    public CanvasContextState ContextState
    {
      get => API.CanvasGetContextState(Handle);
      set
      {
        if (!API.CanvasSetContextState(Handle, value))
          throw new API.Exception("Could not update canvas's context state");
      }
    }

    /// Canvas texture sampling parameters.
    public CanvasSamplerState SamplerState
    {
      get
      {
        if (!API.CanvasGetSamplerState(Handle, out CanvasSamplerState samplerState))
          throw new API.Exception("Could not retrieve canvas's sampler state");

        return samplerState;
      }

      set
      {
        if (!API.CanvasSetSamplerState(Handle, ref value))
          throw new API.Exception("Could not update canvas's sampler state");
      }
    }

    /// Canvas attributes that define rendering characteristics and features.
    public uint Attributes
    {
      get => API.CanvasGetAttributes(Handle);
      set => API.CanvasSetAttributes(Handle, value);
    }

    /// Number of batches that were rendered between current set of begin/end block. Drawing each individual
    /// batch involves some overhead, so when this parameter happens to be considerably high at some point,
    /// the rendering code should be revised for better grouping of images, shapes and blending types.
    public int BatchCount => API.CanvasGetBatchCount(Handle);

    /// Clipping rectangle when performing 2D rendering.
    public Rect ClipRect
    {
      get
      {
        API.CanvasGetClipRect(Handle, out Rect rect);
        return rect;
      }

      set
      {
        if (!API.CanvasSetClipRect(Handle, ref value))
          throw new API.Exception("Could not update canvas's clipping rectangle");
      }
    }
  }

  /// Image atlas, sub-images of which can be rendered with canvas.
  public sealed class ImageAtlas : CustomObject
  {
    /// Creates a wrapped or uninitialized image atlas.
    internal ImageAtlas(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of image atlas associated with a particular device.
    public ImageAtlas(Device device)
    : base(new DestroyDelegate(API.ImageAtlasDestroy), IntPtr.Zero)
    {
      _handle = API.ImageAtlasCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create an image atlas");
    }

    /// Returns texture associated with a particular index or an empty object if such is not available.
    public Texture Texture(int textureIndex) => new (API.ImageAtlasTexture(Handle, textureIndex));

    /// Returns region information for the given index.
    public ImageRegion Region(int regionIndex)
    {
      if (!API.ImageAtlasRegion(Handle, regionIndex, out ImageRegion region))
        throw new API.Exception($"Could not retrieve a region ({regionIndex}) from image atlas");

      return region;
    }

    /// Creates and adds a new region to the list.
    public int CreateRegion(Rect rect, int textureIndex)
    {
      return API.ImageAtlasCreateRegion(Handle, ref rect, textureIndex);
    }

    /// Removes region with given index from the list.
    public void RemoveRegion(int regionIndex) => API.ImageAtlasRemoveRegion(Handle, regionIndex);

    /// Removes all regions.
    public void ClearRegions() => API.ImageAtlasClearRegions(Handle);

    /// Creates list of regions based on repeated rectangular pattern dimensions.
    public void MakeRegions(Point patternSize, Point visibleSize, int patternCount)
    {
      API.ImageAtlasMakeRegions(Handle, ref patternSize, ref visibleSize, patternCount);
    }

    /// Removes all existing textures.
    public void ClearTextures() => API.ImageAtlasClearTextures(Handle);

    /// Removes texture with the given index.
    public void RemoveTexture(int textureIndex) => API.ImageAtlasRemoveTexture(Handle, textureIndex);

    /// Attempts to create and initialize a texture with the given parameters, which is then added to .
    /// the list. In case of failure, -1 is returned.
    public int CreateTexture(Point size, PixelFormat format = PixelFormat.Unknown, uint attributes = 0)
    {
      return API.ImageAtlasCreateTexture(Handle, ref size, format, attributes);
    }

    /// Finds a suitable location to accomodate the given rectangle in existing available space, adds
    /// the appropriate region to the list and returns its index. If no space is available, -1 is returned.
    public int PackRegion(Point size, int padding = 0)
    {
      return API.ImageAtlasPackRegion(Handle, ref size, padding);
    }

    /// Finds a suitable location to accomodate the given surface in existing available space, adds the
    /// appropriate region to the list and copies the contents of surface to the appropriate texture,
    /// returning the final region index. If no space is available or copying fails, -1 is returned.
    public int PackSurface(Surface surface, Rect? sourceRect = null, int padding = 0)
    {
      Rect sourceRect1 = sourceRect ?? default;
      return API.ImageAtlasPackSurface(Handle, surface?.Handle ?? IntPtr.Zero, ref sourceRect1, padding);
    }

    /// Returns device associated with this image atlas.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ImageAtlasGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Number of textures contained within the atlas.
    public int TextureCount => API.ImageAtlasTextureCount(Handle);

    /// Number of regions contained within the atlas.
    public int RegionCount => API.ImageAtlasRegionCount(Handle);
  }

  /// Text renderer utility that can draw text on the canvas.
  public sealed class TextRenderer : CustomObject
  {
    /// Creates a wrapped or uninitialized text renderer.
    internal TextRenderer(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of text renderer associated with a particular canvas.
    public TextRenderer(Canvas canvas, Point textureSize, PixelFormat pixelFormat = PixelFormat.Unknown,
      bool mipMapping = false)
    : base(new DestroyDelegate(API.TextRendererDestroy), IntPtr.Zero)
    {
      _handle = API.TextRendererCreate(canvas?.Handle ?? IntPtr.Zero, ref textureSize, pixelFormat,
        mipMapping);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a text renderer module");
    }

    /// Returns the logical dimensions that the given text will occupy when rendered.
    public Vector2 Extent(string text, TextRenderModifiers? modifiers = null)
    {
      Vector2 dimensions = default;

      InvokeTextRenderModificable(modifiers, delegate(IntPtr modifiers1)
      {
        API.TextRendererExtent(Handle, Encoding.UTF8.GetBytes(text + '\0'), out dimensions, modifiers1);
      });

      return dimensions;
    }

    /// Calculates the actual rectangle in pixels that the given text will occupy when rendered.
    public RectF ExtentByPixels(string text, TextRenderModifiers? modifiers = null)
    {
      RectF rect = default;

      InvokeTextRenderModificable(modifiers, delegate(IntPtr modifiers1)
      {
        API.TextRendererExtentByPixels(Handle, Encoding.UTF8.GetBytes(text + '\0'), out rect, modifiers1);
      });

      return rect;
    }

    /// Provides information regarding individual character position and sizes for the given text string
    /// when rendered. This can be useful for components such as text edit box, for highlighting and
    /// selecting different characters. The actual width and height of the text is, optionally, provided
    /// as well. Returns an actual rectangle dimensions that would encompass all rectangles.
    public Vector2 TextRects(string text, out TextEntryRect[] rects, TextRenderModifiers? modifiers = null)
    {
      Vector2 extent = default;
      TextEntryRect[] rects1 = Array.Empty<TextEntryRect>();

      InvokeTextRenderModificable(modifiers, delegate(IntPtr modifiers1)
      {
        byte[] text1 = Encoding.UTF8.GetBytes(text + '\0');

        int count = API.TextRendererRects(Handle, out extent, null, text1, modifiers1);
        rects1 = new TextEntryRect[count];

        _ = API.TextRendererRects(Handle, out extent, rects1, text1, modifiers1);
      });

      rects = rects1;
      return extent;
    }

    /// Draws text at the given position.
    public void Draw(Vector2 position, string text, ColorPair colors, float alpha = 1.0f,
      TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate(IntPtr modifiers1)
      {
        API.TextRendererDraw(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'), ref colors, alpha,
          modifiers1);
      });
    }

    /// Draws text at the given position with specific alignment.
    public void DrawAligned(Vector2 position, string text, ColorPair colors,
      TextAlignment horizAlign = TextAlignment.Middle, TextAlignment vertAlign = TextAlignment.Middle,
      float alpha = 1.0f, bool alignToPixels = true, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextRendererDrawAligned(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'), ref colors,
          horizAlign, vertAlign, alpha, alignToPixels, modifiers1);
      });
    }

    /// Draws text centered around the specified position.
    public void DrawCentered(Vector2 position, string text, ColorPair colors, float alpha = 1.0f,
      bool alignToPixels = true, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextRendererDrawCentered(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'), ref colors,
          alpha, alignToPixels, modifiers1);
      });
    }

    /// Draws text at the given position with specific alignment.
    public void DrawAlignedByPixels(Vector2 position, string text, ColorPair colors,
      TextAlignment horizAlign = TextAlignment.Middle, TextAlignment vertAlign = TextAlignment.Middle,
      float alpha = 1.0f, bool alignToPixels = true, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextRendererDrawAlignedByPixels(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'),
          ref colors, horizAlign, vertAlign, alpha, alignToPixels, modifiers1);
      });
    }

    /// Draws text centered around the specified position.
    public void DrawCenteredByPixels(Vector2 position, string text, ColorPair colors, float alpha = 1.0f,
      bool alignToPixels = true, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextRendererDrawCenteredByPixels(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'),
          ref colors, alpha, alignToPixels, modifiers1);
      });
    }

    /// Returns canvas class associated with the text renderer.
    public Canvas Canvas
    {
      get
      {
        IntPtr handle = API.TextRendererGetCanvas(Handle);
        return handle != IntPtr.Zero ? new Canvas(handle) : null;
      }
    }

    /// Font settings and characteristics that define how text will appear.
    public FontParameters Parameters
    {
      get
      {
        if (!API.TextRendererGetFontParameters(Handle, out FontParameters parameters))
          throw new API.Exception("Could not retrieve text renderer's font parameters");

        return parameters;
      }

      set
      {
        if (!API.TextRendererSetFontParameters(Handle, ref value))
          throw new API.Exception("Could not update text renderer's font parameters");
      }
    }

    /// Delegate that receives pointer to TextRenderModifiers unmanaged structure.
    private delegate void TextRenderModificable(IntPtr modifiersPtr);

    /// Allocates and provides pointer to TextRenderModifiers unmanaged structure when modifiers parameter
    /// is specified as non-null.
    private static void InvokeTextRenderModificable(TextRenderModifiers? modifiers,
      TextRenderModificable modificable)
    {
      IntPtr modifiers1 = modifiers is not null ?
        Marshal.AllocHGlobal(Marshal.SizeOf(modifiers.Value)) : IntPtr.Zero;
      try
      {
        if (modifiers is not null)
          Marshal.StructureToPtr(modifiers.Value, modifiers1, false);

        modificable(modifiers1);
      }
      finally
      {
        if (modifiers1 != IntPtr.Zero)
          Marshal.FreeHGlobal(modifiers1);
      }
    }
  }

  /// 2D and 3D text rendering module.
  public sealed class TextModeller : CustomObject
  {
    /// A provider of font geometry for the modeller.
    public class FontProvider : CustomObject
    {
      /// Initializes an instance of font provider.
      internal FontProvider(IntPtr handle) : base(null, handle) { }
    }

    /// Creates a wrapped or uninitialized text renderer.
    internal TextModeller(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of text renderer associated with a particular canvas.
    public TextModeller(Device device)
    : base(new DestroyDelegate(API.TextModellerDestroy), IntPtr.Zero)
    {
      _handle = API.TextModellerCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a text modeller module");
    }

    /// Changes currently specified font parameters used by the text modeller. Internally, this searches for
    /// an existing font provider and if such is not found, creates a new one with the given parameters.
    /// Therefore, when working with multiple fonts, it is much more efficient to save provider returned by
    /// this function and then assign it directly, rather than setting the same font parameters again.
    public FontProvider SetFontParameters(FontParameters parameters)
    {
      IntPtr handle = API.TextModellerSetFontParameters(Handle, ref parameters);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Failed setting text modeller's font parameters");

      return new FontProvider(handle);
    }

    /// Returns the logical dimensions that the given text will occupy when rendered.
    public Vector2 Extent(string text, TextRenderModifiers? modifiers = null)
    {
      Vector2 dimensions = default;

      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextModellerExtent(Handle, Encoding.UTF8.GetBytes(text + '\0'), out dimensions, modifiers1);
      });

      return dimensions;
    }

    /// Calculates a physical shape area that a given text occupies when rendered at zero position.
    public RectF ExtentByShape(string text, TextRenderModifiers? modifiers = null)
    {
      RectF rect = default;

      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        API.TextModellerExtentByShape(Handle, Encoding.UTF8.GetBytes(text + '\0'), out rect, modifiers1);
      });

      return rect;
    }

    /// Provides information regarding individual character position and sizes for the given text string when
    /// rendered. This can be useful for components such as text edit box, for highlighting and selecting
    /// different characters. Returns the actual width and height of the text.
    public Vector2 TextRects(string text, out TextEntryRect[] rects, TextRenderModifiers? modifiers = null)
    {
      Vector2 extent = default;
      TextEntryRect[] rects1 = Array.Empty<TextEntryRect>();

      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        byte[] text1 = Encoding.UTF8.GetBytes(text + '\0');

        int count = API.TextModellerRects(Handle, out extent, null, text1, modifiers1);
        rects1 = new TextEntryRect[count];

        _ = API.TextModellerRects(Handle, out extent, rects1, text1, modifiers1);
      });

      rects = rects1;
      return extent;
    }

    /// Draws 2D text at the given position with specific alignment directly to canvas.
    public void Draw(Canvas canvas, Vector2 position, string text, ColorPair colors,
      TextAlignment alignHoriz = TextAlignment.Middle, TextAlignment alignVert = TextAlignment.Middle,
      bool alignByShape = false, TextRenderModifiers? modifiers = null,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawToCanvas(Handle, canvas?.Handle ?? IntPtr.Zero, ref position,
          Encoding.UTF8.GetBytes(text + '\0'), ref colors, alignHoriz, alignVert, alignByShape, modifiers1,
          effect))
          throw new API.Exception("Failed rendering a vector-based 2D text string to canvas");
      });
    }

    /// Draws 2D text at the given position with specific alignment to a canvas buffer.
    public void Draw(CanvasBuffer buffer, Vector2 position, string text, ColorPair colors,
      TextAlignment alignHoriz = TextAlignment.Middle, TextAlignment alignVert = TextAlignment.Middle,
      bool alignByShape = false, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawToCanvasBuffer(Handle, buffer?.Handle ?? IntPtr.Zero, ref position,
          Encoding.UTF8.GetBytes(text + '\0'), ref colors, alignHoriz, alignVert, alignByShape, modifiers1))
          throw new API.Exception("Failed rendering a vector-based 2D text string to canvas buffer");
      });
    }

    /// Draws a curved 2D text at the given position with specific alignment directly to canvas.
    /// Note: if angle is "NaN", then the text is centered vertically according to the curvature; that is,
    /// if the radius would converge to infinity, the text would converge to look like if it wasn't curved.
    public void DrawCurved(Canvas canvas, Vector2 position, string text, float radius, float angle,
      ColorPair colors, TextRenderModifiers? modifiers = null,
      BlendingEffect effect = BlendingEffect.Normal)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawCurvedToCanvas(Handle, canvas?.Handle ?? IntPtr.Zero, ref position,
          Encoding.UTF8.GetBytes(text + '\0'), radius, angle, ref colors, modifiers1, effect))
          throw new API.Exception("Failed rendering a curved vector-based 2D text string to canvas");
      });
    }

    /// Draws a curved 2D text at the given position with specific alignment to a canvas buffer.
    /// Note: if angle is "NaN", then the text is centered vertically according to the curvature; that is,
    /// if the radius would converge to infinity, the text would converge to look like if it wasn't curved.
    public void DrawCurved(CanvasBuffer buffer, Vector2 position, string text, float radius, float angle,
      ColorPair colors, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawCurvedToCanvasBuffer(Handle, buffer?.Handle ?? IntPtr.Zero, ref position,
          Encoding.UTF8.GetBytes(text + '\0'), radius, angle, ref colors, modifiers1))
          throw new API.Exception("Failed rendering a curved vector-based 2D text string to canvas buffer");
      });
    }

    /// Renders a volumetric 3D text at the given position, orientation and alignment. The text is oriented in
    /// XZ plane with front side oriented towards positive Y axis.
    public void Draw(Vector3 position, string text, float depth, ColorPair colors,
      TextAlignment alignHoriz = TextAlignment.Middle, TextAlignment alignVert = TextAlignment.Middle,
      TextAlignment alignDepth = TextAlignment.Start, bool alignByShape = false,
      TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDraw(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'), depth,
          ref colors, alignHoriz, alignVert, alignDepth, alignByShape, modifiers1))
          throw new API.Exception("Failed pre-rendering a 3D text");
      });
    }

    /// Renders a curved volumetric 3D text at the given position, orientation and alignment. The text is
    /// oriented in XZ plane with front side oriented towards positive Y axis, and the rotation is performed
    /// around Y axis. Note: if angle is "NaN", then the text is centered vertically according to the curvature;
    /// that is, if the radius would converge to infinity, the text would converge to look like if it wasn't
    /// curved.
    public void DrawCurved(Vector3 position, string text, float radius, float angle, float depth,
      ColorPair colors, TextAlignment alignDepth = TextAlignment.Start,
      TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawCurved(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'), radius,
          angle, depth, ref colors, alignDepth, modifiers1))
          throw new API.Exception("Failed pre-rendering a curved 3D text");
      });
    }

    /// Renders a curved volumetric 3D text at the given position, orientation and alignment. The text is
    /// oriented in XZ plane with front side oriented towards positive Y axis, and the rotation is performed
    /// around Z axis. Note: if angle is "NaN", then the text is centered vertically according to the curvature;
    /// that is, if the radius would converge to infinity, the text would converge to look like if it wasn't
    /// curved.
    public void DrawDepthCurved(Vector3 position, string text, float radius, float angle, float depth,
      ColorPair colors, TextRenderModifiers? modifiers = null)
    {
      InvokeTextRenderModificable(modifiers, delegate (IntPtr modifiers1)
      {
        if (!API.TextModellerDrawDepthCurved(Handle, ref position, Encoding.UTF8.GetBytes(text + '\0'),
          radius, angle, depth, ref colors, modifiers1))
          throw new API.Exception("Failed pre-rendering a depth-curved 3D text");
      });
    }

    /// Clears GPU buffers for rendering 3D text.
    public void Clear() => API.TextModellerClear(Handle);

    /// Prepares and fills GPU buffers with an existing 3D text rendering data.
    public void Prepare()
    {
      if (!API.TextModellerPrepare(Handle))
        throw new API.Exception("Could not prepare GPU buffers with a 3D text data");
    }

    /// Issues draw call with existing GPU buffers attached.
    public void Render(Program program, uint channel = 0)
    {
      if (!API.TextModellerRender(Handle, program?.Handle ?? IntPtr.Zero, channel))
        throw new API.Exception("Could not render GPU buffers containing 3D text data");
    }

    /// Issues draw call with existing GPU buffers attached.
    public void CopyToMeshBuffer(MeshBuffer buffer)
    {
      if (!API.TextModellerCopyToMeshBuffer(Handle, buffer?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not copy GPU buffers containing 3D text data to a mesh buffer");
    }

    /// Releases existing resources, character records and buffers.
    public void Reset() => API.TextModellerReset(Handle);

    /// Returns device class associated with the text modeller.
    public Device Device
    {
      get
      {
        IntPtr handle = API.TextModellerGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// Returns or changes an existing font provider. When working with multiple fonts, changing provider
    /// is significantly faster than setting font attributes.
    public FontProvider Provider
    {
      get
      {
        IntPtr handle = API.TextModellerGetFontProvider(Handle);
        return handle != IntPtr.Zero ? new FontProvider(handle) : null;
      }
      set => API.TextModellerSetFontProvider(Handle, value?.Handle ?? IntPtr.Zero);
    }

    /// Returns or changes an existing 3D transformation matrix.
    public Matrix4x4 Transform
    {
      get
      {
        API.TextModellerGetTransform(Handle, out Matrix4x4 transform);
        return transform;
      }

      set => API.TextModellerSetTransform(Handle, ref value);
    }

    /// Delegate that receives pointer to TextRenderModifiers unmanaged structure.
    private delegate void TextRenderModificable(IntPtr modifiersPtr);

    /// Allocates and provides pointer to TextRenderModifiers unmanaged structure when modifiers parameter
    /// is specified as non-null.
    private static void InvokeTextRenderModificable(TextRenderModifiers? modifiers,
      TextRenderModificable modificable)
    {
      IntPtr modifiers1 = modifiers is not null ?
        Marshal.AllocHGlobal(Marshal.SizeOf(modifiers.Value)) : IntPtr.Zero;
      try
      {
        if (modifiers is not null)
          Marshal.StructureToPtr(modifiers.Value, modifiers1, false);

        modificable(modifiers1);
      }
      finally
      {
        if (modifiers1 != IntPtr.Zero)
          Marshal.FreeHGlobal(modifiers1);
      }
    }
  }

  /// 3D graph plotting module.
  public sealed class Grapher : CustomObject
  {
    /// Creates a wrapped or uninitialized grapher module.
    internal Grapher(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D graph plotting module associated with a particular device.
    public Grapher(Device device)
    : base(new DestroyDelegate(API.GrapherDestroy), IntPtr.Zero)
    {
      _handle = API.GrapherCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D graph plotting module");
    }

    /// Prepares grapher for rendering.
    public void Begin()
    {
      if (!API.GrapherBegin(Handle))
        throw new API.Exception("Could not start rendering with 3D graph plotting module");
    }

    /// Finishes grapher rendering.
    public void End() => API.GrapherEnd(Handle);

    /// Draws points at their given vertices with their respective sizes ("w" component of each vertex),
    /// colors, angles and the appropriate shape in 3D space.
    public void Points(Vector4[] vertices, uint[] colors, float[] angles, int elementCount,
      PointShape shape = PointShape.Square)
    {
      API.GrapherPoints(Handle, vertices, colors, angles, elementCount, shape);
    }

    /// Draws lines at their given vertices, thicknesses ("w" component of each vertex), colors and indices.
    public void Lines(Vector4[] vertices, uint[] colors, int[] indices, int vertexCount, int indexCount,
      LineCaps caps = LineCaps.Butt)
    {
      API.GrapherLines(Handle, vertices, colors, indices, vertexCount, indexCount, caps);
    }

    /// Draws a single point with the given parameters.
    public void Point(Vector3 position, uint color, float size, PointShape shape = PointShape.Square,
      float angle = 0.0f)
    {
      API.GrapherPoint(Handle, ref position, color, size, shape, angle);
    }

    /// Draws a single line with the given parameters.
    public void Line(Vector3 position1, Vector3 position2, uint color1, uint color2, float thickness1,
      float thickness2, LineCaps caps = LineCaps.Butt)
    {
      API.GrapherLine(Handle, ref position1, ref position2, color1, color2, thickness1, thickness2, caps);
    }

    /// Draws a line of dots with the given parameters.
    public void DottedLine(Vector3 position1, Vector3 position2, uint color1, uint color2, float thickness1,
      float thickness2, float sparsity, PointShape shape = PointShape.Square, float angle = 0.0f)
    {
      API.GrapherDottedLine(Handle, ref position1, ref position2, color1, color2, thickness1, thickness2,
        sparsity, shape, angle);
    }

    /// Draws an arrow from origin to destination and the given parameters.
    /// "targetSize" is the rendering surface size (required for proper arrow angle calculation).
    public void Arrow(Vector2 targetSize, Vector3 origin, Vector3 destination, uint color, float thickness,
      float size, LineCaps caps = LineCaps.Butt)
    {
      API.GrapherArrow(Handle, ref targetSize, ref origin, ref destination, color, thickness, size, caps);
    }

    /// Draws lines around a bounding box for the given volume matrix.
    public void BoundingBox(Matrix4x4 volume, uint color, float thickness, float length = 0.5f,
      LineCaps caps = LineCaps.Butt)
    {
      API.GrapherBoundingBox(Handle, ref volume, color, thickness, length, caps);
    }

    /// Flushes grapher cache and draws any pending primitives on the rendering surface. This can be useful
    /// to make sure that nothing remains in graph cache before changing any device states.
    public void Flush() => API.GrapherFlush(Handle);

    /// Resets grapher internal resources and releases any non-critical memory buffers. This can reduce
    /// overall memory consumption, when switching from one complex visual scene to another, to allow graph
    /// to "adapt" to a new scene from a fresh start.
    public void Reset() => API.GrapherReset(Handle);

    /// Returns device associated with this module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.GrapherGetDevice(Handle);
        return handle != IntPtr.Zero ? new Device(handle) : null;
      }
    }

    /// 3D transformation matrix that combines world, view and projection matrices.
    public Matrix4x4 Transform
    {
      get
      {
        API.GrapherGetTransform(Handle, out Matrix4x4 transform);
        return transform;
      }

      set => API.GrapherSetTransform(Handle, ref value);
    }

    /// Returns number of batches that were sent to rendering pipeline. Drawing each individual batch
    /// involves some overhead, which is implementation-specific. If this parameter happens to be
    /// considerably high at some point, the rendering code should be revised for better grouping of
    /// plotting primitives.
    public int BatchCount => API.GrapherGetBatchCount(Handle);
  }

  /// 3D mesh model that contains both vertex and index buffers.
  public sealed class MeshModel : CustomObject
  {
    /// Creates a wrapped or uninitialized 3D mesh model container.
    internal MeshModel(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D mesh model container associated with a particular device and
    /// pre-initialized vertex and/or index buffers.
    public MeshModel(Device device, int vertexCount, int vertexElementPitch, int indexCount,
      uint channel = 0, Array initialVertexData = null, Array initialIndexData = null)
    : base(new DestroyDelegate(API.MeshModelDestroy), IntPtr.Zero)
    {
      GCHandle? pinnedInitialVertexData = initialVertexData is not null && initialVertexData.Length > 0 ?
        GCHandle.Alloc(initialVertexData, GCHandleType.Pinned) : null;
      try
      {
        GCHandle? pinnedInitialIndexData = initialIndexData is not null && initialIndexData.Length > 0 ?
          GCHandle.Alloc(initialIndexData, GCHandleType.Pinned) : null;
        try
        {
          _handle = API.MeshModelCreate(device?.Handle ?? IntPtr.Zero, vertexCount,
            (uint)vertexElementPitch, indexCount, channel,
            pinnedInitialVertexData?.AddrOfPinnedObject() ?? IntPtr.Zero,
            pinnedInitialIndexData?.AddrOfPinnedObject() ?? IntPtr.Zero);
        }
        finally
        {
          pinnedInitialIndexData?.Free();
        }
      }
      finally
      {
        pinnedInitialVertexData?.Free();
      }
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D mesh model container");
    }

    /// Releases vertex and/or index buffers, setting number of vertices and indices to zero.
    /// This basically turns an existing model into a "dummy" or a placeholder.
    public void Dismantle() => API.MeshModelDismantle(Handle);

    /// Takes away the internal contents from another 3D mesh model container without doing any copying.
    public void TakeAway(MeshModel other) => API.MeshModelTakeAway(Handle, other?.Handle ?? IntPtr.Zero);

    /// Renders the mesh model with the given program.
    public void Draw(Program program, PrimitiveTopology topology = PrimitiveTopology.Triangles,
      int elementCount = 0, int firstIndex = 0, int baseVertex = 0, bool postUnbind = true)
    {
      if (!API.MeshModelDraw(Handle, program?.Handle ?? IntPtr.Zero, topology, elementCount, firstIndex,
        baseVertex, postUnbind))
        throw new API.Exception("Failed rendering a 3D mesh model");
    }

    /// Renders multiple instances of the mesh model with a given program.
    public void DrawInstances(Program program, int instanceCount,
      PrimitiveTopology topology = PrimitiveTopology.Triangles, int elementCount = 0, int firstIndex = 0,
      int baseVertex = 0, bool postUnbind = true)
    {
      if (!API.MeshModelDrawInstances(Handle, program?.Handle ?? IntPtr.Zero, instanceCount, topology,
        elementCount, firstIndex, baseVertex, postUnbind))
        throw new API.Exception("Failed rendering multiple instances of a 3D mesh model");
    }

    /// Tests whether the model can actually be rendered. For that, it must contain at least a non-empty
    /// vertex buffer. Non-renderable mesh is basically a "dummy" that serves as a placeholder.
    public bool Renderable => API.MeshModelRenderable(Handle);

    /// Returns total number of vertices stored in the model.
    public int VertexCount => API.MeshModelGetVertexCount(Handle);

    /// Returns total number of indices stored in the model.
    public int IndexCount => API.MeshModelGetIndexCount(Handle);

    /// Returns channel associated with vertex buffer format.
    public uint Channel => API.MeshModelGetChannel(Handle);

    /// Returns an integrated vertex buffer.
    public Buffer VertexBuffer
    {
      get
      {
        IntPtr handle = API.MeshModelGetVertexBuffer(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve vertex buffer from the mesh model");

        return new Buffer(handle);
      }
    }

    /// Returns pointer to integrated index buffer.
    public Buffer IndexBuffer
    {
      get
      {
        IntPtr handle = API.MeshModelGetIndexBuffer(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve index buffer from the mesh model");

        return new Buffer(handle);
      }
    }
  }

  /// Meta-tag, which describes a certain important axis-aligned bounding area inside a mesh.
  public sealed class MeshMetaTag : CustomObject
  {
    /// Creates an uninitialized meta-tag container.
    internal MeshMetaTag(IntPtr handle) : base(null, handle) { }

    /// Returns a portion that corresponds to the tag with a given index.
    public MeshMetaTagPortion PortionGet(int index)
    {
      if (!API.MeshMetaTagPortionGet(Handle, out MeshMetaTagPortion portion, index))
        throw new API.Exception($"Could not retrieve mesh meta-tag portion ({index})");

      return portion;
    }

    /// Adds a new portion to the tag.
    public void PortionAdd(MeshMetaTagPortion portion)
    {
      if (!API.MeshMetaTagPortionAdd(Handle, ref portion))
        throw new API.Exception("Could not add a new mesh meta tag portion");
    }

    /// Erases a portion with the given index.
    public void PortionErase(int index) => API.MeshMetaTagPortionErase(Handle, index);

    /// Removes all existing portions.
    public void PortionsClear() => API.MeshMetaTagPortionsClear(Handle);

    /// Removes any existing portions and then copies them from an existing tag.
    public void PortionsCopy(MeshMetaTag tag)
    {
      if (!API.MeshMetaTagPortionsCopy(Handle, tag?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not copy portions from an existing mesh meta-tag");
    }

    /// Calculates and returns boundaries for all existing tag's portions, if such exist.
    public void GetBounds(out Vector3 boundsMin, out Vector3 boundsMax)
    {
      API.MeshMetaTagGetBounds(Handle, out boundsMin, out boundsMax);
    }

    /// Returns mesh meta tag's parent container.
    public MeshMetaTags Parent
    {
      get
      {
        IntPtr handle = API.MeshMetaTagGetParent(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve parent container of mesh meta-tag");

        return new MeshMetaTags(handle);
      }
    }

    /// Returns name of the tag.
    public string Name
    {
      get
      {
        int nameLength = 0;
        API.MeshMetaTagGetName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle namePinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.MeshMetaTagGetName(Handle, namePinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            namePinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }
    }

    /// Returns type of the tag.
    public byte Type => API.MeshMetaTagGetType(Handle);

    /// Returns number of existing portions associated with the tag.
    public int PortionCount => API.MeshMetaTagGetPortionCount(Handle);
  }

  /// List of meta-tags that describe important axis-aligned bounding areas inside a mesh.
  public sealed class MeshMetaTags : CustomObject
  {
    /// Creates a wrapped or uninitialized meta-tags container.
    internal MeshMetaTags(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of mesh meta-tags container.
    public MeshMetaTags()
    : base(new DestroyDelegate(API.MeshMetaTagsDestroy), IntPtr.Zero)
    {
      _handle = API.MeshMetaTagsCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a mesh meta-tag container");
    }

    /// Returns a particular tag with the given index.
    public MeshMetaTag Tag(int index)
    {
      IntPtr handle = API.MeshMetaTagsGetByIndex(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve mesh meta-tag with the given index ({index})");

      return new MeshMetaTag(handle);
    }

    /// Returns a particular tag with the given name (case-insensitive) and type.
    public MeshMetaTag Tag(string name, byte type = 0)
    {
      IntPtr handle = API.MeshMetaTagsGetByName(Handle, Encoding.UTF8.GetBytes(name + '\0'), type);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not retrieve mesh meta-tag with the given " +
          $"name ({name}) and type ({type})");

      return new MeshMetaTag(handle);
    }

    /// Creates a new tag with the given name (case-insensitive) and type.
    public MeshMetaTag Add(string name, byte type = 0)
    {
      IntPtr handle = API.MeshMetaTagsAdd(Handle, Encoding.UTF8.GetBytes(name + '\0'), type);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not add a new mesh meta-tag with the given " +
          $"name ({name}) and type ({type})");

      return new MeshMetaTag(handle);
    }

    /// Copies tags from another container.
    public void Copy(MeshMetaTags other)
    {
      if (!API.MeshMetaTagsCopy(Handle, other?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not copy mesh meta-tags from an existing container");
    }

    /// Takes away the internal contents from another tags container without doing any copying.
    public void TakeAway(MeshMetaTags other)
    {
      API.MeshMetaTagsTakeAway(Handle, other?.Handle ?? IntPtr.Zero);
    }

    /// Removes an existing tag from container list.
    public void Erase(int index) => API.MeshMetaTagsErase(Handle, index);

    /// Removes all existing tags from container list.
    public void Clear() => API.MeshMetaTagsClear(Handle);

    /// Returns number of existing tags.
    public int Count => API.MeshMetaTagsCount(Handle);
  }

  /// Buffer that contains 3D mesh information.
  public sealed class MeshBuffer : CustomObject
  {
    /// Mesh loading and saving feedback delegate.
    public delegate int LoadSaveFeedback(string message, int progress);

    /// A vertex element of mesh buffer with interleaved data.
    public struct VertexEntry
    {
      /// Creates vertex element structure with the given values.
      public VertexEntry(Vector3 position, Vector3 normal, Vector3 tangent, Vector2 texCoord,
        FloatColor color)
      {
        Position = position;
        Normal = normal;
        Tangent = tangent;
        TexCoord = texCoord;
        Color = color;
      }

      /// Vertex 3D position.
      public Vector3 Position;

      /// Vertex 3D normal (normalized).
      public Vector3 Normal;

      /// Vertex 3D tangent (normalized).
      public Vector3 Tangent;

      /// Vertex 2D texture coordinates.
      public Vector2 TexCoord;

      /// Vertex RGBA color.
      public FloatColor Color;
    }

    /// Creates a wrapped or uninitialized 3D mesh buffer container.
    internal MeshBuffer(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D mesh buffer container.
    public MeshBuffer()
    : base(new DestroyDelegate(API.MeshBufferDestroy), IntPtr.Zero)
    {
      _handle = API.MeshBufferCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D mesh buffer container");
    }

    /// Creates new instance of 3D mesh model with the given vertex elements and actual data from
    /// this buffer.
    public MeshModel Model(Device device, VertexElement[] vertexElements, int firstVertex = 0,
      int vertexCount = 0, int firstIndex = 0, int indexCount = 0, uint channel = 0)
    {
      IntPtr handle = API.MeshBufferCreateModel(Handle, device?.Handle ?? IntPtr.Zero, vertexElements,
        vertexElements.Length, firstVertex, vertexCount, firstIndex, indexCount, channel);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not create 3D mesh model from contents of mesh buffer");

      return new MeshModel(handle);
    }

    /// Creates a 3D superellipse with the given parameters. Longitude's initial and ending angles are
    /// specified in [0, 2 * PI] ranges, whereas latitude's initial and ending angles are specified in
    /// a different range of [-PI / 2, PI / 2].
    public void SuperEllipse(int longitudeSections, int latitudeSections, Vector3 origin, Vector3 radius,
      float initLongitudeAngle = 0.0f, float endLongitudeAngle = (float)Math.PI * 2.0f,
      float longitudeShape = 1.0f, float initLatitudeAngle = (float)Math.PI * -0.5f,
      float endLatitudeAngle = (float)Math.PI * 0.5f, float latitudeShape = 1.0f,
      Vector2? initTexCoord = null, Vector2? endTexCoord = null, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferSuperEllipse(Handle, longitudeSections, latitudeSections, ref origin, ref radius,
        initLongitudeAngle, endLongitudeAngle, longitudeShape, initLatitudeAngle, endLatitudeAngle,
        latitudeShape, ref initTexCoord1, ref endTexCoord1, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a super-ellipse geometry in mesh buffer");
    }

    /// Creates a geosphere with the given parameters.
    public void Geosphere(int subdivisions = 3, FloatColor? color = null, bool flatNormals = false,
      bool indicesClockwise = true)
    {
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferGeosphere(Handle, subdivisions, ref color1, flatNormals, indicesClockwise))
        throw new API.Exception("Could not generate a geosphere geometry in mesh buffer");
    }

    /// Creates a cylinder with the given parameters. Axis vectors define both orientation and size.
    /// Initial and ending angles are specified in [0, 2 * PI] range.
    public void Cylinder(int radialSections, int heightSections, Vector3 origin, Vector3 horizAxis,
      Vector3 vertAxis, Vector3 heightAxis, float initAngle = 0.0f, float endAngle = (float)Math.PI * 2.0f,
      float shape = 1.0f, Vector2? initTexCoord = null, Vector2? endTexCoord = null, float bendAngle = 0.0f,
      float lateralAngle = 0.0f, FloatColor? color = null, bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferCylinder(Handle, radialSections, heightSections, ref origin, ref horizAxis,
        ref vertAxis, ref heightAxis, initAngle, endAngle, shape, ref initTexCoord1, ref endTexCoord1,
        bendAngle, lateralAngle, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a cylinder geometry in mesh buffer");
    }

    /// Creates a simplistic cone with the given parameters in XZ plane with its base at origin.
    public void Cone(int sections, Vector3 origin, float radius, float height, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferConeSimple(Handle, sections, ref origin, radius, height, ref color1,
        indicesClockwise))
        throw new API.Exception("Could not generate a cone geometry in mesh buffer");
    }

    /// Creates a disc with the given parameters. Axis vectors define both orientation and size. Initial and
    /// ending angles are specified in [0, 2 * PI] range. \c initRadius defines initial radius in proportion
    /// to overall disc size.
    public void Disc(int radialSections, int innerSections, Vector3 origin, Vector3 horizAxis,
      Vector3 vertAxis, Vector3 normal, float initAngle = 0.0f, float endAngle = (float)Math.PI * 2.0f,
      float shape = 1.0f, float initRadius = 0.0f, Vector2? initTexCoord = null,
      Vector2? endTexCoord = null, float lateralAngle = 0.0f, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferDisc(Handle, radialSections, innerSections, ref origin, ref horizAxis,
        ref vertAxis, ref normal, initAngle, endAngle, shape, initRadius, ref initTexCoord1,
        ref endTexCoord1, lateralAngle, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a disc geometry in mesh buffer");
    }

    /// Creates a 3D plane with the given parameters. Axis vectors define both orientation and size.
    public void Plane(int horizSections, int vertSections, Vector3 origin, Vector3 horizAxis,
      Vector3 vertAxis, Vector3 normal, Vector2? initTexCoord = null, Vector2? endTexCoord = null,
      FloatColor? color = null, bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferPlane(Handle, horizSections, vertSections, ref origin, ref horizAxis,
        ref vertAxis, ref normal, ref initTexCoord1, ref endTexCoord1, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a plane geometry in mesh buffer");
    }

    /// Creates a 3D cube with the given parameters. Axis vectors define both orientation and size.
    public void Cube(int horizSections, int vertSections, int depthSections, Vector3 origin,
      Vector3 horizAxis, Vector3 vertAxis, Vector3 depthAxis, Vector2? initTexCoord = null,
      Vector2? endTexCoord = null, FloatColor? color = null, bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferCube(Handle, horizSections, vertSections, depthSections, ref origin, ref horizAxis,
        ref vertAxis, ref depthAxis, ref initTexCoord1, ref endTexCoord1, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a cube geometry in mesh buffer");
    }

    /// Creates a 3D cube with minimal number of vertices and indices. Texture coordinates, normals and
    /// tangents are not generated.
    public void CubeMinimal(Vector3 origin, Vector3 size, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferCubeMinimal(Handle, ref origin, ref size, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a minimal cube geometry in mesh buffer");
    }

    /// Creates a 3D cube with round corners.
    public void CubeRound(Vector3 origin, Vector3 size, float roundness = 0.1f, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferCubeRound(Handle, ref origin, ref size, roundness, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a round cube geometry in mesh buffer");
    }

    /// Creates a 3D torus with the given parameters. Axis vectors define both orientation and size. Initial and
    /// ending angles for both inner and outer rings are specified in [0, 2 * PI] range.
    public void Torus(int outerSections, int innerSections, Vector3 origin, Vector3 horizAxis,
      Vector3 vertAxis, Vector2 innerRadius, float initInnerAngle = 0.0f,
      float endInnerAngle = (float)Math.PI * 2.0f, float innerShape = 1.0f, float initOuterAngle = 0.0f,
      float endOuterAngle = (float)Math.PI * 2.0f, float outerShape = 1.0f, Vector2? initTexCoord = null,
      Vector2? endTexCoord = null, float lateralAngle = 0.0f, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferTorus(Handle, outerSections, innerSections, ref origin, ref horizAxis,
        ref vertAxis, initOuterAngle, endOuterAngle, outerShape, initInnerAngle, endInnerAngle, innerShape,
        ref innerRadius, ref initTexCoord1, ref endTexCoord1, lateralAngle, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a torus geometry in mesh buffer");
    }

    /// Creates a 3D torus with the given parameters. Axis vectors define both orientation and size. Initial and
    /// ending angles for both inner and outer rings are specified in [0, 2 * PI] range.
    public void TorusKnot(int outerSections, int innerSections, Vector3 origin, int p, int q,
      Vector2 innerRadius, float outerRadius, float initInnerAngle = 0.0f,
      float endInnerAngle = (float)Math.PI * 2.0f, float innerShape = 1.0f, float initOuterAngle = 0.0f,
      float endOuterAngle = (float)Math.PI * 2.0f, Vector2? initTexCoord = null,
      Vector2? endTexCoord = null, float lateralAngle = 0.0f, FloatColor? color = null,
      bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferTorusKnot(Handle, outerSections, innerSections, ref origin, p, q, initOuterAngle,
        endOuterAngle, outerRadius, initInnerAngle, endInnerAngle, innerShape, ref innerRadius,
        ref initTexCoord1, ref endTexCoord1, lateralAngle, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a torus knot geometry in mesh buffer");
    }

    /// Creates a 3D supertoroid located in XY plane with the given parameters. Initial and ending angles for
    /// both inner and outer rings are specified in [0, 2 * PI] range.
    public void Supertoroid(int outerSections, int innerSections, Vector3 origin, float innerRadius,
      float outerRadius, float initInnerAngle = 0.0f, float endInnerAngle = (float)Math.PI * 2.0f,
      float innerShape = 1.0f, float initOuterAngle = 0.0f, float endOuterAngle = (float)Math.PI * 2.0f,
      float outerShape = 1.0f, Vector2? initTexCoord = null, Vector2? endTexCoord = null,
      FloatColor? color = null, bool indicesClockwise = true)
    {
      Vector2 initTexCoord1 = initTexCoord ?? default;
      Vector2 endTexCoord1 = endTexCoord ?? Vector2.One;
      FloatColor color1 = color ?? FloatColor.White;

      if (!API.MeshBufferSupertoroid(Handle, outerSections, innerSections, ref origin, initOuterAngle,
        endOuterAngle, outerRadius, outerShape, initInnerAngle, endInnerAngle, innerRadius,
        innerShape, ref initTexCoord1, ref endTexCoord1, ref color1, indicesClockwise))
        throw new API.Exception("Could not generate a supertoroid geometry in mesh buffer");
    }

    /// Creates a 3D volume of a view frustum from the given view/projection matrix.
    public void FrustumVolume(Matrix4x4 viewProjectionInverse, bool depthClipNegative = false)
    {
      if (!API.MeshBufferFrustumVolume(Handle, ref viewProjectionInverse, depthClipNegative))
        throw new API.Exception("Could not generate a frustum volume geometry in mesh buffer");
    }

    /// Creates an extruded mesh around the given path. The mesh is colored with a gradient of four colors.
    public void Extrusion(PathElement[] path, float depth, Vector2? texCoords = null,
      FloatColor[] colors = null, bool quality = true)
    {
      if (colors is not null && colors.Length != 4)
        throw new API.Exception("A gradient of four colors is required for extrusion, but " +
          $"({colors.Length}) was provided");

      FloatColor[] colors1 = colors ?? new[] {FloatColor.White, FloatColor.White,
        FloatColor.White, FloatColor.White};
      Vector2 texCoords1 = texCoords ?? Vector2.One;

      if (!API.MeshBufferExtrusion(Handle, path, path.Length, depth, ref texCoords1, colors1, quality))
        throw new API.Exception("Could not generate an extrusion geometry in mesh buffer");
    }

    /// Multiplies vertices in a given range by currently set transformation matrix.
    public void TransformVertices(int firstVertex = 0, int vertexCount = 0)
    {
      API.MeshBufferTransformVertices(Handle, firstVertex, vertexCount);
    }

    /// Recalculates mesh normals for the given range of indices by using normals of triangles.
    /// This rebuilds vertex and index lists entirely.
    public void CalculateFlatNormals(float epsilon = API.Epsilon)
    {
      if (!API.MeshBufferCalculateFlatNormals(Handle, epsilon))
        throw new API.Exception("Failed calculating flat normals for the geometry in mesh buffer");
    }

    /// Recalculates mesh normals for the given range of indices by calculating face normals first and then
    /// averaging the resulting values to face vertices.
    public void CalculateNormals(int firstVertex, int vertexCount, int firstIndex, int indexCount)
    {
      API.MeshBufferCalculateNormals(Handle, firstVertex, vertexCount, firstIndex, indexCount);
    }

    /// Recalculates mesh normals for the given range of indices. Vertices that have their positions almost the
    /// same (depends on "weldEpsilon") will be considered duplicated and thus will receive weighted normals
    /// from neighbor faces as if it would be the same shared vertex.
    public void CalculateNormalsWeld(int firstVertex, int vertexCount, int firstIndex, int indexCount,
      float weldEpsilon = API.Epsilon)
    {
      API.MeshBufferCalculateNormalsWeld(Handle, firstVertex, vertexCount, firstIndex, indexCount,
        weldEpsilon);
    }

    /// Invert the normals for vertices in the given range. If vertex count is not specified, then remaining
    /// vertices starting at the given vertex and up to the end of the list are processed.
    public void InvertNormals(int firstVertex = 0, int vertexCount = 0)
    {
      API.MeshBufferInvertNormals(Handle, firstVertex, vertexCount);
    }

    /// Inverts order of indices (e.g. from clockwise to counter-clockwise and vice-versa).
    public void InvertIndexOrder(int firstIndex = 0, int indexCount = 0)
    {
      API.MeshBufferInvertIndexOrder(Handle, firstIndex, indexCount);
    }

    /// Calculates minimum and maximum vertex coordinate boundaries in the given range of mesh.
    public void CalculateBounds(out Vector3 vertexMin, out Vector3 vertexMax, int firstVertex = 0,
      int vertexCount = 0)
    {
      API.MeshBufferCalculateBounds(Handle, out vertexMin, out vertexMax, firstVertex, vertexCount);
    }

    /// Calculates coordinate boundaries and centralizes the vertices in the given range of mesh.
    public void Centralize(int firstVertex = 0, int vertexCount = 0)
    {
      API.MeshBufferCentralize(Handle, firstVertex, vertexCount);
    }

    /// Processes given range of indices, marking vertices that are used and then eliminating vertices that
    /// were left unused, updating the entire array of indices.
    public void EliminateUnusedVertices(int firstVertex = 0, int vertexCount = 0)
    {
      API.MeshBufferEliminateUnusedVertices(Handle, firstVertex, vertexCount);
    }

    /// Joins vertices that have duplicate positions, averaging their normals, tangents and colors.
    /// This has complexity of O(n^2), where n is number of vertices to process.
    public void JoinDuplicateVertices(int firstVertex = 0, int vertexCount = 0,
      float treshold = API.Epsilon)
    {
      if (!API.MeshBufferJoinDuplicateVertices(Handle, firstVertex, vertexCount, treshold))
        throw new API.Exception("Failed joining duplicate vertices for the geometry in mesh buffer");
    }

    /// Includes vertices and indices from source mesh buffer into the current one.
    public void Combine(MeshBuffer source, int firstVertex = 0, int vertexCount = 0, int firstIndex = 0,
      int indexCount = 0)
    {
      if (!API.MeshBufferCombine(Handle, source?.Handle ?? IntPtr.Zero, firstVertex, vertexCount,
        firstIndex, indexCount))
        throw new API.Exception("Failed combining geometry in mesh buffer from another mesh buffer");
    }

    /// Removes all vertices and indices in the mesh buffer.
    public void Clear() => API.MeshBufferClear(Handle);

    /// Loads natively Wavefront OBJ mesh file format (or a proprietary mesh binary format) from disk to
    /// the mesh buffer. Optionally loads materials included with the mesh file and/or meta tags (important
    /// boundaries of sub-meshes). Invokes "feedback" delegate regularly to inform on loading progres.
    /// If loading fails, optionally provides debug information.
    public bool TryLoadFromFile(string fileName, SceneMeshMaterials materials, MeshMetaTags tags,
      ref uint options, LoadSaveFeedback feedback, out string debug)
    {
      bool loaded = false;

      GCHandle? feedbackPinned = feedback is not null ? GCHandle.Alloc(feedback) : null;
      try
      {
        byte[] debugBytes = ArrayPool<byte>.Shared.Rent(4096);
        try
        {
          int debugLength = debugBytes.Length;

          GCHandle debugBytesPinned = GCHandle.Alloc(debugBytes, GCHandleType.Pinned);
          try
          {
            loaded = API.MeshBufferLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'),
              materials?.Handle ?? IntPtr.Zero, tags?.Handle ?? IntPtr.Zero, ref options,
              WrapperSaveFeedback, feedback is not null ?
              Marshal.GetFunctionPointerForDelegate(feedback) : IntPtr.Zero,
              debugBytesPinned.AddrOfPinnedObject(), ref debugLength);
          }
          finally
          {
            debugBytesPinned.Free();
          }
          if (debugLength > 0)
            debug = Encoding.UTF8.GetString(debugBytes, 0, debugLength);
          else
            debug = string.Empty;
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(debugBytes);
        }
      }
      finally
      {
        feedbackPinned?.Free();
      }
      return loaded;
    }

    /// Loads natively Wavefront OBJ mesh file format (or a proprietary mesh binary format) from disk to
    /// the mesh buffer. Optionally loads materials included with the mesh file and/or meta tags (important
    /// boundaries of sub-meshes). Invokes "feedback" delegate regularly to inform on loading progres.
    public void LoadFromFile(string fileName, ref uint options, SceneMeshMaterials materials = null,
      MeshMetaTags tags = null, LoadSaveFeedback feedback = null)
    {
      if (!TryLoadFromFile(fileName, materials, tags, ref options, feedback, out string debug))
        throw new API.Exception($"Could not load 3D mesh ({fileName}) natively from file: {debug}");
    }

    /// Saves mesh buffer contents to a Wavefront OBJ mesh format natively to file on disk. Materials and
    /// tags are optional and will also be saved, if provided. If saving fails, optionally provides debug
    /// information.
    public bool TrySaveToFile(string fileName, SceneMeshMaterials materials, MeshMetaTags tags,
      uint options, LoadSaveFeedback feedback, out string debug)
    {
      bool loaded = false;

      GCHandle? feedbackPinned = feedback is not null ? GCHandle.Alloc(feedback) : null;
      try
      {
        byte[] debugBytes = ArrayPool<byte>.Shared.Rent(4096);
        try
        {
          int debugLength = debugBytes.Length;

          GCHandle debugBytesPinned = GCHandle.Alloc(debugBytes, GCHandleType.Pinned);
          try
          {
            loaded = API.MeshBufferSaveToFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'),
              materials?.Handle ?? IntPtr.Zero, tags?.Handle ?? IntPtr.Zero, options, WrapperSaveFeedback,
              feedback is not null ? Marshal.GetFunctionPointerForDelegate(feedback) : IntPtr.Zero,
              debugBytesPinned.AddrOfPinnedObject(), ref debugLength);
          }
          finally
          {
            debugBytesPinned.Free();
          }
          if (debugLength > 0)
            debug = Encoding.UTF8.GetString(debugBytes, 0, debugLength);
          else
            debug = string.Empty;
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(debugBytes);
        }
      }
      finally
      {
        feedbackPinned?.Free();
      }
      return loaded;
    }

    /// Saves mesh buffer contents to a Wavefront OBJ mesh format natively to file on disk. Materials and
    /// tags are optional and will also be saved, if provided. If saving fails, optionally provides debug
    /// information.
    public void SaveToFile(string fileName, SceneMeshMaterials materials = null, MeshMetaTags tags = null,
      uint options = 0, LoadSaveFeedback feedback = null)
    {
      if (!TrySaveToFile(fileName, materials, tags, options, feedback, out string debug))
        throw new API.Exception($"Could not save 3D mesh ({fileName}) natively to file: {debug}");
    }

    /// Internal wrapper callback function for channeling mesh load/save feedback information.
    internal static int WrapperSaveFeedback(string message, int progress, IntPtr user)
    {
      if (user != IntPtr.Zero)
      {
        LoadSaveFeedback feedback = Marshal.GetDelegateForFunctionPointer<LoadSaveFeedback>(user);
        return feedback(message, progress);
      }
      else
        return 0;
    }

    /// Attempts to load the mesh file from disk using Assimp DLL to mesh buffer, calculates its boundaries
    /// and provides debug information. Optionally loads mesh meta tags (important boundaries of sub-meshes).
    /// If "normals" parameter is true, then normals will be optionally generated when such are not present.
    public bool TryLoadFromFileEx(string fileName, MeshMetaTags tags, out Vector3 minBounds,
      out Vector3 maxBounds, bool normals, out string debug)
    {
      bool loaded = false;

      byte[] debugBytes = ArrayPool<byte>.Shared.Rent(4096);
      try
      {
        int debugLength = debugBytes.Length;
        GCHandle debugBytesPinned = GCHandle.Alloc(debugBytes, GCHandleType.Pinned);
        try
        {
          loaded = API.MeshBufferLoadFromFileEx(Handle, tags?.Handle ?? IntPtr.Zero,
            Encoding.UTF8.GetBytes(fileName + '\0'), out minBounds, out maxBounds, normals,
            debugBytesPinned.AddrOfPinnedObject(), ref debugLength);
        }
        finally
        {
          debugBytesPinned.Free();
        }
        if (debugLength > 0)
          debug = Encoding.UTF8.GetString(debugBytes, 0, debugLength);
        else
          debug = string.Empty;
      }
      finally
      {
        ArrayPool<byte>.Shared.Return(debugBytes);
      }
      return loaded;
    }

    /// Loads the mesh file from disk using Assimp DLL to mesh buffer, calculates its boundaries and
    /// provides debug information. Optionally loads mesh meta tags (important boundaries of sub-meshes).
    /// If "normals" parameter is true, then normals will be optionally generated when such are not present.
    public void LoadFromFileEx(string fileName, MeshMetaTags tags, out Vector3 minBounds,
      out Vector3 maxBounds, bool normals)
    {
      if (!TryLoadFromFileEx(fileName, tags, out minBounds, out maxBounds, normals, out string debug))
        throw new API.Exception($"Could not load 3D mesh ({fileName}) using Assimp from file: {debug}");
    }

    /// Attempts to save the mesh to file on disk using Assimp DLL from an existing mesh buffer.
    public bool TrySaveToFileEx(string fileName, uint options, out string debug)
    {
      bool saved = false;

      byte[] debugBytes = ArrayPool<byte>.Shared.Rent(4096);
      try
      {
        int debugLength = debugBytes.Length;
        GCHandle debugBytesPinned = GCHandle.Alloc(debugBytes, GCHandleType.Pinned);
        try
        {
          saved = API.MeshBufferSaveToFileEx(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), ref options,
            debugBytesPinned.AddrOfPinnedObject(), ref debugLength);
        }
        finally
        {
          debugBytesPinned.Free();
        }
        if (debugLength > 0)
          debug = Encoding.UTF8.GetString(debugBytes, 0, debugLength);
        else
          debug = string.Empty;
      }
      finally
      {
        ArrayPool<byte>.Shared.Return(debugBytes);
      }
      return saved;
    }

    /// Saves the mesh to file on disk using Assimp DLL from an existing mesh buffer.
    public void SaveToFileEx(string fileName, uint options)
    {
      if (!TrySaveToFileEx(fileName, options, out string debug))
        throw new API.Exception($"Could not save 3D mesh ({fileName}) using Assimp to file: {debug}");
    }

    /// Transfers mesh vertex elements to an interleaved vertex buffer object with the given format at the
    /// specified channel and offset.
    public void Transfer(Buffer buffer, VertexElement[] vertexElements, int firstVertex, int vertexCount,
      uint channel = 0, uint offset = 0, uint semanticIndex = 0)
    {
      if (!API.MeshBufferTransferVertices(Handle, buffer?.Handle ?? IntPtr.Zero, vertexElements,
        vertexElements.Length, firstVertex, vertexCount, channel, offset, semanticIndex))
        throw new API.Exception("Could not transfer mesh geometry to a vertex buffer");
    }

    /// Transfers mesh index elements to an index buffer object with the given format at the specified
    /// offset.
    public void Transfer(Buffer buffer, int firstVertex, int firstIndex, int indexCount, uint offset = 0)
    {
      if (!API.MeshBufferTransferIndices(Handle, buffer?.Handle ?? IntPtr.Zero, firstVertex, firstIndex,
        indexCount, offset))
        throw new API.Exception("Could not transfer mesh geometry to an index buffer");
    }

    /// Transfers mesh vertex elements to an interleaved array, with the given format at the specified
    /// channel. Returns number of bytes that the resulting data occupies. If "vertices" is null, this
    /// function only calculates how many bytes the data will occupy.
    public uint TransferEx(Array vertices, VertexElement[] vertexElements, int firstVertex,
      int vertexCount, uint channel = 0, uint semanticIndex = 0)
    {
      GCHandle? pinnedInitialData = vertices is not null && vertices.Length > 0 ?
        GCHandle.Alloc(vertices, GCHandleType.Pinned) : null;
      try
      {
        return API.MeshBufferTransferVertexElements(Handle, pinnedInitialData?.AddrOfPinnedObject() ??
          IntPtr.Zero, vertexElements, vertexElements.Length, firstVertex, vertexCount, channel,
          semanticIndex);
      }
      finally
      {
        pinnedInitialData?.Free();
      }
    }

    /// Transfers mesh indices to an array. Returns number of bytes that the resulting data occupies.
    /// If "indices" is zerp, this function only calculates how many bytes the data will occupy.
    public uint TransferEx(Array indices, uint pitch, int firstVertex, int firstIndex, int indexCount)
    {
      GCHandle? pinnedInitialData = indices is not null && indices.Length > 0 ?
        GCHandle.Alloc(indices, GCHandleType.Pinned) : null;
      try
      {
        return API.MeshBufferTransferIndexElements(Handle, pinnedInitialData?.AddrOfPinnedObject() ??
          IntPtr.Zero, pitch, firstVertex, firstIndex, indexCount);
      }
      finally
      {
        pinnedInitialData?.Free();
      }
    }

    /// Creates and builds a 3D voxel representation for geometry in the mesh buffer. A special care must
    /// be taken to define levels, as using high values may exhaust available RAM resources; typical values
    /// should be between 3 and 4.
    public MeshVoxel Voxelize(byte levels, bool colors = false)
    {
      IntPtr handle = API.MeshBufferVoxelize(Handle, levels, colors);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not voxelize mesh buffer with the requested parameters");

      return new MeshVoxel(handle);
    }

    /// Total number of vertices in 3D mesh buffer.
    public int VertexCount
    {
      get => API.MeshBufferGetVertexCount(Handle);

      set
      {
        if (!API.MeshBufferSetVertexCount(Handle, value))
          throw new API.Exception($"Could not update number of vertices to ({value}) in mesh buffer");
      }
    }

    /// Total number of indices in 3D mesh buffer.
    public int IndexCount
    {
      get => API.MeshBufferGetIndexCount(Handle);

      set
      {
        if (!API.MeshBufferSetIndexCount(Handle, value))
          throw new API.Exception($"Could not update number of indices to ({value}) in mesh buffer");
      }
    }

    /// Transformation matrix that is applied to generated vertices during construction.
    public Matrix4x4 Transform
    {
      get
      {
        API.MeshBufferGetTransform(Handle, out Matrix4x4 transform);
        return transform;
      }

      set { API.MeshBufferSetTransform(Handle, ref value); }
    }

    /// Either returns or updates mesh buffer vertices.
    public VertexEntry[] Vertices
    {
      get
      {
        VertexEntry[] vertices = new VertexEntry[VertexCount];
        IntPtr verticesPtr = API.MeshBufferGetVertices(Handle);

        for (int i = 0; i < vertices.Length; ++i)
        {
          IntPtr nativeOffset = new (verticesPtr.ToInt64() + i * Marshal.SizeOf<VertexEntry>());
          vertices[i] = Marshal.PtrToStructure<VertexEntry>(nativeOffset);
        }
        return vertices;
      }

      set
      {
        VertexCount = value.Length;
        IntPtr verticesPtr = API.MeshBufferGetVertices(Handle);

        for (int i = 0; i < value.Length; ++i)
        {
          IntPtr nativeOffset = new (verticesPtr.ToInt64() + i * Marshal.SizeOf<VertexEntry>());
          Marshal.StructureToPtr(value[i], nativeOffset, false);
        }
      }
    }

    /// Either returns or updates mesh buffer indices.
    public int[] Indices
    {
      get
      {
        int[] indices = new int[IndexCount];
        Marshal.Copy(API.MeshBufferGetIndices(Handle), indices, 0, indices.Length);
        return indices;
      }

      set
      {
        IndexCount = value.Length;
        Marshal.Copy(value, 0, API.MeshBufferGetIndices(Handle), value.Length);
      }
    }

    /// Returns pointer to unmanaged memory containing vertices stored in 3D mesh buffer.
    public IntPtr VerticesPtr => API.MeshBufferGetVertices(Handle);

    /// Returns pointer to unmanaged memory containing indices stored in the 3D mesh buffer.
    public IntPtr IndicesPtr => API.MeshBufferGetIndices(Handle);
  }

  /// Gaussian Blur module.
  public sealed class GaussianBlur : CustomObject
  {
    /// Creates a wrapped or uninitialized Gaussian Blur module.
    internal GaussianBlur(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of Gaussian Blur module.
    public GaussianBlur(Device device, float sigma = 3.0f, int samples = 9, bool hardwareFiltering = true)
    : base(new DestroyDelegate(API.GaussianBlurDestroy), IntPtr.Zero)
    {
      _handle = API.GaussianBlurCreate(device?.Handle ?? IntPtr.Zero, sigma, samples, hardwareFiltering);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a Gaussian Blur module");
    }

    /// Applies blur through use of an an intermediate texture. Both destination and intermediary textures
    /// must have drawable attribute. If source texture has drawable attribute, then it can be passed as
    /// destination texture as well.
    public void Update(Texture destination, Texture intermediary, Texture source)
    {
      if (!API.GaussianBlurUpdate(Handle, destination?.Handle ?? IntPtr.Zero,
        intermediary?.Handle ?? IntPtr.Zero, source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not execute Gaussian Blur module");
    }

    /// Applies a horizontal blur from source to intermediary texture, then a vertical blur from
    /// intermediary to destination texture. Both destination and intermediary textures must have drawable
    /// attribute. If source texture has drawable attribute, then it can be passed as destination texture as
    /// well. If destination position and/or intermediate rectangle are specified, then only a portion of
    /// intermediary texture will be copied to destination at the given offset.
    public void UpdateAt(Texture destination, Texture intermediary, Texture source, Point? destPos = null,
      Rect? intermRect = null)
    {
      Point destPos1 = destPos ?? default;
      Rect intermRect1 = intermRect ?? default;
      if (!API.GaussianBlurUpdateAt(Handle, destination?.Handle ?? IntPtr.Zero,
        intermediary?.Handle ?? IntPtr.Zero, source?.Handle ?? IntPtr.Zero, ref destPos1, ref intermRect1))
        throw new API.Exception("Could not execute Gaussian Blur module at the given rectangle");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.GaussianBlurGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the gaussian blur module");

        return new Device(handle);
      }
    }

    /// Returns maximum number of samples supported by a fixed-sample blur, which enables higher performance.
    public int FixedSamples => API.GaussianBlurGetFixedSamples(Handle);

    /// Returns or changes a number of samples in the blur kernel. The value must be odd, between 5 and 15.
    public int Samples
    {
      get => API.GaussianBlurGetSamples(Handle);
      set => API.GaussianBlurSetSamples(Handle, value);
    }

    /// Returns or changes a value of sigma, which defines the curve distribution of the blur. It must be
    /// equal or higher than zero.
    public float Sigma
    {
      get => API.GaussianBlurGetSigma(Handle);
      set => API.GaussianBlurSetSigma(Handle, value);
    }

    /// Returns or changes chroma offset for an optional color adjustment. Chroma offset must be in range
    /// of [0, inf), where 1 means no adjustment is to be performed.
    public float Chroma
    {
      get => API.GaussianBlurGetSigma(Handle);
      set => API.GaussianBlurSetSigma(Handle, value);
    }

    /// Returns or changes whether hardware filtering is used to accelerate the blur.
    public bool HardwareFiltering
    {
      get => API.GaussianBlurGetHardwareFiltering(Handle);
      set => API.GaussianBlurSetHardwareFiltering(Handle, value);
    }
  }

  /// Kawase Blur module.
  public sealed class KawaseBlur : CustomObject
  {
    /// Creates a wrapped or uninitialized Kawase Blur module.
    internal KawaseBlur(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of Kawase Blur module.
    public KawaseBlur(Device device)
    : base(new DestroyDelegate(API.KawaseBlurDestroy), IntPtr.Zero)
    {
      _handle = API.KawaseBlurCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a Kawase Blur module");
    }

    /// Applies an advancing blur kernel from source to intermediary texture, then a vertical blur from
    /// intermediary to destination texture. Both destination and intermediary textures must have drawable
    /// attribute. If source texture has drawable attribute, then it can be passed as destination texture
    /// as well.
    public void Update(Texture destination, Texture intermediary, Texture source)
    {
      if (!API.KawaseBlurUpdate(Handle, destination?.Handle ?? IntPtr.Zero,
        intermediary?.Handle ?? IntPtr.Zero, source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not execute Kawase Blur module");
    }

    /// Applies blur in a single pass with the given custom offset. This can be used simultaneously when
    /// performing downsampling.
    public void SinglePass(Texture destination, Texture source, Vector2 offset)
    {
      if (!API.KawaseBlurSinglePass(Handle, destination?.Handle ?? IntPtr.Zero,
        source?.Handle ?? IntPtr.Zero, ref offset))
        throw new API.Exception("Could not execute a single pass of Kawase Blur");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.KawaseBlurGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the kawase blur module");

        return new Device(handle);
      }
    }

    /// Returns or changes a number of blur passes (each pass includes two processing steps).
    public int Passes
    {
      get => API.KawaseBlurGetPasses(Handle);
      set => API.KawaseBlurSetPasses(Handle, value);
    }

    /// Returns or changes pixel offset for each individual step.
    public Vector2 Offset
    {
      get
      {
        API.KawaseBlurGetOffset(Handle, out Vector2 offset);
        return offset;
      }

      set => API.KawaseBlurSetOffset(Handle, ref value);
    }
  }

  /// Color dithering module that can be used when rendering from higher precision color formats (e.g. HDR)
  /// to lower precision, effectively reducing banding effect.
  public sealed class ColorDithering : CustomObject
  {
    /// Creates a wrapped or uninitialized Color Dithering module.
    internal ColorDithering(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of Color Dithering module.
    public ColorDithering(Device device)
    : base(new DestroyDelegate(API.ColorDitheringDestroy), IntPtr.Zero)
    {
      _handle = API.ColorDitheringCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a Color Dithering module");
    }

    /// Performs color dithering of the source texture rendering a full-screen quad.
    public void Execute(Texture source)
    {
      if (!API.ColorDitheringExecute(Handle, source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not execute Color Dithering module");
    }

    /// Performs color dithering of the source texture rendering to destination texture.
    public void Execute(Texture destination, Texture source)
    {
      if (!API.ColorDitheringExecuteTo(Handle, destination?.Handle ?? IntPtr.Zero,
        source?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not execute Color Dithering module for the destination texture");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ColorDitheringGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the color dithering module");

        return new Device(handle);
      }
    }

    /// Returns or changes a target color quantization format for dithering.
    public ColorDitheringFormat Format
    {
      get => API.ColorDitheringGetFormat(Handle);
      set => API.ColorDitheringSetFormat(Handle, value);
    }
  }

  /// A module that shows a selection highlight around one or more 2D and/or 3D objects in multiple colors
  /// with an optional outline.
  public sealed class SelectionHighlight : CustomObject
  {
    /// Creates a wrapped or uninitialized Selection Highlight module.
    internal SelectionHighlight(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of Selection Highlight module.
    public SelectionHighlight(Device device, Point size, PixelFormat depthStencil = PixelFormat.D32F,
      int samples = 8, bool grayscale = false)
    : base(new DestroyDelegate(API.SelectionHighlightDestroy), IntPtr.Zero)
    {
      _handle = API.SelectionHighlightCreate(device?.Handle ?? IntPtr.Zero, ref size, depthStencil, samples,
        grayscale);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a Selection Highlight module");
    }

    /// Clears the primary rendering textures.
    public void Clear()
    {
      if (!API.SelectionHighlightClear(Handle))
        throw new API.Exception("Could not clear Selection Highlight's rendering textures");
    }

    /// Begins rendering with the module.
    public void Begin()
    {
      if (!API.SelectionHighlightBegin(Handle))
        throw new API.Exception("Could not start rendering with Selection Highlight module");
    }

    /// Finishes rendering with the module.
    public void End() => API.SelectionHighlightEnd(Handle);

    /// Performs final post-filtering of the textures to produce the highlight selection effect.
    public void Filter()
    {
      if (!API.SelectionHighlightFilter(Handle))
        throw new API.Exception("Could not perform filtering in Selection Highlight module");
    }

    /// Returns texture of the given type.
    public Texture Texture(SelectionHighlightTextureType type = SelectionHighlightTextureType.Highlight)
    {
      IntPtr handle = API.SelectionHighlightGetTexture(Handle, type);
      return handle != IntPtr.Zero ? new Texture(handle) : null;
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SelectionHighlightGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the selection " +
            "highlight module");

        return new Device(handle);
      }
    }

    /// Returns or updates size of the rendering surfaces.
    public Point Size
    {
      get
      {
        API.SelectionHighlightGetSize(Handle, out Point size);
        return size;
      }

      set
      {
        if (!API.SelectionHighlightSetSize(Handle, ref value))
          throw new API.Exception("Could not update the size of Selection Highlight rendering textures");

      }
    }

    /// Pixel format used for depth/stencil buffer.
    public PixelFormat DepthStencil => API.SelectionHighlightGetDepthStencil(Handle);

    /// Returns number of samples used for multisampling.
    public int Samples => API.SelectionHighlightGetSamples(Handle);

    /// Whether the technique is performed in a more optimal, grayscale mode, instead of full color.
    public bool Grayscale => API.SelectionHighlightGetGrayscale(Handle);

    /// Returns or changes parameters that define the behavior and characteristics of the technique.
    public SelectionHighlightParameters Parameters
    {
      get
      {
        API.SelectionHighlightGetParameters(Handle, out SelectionHighlightParameters parameters);
        return parameters;
      }

      set
      {
        if (!API.SelectionHighlightSetParameters(Handle, ref value))
          throw new API.Exception("Could not update Selection Highlight's module parameters");
      }
    }

    /// Indicates that the rendering is currently performed to the primary textures.
    public bool Rendering => API.SelectionHighlightRendering(Handle);

    /// Returns texture coordinates that can be passed to canvas for rendering the highlight.
    public Quad TextureCoordinates
    {
      get
      {
        API.SelectionHighlightGetTextureCoordinates(Handle, out Quad coords);
        return coords;
      }
    }
  }

  /// Module that performs both ground fog and distance-based fog simultaneously.
  public sealed class SpatialFog : CustomObject
  {
    /// Creates a wrapped or uninitialized spatial fog module.
    internal SpatialFog(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of spatial fog module.
    public SpatialFog(Device device)
    : base(new DestroyDelegate(API.SpatialFogDestroy), IntPtr.Zero)
    {
      _handle = API.SpatialFogCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a spatial fog module");
    }

    /// Computes fog and applies it to the destination high-dynamic range (HDR) color texture. The module
    /// requires an existing linear depths texture (optionally multisampled) to be provided.
    public void Execute(Texture destination, Texture linearDepths)
    {
      if (!API.SpatialFogExecute(Handle, destination?.Handle ?? IntPtr.Zero,
        linearDepths?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Failed executing spatial fog module");
    }

    /// Computes fog and applies it to the destination "glassy" composition texture. The module
    /// requires an existing linear depths texture (optionally multisampled) to be provided.
    public void ExecuteGlassy(Texture destination, Texture linearDepths)
    {
      if (!API.SpatialFogExecuteGlassy(Handle, destination?.Handle ?? IntPtr.Zero,
        linearDepths?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Failed executing 'glassy' variation of spatial fog module");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SpatialFogGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the spatial fog module");

        return new Device(handle);
      }
    }

    /// Returns or changes parameters that define spatial fog characteristics.
    public FogParameters Parameters
    {
      get
      {
        API.SpatialFogGetParameters(Handle, out FogParameters parameters);
        return parameters;
      }

      set
      {
        if (!API.SpatialFogSetParameters(Handle, ref value))
          throw new API.Exception("Could not update spatial fog characteristics");
      }
    }

    /// Returns or changes 3D view transformation matrix.
    public Matrix4x4 View
    {
      get
      {
        API.SpatialFogGetView(Handle, out Matrix4x4 view);
        return view;
      }

      set
      {
        if (!API.SpatialFogSetView(Handle, ref value))
          throw new API.Exception("Could not update spatial fog's 3D view transformation matrix");
      }
    }

    /// Returns or changes 3D projection transformation matrix.
    public Matrix4x4 Projection
    {
      get
      {
        API.SpatialFogGetProjection(Handle, out Matrix4x4 projection);
        return projection;
      }

      set
      {
        if (!API.SpatialFogSetProjection(Handle, ref value))
          throw new API.Exception("Could not update spatial fog's 3D projection transformation matrix");
      }
    }

    /// Returns or changes spatial fog calculation formula.
    public FogFormula Formula
    {
      get => API.SpatialFogGetFormula(Handle);

      set
      {
        if (!API.SpatialFogSetFormula(Handle, value))
          throw new API.Exception("Could not update spatial fog's calculation formula");
      }
    }

    /// Returns or changes distance calculation formula for the spatial fog.
    public DepthFogDistance Distance
    {
      get => API.SpatialFogGetDepthDistance(Handle);

      set
      {
        if (!API.SpatialFogSetDepthDistance(Handle, value))
          throw new API.Exception("Could not update distance calculation formula for the spatial fog");
      }
    }
  }

  /// Module for storing and working with lights in a 3D scene.
  public sealed class SceneLights : CustomObject
  {
    /// Creates a wrapped or uninitialized 3D lights management module.
    internal SceneLights(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D lights management module.
    public SceneLights(Device device)
    : base(new DestroyDelegate(API.SceneLightsDestroy), IntPtr.Zero)
    {
      _handle = API.SceneLightsCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D lights management module");
    }

    /// Adds and returns a new light with default parameters.
    public void Add(SceneLight? light = null)
    {
      IntPtr lightPtr = API.SceneLightsAdd(Handle);
      if (lightPtr == IntPtr.Zero)
        throw new API.Exception("Could not add a new 3D light element");

      if (light is not null)
        Marshal.StructureToPtr(light, lightPtr, false);
    }

    /// Retrieves an existing 3D light parameters associated with the given index.
    public SceneLight GetLight(int index)
    {
      IntPtr lightPtr = API.SceneLightsGetElement(Handle, index);
      if (lightPtr == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve a 3D light element ({index})");

      return Marshal.PtrToStructure<SceneLight>(lightPtr);
    }

    /// Changes an existing 3D light parameters associated with the given index.
    public void SetLight(int index, SceneLight light)
    {
      IntPtr lightPtr = API.SceneLightsGetElement(Handle, index);
      if (lightPtr == IntPtr.Zero)
        throw new API.Exception($"Cannot access a 3D light element ({index})");

      Marshal.StructureToPtr(light, lightPtr, false);
    }

    /// Erases light with the given index.
    public void Erase(int index)
    {
      if (!API.SceneLightsErase(Handle, index))
        throw new API.Exception($"Failed erasing a given 3D light ({index})");
    }

    /// Erases light with the given index.
    public void Clear() => API.SceneLightsClear(Handle);

    /// Calculates cluster frustums, then performs light culling for each of the clusters.
    /// This populates an internal GPU buffer containing light indices for each of the clusters.
    public void Execute(Matrix4x4 view, Matrix4x4 projection)
    {
      if (!API.SceneLightsExecute(Handle, ref view, ref projection))
        throw new API.Exception("Failed performing a clustered 3D light computation");
    }

    /// Renders light culling debug information. A call to "Execute" must be performed before this.
    public void RenderDebug(bool intensity)
    {
      if (!API.SceneLightsRenderDebug(Handle, intensity))
        throw new API.Exception("Failed rendering clustered 3D light information");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SceneLightsGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the scene lights module");

        return new Device(handle);
      }
    }

    /// Returns total number of light sources.
    public int Count => API.SceneLightsGetCount(Handle);

    /// Returns or updates size of viewing area, which should match the rendereable surface size in pixels.
    public Point Size
    {
      get
      {
        API.SceneLightsGetViewSize(Handle, out Point size);
        return size;
      }

      set => API.SceneLightsSetViewSize(Handle, ref value);
    }

    /// Size of each individual light cluster on the screen. Visible light indices will be calculated for
    /// each of the clusters, so scenes will more tighly packed light sources would benefit from smaller
    /// cluster size. A scene with either few light sources or light sources that occupy very large visible
    /// areas would benefit from bigger cluster sizes.
    public int ClusterSize
    {
      get => API.SceneLightsGetClusterSize(Handle);
      set => API.SceneLightsSetClusterSize(Handle, value);
    }

    /// Number of cluster slices per viewable depth on the screen. Visible light indices will be calculated
    /// for each of the clusters, so scenes will more tighly packed light sources would benefit from bigger
    /// number of depth slices. A scene with either few light sources or light sources that occupy very
    /// large visible areas would benefit from smaller number of depth slices.
    public int DepthSlices
    {
      get => API.SceneLightsGetDepthSlices(Handle);
      set => API.SceneLightsSetDepthSlices(Handle, value);
    }

    /// Returns or changes a mode used for packing calculated light indices for each of the clusters.
    /// High quality mode enables more light sources to be used simultaneously, whereas performance mode
    /// improves rendering performance on weaker GPUs assuming that few light sources are present.
    public ClustersCullingMode CullingMode
    {
      get => API.SceneLightsGetCullingMode(Handle);
      set => API.SceneLightsSetCullingMode(Handle, value);
    }

    /// Returns number of calculated clusters that are accomodated horizontally and vertically accordingly.
    public Point Clusters
    {
      get
      {
        API.SceneLightsGetClusters(Handle, out Point clusters);
        return clusters;
      }
    }

    /// Returns light indices that have been calculated for each of the clusters.
    /// This is useful for a custom rendering engine that want to use clustered light information.
    public Buffer Indices
    {
      get
      {
        IntPtr handle = API.SceneLightsGetIndices(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve cluster indices from the scene lights module");

        return new Buffer(handle);
      }
    }
  }

  /// Container for storing 3D object materials.
  public sealed class ObjectMaterials : CustomObject
  {
    /// Creates a wrapped or uninitialized 3D object materials container.
    internal ObjectMaterials(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D object materials container.
    public ObjectMaterials()
    : base(new DestroyDelegate(API.ObjectMaterialsDestroy), IntPtr.Zero)
    {
      _handle = API.ObjectMaterialsCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D object materials container");
    }

    /// Adds and returns a new 3D object material with default parameters.
    public void Add(ObjectMaterial? material)
    {
      IntPtr materialPtr = API.ObjectMaterialsAdd(Handle);
      if (materialPtr == IntPtr.Zero)
        throw new API.Exception("Could not add a new 3D object material to the container");

      if (material is not null)
        Marshal.StructureToPtr(material, materialPtr, false);
    }

    /// Retrieves an existing 3D object material associated with the given index.
    public ObjectMaterial GetMaterial(int index)
    {
      IntPtr materialPtr = API.ObjectMaterialsGetElement(Handle, index);
      if (materialPtr == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve a 3D object material ({index})");

      return Marshal.PtrToStructure<ObjectMaterial>(materialPtr);
    }

    /// Changes an existing 3D object material associated with the given index.
    public void SetMaterial(int index, ObjectMaterial material)
    {
      IntPtr materialPtr = API.ObjectMaterialsGetElement(Handle, index);
      if (materialPtr == IntPtr.Zero)
        throw new API.Exception($"Cannot access a 3D object material ({index})");

      Marshal.StructureToPtr(material, materialPtr, false);
    }

    /// Erases material with the given index.
    public void Erase(int index)
    {
      if (!API.ObjectMaterialsErase(Handle, index))
        throw new API.Exception($"Failed erasing a given 3D object material ({index})");
    }

    /// Removes all existing materials.
    public void Clear() => API.ObjectMaterialsClear(Handle);

    /// Returns total number of materials.
    public int Count => API.ObjectMaterialsGetCount(Handle);
  }

  /// A container and management module for drawable textures for rendering and processing 3D scene.
  public sealed class TextureCabinet : CustomObject
  {
    /// Cumulative attributes that define rendering characteristics of the scene.
    public struct Attribute
    {
      /// Screen-Space Ambient Occlusion technique.
      public const uint AmbientOcclusion = 0x0001u;

      /// Downscales SSAO texture to enable 4x faster performance on low and mid-end GPUs.
      public const uint AmbientOcclusionDownscale = 0x0002u;

      /// Bloom (glare) technique.
      public const uint Bloom = 0x0004u;

      /// Bloom is rendered at half of resolution to accomodate for higher display scales.
      public const uint BloomDownscale = 0x0008u;

      /// Bloom is applied using cubic interpolation for higher quality look.
      public const uint BloomCubic = 0x0010u;

      /// Linear depths are provided for techniques such as spatial fog or water.
      public const uint LinearDepths = 0x0020u;

      /// Accurate order-independent transparency (OIT) single-pass technique.
      public const uint Glassy = 0x0040u;

      /// Approximate order-independent transparency (OIT) single-pass technique.
      public const uint GlassyFast = 0x0080u;

      /// Add to order-independent transparency (OIT) technique support for spatial fog effects.
      public const uint GlassyFog = 0x0100u;

      /// Frosted Glass effect added to order-independent transparency (OIT) techniques.
      public const uint GlassyFrosted = 0x0200u;
    }

    /// Creates a wrapped or uninitialized texture container and management module.
    internal TextureCabinet(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of texture container and management module.
    public TextureCabinet(Device device, Point size, TextureFidelity fidelity = TextureFidelity.High,
      int samples = 8, PixelFormat depthStencil = PixelFormat.Unknown)
    : base(new DestroyDelegate(API.TextureCabinetDestroy), IntPtr.Zero)
    {
      _handle = API.TextureCabinetCreate(device?.Handle ?? IntPtr.Zero, ref size, fidelity, samples,
        depthStencil);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a texture container and management module");
    }

    /// Returns texture from the container of the given type.
    public Texture GetTexture(TextureCabinetType type)
    {
      IntPtr handle = API.TextureCabinetGetTexture(Handle, type);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve texture ({type}) from the texture cabinet");

      return new Texture(handle);
    }

    /// Clears the content of textures related to the given pass.
    /// Note: the given color is used only in main/color pass.
    public void Clear(TextureCabinetPass pass, FloatColor? color = null)
    {
      GCHandle? colorPinned = color is not null ? GCHandle.Alloc(color, GCHandleType.Pinned) : null;
      try
      {
        if (!API.TextureCabinetClear(Handle, pass, colorPinned?.AddrOfPinnedObject() ?? IntPtr.Zero))
          throw new API.Exception($"Could not clear texture cabinet for the given pass ({pass})");
      }
      finally
      {
        colorPinned?.Free();
      }
    }

    /// Begins rendering the given pass.
    public void Begin(TextureCabinetPass pass)
    {
      if (!API.TextureCabinetBegin(Handle, pass))
        throw new API.Exception($"Could not begin rendering to texture cabinet for the given " +
          "pass ({pass})");
    }

    /// Ends rendering the pass that was previously started.
    public void End() => API.TextureCabinetEnd(Handle);

    /// Performs processing and filtering of the textures according to the filter type. For occlusion,
    /// performs depth linearization and computes ambient occlusion. For bloom, downscales the color texture,
    /// applies blur to compute glare, combines the effects and then performs HDR tone-mapping.
    /// For order-independent transparency (OIT or "glassy") pass, performs compositioning of the textures.
    /// This step must be performed outside of "Begin" and "End" block after finishing each corresponding
    /// rendering pass but before starting next one. For performance reasons, the filtering should be
    /// performed as late as possible after finishing previous pass (e.g. after doing some CPU calculations)
    /// to enable GPU to do work in parallel. Note: OIT rendering pass does not require projection matrix.
    public void Filter(TextureCabinetFilterType filter, Matrix4x4? projection = null)
    {
      Matrix4x4 projection1 = projection ?? Matrix4x4.Identity;
      if (!API.TextureCabinetFilter(Handle, filter, ref projection1))
        throw new API.Exception("Could not perform post-processing of texture cabinet for the given " +
          $"filter ({filter})");
    }

    /// Renders a multisample HDR color texture into a final color texture.
    public void Resolve()
    {
      if (!API.TextureCabinetResolve(Handle))
        throw new API.Exception("Could not render a multisample HDR texture to final color texture");
    }

    /// Renders the final color texture on the current rendering surface.
    public void Present()
    {
      if (!API.TextureCabinetPresent(Handle))
        throw new API.Exception("Could not render a final color texture on the active rendering surface");
    }

    /// Returns device associated with the container.
    public Device Device
    {
      get
      {
        IntPtr handle = API.TextureCabinetGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the texture cabinet");

        return new Device(handle);
      }
    }

    /// Returns or updates the size of textures in pixels.
    public Point Size
    {
      get
      {
        API.TextureCabinetGetSize(Handle, out Point size);
        return size;
      }

      set
      {
        if (!API.TextureCabinetSetSize(Handle, ref value))
          throw new API.Exception($"Failed changing texture cabinet to size ({value})");
      }
    }

    /// Returns number of samples used in the textures.
    public int Samples => API.TextureCabinetGetSamples(Handle);

    /// Returns format used for depth/stencil buffers.
    public PixelFormat DepthStencil => API.TextureCabinetGetDepthStencil(Handle);

    /// Returns level of fidelity used for choosing integrated pixel formats.
    public TextureFidelity Fidelity => API.TextureCabinetGetFidelity(Handle);

    /// Returns or changes attributes that define the rendering characteristics of the scene.
    public uint Attributes
    {
      get => API.TextureCabinetGetAttributes(Handle);

      set
      {
        if (!API.TextureCabinetSetAttributes(Handle, value))
          throw new API.Exception("Failed updating texture cabinet's rendering characteristics");
      }
    }

    /// Returns final color texture that can contains the scene.
    public Texture Texture
    {
      get
      {
        IntPtr handle = API.TextureCabinetGetFinalTexture(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve final color texture from the texture cabinet");

        return new Texture(handle);
      }
    }

    /// Indicates that one of the passes is currently being rendered.
    public bool Rendering => API.TextureCabinetRendering(Handle);

    /// Returns or changes tone-mapping and bloom rendering characteristics.
    public ToneMappingBloom ToneMappingBloom
    {
      get
      {
        API.TextureCabinetGetToneMappingBloom(Handle, out ToneMappingBloom parameters);
        return parameters;
      }

      set => API.TextureCabinetSetToneMappingBloom(Handle, ref value);
    }

    /// Returns or changes ambient occlusion characteristics.
    public AmbientOcclusionParameters AmbientOcclusionParameters
    {
      get
      {
        API.TextureCabinetGetAmbientOcclusionParameters(Handle, out AmbientOcclusionParameters parameters);
        return parameters;
      }

      set => API.TextureCabinetSetAmbientOcclusionParameters(Handle, ref value);
    }
  }

  /// A source that casts shadows that can be shared among multiple light sources.
  public sealed class ShadowCaster : CustomObject
  {
    /// Creates a wrapped or uninitialized shadow caster.
    internal ShadowCaster(IntPtr handle) : base(null, handle) { }

    /// Clears the integrated textures.
    public void Clear()
    {
      if (!API.ShadowCasterClear(Handle))
        throw new API.Exception("Could not clear shadow caster's textures");
    }

    /// Starts rendering to shadow caster's texture to build a shadow map.
    public void Begin()
    {
      if (!API.ShadowCasterBegin(Handle))
        throw new API.Exception("Could not start rendering to shadow caster's texture");
    }

    /// Finishes rendering to shadow caster's texture.
    public void End() => API.ShadowCasterEnd(Handle);

    /// Performs filtering on the shadow map and accomodates it on the atlas.
    public void Filter()
    {
      if (!API.ShadowCasterFilter(Handle))
        throw new API.Exception("Could not filter shadow caster's texture into a shadow map");
    }

    /// Returns one of the textures currently used by the shadow caster.
    public Texture GetTexture(int index)
    {
      IntPtr handle = API.ShadowCasterGetTexture(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve shadow caster's texture ({index})");

      return new Texture(handle);
    }

    /// Returns device associated with the shadow caster.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ShadowCasterGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the shadow caster");

        return new Device(handle);
      }
    }

    /// Returns position in pixels of the shadow map in parent atlas.
    public Point Position
    {
      get
      {
        API.ShadowCasterGetPosition(Handle, out Point position);
        return position;
      }
    }

    /// Returns size of the shadow map in pixels.
    public Point Size
    {
      get
      {
        API.ShadowCasterGetSize(Handle, out Point size);
        return size;
      }
    }

    /// Returns or updates a combined view and projection matrices of the shadow caster.
    public Matrix4x4 ViewProjection
    {
      get
      {
        API.ShadowCasterGetViewProjection(Handle, out Matrix4x4 viewProjection);
        return viewProjection;
      }

      set => API.ShadowCasterSetViewProjection(Handle, ref value);
    }

    /// Indicates whether rendering is taking place.
    public bool Rendering => API.ShadowCasterRendering(Handle);

    /// Returns parent texture atlas associated with the shadow caster.
    public ShadowCastingAtlas Atlas
    {
      get
      {
        IntPtr handle = API.ShadowCasterGetAtlas(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve texture atlas associated with the shadow caster");

        return new ShadowCastingAtlas(handle);
      }
    }
  }

  /// Module that manages shadow maps produced by one or more shadow casters.
  public sealed class ShadowCastingAtlas : CustomObject
  {
    /// Creates a wrapped or uninitialized shadow casting atlas.
    internal ShadowCastingAtlas(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of shadow casting atlas module.
    public ShadowCastingAtlas(Device device, Point size, TechniqueShadows technique = TechniqueShadows.EVSM,
      int samples = 8, int padding = 1)
    : base(new DestroyDelegate(API.ShadowCastingAtlasDestroy), IntPtr.Zero)
    {
      _handle = API.ShadowCastingAtlasCreate(device?.Handle ?? IntPtr.Zero, ref size, technique, samples,
        padding);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a shadow casting atlas container");
    }

    /// Creates a new shadow caster with the given size and associated with the given view. Shared parameter
    /// determines whether the caster should share its shadow map textures with others to reduce memory
    /// footprint. In a partial rendering, where more portions are added to shadow map in next frames,
    /// the shadow caster must own its shadow map.
    public ShadowCaster Add(Point size, bool shared = true)
    {
      IntPtr handle = API.ShadowCastingAtlasAdd(Handle, ref size, shared);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Failed adding a new shadow caster to shadow mapping atlas");

      return new ShadowCaster(handle);
    }

    /// Removes an existing shadow caster.
    public void Erase(ShadowCaster caster) => API.ShadowCastingAtlasErase(Handle, caster.Handle);

    /// Removes all existing shadow casters.
    public void Clear() => API.ShadowCastingAtlasClear(Handle);

    /// Returns shadow caster for the given index.
    public ShadowCaster Caster(int index)
    {
      IntPtr handle = API.ShadowCastingAtlasGetCaster(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve a shadow mapping caster ({index}) from the atlas");

      return new ShadowCaster(handle);
    }

    /// Clears the surface of shadow mapping atlas texture using border color to avoid edge artifacts.
    /// This is typically required after shadow parameters have been changed.
    public void BorderFill()
    {
      if (!API.ShadowCastingAtlasBorderFill(Handle))
        throw new API.Exception("Could not clear shadow mapping atlas texture with border color");
    }

    /// Returns device associated with the atlas.
    public Device Device
    {
      get
      {
        IntPtr handle = API.ShadowCastingAtlasGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the shadow casting atlas");

        return new Device(handle);
      }
    }

    /// Returns shadow rendering technique.
    public TechniqueShadows Technique => API.ShadowCastingAtlasGetTechnique(Handle);

    /// Parameters that define how shadow maps are rendered and their characteristics.
    public ShadowParameters Parameters
    {
      get
      {
        API.ShadowCastingAtlasGetParameters(Handle, out ShadowParameters parameters);
        return parameters;
      }

      set
      {
        if (!API.ShadowCastingAtlasSetParameters(Handle, ref value))
          throw new API.Exception("Could not update shadow mapping parameters in atlas");
      }
    }

    /// Returns size of the atlas in pixels.
    public Point Size
    {
      get
      {
        API.ShadowCastingAtlasGetSize(Handle, out Point size);
        return size;
      }
    }

    /// Returns number of samples used for shadow map multisampling.
    public int Samples => API.ShadowCastingAtlasGetSamples(Handle);

    /// Returns padding used to accomodate shadow maps.
    public int Padding => API.ShadowCastingAtlasGetPadding(Handle);

    /// Returns shadow mapping atlas texture containing existing shadow maps.
    public Texture Texture
    {
      get
      {
        IntPtr handle = API.ShadowCastingAtlasGetTexture(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve texture from the shadow casting atlas");

        return new Texture(handle);
      }
    }

    /// Returns number of existing shadow casters.
    public int Count => API.ShadowCastingAtlasGetCount(Handle);
  }

  /// A module for rendering 3D scenes.
  public sealed class Scene : CustomObject
  {
    /// Cumulative attributes that define rendering behavior of the scene.
    public struct Attribute
    {
      /// Instancing technique will be used during rendering.
      public const uint Instancing = 0x01u;

      /// Vertex normals will be processed and taken into account.
      public const uint Normals = 0x02u;

      /// Vertex colors will be processed and taken into account.
      public const uint Coloring = 0x04u;

      /// Cubic interpolation will be used for texture mapping.
      public const uint TexturingCubic = 0x08u;

      /// Cubic interpolation will be used for shadow mapping.
      public const uint ShadowsCubic = 0x10u;

      /// Perform adjustments for Order-independent transparency (OIT) rendering.
      public const uint Glassy = 0x20u;

      /// The whole scene is rendered in a single pass with ambient occlusion.
      public const uint SinglePass = 0x40u;

      // Attributes specific to depth/normals scene.

      /// Generate output for depth pre-pass, which at minimum includes normals.
      /// Note: it is possible to do depth pre-pass without normals by excluding this attribute.
      public const uint DepthPrePass = 0x80u;

      /// Produce linear depths as part of depth pre-pass for spatial fog or water effects.
      /// Note: this requires DepthPrePass flag to be enabled and thus always includes normals.
      public const uint LinearDepths = 0x100u;

      // Status attributes.

      /// Scene is a modeling scene, which means it supports textures and materials.
      public const uint Modeling = 0x1000u;
    }

    /// Creates a wrapped or uninitialized 3D scene rendering module.
    internal Scene(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of 3D scene rendering module from an existing handle.
    private Scene(Device _, IntPtr handle)
    : base(new DestroyDelegate(API.SceneDestroy), handle)
    {
    }

    /// Creates a new instance of 3D depths/normals rendering scene.
    public static Scene CreateDepthNormals(Device device)
    {
      IntPtr handle = API.SceneCreateDepthsNormals(device?.Handle ?? IntPtr.Zero);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D depth/normals rendering scene");

      return new Scene(device, handle);
    }

    /// Creates a new instance of 3D modeling rendering scene.
    public static Scene CreateModeling(Device device)
    {
      IntPtr handle = API.SceneCreateModeling(device?.Handle ?? IntPtr.Zero);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D modeling rendering scene");

      return new Scene(device, handle);
    }

    /// Returns items in vertex element declaration associated with the given index.
    public VertexElement[] GetVertexElements(int index)
    {
      int elementCount = API.SceneGetVertexElementCount(Handle, index);
      IntPtr nativePtr = API.SceneGetVertexElements(Handle, index);
      VertexElement[] elements = new VertexElement[elementCount];

      for (int i = 0; i < elementCount; ++i)
      {
        IntPtr nativeOffset = new (nativePtr.ToInt64() + i * Marshal.SizeOf<VertexElement>());
        elements[i] = Marshal.PtrToStructure<VertexElement>(nativeOffset);
      }
      return elements;
    }

    /// Changes vertex element declaration associated with the given index.
    public void SetVertexElements(int index, VertexElement[] elements)
    {
      if (!API.SceneSetVertexElements(Handle, index, elements, elements.Length))
        throw new API.Exception($"Failed to modify vertex element declaration ({index})" +
          "of a 3D rendering scene");
    }

    /// Changes vertex element declaration to match the one of 2D and 3D text rendering module, associated with
    /// the given index.
    public void SetVertexElementsFromTextModeller(int index)
    {
      if (!API.SceneSetVertexElementsFromTextModeller(Handle, index))
        throw new API.Exception($"Failed to modify vertex element declaration ({index}) of " +
          "a 3D rendering scene from text modeller");
    }

    /// Supplies instance information to the pipeline. The maximum number of elements must not exceed
    /// the value returned by "InstancesCount".
    public void Instances(Matrix4x4[] transforms, FloatColor[] colors, int count)
    {
      if (count > transforms.Length || count > colors.Length)
        throw new API.Exception($"Number of instances ({count}) exceends the length of supplied array(s)");

      if (!API.SceneInstances(Handle, transforms, colors, count))
        throw new API.Exception("Could not supply instance information to 3D rendering scene");
    }

    /// Activates the appropriate shader program and begins rendering the scene.
    public void Begin()
    {
      if (!API.SceneBegin(Handle))
        throw new API.Exception("Could not start rendering a 3D scene");
    }

    /// Finishes rendering the scene and deactivates previously activated shader program.
    public void End() => API.SceneEnd(Handle);

    /// Clears cached buffers and resources in the scene.
    public void ResetCache() => API.SceneResetCache(Handle);

    /// Returns sampler of the given type associated with the scene.
    public Sampler GetSampler(SceneSamplerType type)
    {
      IntPtr handle = API.SceneGetSampler(Handle, type);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve sampler ({type}) associated with " +
          "a 3D rendering scene");

      return new Sampler(handle);
    }

    /// Returns texture of the given type associated with the scene.
    public Texture GetTexture(SceneTextureType type)
    {
      IntPtr handle = API.SceneGetTexture(Handle, type);
      return handle != IntPtr.Zero ? new Texture(handle) : null;
    }

    /// Changes texture of the given type associated with the scene.
    public void SetTexture(SceneTextureType type, Texture texture)
    {
      if (!API.SceneSetTexture(Handle, texture?.Handle ?? IntPtr.Zero, type))
        throw new API.Exception($"Could not change texture type ({type}) in a 3D rendering scene");
    }

    /// Configures rendering parameters for the scene, retrieves light indices from light container module
    /// and updates internal buffers required for rendering the scene. This must be called outside of
    /// "Begin" / "End" block, but before starting any rendering. Ambient occlusion texture, shadow casting
    /// atlas, texture cabinet, view and projection matrices have been assigned prior this call.
    /// Returns true on success. For depth/normals rendering, this step is not required.
    public bool TryPrepare() => API.ScenePrepare(Handle);

    /// Configures rendering parameters for the scene, retrieves light indices from light container module
    /// and updates internal buffers required for rendering the scene. This must be called outside of
    /// "Begin" / "End" block, but before starting any rendering. Ambient occlusion texture, shadow casting
    /// atlas, view and projection matrices have been assigned prior this call.
    public void Prepare()
    {
      if (!TryPrepare())
        throw new API.Exception("Could not prepare 3D scene for rendering");
    }

    /// Returns device associated with the scene.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SceneGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the 3D rendering scene");

        return new Device(handle);
      }
    }

    /// Returns or changes currently active index of vertex elements declaration.
    public int ActiveVertexElements
    {
      get => API.SceneGetActiveVertexElements(Handle);

      set
      {
        if (!API.SceneSetActiveVertexElements(Handle, value))
          throw new API.Exception($"Could not set active vertex element declaration ({value})" +
            "of a 3D rendering scene");
      }
    }

    /// Returns or changes attributes that define the rendering behavior of the scene.
    public uint Attributes
    {
      get => API.SceneGetAttributes(Handle);

      set
      {
        if (!API.SceneSetAttributes(Handle, value))
          throw new API.Exception("Could not change rendering attributes of a 3D rendering scene");
      }
    }

    /// Returns or changes "World" transformation matrix.
    public Matrix4x4 World
    {
      get
      {
        API.SceneGetWorld(Handle, out Matrix4x4 world);
        return world;
      }

      set => API.SceneSetWorld(Handle, ref value);
    }

    /// Returns or changes "View" transformation matrix.
    public Matrix4x4 View
    {
      get
      {
        API.SceneGetView(Handle, out Matrix4x4 view);
        return view;
      }

      set => API.SceneSetView(Handle, ref value);
    }

    /// Returns or changes "Projection" transformation matrix.
    public Matrix4x4 Projection
    {
      get
      {
        API.SceneGetProjection(Handle, out Matrix4x4 projection);
        return projection;
      }

      set => API.SceneSetProjection(Handle, ref value);
    }

    /// Indicates that the rendering is currently taking place (inside "Begin" / "End" block).
    public bool Rendering => API.SceneRendering(Handle);

    /// Returns maximum number of supported instances.
    public int InstancesCount => API.SceneGetInstancesCount(Handle);

    /// Returns or changes scene object's material properties.
    public ObjectMaterial Material
    {
      get
      {
        API.SceneGetMaterial(Handle, out ObjectMaterial material);
        return material;
      }

      set
      {
        if (!API.SceneSetMaterial(Handle, ref value))
          throw new API.Exception("Could not change object material properties in a 3D rendering scene");
      }
    }

    /// Returns or changes tone-mapping RGB luma coefficients and maximum allowed level of white.
    /// These parameters are used only when "glassy" (OIT) technique is being performed.
    public Vector4 ToneMappingCoefficients
    {
      get
      {
        API.SceneGetToneMappingCoefficients(Handle, out Vector4 coefficients);
        return coefficients;
      }

      set
      {
        if (!API.SceneSetToneMappingCoefficients(Handle, ref value))
          throw new API.Exception("Could not change tone-mapping coefficients in a 3D rendering scene");
      }
    }

    /// Returns or changes parameters that define Parallax Mapping technique is performed.
    public ParallaxMappingParameters ParallaxMapping
    {
      get
      {
        API.SceneGetParallaxMappingParameters(Handle, out ParallaxMappingParameters parameters);
        return parameters;
      }

      set
      {
        if (!API.SceneSetParallaxMappingParameters(Handle, ref value))
          throw new API.Exception("Could not change parallax mapping parameters in a 3D rendering scene");
      }
    }

    /// Returns or changes lights container module associated with the scene. Note: this must be called
    /// outside of "Begin" / "End" block and before calling "Prepare" function.
    public SceneLights Lights
    {
      get
      {
        IntPtr handle = API.SceneGetLights(Handle);
        return handle != IntPtr.Zero ? new SceneLights(handle) : null;
      }

      set => API.SceneSetLights(Handle, value?.Handle ?? IntPtr.Zero);
    }

    /// Returns or changes shadow casting atlas associated with the scene. Note: this must be called
    /// outside of "Begin" / "End" block and before calling "Prepare" function.
    public ShadowCastingAtlas Atlas
    {
      get
      {
        IntPtr handle = API.SceneGetShadowCastingAtlas(Handle);
        return handle != IntPtr.Zero ? new ShadowCastingAtlas(handle) : null;
      }

      set => API.SceneSetShadowCastingAtlas(Handle, value?.Handle ?? IntPtr.Zero);
    }

    /// Returns or changes texture cabinetc associated with the scene. Note: this must be called
    /// outside of "Begin" / "End" block and before calling "Prepare" function.
    public TextureCabinet TextureCabinet
    {
      get
      {
        IntPtr handle = API.SceneGetTextureCabinet(Handle);
        return handle != IntPtr.Zero ? new TextureCabinet(handle) : null;
      }

      set => API.SceneSetTextureCabinet(Handle, value?.Handle ?? IntPtr.Zero);
    }

    /// Returns currently active shader program handle.
    public Program Program
    {
      get
      {
        IntPtr handle = API.SceneGetProgram(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve current shader program from a 3D rendering scene");

        return new Program(handle);
      }
    }
  }

  /// 3D ocean semi-infinite wave field rendering module.
  public sealed class OceanSimulation : CustomObject
  {
    /// Creates a wrapped or uninitialized 3D ocean rendering module.
    internal OceanSimulation(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of 3D ocean rendering module.
    public OceanSimulation(Device device)
    : base(new DestroyDelegate(API.OceanSimulationDestroy), IntPtr.Zero)
    {
      _handle = API.OceanSimulationCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D ocean rendering module");
    }

    /// Returns an existing ocean waves simulation texture. This would be mostly useful for debug purposes.
    public Texture GetWavesTexture(int index)
    {
      IntPtr handle = API.OceanSimulationGetWavesTexture(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve waves texture ({index}) from " +
          "a 3D ocean rendering module");

      return new Texture(handle);
    }

    /// Executes the simulation of ocean waves and updates internal textures.
    public void Update(double latency)
    {
      if (!API.OceanSimulationUpdate(Handle, latency))
        throw new API.Exception("Could not perform an update in a 3D ocean rendering module");
    }

    /// Renders the simulation to an existing active rendering surface.
    public void Render(Texture linearDepths, SceneLights lights, ShadowCastingAtlas atlas = null)
    {
      if (!API.OceanSimulationRender(Handle, linearDepths?.Handle ?? IntPtr.Zero,
        lights?.Handle ?? IntPtr.Zero, atlas?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Could not perform rendering of a 3D ocean rendering module");
    }

    /// Returns device associated with the module.
    public Device Device
    {
      get
      {
        IntPtr handle = API.OceanSimulationGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with the 3D ocean " +
            "rendering module");

        return new Device(handle);
      }
    }

    /// Returns or changes ocean waves simulation parameters.
    public OceanWavesParameters Parameters
    {
      get
      {
        API.OceanSimulationGetWavesParameters(Handle, out OceanWavesParameters parameters);
        return parameters;
      }

      set
      {
        if (!API.OceanSimulationSetWavesParameters(Handle, ref value))
          throw new API.Exception("Could not change wave simulation parameters in " +
            "3D ocean rendering module");
      }
    }

    /// Returns or changes the number of grid sub-divisions in screen-space.
    public Point Sections
    {
      get
      {
        API.OceanSimulationGetSections(Handle, out Point sections);
        return sections;
      }

      set
      {
        if (!API.OceanSimulationSetSections(Handle, ref value))
          throw new API.Exception($"Failed changing number of sections ({value}) in " +
            "a 3D ocean rendering module");
      }
    }

    /// Returns or changes "View" transformation matrix.
    public Matrix4x4 View
    {
      get
      {
        API.OceanSimulationGetView(Handle, out Matrix4x4 view);
        return view;
      }

      set => API.OceanSimulationSetView(Handle, ref value);
    }

    /// Returns or changes "Projection" transformation matrix.
    public Matrix4x4 Projection
    {
      get
      {
        API.OceanSimulationGetProjection(Handle, out Matrix4x4 projection);
        return projection;
      }

      set => API.OceanSimulationSetProjection(Handle, ref value);
    }

    /// Returns or changes scane of the ocean's displacement map.
    public Vector3 Scale
    {
      get
      {
        API.OceanSimulationGetScale(Handle, out Vector3 scale);
        return scale;
      }

      set
      {
        if (!API.OceanSimulationSetScale(Handle, ref value))
          throw new API.Exception("Could not change scale of a displacement map in " +
            "a 3D ocean rendering module");
      }
    }

    /// Returns or changes grid's plane equation.
    public Vector4 Plane
    {
      get
      {
        API.OceanSimulationGetPlane(Handle, out Vector4 plane);
        return plane;
      }

      set
      {
        if (!API.OceanSimulationSetPlane(Handle, ref value))
          throw new API.Exception("Could not change grid's plane equation in " +
            "a 3D ocean rendering module");
      }
    }

    /// Returns or changes maximum height-map viewing distance.
    public float ViewDistance
    {
      get { return API.OceanSimulationGetViewDistance(Handle); }

      set
      {
        if (!API.OceanSimulationSetViewDistance(Handle, value))
          throw new API.Exception("Could not change maximum height-map viewing distance in " +
            "a 3D ocean rendering module");
      }
    }

    /// Returns or changes attributes that define the rendering behavior of the module.
    public uint Attributes
    {
      get => API.OceanSimulationGetAttributes(Handle);
      set => API.OceanSimulationSetAttributes(Handle, value);
    }

    /// Returns or changes ocean's material.
    public OceanMaterial Material
    {
      get
      {
        API.OceanSimulationGetMaterial(Handle, out OceanMaterial material);
        return material;
      }

      set
      {
        if (!API.OceanSimulationSetMaterial(Handle, ref value))
          throw new API.Exception("Could not change object material properties in a 3D ocean " +
            "rendering module");
      }
    }

    /// Returns sampler for reading from shadow map.
    public Sampler SamplerShadow
    {
      get
      {
        IntPtr handle = API.OceanSimulationGetSamplerShadow(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve shadow mapping sampler associated with " +
            "the 3D ocean rendering module");

        return new Sampler(handle);
      }
    }
  }

  /// Object that represents a 3D model in the scene.
  public sealed class ObjectModel : CustomObject
  {
    /// Attribute bit flags for an object that define its status.
    public struct Attribute
    {
      /// Object is visible so can be rendered and selected (if selected flag is set).
      public const uint Visible = 0x01u;

      /// Object can be selected.
      public const uint Selectable = 0x02u;

      /// Object is semi-transparent (might be rendered through a different mechanism).
      public const uint Transparent = 0x04u;

      /// Object does not participate in bounding volume hierarchy (BVH) calculation. This is particularly
      /// useful for constantly moving objects or those objects that have unusually large dimensions
      /// overlapping with other objects.
      public const uint Hierarchyless = 0x08u;

      /// Object model should not automatically realign itself.
      public const uint DisableRealign = 0x10u;

      /// Object does not appear among visible objects in the view. This is useful for abstract objects that
      /// have no associated mesh, so aren't rendered in any way, but can still be selected.
      public const uint NonViewable = 0x20u;
    }

    /// Creates a wrapped or uninitialized object model.
    internal ObjectModel(IntPtr handle) : base(null, handle) { }

    /// Returns object's child with the given index.
    public ObjectModel Child(int index)
    {
      IntPtr handle = API.ObjectModelGetChild(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve 3D object model's child ({index})");

      return new ObjectModel(handle);
    }

    /// Retrieves one of current object model's transforms.
    public Matrix4x4 GetTransform(ModelTransform transform)
    {
      API.ObjectModelGetTransform(Handle, transform, out Matrix4x4 matrix);
      return matrix;
    }

    /// Updates one of current object model's transforms. Returns "true" if the transform really changed.
    public bool SetTransform(ModelTransform transform, Matrix4x4 matrix)
    {
      return API.ObjectModelSetTransform(Handle, transform, ref matrix);
    }

    /// Updates and returns axis-aligned bounding box of the object.
    public void AABB(out Vector3 boxMin, out Vector3 boxMax)
    {
      API.ObjectModelGetAABB(Handle, out boxMin, out boxMax);
    }

    /// Marks local transform, volume and position as dirty, including children.
    public void Invalidate() => API.ObjectModelInvalidate(Handle);

    /// Connects a local bone joint with the given index to a corresponding joint of the parent.
    public void ConnectLatches(int latchParent, int latchLocal)
    {
      API.ObjectModelConnectLatches(Handle, latchParent, latchLocal);
    }

    /// Connects a local bone joint with the given name (case-insensitive) to a corresponding joint of
    /// the parent.
    public void ConnectLatches(string latchParentName, string latchLocalName)
    {
      API.ObjectModelConnectLatchesByName(Handle, Encoding.UTF8.GetBytes(latchParentName + '\0'),
        Encoding.UTF8.GetBytes(latchLocalName + '\0'));
    }

    /// Returns indexes of currently connected bone joints.
    public void GetConnectedLatches(out int latchParent, out int latchLocal)
    {
      API.ObjectModelGetConnectedLatches(Handle, out latchParent, out latchLocal);
    }

    /// Returns calculated waypoint traveling distance for the given latch group.
    public float GetWaypointDistance(int group) => API.ObjectModelGetWaypointDistance(Handle, group);

    /// Calculates interpolated position and orientation based on the given distance for the given latch
    /// group.
    public void GetLatchWaypointCouple(int group, float distance, out Vector3 position,
      out Quaternion orientation)
    {
      API.ObjectModelGetLatchWaypointCouple(Handle, group, distance, out position, out orientation);
    }

    /// Calculates transformation matrix with interpolated position and orientation based on the given
    /// distance for the given latch group.
    public Matrix4x4 GetLatchWaypointCoupleMatrix(int group, float distance)
    {
      API.ObjectModelGetLatchWaypointCoupleMatrix(Handle, group, distance, out Matrix4x4 transform);
      return transform;
    }

    /// Retrieves either local or global transformation matrix for a given latch index.
    public Matrix4x4 GetLatchTransform(int latchIndex, bool local = false)
    {
      API.ObjectModelGetLatchTransform(Handle, out Matrix4x4 transform, latchIndex, local);
      return transform;
    }

    /// Retrieves either local or global transformation matrix for a latch with the given name.
    public Matrix4x4 GetLatchTransform(string latchName, bool local = false)
    {
      API.ObjectModelGetLatchTransformByName(Handle, out Matrix4x4 transform,
        Encoding.UTF8.GetBytes(latchName + '\0'), local);
      return transform;
    }

    /// Returns a container that owns the object.
    public ObjectModels Owner
    {
      get
      {
        IntPtr handle = API.ObjectModelGetOwner(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve a parent container of a 3D object model");

        return new ObjectModels(handle);
      }
    }

    /// Returns object's unique identification number.
    public IntPtr ID => API.ObjectModelGetID(Handle);

    /// Returns or changes object's name (case-insensitive).
    public string Name
    {
      get
      {
        int nameLength = 0;
        API.ObjectModelGetName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.ObjectModelGetName(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }

      set
      {
        if (!API.ObjectModelSetName(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change 3D object model's name ({value})");
      }
    }

    /// Returns or changes object's description.
    public string Description
    {
      get
      {
        int descLength = 0;
        API.ObjectModelGetDescription(Handle, IntPtr.Zero, ref descLength);
        byte[] desc = ArrayPool<byte>.Shared.Rent(descLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(desc, GCHandleType.Pinned);
          try
          {
            API.ObjectModelGetDescription(Handle, pinned.AddrOfPinnedObject(), ref descLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(desc, 0, descLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(desc);
        }
      }

      set
      {
        if (!API.ObjectModelSetDescription(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change 3D object model's description ({value})");
      }
    }

    /// Returns object's payload.
    public IntPtr Payload => API.ObjectModelGetPayload(Handle);

    /// Returns or changes object model's voxel representation.
    public MeshVoxel Voxel
    {
      get
      {
        IntPtr handle = API.ObjectModelGetVoxel(Handle);
        return handle != IntPtr.Zero ? new MeshVoxel(handle) : null;
      }

      set => API.ObjectModelSetVoxel(Handle, value?.Handle ?? IntPtr.Zero);
    }

    /// Returns or changes object's rendereable mesh.
    public SceneMesh Mesh
    {
      get
      {
        IntPtr handle = API.ObjectModelGetMesh(Handle);
        return handle != IntPtr.Zero ? new SceneMesh(handle) : null;
      }

      set
      {
        if (!API.ObjectModelSetMesh(Handle, value?.Handle ?? IntPtr.Zero))
          throw new API.Exception("Could not change scene mesh in a 3D object model");
      }
    }

    /// Returns or changes object's rendereable mesh name (must be one of the mesh names from the
    /// associated container).
    public string MeshName
    {
      get
      {
        int nameLength = 0;
        API.ObjectModelGetMeshName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.ObjectModelGetMeshName(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }

      set
      {
        if (!API.ObjectModelSetMeshName(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change 3D object model's scene mesh name ({value})");
      }
    }

    /// Returns or changes object model's parent.
    public ObjectModel Parent
    {
      get
      {
        IntPtr handle = API.ObjectModelGetParent(Handle);
        return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
      }

      set
      {
        if (!API.ObjectModelSetParent(Handle, value?.Handle ?? IntPtr.Zero))
          throw new API.Exception("Could not change 3D object model's parent");
      }
    }

    /// Extracts and returns current object position.
    public Vector3 Position
    {
      get
      {
        API.ObjectModelGetPosition(Handle, out Vector3 position);
        return position;
      }
    }

    /// Returns or changes current depth bias, which is used during object selection.
    public float DepthBias
    {
      get => API.ObjectModelGetDepthBias(Handle);
      set => API.ObjectModelSetDepthBias(Handle, value);
    }

    /// Returns or changes object model's attributes.
    public uint Attributes
    {
      get => API.ObjectModelGetAttributes(Handle);
      set => API.ObjectModelSetAttributes(Handle, value);
    }

    /// Returns or changes object's priority index.
    public int OrderIndex
    {
      get => API.ObjectModelGetOrderIndex(Handle);
      set => API.ObjectModelSetOrderIndex(Handle, value);
    }

    /// Returns or changes layers on which the object is visible.
    public ulong Layers
    {
      get => API.ObjectModelGetLayers(Handle);
      set => API.ObjectModelSetLayers(Handle, value);
    }

    /// Returns or changes object's size.
    public Vector3 Size
    {
      get
      {
        API.ObjectModelGetSize(Handle, out Vector3 size);
        return size;
      }

      set => API.ObjectModelSetSize(Handle, ref value);
    }

    /// Returns or changes object's mesh alignments.
    public MeshAligns Alignments
    {
      get
      {
        API.ObjectModelGetAlignments(Handle, out MeshAligns alignments);
        return alignments;
      }

      set => API.ObjectModelSetAlignments(Handle, ref value);
    }

    /// Returns or changes object's material.
    public int Material
    {
      get => API.ObjectModelGetMaterial(Handle);
      set => API.ObjectModelSetMaterial(Handle, value);
    }

    /// Returns or changes object's color.
    public FloatColor Color
    {
      get
      {
        API.ObjectModelGetColor(Handle, out FloatColor color);
        return color;
      }

      set => API.ObjectModelSetColor(Handle, ref value);
    }

    /// Returns or changes object's highlight color.
    public FloatColor Highlight
    {
      get
      {
        API.ObjectModelGetHighlight(Handle, out FloatColor highlight);
        return highlight;
      }

      set => API.ObjectModelSetHighlight(Handle, ref value);
    }

    /// Returns number of object model's children.
    public int ChildCount => API.ObjectModelGetChildCount(Handle);
  }

  /// A container of objects and their volume representation in 3D world.
  public sealed class ObjectModels : CustomObject, IEnumerable<ObjectModel>
  {
    /// Iterator for accessing individual objects in the container.
    public sealed class Iterator : IEnumerator<ObjectModel>
    {
      private readonly IntPtr _parent;
      private IntPtr _current;
      private bool _started;
      internal Iterator(IntPtr parent)
      {
        _parent = parent;
        _current = IntPtr.Zero;
        _started = false;
      }
      void IDisposable.Dispose() {}
      public bool MoveNext()
      {
        if (!_started)
        {
          _current = API.ObjectModelsGetFirst(_parent);
          _started = true;
        }
        else if (_current != IntPtr.Zero)
          _current = API.ObjectModelGetNext(_current);

        return _current != IntPtr.Zero;
      }
      public void Reset()
      {
        _current = IntPtr.Zero;
        _started = false;
      }
      object IEnumerator.Current => Current;
      public ObjectModel Current
      {
        get
        {
          if (_current == IntPtr.Zero)
            throw new API.Exception("Current object model has not been initialized.");

          return new ObjectModel(_current);
        }
      }
    }

    /// Creates a wrapped or uninitialized object container.
    internal ObjectModels(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of object container.
    public ObjectModels(SceneMeshes meshes = null)
    : base(new DestroyDelegate(API.ObjectModelsDestroy), IntPtr.Zero)
    {
      _handle = API.ObjectModelsCreate(meshes?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a container for 3D objects");
    }

    /// Creates a new object with a given id, name and/or payload association. If the given ID already
    /// exists, a new unique one will be assigned to the object. Unique IDs are generated incrementally with
    /// roll-over when highest possible ID is reached, so eventually lower order IDs might be reused;
    /// the function ensures, however, that a given ID is unique in sense that it is not assigned to another
    /// other object.
    public ObjectModel Add(IntPtr id, string name = null, IntPtr payload = default, uint attributes =
      ObjectModel.Attribute.Visible | ObjectModel.Attribute.Selectable)
    {
      IntPtr handle = API.ObjectModelsAddWithID(Handle, id, string.IsNullOrEmpty(name) ? null :
        Encoding.UTF8.GetBytes(name + '\0'), payload, attributes);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not add a new 3D object model ({id}, {name}) to container");

      return new ObjectModel(handle);
    }

    /// Creates a new object with the given name and/or payload association.
    public ObjectModel Add(string name = null, IntPtr payload = default, uint attributes =
      ObjectModel.Attribute.Visible | ObjectModel.Attribute.Selectable)
    {
      IntPtr handle = API.ObjectModelsAdd(Handle, string.IsNullOrEmpty(name) ? null :
        Encoding.UTF8.GetBytes(name + '\0'), payload, attributes);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not add a new 3D object model ({name}) to container");

      return new ObjectModel(handle);
    }

    /// Removes the given object.
    public void Erase(ObjectModel objectModel)
    {
      API.ObjectModelsErase(Handle, objectModel?.Handle ?? IntPtr.Zero);
    }

    /// Removes all objects.
    public void Clear() => API.ObjectModelsClear(Handle);

    /// Returns an object with the given ID if such exists.
    public ObjectModel TryObject(IntPtr id)
    {
      IntPtr handle = API.ObjectModelsGetObjectByID(Handle, id);
      return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
    }

    /// Returns an object with the given ID.
    public ObjectModel Object(IntPtr id)
    {
      ObjectModel obj = TryObject(id);
      if (obj is null || obj.Handle == IntPtr.Zero)
        throw new API.Exception($"An object with the given id ({id}) could not be retrieved");

      return obj;
    }

    /// Returns an object with the given name (case-insensitive) if such exists.
    public ObjectModel TryObject(string name)
    {
      IntPtr handle = API.ObjectModelsGetObjectByName(Handle, Encoding.UTF8.GetBytes(name + '\0'));
      return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
    }

    /// Returns an object with the given name (case-insensitive).
    public ObjectModel Object(string name)
    {
      ObjectModel obj = TryObject(name);
      if (obj is null || obj.Handle == IntPtr.Zero)
        throw new API.Exception($"An object with the given name ({name}) could not be retrieved");

      return obj;
    }

    /// Returns an object with the given payload, if such exists.
    public ObjectModel TryPayload(IntPtr payload)
    {
      IntPtr handle = API.ObjectModelsPayload(Handle, payload);
      return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
    }

    /// Returns an object with the given payload.
    public ObjectModel Payload(IntPtr payload)
    {
      ObjectModel obj = TryPayload(payload);
      if (obj is null || obj.Handle == IntPtr.Zero)
        throw new API.Exception($"An object with the given payload ({payload}) could not be retrieved");

      return obj;
    }

    /// Tests whether an object with the given ID exists.
    public bool Exists(IntPtr id) => TryObject(id) is not null;

    /// Tests whether an object with the given name exists.
    public bool Exists(string name) => TryObject(name) is not null;

    /// Tests whether an object with the given payload exists.
    public bool PayloadExists(IntPtr payload) => TryPayload(payload) is not null;

    /// Creates a new view associated with this container.
    public ObjectModelView CreateView()
    {
      IntPtr handle = API.ObjectModelsCreateView(Handle);
      if (handle == IntPtr.Zero)
        throw new API.Exception("Could not create a new view for object model container");

      return new ObjectModelView(handle);
    }

    /// Removes a given view from the container.
    public void EraseView(ObjectModelView view)
    {
      API.ObjectModelsEraseView(Handle, view?.Handle ?? IntPtr.Zero);
    }

    /// Removes all existing views from the container.
    public void ClearViews() => API.ObjectModelsClearViews(Handle);

    /// Creates enumerator for iterating through the container.
    public IEnumerator<ObjectModel> GetEnumerator() => new Iterator(Handle);

    /// Creates enumerator for iterating through the container.
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    /// Returns the associated mesh container.
    public SceneMeshes Meshes
    {
      get
      {
        IntPtr handle = API.ObjectModelsGetMeshes(Handle);
        return handle != IntPtr.Zero ? new SceneMeshes(handle) : null;
      }
    }

    /// Returns number of existing objects.
    public int Count => API.ObjectModelsGetObjectCount(Handle);
  }

  /// A container that represents a particular view that watches objects in the world.
  public sealed class ObjectModelView : CustomObject
  {
    /// Comparison function for a pair of objects in a 3D scene.
    public delegate int CompareFunc(ObjectModel object1, ObjectModel object2);

    /// Creates a wrapped or uninitialized view container.
    internal ObjectModelView(IntPtr handle) : base(null, handle) { }

    /// Notifies the container that all visible objects must be recalculated.
    public void UpdateNeeded() => API.ObjectModelViewUpdateNeeded(Handle);

    /// Notifies the container that it needs to be repainted.
    public void Invalidate() => API.ObjectModelViewInvalidate(Handle);

    /// Returns an object with the given index from the view container.
    public ObjectModel Object(int index)
    {
      IntPtr handle = API.ObjectModelViewGetObject(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve object ({index}) from object model's view container");

      return new ObjectModel(handle);
    }

    /// Creates a list of objects for selection and rendering from the view container.
    /// Returns "true" if there are any objects visible in the view.
    public bool Update() => API.ObjectModelViewUpdate(Handle);

    /// Sorts the list of objects using a predefined comparison function.
    public void Sort(ObjectModelViewCompare compare) => API.ObjectModelViewSort(Handle, compare);

    /// Sorts the list of objects using a custom comparison function.
    public void Sort(CompareFunc compare)
    {
      GCHandle compareHandle = GCHandle.Alloc(compare);
      try
      {
        API.ObjectModelViewSortWith(Handle,
          delegate(IntPtr object1, IntPtr object2, IntPtr user)
          {
            CompareFunc compare = Marshal.GetDelegateForFunctionPointer<CompareFunc>(user);
            return compare(new ObjectModel(object1), new ObjectModel(object2));
          },
          Marshal.GetFunctionPointerForDelegate(compare));
      }
      finally
      {
        compareHandle.Free();
      }
    }

    /// Performs object selection either through object's voxel hierarchy if such is present and if not,
    /// through ray-volume intersection. All selectable and visible objects are tested and the object with
    /// closest intersection is finally chosen.
    public ObjectModel Select(Ray ray, out float distance)
    {
      IntPtr handle = API.ObjectModelViewSelect(Handle, ref ray.Origin, ref ray.Direction, out distance);
      return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
    }

    /// Performs object selection either through object's voxel hierarchy if such is present and if not,
    /// through ray-volume intersection. All visible objects are tested and the object with closest
    /// intersection is finally chosen.
    public ObjectModel SelectAny(Ray ray, out float distance)
    {
      IntPtr handle = API.ObjectModelViewSelectAny(Handle, ref ray.Origin, ref ray.Direction, out distance);
      return handle != IntPtr.Zero ? new ObjectModel(handle) : null;
    }

    /// Renders objects that are visible in the view according to the provided options.
    /// If \a scene is \c null, this does not perform instancing or issue actual draw calls, instead
    /// performing a simple non-instancing run and counting any tentative draw calls that would be issued;
    /// this can be used to determine if any objects would be rendered at all or not to decide whether to
    /// perform a certain rendering pass.
    public int AutoDraw(Scene scene, ObjectMaterials materials = null, uint options = 0)
    {
      int drawCalls = 0;

      if (!API.ObjectModelViewAutoDraw(Handle, scene?.Handle ?? IntPtr.Zero,
        materials?.Handle ?? IntPtr.Zero, options, ref drawCalls))
        throw new API.Exception("Could not draw automatically a given scene");

      return drawCalls;
    }

    /// Returns parent of the object model's view.
    public ObjectModels Owner
    {
      get
      {
        IntPtr handle = API.ObjectModelViewGetOwner(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve object model view's parent container");

        return new ObjectModels(handle);
      }
    }

    /// Returns or changes "View" transformation matrix of the container.
    public Matrix4x4 View
    {
      get
      {
        API.ObjectModelViewGetView(Handle, out Matrix4x4 view);
        return view;
      }

      set => API.ObjectModelViewSetView(Handle, ref value);
    }

    /// Returns or changes "Projection" transformation matrix of the container.
    public Matrix4x4 Projection
    {
      get
      {
        API.ObjectModelViewGetProjection(Handle, out Matrix4x4 projection, out bool _);
        return projection;
      }

      set => API.ObjectModelViewSetProjection(Handle, ref value, false);
    }

    /// Returns combined "View/Projection" matrix of the container.
    public Matrix4x4 ViewProjection
    {
      get
      {
        API.ObjectModelViewGetViewProjection(Handle, out Matrix4x4 viewProjection);
        return viewProjection;
      }
    }

    /// Returns or changes layers that determine object visibility in the view.
    public ulong Layers
    {
      get => API.ObjectModelViewGetLayers(Handle);
      set => API.ObjectModelViewSetLayers(Handle, value);
    }

    /// Returns number of existing objects that are visible in the view.
    public int ObjectCount => API.ObjectModelViewGetObjectCount(Handle);

    /// Returns number of objects that were not performed cull test, either because they were determined to be
    /// fully visible, or because they were marked as hierarchyless.
    public int ObjectsNotCulled => API.ObjectModelViewGetObjectsNotCulled(Handle);

    /// Returns number of ray vs AABB intersections performed last time selection was performed.
    public int IntersectedRays => API.ObjectModelViewGetIntersectedRays(Handle);

    /// Returns number of objects that passed intersection during last selection attempt.
    public int IntersectedObjects => API.ObjectModelViewGetIntersectedObjects(Handle);
  }

  /// 3D mesh voxel representation object.
  public sealed class MeshVoxel : CustomObject
  {
    /// Delegate function that is called for voxel visualization.
    public delegate void VisualizeFunc(Vector3 position, Vector3 size, FloatColor color);

    /// Creates a wrapped or uninitialized voxel representation.
    internal MeshVoxel(IntPtr handle) : base(null, handle) { }

    /// Creates an empty 3D mesh voxel representation.
    public MeshVoxel()
    : base(new DestroyDelegate(API.MeshVoxelDestroy), IntPtr.Zero)
    {
      _handle = API.MeshVoxelCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create an empty 3D mesh voxel representation");
    }

    /// Loads an existing 3D mesh voxel representation from a file on disk.
    public bool TryLoadFromFile(string fileName, out int[] dimensions, out byte levels, out bool colors)
    {
      dimensions = new int[3];
      return API.MeshVoxelLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), dimensions,
        out levels, out colors);
    }

    /// Loads an existing 3D mesh voxel representation from a file on disk.
    public void LoadFromFile(string fileName, out int[] dimensions, out byte levels, out bool colors)
    {
      if (!TryLoadFromFile(fileName, out dimensions, out levels, out colors))
        throw new API.Exception("Could not load a 3D mesh voxel representation from external file " +
          $"({fileName})");
    }

    /// Loads an existing 3D mesh voxel representation from a file in memory.
    public bool TryLoadFromFileInMemory(Array buffer, out int[] dimensions, out byte levels,
      out bool colors)
    {
      dimensions = new int[3];

      GCHandle pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned);
      try
      {
        return API.MeshVoxelLoadFromFileInMemory(Handle, pinned.AddrOfPinnedObject(), (uint)buffer.Length,
          dimensions, out levels, out colors);
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Loads an existing 3D mesh voxel representation from a file in memory.
    public void LoadFromFileInMemory(Array buffer, out int[] dimensions, out byte levels, out bool colors)
    {
      if (!TryLoadFromFileInMemory(buffer, out dimensions, out levels, out colors))
        throw new API.Exception("Could not load 3D mesh voxel representation from file in memory");
    }

    /// Saves 3D mesh voxel representation to a file on disk.
    public void SaveToFile(string fileName, int[] dimensions, out byte levels, out bool colors)
    {
      if (!API.MeshVoxelLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'), dimensions,
        out levels, out colors))
        throw new API.Exception("Could not load a 3D mesh voxel representation from external file " +
          $"({fileName})");
    }

    /// Returns position and size of 3D mesh voxel representation.
    public void Extents(out Vector3 position, out Vector3 size)
    {
      API.MeshVoxelExtents(Handle, out position, out size);
    }

    /// Calculates existing voxel dimensions, levels and whether colors are present.
    public void ComputeParameters(out int[] dimensions, out byte levels, out bool colors)
    {
      dimensions = new int[3];
      API.MeshVoxelComputeParameters(Handle, dimensions, out levels, out colors);
    }

    /// Visualizes 3D mesh voxel representation by invoking delegate function for each cube.
    public void Visualize(byte levelMax, VisualizeFunc visualize)
    {
      GCHandle visualizeHandle = GCHandle.Alloc(visualize);
      try
      {
        API.MeshVoxelVisualize(Handle, levelMax,
          delegate (ref Vector3 position, ref Vector3 size, ref FloatColor color, IntPtr user)
          {
            VisualizeFunc visualize = Marshal.GetDelegateForFunctionPointer<VisualizeFunc>(user);
            visualize(position, size, color);
          },
          Marshal.GetFunctionPointerForDelegate(visualize));
      }
      finally
      {
        visualizeHandle.Free();
      }
    }

    /// Performs intersection between voxel representation and ray.
    public bool Intersect(Vector3 origin, Vector3 direction, Matrix4x4 world, Matrix4x4 view,
      out float distance, out int testsCount)
    {
      return API.MeshVoxelIntersect(Handle, ref origin, ref direction, ref world, ref view, out distance,
        out testsCount);
    }

    /// Takes away the internal contents from another 3D mesh voxel representation replacing any existing
    /// contents, without doing any copying.
    public void TakeAway(MeshVoxel other)
    {
      API.MeshVoxelTakeAway(Handle, other?.Handle ?? IntPtr.Zero);
    }
  }

  /// Material that describes how a portion of a scene mesh geometry should look like.
  public sealed class SceneMeshMaterial : CustomObject
  {
    /// Creates a wrapped or uninitialized scene mesh material.
    internal SceneMeshMaterial(IntPtr handle) : base(null, handle) { }

    /// Returns a texture associated with the given type or \c null if such does not exist.
    public Texture GetTexture(SceneMeshTexture type)
    {
      IntPtr handle = API.SceneMeshMaterialGetTexture(Handle, (byte)type);
      return handle != IntPtr.Zero ? new Texture(handle) : null;

    }

    /// Changes texture of the given type. Note: material will take ownership of the given texture instance
    /// and reliquish ownership of instance that was previously assigned (if it is not \c null).
    public void SetTexture(Texture texture, SceneMeshTexture type)
    {
      if (!API.SceneMeshMaterialSetTexture(Handle, texture?.Handle ?? IntPtr.Zero, (byte)type))
        throw new API.Exception("Could not change scene mesh material texture for the given type" +
          $"({type})");
    }

    /// Releases existing textures.
    public void ReleaseTextures() => API.SceneMeshMaterialReleaseTextures(Handle);

    /// Returns a range with the given index.
    public SceneMeshMaterialRange GetRange(int index)
    {
      if (!API.SceneMeshMaterialGetRange(Handle, index, out SceneMeshMaterialRange range))
        throw new API.Exception("Could not retrieve scene mesh material range" + $"({index})");

      return range;
    }

    /// Updates a range with the given index.
    public void SetRange(int index, SceneMeshMaterialRange range)
    {
      if (!API.SceneMeshMaterialSetRange(Handle, index, ref range))
        throw new API.Exception("Could not update scene mesh material range" + $"({index})");
    }

    /// Adds a given range to the list of existing ranges and returns its index.
    public int AddRange(SceneMeshMaterialRange range)
    {
      int index = API.SceneMeshMaterialAddRange(Handle, ref range);
      if (index == -1)
        throw new API.Exception("Could not add scene mesh material range");

      return index;
    }

    /// Removes all existing ranges.
    public void ClearRanges() => API.SceneMeshMaterialClearRanges(Handle);

    /// Converts existing "scratch" textures into hardware-based textures.
    public void Commit(Device device, uint attributes = 0)
    {
      if (!API.SceneMeshMaterialCommit(Handle, device?.Handle ?? IntPtr.Zero, attributes))
        throw new API.Exception("Failed converting scratch scene mesh textures into hardware-based");
    }

    /// Copies contents from another material. In case of textures, creates new instances of textures and
    /// copies the contents of textures from the source.
    public void Copy(SceneMeshMaterial material)
    {
      if (!API.SceneMeshMaterialCopy(Handle, material?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Failed copying scene mesh material from another container");
    }

    /// Material name.
    public string Name
    {
      get
      {
        int nameLength = 0;
        API.SceneMeshMaterialGetName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.SceneMeshMaterialGetName(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }
    }

    /// Shading parameters of the material.
    public SceneMeshMaterialShading Shading
    {
      get
      {
        API.SceneMeshMaterialGetShading(Handle, out SceneMeshMaterialShading shading);
        return shading;
      }

      set => API.SceneMeshMaterialSetShading(Handle, ref value);
    }

    /// Returns number of existing ranges.
    public int RangeCount => API.SceneMeshMaterialGetRangeCount(Handle);
  }

  /// Container for scene mesh materials that describe the appearance of scene mesh geometry.
  public sealed class SceneMeshMaterials : CustomObject
  {
    /// Creates a wrapped or uninitialized scene mesh material container.
    internal SceneMeshMaterials(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of scene mesh material container.
    public SceneMeshMaterials()
    : base(new DestroyDelegate(API.SceneMeshMaterialsDestroy), IntPtr.Zero)
    {
      _handle = API.SceneMeshMaterialsCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a container for scene mesh materials");
    }

    /// Returns an existing material with the given index.
    public SceneMeshMaterial GetMaterial(int index)
    {
      IntPtr handle = API.SceneMeshMaterialsGetMaterial(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve scene mesh material ({index}) from the container");

      return new SceneMeshMaterial(handle);
    }

    /// Adds a new material to the scene mesh and returns its index.
    public int Add(string name)
    {
      int index = API.SceneMeshMaterialsAdd(Handle, Encoding.UTF8.GetBytes(name + '\0'));
      if (index == -1)
        throw new API.Exception($"Could not add a new material ({name}) to the scene mesh");

      return index;
    }

    /// Removes a material with the given index.
    public void Erase(int index) => API.SceneMeshMaterialsErase(Handle, index);

    /// Removes and releases all existing materials.
    public void Clear() => API.SceneMeshMaterialsClear(Handle);

    /// Converts existing "scratch" textures into hardware-based textures.
    public void Commit(Device device, uint attributes = 0)
    {
      if (!API.SceneMeshMaterialsCommit(Handle, device?.Handle ?? IntPtr.Zero, attributes))
        throw new API.Exception("Failed converting scratch scene mesh textures into hardware-based in" +
          " a container");
    }

    /// Copies materials into the collection from another container.
    public void Copy(SceneMeshMaterials other)
    {
      if (!API.SceneMeshMaterialsCopy(Handle, other?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Failed copying scene mesh materials from another container");
    }

    /// Takes away the internal contents from another material container without doing any copying.
    public void TakeAway(SceneMeshMaterials other)
    {
      API.SceneMeshMaterialsTakeAway(Handle, other?.Handle ?? IntPtr.Zero);
    }

    /// Returns or changes the name of the library.
    public string Name
    {
      get
      {
        int nameLength = 0;
        API.SceneMeshMaterialsGetName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.SceneMeshMaterialsGetName(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }

      set
      {
        if (!API.SceneMeshMaterialsSetName(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change scene mesh materials library name ({value})");
      }
    }

    /// Returns total number of existing materials.
    public int Count => API.SceneMeshMaterialsGetCount(Handle);

    /// Reviews existing materials to determine whether any of them use texturing.
    public bool Texturing => API.SceneMeshMaterialsTexturing(Handle);
  }

  /// Container for scene mesh latches that define how meshes connect with each other.
  public sealed class SceneMeshLatches : CustomObject
  {
    /// Creates a wrapped or uninitialized scene mesh material container.
    internal SceneMeshLatches(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of scene mesh material container.
    public SceneMeshLatches()
    : base(new DestroyDelegate(API.SceneMeshLatchesDestroy), IntPtr.Zero)
    {
      _handle = API.SceneMeshLatchesCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a container for scene mesh latches");
    }

    /// Returns an existing latch with the given index.
    public SceneMeshLatch GetLatch(int index)
    {
      if (!API.SceneMeshLatchesGetLatch(Handle, index, out SceneMeshLatch latch))
        throw new API.Exception($"Could not retrieve scene mesh latch ({index}) from the container");

      return latch;
    }

    /// Updates an existing latch with the given index.
    public void SetLatch(int index, SceneMeshLatch latch)
    {
      if (!API.SceneMeshLatchesSetLatch(Handle, index, ref latch))
        throw new API.Exception($"Could not update scene mesh latch ({index}) in the container");
    }

    /// Returns an index of a latch with the given name (case-insensitive) index or -1 if it doesn't exist.
    public int GetLatchIndex(string name)
    {
      return API.SceneMeshLatchesGetLatchIndex(Handle, Encoding.UTF8.GetBytes(name + '\0'));
    }

    /// Adds a new latch to the container and returns its index.
    public int Add(string name, int type = SceneMeshLatchType.Waypoint, int group = 0,
      Vector3? position = null, Quaternion? orientation = null)
    {
      GCHandle? positionPinned = position is not null ? GCHandle.Alloc(position, GCHandleType.Pinned) : null;
      try
      {
        GCHandle? orientationPinned = orientation is not null ?
          GCHandle.Alloc(orientation, GCHandleType.Pinned) : null;
        try
        {
          int index = API.SceneMeshLatchesAdd(Handle, Encoding.UTF8.GetBytes(name + '\0'), type, group,
            positionPinned?.AddrOfPinnedObject() ?? IntPtr.Zero,
            orientationPinned?.AddrOfPinnedObject() ?? IntPtr.Zero);
          if (index == -1)
            throw new API.Exception($"Could not add a new latch ({name}) to the scene mesh");

          return index;
        }
        finally
        {
          orientationPinned?.Free();
        }
      }
      finally
      {
        positionPinned?.Free();
      }
    }

    /// Removes an existing latch with the given index.
    public void Erase(int index) => API.SceneMeshLatchesErase(Handle, index);

    /// Removes and releases all existing latches.
    public void Clear() => API.SceneMeshLatchesClear(Handle);

    /// Copies latches from another collection.
    public void Copy(SceneMeshLatches other)
    {
      if (!API.SceneMeshLatchesCopy(Handle, other?.Handle ?? IntPtr.Zero))
        throw new API.Exception("Failed copying scene mesh latches from another container");
    }

    /// Takes away the internal contents from another collection without doing any copying.
    public void TakeAway(SceneMeshLatches other)
    {
      API.SceneMeshLatchesTakeAway(Handle, other?.Handle ?? IntPtr.Zero);
    }

    /// Loads a collection of latches from file on disk.
    public bool TryLoadFromFile(string fileName)
    {
      return API.SceneMeshLatchesLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0'));
    }

    /// Loads a collection of latches from file on disk.
    public void LoadFromFile(string fileName)
    {
      if (!API.SceneMeshLatchesLoadFromFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0')))
        throw new API.Exception($"Could not load scene mesh latches from file on disk ({fileName})");
    }

    /// Loads a collection of latches from file in memory.
    public bool TryLoadFromFileInMemory(Array buffer)
    {
      GCHandle pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned);
      try
      {
        return API.SceneMeshLatchesLoadFromFileInMemory(Handle, pinned.AddrOfPinnedObject(),
          (uint)buffer.Length);
      }
      finally
      {
        pinned.Free();
      }
    }

    /// Loads a collection of latches from file in memory.
    public void LoadFromFileInMemory(Array buffer)
    {
      if (!TryLoadFromFileInMemory(buffer))
        throw new API.Exception("Could not load scene mesh latches from file in memory");
    }

    /// Saves a collection of latches to file on disk.
    public void SaveToFile(string fileName)
    {
      if (!API.SceneMeshLatchesSaveToFile(Handle, Encoding.UTF8.GetBytes(fileName + '\0')))
        throw new API.Exception($"Could not save scene mesh latches to file on disk ({fileName})");
    }

    /// Returns calculated waypoint traveling distance for the given latch group.
    public float GetWaypointDistance(int group = 0)
    {
      return API.SceneMeshLatchesGetWaypointDistance(Handle, group);
    }

    /// Triggers recalculation of the interpolated waypoint positions.
    /// This should be done when one or more latches have been modified.
    public void InvalidateWaypoints() => API.SceneMeshLatchesInvalidateWaypoints(Handle);

    /// Returns total number of existing latches.
    public int Count => API.SceneMeshLatchesGetCount(Handle);
  }

  /// A container for a 3D scene mesh model, its optional voxel representation and a bounding volume.
  public sealed class SceneMesh : CustomObject
  {
    /// A special "undefined" value for an vertex elements index.
    public const byte VertexElementsIndexUndefined = 0xFF;

    /// Creates a wrapped or uninitialized scene mesh.
    internal SceneMesh(IntPtr handle) : base(null, handle) { }

    /// Returns minimum and maximum mesh boundaries.
    public void Bounds(out Vector3 boundsMin, out Vector3 boundsMax)
    {
      API.SceneMeshGetBounds(Handle, out boundsMin, out boundsMax);
    }

    /// Specify the parent mesh and the tag to which this slice would refer to.
    public void SetSlice(SceneMesh parent, MeshMetaTag tag)
    {
      API.SceneMeshSetSlice(Handle, parent?.Handle ?? IntPtr.Zero, tag?.Handle ?? IntPtr.Zero);
    }

    /// Returns parent mesh that contains the actual 3D model to be rendered and tag that contains information
    /// about what portion of parent mesh should be rendered
    public void GetSlice(out SceneMesh parent, out MeshMetaTag parentTag)
    {
      API.SceneMeshGetSlice(Handle, out IntPtr handleParent, out IntPtr handleParentTag);
      parent = handleParent != IntPtr.Zero ? new SceneMesh(handleParent) : null;
      parentTag = handleParentTag != IntPtr.Zero ? new MeshMetaTag(handleParentTag) : null;
    }

    /// Issues one or more draw calls for the scene mesh, assigning materials and textures accordingly.
    /// If \a material is \c null, default material will be used. If \a instanceCount is zero, then
    /// non-instancing draw calls will be issued. If \a scene is \c null, this does not issue any draw
    /// calls, instead counting any tentative draw calls that would be issued; this can be used to determine
    /// if any portions of the object would be rendered to decide whether to perform certain pass or not.
    public int AutoDraw(Scene scene, ObjectMaterial? material = null, FloatColor? color = null,
      PrimitiveTopology topology = PrimitiveTopology.Triangles, int instanceCount = 0, int elementCount = 0,
      int firstIndex = 0, int baseVertex = 0, uint options = 0)
    {
      ObjectMaterial material1 = material ?? ObjectMaterial.Default;
      FloatColor color1 = color ?? FloatColor.White;
      int drawCalls = 0;

      if (!API.SceneMeshAutoDraw(Handle, scene?.Handle ?? IntPtr.Zero, ref material1, ref color1, topology,
        instanceCount, elementCount, firstIndex, baseVertex, options, ref drawCalls))
        throw new API.Exception("Could not perform auto-drawing of a 3D scene mesh");

      return drawCalls;
    }

    /// Issues one or more draw calls for the scene mesh, assigning materials and textures accordingly.
    /// If scene mesh is a slice, then renders the portions of the slice accordingly. If \a material is
    /// \c null, default material will be used. If \a instanceCount is zero, then non-instancing draw call
    /// is issued. If \a scene is \c null, this does not issue any draw calls, instead counting any
    /// tentative draw calls that would be issued; this can be used to determine if any portions of
    /// the object would be rendered to decide whether to perform certain pass or not.
    public int AutoDrawSliced(Scene scene, ObjectMaterial? material = null, FloatColor? color = null,
      PrimitiveTopology topology = PrimitiveTopology.Triangles, int instanceCount = 0, uint options = 0)
    {
      ObjectMaterial material1 = material ?? ObjectMaterial.Default;
      FloatColor color1 = color ?? FloatColor.White;
      int drawCalls = 0;

      if (!API.SceneMeshAutoDrawSliced(Handle, scene?.Handle ?? IntPtr.Zero, ref material1, ref color1,
        topology, instanceCount, options, ref drawCalls))
        throw new API.Exception("Could not perform sliced auto-drawing of a 3D scene mesh");

      return drawCalls;
    }

    /// Returns name of the mesh (case-insensitive).
    public string Name
    {
      get
      {
        int nameLength = 0;
        API.SceneMeshGetName(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.SceneMeshGetName(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }
    }

    /// Returns payload associated with the mesh.
    public IntPtr Payload => API.SceneMeshGetPayload(Handle);

    /// Returns a rendereable mesh model, if such is present.
    public MeshModel Model
    {
      get
      {
        IntPtr handle = API.SceneMeshGetModel(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception($"Could not retrieve model associated with the scene mesh");

        return new MeshModel(handle);
      }
    }

    /// A voxel representation of the mesh.
    public MeshVoxel Voxel
    {
      get
      {
        IntPtr handle = API.SceneMeshGetVoxel(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception($"Could not retrieve voxel representation of the scene mesh");

        return new MeshVoxel(handle);
      }
    }

    /// Returns mesh materials associated with the mesh.
    public SceneMeshMaterials Materials
    {
      get
      {
        IntPtr handle = API.SceneMeshGetMaterials(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception($"Could not retrieve materials associated with the scene mesh");

        return new SceneMeshMaterials(handle);
      }
    }

    /// Returns meta tags that represent relevant regions in a 3D mesh, if such are available.
    public MeshMetaTags Tags
    {
      get
      {
        IntPtr handle = API.SceneMeshGetTags(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception($"Could not retrieve tags associated with the scene mesh");

        return new MeshMetaTags(handle);
      }
    }

    /// Returns an integrated collection of latches associated with the 3D mesh.
    public SceneMeshLatches Latches
    {
      get
      {
        IntPtr handle = API.SceneMeshGetLatches(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve latches container from the scene mesh");

        return new SceneMeshLatches(handle);
      }
    }

    /// Returns scale of the mesh.
    public float Scale => API.SceneMeshGetScale(Handle);

    /// Returns size of the mesh.
    public Vector3 Size
    {
      get
      {
        API.SceneMeshGetSize(Handle, out Vector3 size);
        return size;
      }
    }

    /// Returns or changes index of vertex elements that describe the format of mesh vertex buffer.
    /// A value of \c 0xFF means index is undefined.
    public byte VertexElementsIndex
    {
      get => API.SceneMeshGetVertexElementsIndex(Handle);
      set => API.SceneMeshSetVertexElementsIndex(Handle, value);
    }
  }

  /// A collection of 3D scene mesh containers.
  public sealed class SceneMeshes : CustomObject
  {
    /// Creates a wrapped or uninitialized collection.
    internal SceneMeshes(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of object container.
    public SceneMeshes(Device device)
    : base(new DestroyDelegate(API.SceneMeshesDestroy), IntPtr.Zero)
    {
      _handle = API.SceneMeshesCreate(device?.Handle ?? IntPtr.Zero);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a collection of 3D scene meshes");
    }

    /// Creates a new mesh with the given parameters.
    public SceneMesh Add(string name, Vector3 boundsMin, Vector3 boundsMax, float scale = 1.0f,
      IntPtr payload = default)
    {
      IntPtr handle = API.SceneMeshesAdd(Handle, Encoding.UTF8.GetBytes(name + '\0'), payload,
        ref boundsMin, ref boundsMax, scale);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a new 3D scene mesh ({name})");

      return new SceneMesh(handle);
    }

    /// Creates a new mesh, loading contents from a given mesh buffer.
    public SceneMesh AddFromBuffer(string name, MeshBuffer buffer, VertexElement[] vertexElements,
      uint channel = 0, uint semanticIndex = 0, Vector3? boundsMin = null, Vector3? boundsMax = null,
      float scale = 1.0f, IntPtr payload = default)
    {
      GCHandle? boundsMinPinned = boundsMin is not null ?
        GCHandle.Alloc(boundsMin, GCHandleType.Pinned) : null;
      try
      {
        GCHandle? boundsMaxPinned = boundsMax is not null ?
          GCHandle.Alloc(boundsMax, GCHandleType.Pinned) : null;
        try
        {
          IntPtr handle = API.SceneMeshesAddFromBuffer(Handle, Encoding.UTF8.GetBytes(name + '\0'),
            buffer?.Handle ?? IntPtr.Zero, payload, vertexElements, vertexElements.Length,
            boundsMinPinned?.AddrOfPinnedObject() ?? IntPtr.Zero,
            boundsMaxPinned?.AddrOfPinnedObject() ?? IntPtr.Zero, channel, semanticIndex, scale);

          if (handle == IntPtr.Zero)
            throw new API.Exception($"Could not create a new 3D scene mesh ({name}) from buffer");

          return new SceneMesh(handle);
        }
        finally
        {
          boundsMaxPinned?.Free();
        }
      }
      finally
      {
        boundsMinPinned?.Free();
      }
    }

    /// Creates a new mesh, loading contents from a file on disk, which can be either a Wavefront OBJ file
    /// (with ".obj" extension), or a proprietary binary file (with ".mesh" extension). If the source file
    /// is in OBJ format, this function will also try to automatically load an accompanying voxel file (same
    /// file name, but with ".voxel" extension) and/or latches (".latch" extension), if such is present at
    /// the same path. If mesh could not be loaded, a null-handle object will be returned and debug string
    /// will contain error information.
    public SceneMesh TryAddFromFile(string name, string fileName, VertexElement[] vertexElements,
      MeshBuffer.LoadSaveFeedback feedback, out string debug, ref uint options, uint channel = 0,
      uint semanticIndex = 0, float scale = 1.0f, IntPtr payload = default)
    {
      IntPtr handle;
      GCHandle? feedbackPinned = feedback is not null ? GCHandle.Alloc(feedback) : null;
      try
      {
        byte[] debugBytes = ArrayPool<byte>.Shared.Rent(4096);
        try
        {
          int debugLength = debugBytes.Length;

          GCHandle debugBytesPinned = GCHandle.Alloc(debugBytes, GCHandleType.Pinned);
          try
          {
            handle = API.SceneMeshesAddFromFile(Handle, Encoding.UTF8.GetBytes(name + '\0'),
              Encoding.UTF8.GetBytes(fileName + '\0'), payload, vertexElements, vertexElements.Length,
              channel, semanticIndex, ref options, scale, MeshBuffer.WrapperSaveFeedback,
              feedback is not null ? Marshal.GetFunctionPointerForDelegate(feedback) : IntPtr.Zero,
              debugBytesPinned.AddrOfPinnedObject(), ref debugLength);
          }
          finally
          {
            debugBytesPinned.Free();
          }
          if (debugLength > 0)
            debug = Encoding.UTF8.GetString(debugBytes, 0, debugLength);
          else
            debug = string.Empty;
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(debugBytes);
        }
      }
      finally
      {
        feedbackPinned?.Free();
      }
      return handle != IntPtr.Zero ? new SceneMesh(handle) : null;
    }

    /// Creates a new mesh, loading contents from a file on disk, which can be either a Wavefront OBJ file
    /// (with ".obj" extension), or a proprietary binary file (with ".mesh" extension). If the source file
    /// is in OBJ format, this function will also try to automatically load an accompanying voxel file (same
    /// file name, but with ".voxel" extension) and/or latches (".latch" extension), if such is present at
    /// the same path.
    public SceneMesh AddFromFile(string name, string fileName, VertexElement[] vertexElements,
      ref uint options, MeshBuffer.LoadSaveFeedback feedback = null, uint channel = 0,
      uint semanticIndex = 0, float scale = 1.0f, IntPtr payload = default)
    {
      return TryAddFromFile(name, fileName, vertexElements, feedback, out string debug, ref options,
        channel, semanticIndex, scale, payload) ?? throw new API.Exception(
          $"Could not add 3D scene mesh ({name}) from file ({fileName}): {debug}");
    }

    /// Returns mesh with the given index.
    public SceneMesh Mesh(int index)
    {
      IntPtr handle = API.SceneMeshesGetMeshByIndex(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve 3D scene mesh with the given index ({index})");

      return new SceneMesh(handle);
    }

    /// Attempts to retrieve an existing mesh with the given name (case-insensitive).
    public SceneMesh TryMesh(string name)
    {
      IntPtr handle = API.SceneMeshesGetMeshByName(Handle, Encoding.UTF8.GetBytes(name + '\0'));
      return handle != IntPtr.Zero ? new SceneMesh(handle) : null;
    }

    /// Returns mesh with the given name (case-insensitive).
    public SceneMesh Mesh(string name)
    {
      SceneMesh mesh = TryMesh(name);
      if (mesh is null || mesh.Handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve 3D scene mesh with the given name ({name})");

      return mesh;
    }

    /// Returns a mesh that corresponds to the given payload, if such exists.
    public SceneMesh TryPayload(IntPtr payload)
    {
      IntPtr handle = API.SceneMeshesPayload(Handle, payload);
      return handle != IntPtr.Zero ? new SceneMesh(handle) : null;
    }

    /// Returns a mesh that corresponds to the given payload.
    public SceneMesh Payload(IntPtr payload)
    {
      SceneMesh mesh = TryPayload(payload);
      if (mesh is null || mesh.Handle == IntPtr.Zero)
        throw new API.Exception("Could not retrieve 3D scene mesh for the given payload");

      return mesh;
    }

    /// Tests whether a mesh with the given name exists.
    public bool Exists(string name) => TryMesh(name) is not null;

    /// Tests whether a mesh with the given payload exists.
    public bool PayloadExists(IntPtr payload) => TryPayload(payload) is not null;

    /// Erases the given mesh.
    public void Erase(SceneMesh mesh) => API.SceneMeshesErase(Handle, mesh.Handle);

    /// Removes all existing meshes.
    public void Clear() => API.SceneMeshesClear(Handle);

    /// Splits the given parent mesh into "dummy" sub-meshes by specific meta-tag type. Each sub-mesh splitted
    /// this way will refer to parent and one of its tag accordingly. This enables sharing the same 3D model
    /// for rendering, while having customized voxel representation and/or latches for each splitted segment.
    public void Slice(SceneMesh parent, byte tag = MeshMetaTagType.Object, string prefix = null)
    {
      if (!API.SceneMeshesSlice(Handle, parent?.Handle ?? IntPtr.Zero, tag, string.IsNullOrEmpty(prefix) ?
        null : Encoding.UTF8.GetBytes(prefix + '\0')))
        throw new API.Exception("Could not perform slicing of a given mesh");

    }

    /// Returns device associated with the collection.
    public Device Device
    {
      get
      {
        IntPtr handle = API.SceneMeshesGetDevice(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve device associated with " +
            "the 3D scene meshes collection");

        return new Device(handle);
      }
    }

    /// Returns number of existing meshes.
    public int Count => API.SceneMeshesGetCount(Handle);
  }

  /// A camera module that can represent both first-person and third-person views for 3D world exploration
  /// and object manipulation.
  public sealed class ActorCamera : CustomObject
  {
    /// Creates a wrapped or uninitialized camera module.
    internal ActorCamera(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of camera module.
    public ActorCamera(Vector3 position, Vector3 rotation, float distance)
    : base(new DestroyDelegate(API.ActorCameraDestroy), IntPtr.Zero)
    {
      _handle = API.ActorCameraCreate(ref position, ref rotation, distance);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D camera module");
    }

    /// Creates new instance of camera module.
    public ActorCamera(float distance)
    : base(new DestroyDelegate(API.ActorCameraDestroy), IntPtr.Zero)
    {
      Vector3 position = default;
      Vector3 rotation = new ((float)Math.PI * 0.25f, 0.0f, 0.0f);

      _handle = API.ActorCameraCreate(ref position, ref rotation, distance);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a 3D camera module");
    }

    /// Returns camera's position.
    public Vector3 GetPosition()
    {
      API.ActorCameraGetPosition(Handle, out Vector3 position);
      return position;
    }

    /// Updates camera's position. Returns "true" if position has actually changed.
    public bool SetPosition(Vector3 position) => API.ActorCameraSetPosition(Handle, ref position);

    /// Returns current camera rotation. "w" component is a rotation for Lenticular printing.
    public Vector4 GetRotation()
    {
      API.ActorCameraGetRotation(Handle, out Vector4 rotation);
      return rotation;
    }

    /// Sets new camera rotation. "w" component is a rotation for Lenticular printing.
    /// Returns "true" if rotation has actually changed.
    public bool SetRotation(Vector4 rotation) => API.ActorCameraSetRotation(Handle, ref rotation);

    /// Returns current camera rotation as quaternion.
    public Quaternion GetQuaternion()
    {
      API.ActorCameraGetQuaternion(Handle, out Quaternion quaternion);
      return quaternion;
    }

    /// Changes current camera's rotation as quaternion. Returns "true" if rotation has actually changed.
    public bool SetQuaternion(Quaternion quaternion)
    {
      return API.ActorCameraSetQuaternion(Handle, ref quaternion);
    }

    /// Changes camera's zoom by adjusting its distance and position around the specified point.
    public void Zoom(Vector2 position, Vector2 size, Matrix4x4 projection, float delta)
    {
      API.ActorCameraZoom(Handle, ref position, ref size, ref projection, delta);
    }

    /// Adjusts camera position while performing orthographic zoom.
    public void ZoomOrtho(Vector2 position, Vector2 size, float distance, float adjustedDistance,
      Matrix4x4 projection, Matrix4x4 adjustedProjection)
    {
      API.ActorCameraZoomOrtho(Handle, ref position, ref size, distance, adjustedDistance, ref projection,
        ref adjustedProjection);
    }

    /// Changes camera's zoom by adjusting its distance and position around the specified point.
    public bool Command(CameraCommand command, Vector3 position, Vector2 size, Vector3? sense = null,
      Matrix4x4? projection = null)
    {
      Vector3 sense1 = sense ?? default;
      Matrix4x4 projection1 = projection ?? Matrix4x4.Identity;
      return API.ActorCameraCommand(Handle, command, ref position, ref size, ref sense1, ref projection1);
    }

    /// Returns camera's forward vector (looking direction).
    public Vector3 Forward
    {
      get
      {
        API.ActorCameraGetForward(Handle, out Vector3 forward);
        return forward;
      }
    }

    /// Returns camera's right vector (sideway direction).
    public Vector3 Right
    {
      get
      {
        API.ActorCameraGetRight(Handle, out Vector3 right);
        return right;
      }
    }

    /// Returns camera's ceiling vector (up direction).
    public Vector3 Ceiling
    {
      get
      {
        API.ActorCameraGetCeiling(Handle, out Vector3 ceiling);
        return ceiling;
      }
    }

    /// Returns or changes the distance between camera's position and the viewer (for third-person view).
    public float Distance
    {
      get => API.ActorCameraGetDistance(Handle);
      set => API.ActorCameraSetDistance(Handle, value);
    }

    /// Returns camera's resulting 3D view matrix.
    public Matrix4x4 View
    {
      get
      {
        API.ActorCameraGetView(Handle, out Matrix4x4 view);
        return view;
      }
    }

    /// Returns or changes 3D camera constraints.
    public CameraConstraints Constraints
    {
      get
      {
        API.ActorCameraGetConstraints(Handle, out CameraConstraints constraints);
        return constraints;
      }
      set => API.ActorCameraSetConstraints(Handle, ref value);
    }
  }

  /// Timer that can be used in multimedia interactive applications to ensure fixed processing rate and/or
  /// variable processing rate (e.g. as fast as possible) with a fixed-rate counter.
  public sealed class Timer : CustomObject
  {
    /// Creates a wrapped or uninitialized timer module.
    internal Timer(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of timer module.
    public Timer()
    : base(new DestroyDelegate(API.TimerDestroy), IntPtr.Zero)
    {
      _handle = API.TimerCreate();
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create a timer module");
    }

    /// Ensures that next processing time slice is reached. Returns whether the method was executed within
    /// the correct time slice (in other words, no timer slices have been lost, which could result in
    /// stuttering).
    public bool NextSlice() => API.TimerNextSlice(Handle);

    /// Ensures that next processing time slice is reached, updates latency and calculates average frame
    /// rate. Returns whether the method was executed within the correct time slice (in other words, no
    /// timer slices have been lost, which could result in stuttering). Updates fixed-rate token counter.
    public bool UpdateNextSlice() => API.TimerUpdateNextSlice(Handle);

    /// Updates latency, calculates average frame rate and updates fixed-rate token counter.
    public void Update() => API.TimerUpdate(Handle);

    /// Resets internal structures of the timer, restarts the timing calculations and sets current time
    /// slice to zero. This can be useful when an unscheduled and/or a time-consuming task was executed.
    public void Reset() => API.TimerReset(Handle);

    /// Reduces number of skipped time slices by the given quantity. If the quantity is larger than
    /// the actual number of skipped time slices, resets that number to zero.
    public void TrimSkippedTimeSlices(long slicesToTrim)
    {
      API.TimerTrimSkippedTimeSlices(Handle, slicesToTrim);
    }

    /// Returns or changes multimedia timer processing speed (in terms of frames per second).
    /// Typical (and default) speed is 60 frames per second.
    public double Speed
    {
      get => API.TimerGetSpeed(Handle);
      set => API.TimerSetSpeed(Handle, value);
    }

    /// Returns calculated average frame rate in frames per second. This value is updated roughly two times
    /// per second and can only be used for informative purposes (e.g. displaying frame rate in the
    /// application). For precise real-time indications it is recommended to use "Latency".
    public float FrameRate => API.TimerGetFrameRate(Handle);

    /// Returns time (in microseconds) calculated between previous and current frames. This can be a direct
    /// indication of rendering performance as it tells how much time it took to render (and possibly,
    /// process) the frame.
    public ulong Latency => API.TimerGetLatency(Handle);

    /// Returns current time slice.
    public long TimeSlice => API.TimerGetTimeSlice(Handle);

    /// Returns total number of skipped time slices.
    public long SkippedTimeSlices => API.TimerGetSkippedTimeSlices(Handle);

    /// Extracts existing fixed-rate token counter from the timer.
    public int ExtractTokens => API.TimerExtractTokens(Handle);
  }

  /// Application execution manager that handles OS events and controls application's window.
  public sealed class Application : CustomObject
  {
    /// Basic application event.
    public delegate void EventCallback(object sender);

    /// Application creation event.
    public delegate bool BoolCallback(object sender);

    /// Application mouse handling event.
    public delegate void MouseCallback(object sender, MouseEvent mouseEvent, MouseButton button,
      Point position);

    /// Application keyboard handling event.
    public delegate void KeyboardCallback(object sender, KeyEvent keyEvent, int virtualKey, ushort keyCode);

    /// Event function for application status change events.
    public delegate void StatusCallback(object sender, ApplicationEvent appEvent);

    /// Custom application message handling event.
    public delegate void EventHookCallback(object sender, IntPtr eventData);

    /// Event invoked when application is created.
    public event BoolCallback OnCreate;

    /// Event invoked when application is destroyed.
    public event EventCallback OnDestroy;

    /// Event invoked when application is rendered.
    public event EventCallback OnRender;

    /// Event invoked when mouse has performed an action.
    public event MouseCallback OnMouse;

    /// Event invoked when keyboard has performed an action.
    public event KeyboardCallback OnKeyboard;

    /// Event invoked when application and/or window status has changed.
    public event StatusCallback OnStatus;

    /// Event invoked when application is idle.
    public event BoolCallback OnIdle;

    /// Event invoked when application's window has been resized.
    public event EventCallback OnResize;

    /// Event invoked to handle application's internal processes.
    public event EventHookCallback OnHook;

    /// Creates a wrapped or uninitialized application manager.
    internal Application(IntPtr handle) : base(null, handle) { }

    /// Creates new instance of application manager.
    public Application(ApplicationConfiguration configuration)
    : base(new DestroyDelegate(API.ApplicationDestroy), IntPtr.Zero)
    {
      _createFunc = new ApplicationEvents.BoolFunc(EventCreateWrapper);
      _destroyFunc = new ApplicationEvents.EventFunc(EventDestroyWrapper);
      _renderFunc = new ApplicationEvents.EventFunc(EventRenderWrapper);
      _mouseFunc = new ApplicationEvents.MouseFunc(EventMouseWrapper);
      _keyboardFunc = new ApplicationEvents.KeyboardFunc(EventKeyboardWrapper);
      _statusFunc = new ApplicationEvents.StatusFunc(EventStatusWrapper);
      _idleFunc = new ApplicationEvents.BoolFunc(EventIdleWrapper);
      _resizeFunc = new ApplicationEvents.EventFunc(EventResizeWrapper);
      _hookFunc = new ApplicationEvents.EventHookFunc(EventHookWrapper);

      _handle = API.ApplicationCreate(ref configuration);
      if (_handle == IntPtr.Zero)
        throw new API.Exception("Could not create an application manager");

      ApplicationEvents events = new (_createFunc, _destroyFunc, _renderFunc, _mouseFunc,
        _keyboardFunc, _statusFunc, _idleFunc, _resizeFunc, _hookFunc);

      API.ApplicationSetEvents(_handle, ref events, IntPtr.Zero);
    }

    /// Requests the application window to be repainted.
    public void Invalidate() => API.ApplicationInvalidate(Handle);

    /// Executes application's main loop (or returns "false" when there were errors during application
    /// create event.
    public bool Execute() => API.ApplicationExecute(Handle);

    /// Finishes execution of the application.
    public void Terminate() => API.ApplicationTerminate(Handle);

    /// Converts virtual key code to portable key code. Returns Key::Null if no equivalent is found.
    public Key TranslateVirtualKey(int virtualKey)
    {
      return API.ApplicationTranslateVirtualKey(Handle, virtualKey);
    }

    /// Converts portable key code to virtual key code. Returns 0 if no equivalent is found.
    public int ConvertPortableKey(Key key)
    {
      return API.ApplicationConvertPortableKey(Handle, key);
    }

    /// Attempts to read text from clipboard.
    public string ReadTextFromClipboard(int textBufferSize = 4096)
    {
      byte[] text = ArrayPool<byte>.Shared.Rent(textBufferSize);
      try
      {
        GCHandle pinned = GCHandle.Alloc(text, GCHandleType.Pinned);
        try
        {
          API.ApplicationGetTitle(Handle, pinned.AddrOfPinnedObject(), ref textBufferSize);
        }
        finally
        {
          pinned.Free();
        }
        return Encoding.UTF8.GetString(text, 0, textBufferSize);
      }
      finally
      {
        ArrayPool<byte>.Shared.Return(text);
      }
    }

    /// Attempts to write text to clipboard.
    public void WriteTextToClipboard(string text)
    {
      if (!API.ApplicationWriteTextToClipboard(Handle, Encoding.UTF8.GetBytes(text + '\0')))
        throw new API.Exception($"Could not write given text ({text}) to application's clipboard");
    }

    /// Captures mouse input for the application window.
    public void CaptureMouseInput()
    {
      if (!API.ApplicationCaptureMouseInput(Handle))
        throw new API.Exception($"Could not capture application's mouse input");
    }

    /// Releases mouse input for the application window.
    public bool ReleaseMouseInput() => API.ApplicationReleaseMouseInput(Handle);

    /// Specifies one or more application icons.
    public void SetIcons(Surface[] surfaces)
    {
      IntPtr[] surfaceHandles = new IntPtr[surfaces.Length];

      for (int i = 0; i < surfaces.Length; i++)
        surfaceHandles[i] = surfaces[i].Handle;

      IntPtr unmanagedHandles = Marshal.AllocHGlobal(IntPtr.Size * surfaceHandles.Length);
      try
      {
        Marshal.Copy(surfaceHandles, 0, unmanagedHandles, surfaceHandles.Length);
        if (!API.ApplicationSetIcons(_handle, unmanagedHandles, surfaceHandles.Length))
          throw new API.Exception("Could not change application icon(s)");
      }
      finally
      {
        Marshal.FreeHGlobal(unmanagedHandles);
      }
    }

    /// Changes application icons by loading them from external files.
    /// Note: this method will only work while at least one device instance currently exists.
    public void SetIcons(string[] fileNames)
    {
      IntPtr[] unmanagedFileNames = new IntPtr[fileNames.Length];
      try
      {
        for (int i = 0; i < fileNames.Length; i++)
        {
          byte[] fileNameBytes = Encoding.UTF8.GetBytes(fileNames[i] + '\0');
          unmanagedFileNames[i] = Marshal.AllocHGlobal(fileNameBytes.Length);
          Marshal.Copy(fileNameBytes, 0, unmanagedFileNames[i], fileNameBytes.Length);
        }
        IntPtr unmanagedPointers = Marshal.AllocHGlobal(IntPtr.Size * unmanagedFileNames.Length);
        try
        {
          Marshal.Copy(unmanagedFileNames, 0, unmanagedPointers, unmanagedFileNames.Length);
          if (!API.ApplicationSetIconsFromFiles(_handle, unmanagedPointers, unmanagedFileNames.Length))
            throw new API.Exception("Could not load application icon(s) from external file(s)");
        }
        finally
        {
          Marshal.FreeHGlobal(unmanagedPointers);
        }
      }
      finally
      {
        foreach (IntPtr unmanagedFileName in unmanagedFileNames)
        {
          if (unmanagedFileName != IntPtr.Zero)
            Marshal.FreeHGlobal(unmanagedFileName);
        }
      }
    }

    /// Captures mouse input for the application window.
    public bool FileChooserDialog(ref string filePath, FileChooserDialog dialog, string filters,
      string caption, string accept, string reject, int filePathBufferSize = 4096)
    {
      bool success;
      byte[] filePathBuffer = Encoding.UTF8.GetBytes(filePath + '\0');
      Array.Resize(ref filePathBuffer, filePathBufferSize);
      GCHandle pinned = GCHandle.Alloc(filePathBuffer, GCHandleType.Pinned);
      try
      {
        success = API.ApplicationFileChooserDialog(Handle, pinned.AddrOfPinnedObject(),
          ref filePathBufferSize, dialog, Encoding.UTF8.GetBytes(filters + '\0'),
          Encoding.UTF8.GetBytes(caption + '\0'), Encoding.UTF8.GetBytes(accept + '\0'),
          Encoding.UTF8.GetBytes(reject + '\0'));
      }
      finally
      {
        pinned.Free();
      }
      if (success)
        filePath = Encoding.UTF8.GetString(filePathBuffer, 0, filePathBufferSize);

      return success;
    }

    /// Retrieves the handle of main application window.
    public IntPtr WindowHandle => API.ApplicationGetWindowHandle(_handle);

    /// Application's title.
    public string Title
    {
      get
      {
        int nameLength = 0;
        API.ApplicationGetTitle(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.ApplicationGetTitle(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }

      set
      {
        if (!API.ApplicationSetTitle(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change application's title ({value})");
      }
    }

    /// Application icon's title.
    public string IconTitle
    {
      get
      {
        int nameLength = 0;
        API.ApplicationGetIconTitle(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.ApplicationGetIconTitle(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }

      set
      {
        if (!API.ApplicationSetIconTitle(Handle, Encoding.UTF8.GetBytes(value + '\0')))
          throw new API.Exception($"Could not change application's title ({value})");
      }
    }

    /// Path to application's executable.
    public string ExecutablePath
    {
      get
      {
        int nameLength = 0;
        API.ApplicationGetExecutablePath(Handle, IntPtr.Zero, ref nameLength);
        byte[] name = ArrayPool<byte>.Shared.Rent(nameLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(name, GCHandleType.Pinned);
          try
          {
            API.ApplicationGetExecutablePath(Handle, pinned.AddrOfPinnedObject(), ref nameLength);
          }
          finally
          {
            pinned.Free();
          }
          return Encoding.UTF8.GetString(name, 0, nameLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(name);
        }
      }
    }

    /// Current application's window rectangle.
    public Rect WindowRect
    {
      get
      {
        API.ApplicationGetWindowRect(_handle, out Rect rect);
        return rect;
      }

      set => API.ApplicationSetWindowRect(_handle, ref value);
    }

    /// Current application's window client rectangle.
    public Rect ClientRect
    {
      get
      {
        API.ApplicationGetClientRect(_handle, out Rect rect);
        return rect;
      }
    }

    /// Sets new application's window client size.
    public Point ClientSize
    {
      set => API.ApplicationSetClientSize(_handle, ref value);
    }

    /// Current minimal size of the window. Values of zero means that there is no limit.
    public Point MinimalSize
    {
      get
      {
        API.ApplicationGetMinimalSize(_handle, out Point size);
        return size;
      }

      set => API.ApplicationSetMinimalSize(_handle, ref value);
    }

    /// Returns the scale of application window.
    public double WindowScale => API.ApplicationGetWindowScale(_handle);

    /// Current application's window state.
    public ApplicationWindowState WindowState
    {
      get => API.ApplicationGetWindowState(_handle);

      set
      {
        if (!API.ApplicationSetWindowState(_handle, value))
          throw new API.Exception($"Could not change application's window state ({value})");
      }
    }

    /// Current application window's cursor.
    public AppCursor Cursor
    {
      get => API.ApplicationGetCursor(_handle);

      set
      {
        if (!API.ApplicationSetCursor(_handle, value))
          throw new API.Exception($"Could not change application's window cursor ({value})");
      }
    }

    /// Indicates whether the mouse input is currently captured.
    public bool MouseInputCaptured => API.ApplicationMouseInputCaptured(_handle);

    /// Internal wrapper function for application creation event.
    private bool EventCreateWrapper(IntPtr application, IntPtr user)
    {
      return OnCreate?.Invoke(this) ?? true;
    }

    /// Internal wrapper function for application destruction event.
    private void EventDestroyWrapper(IntPtr application, IntPtr user)
    {
      OnDestroy?.Invoke(this);
    }

    /// Internal wrapper function for application rendering event.
    private void EventRenderWrapper(IntPtr application, IntPtr user)
    {
      OnRender?.Invoke(this);
    }

    /// Internal wrapper function for application mouse handling event.
    private void EventMouseWrapper(IntPtr application, MouseEvent mouseEvent, MouseButton button,
      ref Point position, IntPtr user)
    {
      OnMouse?.Invoke(this, mouseEvent, button, position);
    }

    /// Internal wrapper function for application keyboard handling event.
    private void EventKeyboardWrapper(IntPtr application, KeyEvent keyEvent, int virtualKey,
      ushort keyCode, IntPtr user)
    {
      OnKeyboard?.Invoke(this, keyEvent, virtualKey, keyCode);
    }

    /// Internal wrapper function for application status change event.
    private void EventStatusWrapper(IntPtr application, ApplicationEvent appEvent, IntPtr user)
    {
      OnStatus?.Invoke(this, appEvent);
    }

    /// Internal wrapper function for application idle event.
    private bool EventIdleWrapper(IntPtr application, IntPtr user)
    {
      return OnIdle?.Invoke(this) ?? false;
    }

    /// Internal wrapper function for application's window resize event.
    private void EventResizeWrapper(IntPtr application, IntPtr user)
    {
      OnResize?.Invoke(this);
    }

    /// Internal wrapper function for application hook event.
    private void EventHookWrapper(IntPtr application, IntPtr eventData, IntPtr user)
    {
      OnHook?.Invoke(this, eventData);
    }

    /// Pinned application create delegate.
    private readonly ApplicationEvents.BoolFunc _createFunc;

    /// Pinned application destroy delegate.
    private readonly ApplicationEvents.EventFunc _destroyFunc;

    /// Pinned application render delegate.
    private readonly ApplicationEvents.EventFunc _renderFunc;

    /// Pinned application mouse handling delegate.
    private readonly ApplicationEvents.MouseFunc _mouseFunc;

    /// Pinned application keyboard handling delegate.
    private readonly ApplicationEvents.KeyboardFunc _keyboardFunc;

    /// Pinned application status change delegate.
    private readonly ApplicationEvents.StatusFunc _statusFunc;

    /// Pinned application idle delegate.
    private readonly ApplicationEvents.BoolFunc _idleFunc;

    /// Pinned application window resize delegate.
    private readonly ApplicationEvents.EventFunc _resizeFunc;

    /// Pinned application hook delegate.
    private readonly ApplicationEvents.EventHookFunc _hookFunc;
  }

  /// A widget module serving as a base element of user interface.
  public class Widget : CustomObject, IEnumerable<Widget.PropertyValue>
  {
    /// A widget iteration value, consisting of a property name, type, behavior and its value.
    public readonly struct PropertyValue
    {
      /// Constructs a widget property iteration value.
      public PropertyValue(string name, WidgetPropertyType type, WidgetPropertyBehavior behavior,
        object value)
      {
        Name = name;
        Type = type;
        Behavior = behavior;
        Value = value;
      }

      /// Name of the property (case-insensitive).
      public string Name { get; }

      /// Type of the property.
      public WidgetPropertyType Type { get; }

      /// Behavior of the property.
      public WidgetPropertyBehavior Behavior { get; }

      /// Value of the property.
      public object Value { get; }
    }

    /// Iterator for accessing properties in the widget.
    public sealed class Iterator : IEnumerator<PropertyValue>
    {
      private readonly IntPtr _parent;
      private PropertyValue _value;
      private ushort _location;
      private bool _started;
      internal Iterator(IntPtr parent)
      {
        _parent = parent;
      }
      void IDisposable.Dispose() { }
      public bool MoveNext()
      {
        if (_started && _location == 0)
          return false;

        _started = true;

        WidgetProperty property = new();
        property.Location = _location;

        if (!API.WidgetPropertyIdentify(_parent, ref property) || property.NameBytes.Length < 1)
        {
          _location = 0;
          return false;
        }
        _location = property.Location;
        _value = new PropertyValue(property.Name, property.Type, property.Behavior,
          RetrievePropertyValue(_parent, property));

        return true;
      }
      public void Reset()
      {
        _location = 0;
        _started = false;
      }
      object IEnumerator.Current => Current;
      public PropertyValue Current
      {
        get
        {
          if (string.IsNullOrEmpty(_value.Name))
            throw new API.Exception("Could not identify widget's property.");

          return _value;
        }
      }
    }

    /// External callback event type.
    public delegate bool ExternalEventFunc(Widget sender, string eventName);

    /// Creates a wrapped or uninitialized widget module.
    internal Widget(IntPtr handle) : base(null, handle) { }

    /// Creates a wrapped or uninitialized widget module.
    internal Widget(DestroyDelegate destroy, IntPtr handle) : base(destroy, handle) { }

    /// Creates a new instance of widget.
    public Widget(Widget manager, string className, string instanceName = null, IntPtr payload = new IntPtr())
    : base(new DestroyDelegate(API.WidgetDestroy), IntPtr.Zero)
    {
      _handle = API.WidgetCreate(manager?.Handle ?? IntPtr.Zero, Encoding.UTF8.GetBytes(className + '\0'),
        instanceName is not null ? Encoding.UTF8.GetBytes(instanceName + '\0') : null, payload);
      if (_handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a widget ({className}) with name ({instanceName})");
    }

    /// Converts local widget position to screen coordinates.
    public Vector2 LocalToScreen(Vector2 localPos)
    {
      API.WidgetLocalToScreen(Handle, out Vector2 screenPos, ref localPos);
      return screenPos;
    }

    /// Converts screen coordinates to local widget position.
    public Vector2 ScreenToLocal(Vector2 screenPos)
    {
      API.WidgetScreenToLocal(Handle, out Vector2 localPos, ref screenPos);
      return localPos;
    }

    /// Puts the widget on front of other widgets.
    public void BringToFront() => API.WidgetBringToFront(Handle);

    /// Puts the widget behind all other widgets.
    public void SendToBack() => API.WidgetSendToBack(Handle);

    /// Schedules widget repaint during next update cycle.
    public void Invalidate() => API.WidgetInvalidate(Handle);

    /// Schedules widget repositioning during next update cycle.
    public void Accomodate() => API.WidgetAccomodate(Handle);

    /// Updates widget and its children. \c latency must be a number of milliseconds elapsed since previous
    /// call to update; this is necessary to ensure proper animations in the widgets. Returns \c true if
    /// the application needs to be repainted or \c false otherwise.
    public bool Update(double latency) => API.WidgetUpdate(Handle, latency);

    /// Makes the widget receive and process mouse input.
    public void AcceptMouse(MouseEvent _event, MouseButton button, Vector2 position)
    {
      API.WidgetAcceptMouse(Handle, _event, button, ref position);
    }

    /// Makes the widget receive and process keyboard input.
    public void AcceptKey(KeyEvent _event, Key key, uint charCode)
    {
      API.WidgetAcceptKey(Handle, _event, key, charCode);
    }

    /// Returns an existing widget's child with the given index.
    public Widget GetChild(int index)
    {
      IntPtr handle = API.WidgetGetChildByIndex(Handle, index);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not obtain widget child at index ({index})");

      return new Widget(handle);
    }

    /// Attempts to find a widget among given parent's widget children and returns its index.
    /// If the given child is not found among parent's widget children, returns -1.
    public int GetChildIndex(Widget widget)
    {
      return API.WidgetGetChildIndex(Handle, widget?.Handle ?? IntPtr.Zero);
    }

    /// Returns an existing widget's child with the given index.
    public Widget FindAt(Vector2 position)
    {
      IntPtr handle = API.WidgetFindAt(Handle, ref position);
      return handle != IntPtr.Zero ? new Widget(handle) : null;
    }

    /// Returns an existing widget's child with the given index.
    public bool InvokeEvent(string eventName)
    {
      return API.WidgetInvokeEvent(Handle, Encoding.UTF8.GetBytes(eventName + '\0'));
    }

    /// Determines property type from the given type.
    private static WidgetPropertyType DeterminePropertyType(Type type)
    {
      if (type == typeof(int))
        return WidgetPropertyType.Integer;
      else if (type == typeof(long))
        return WidgetPropertyType.Int64;
      else if (type == typeof(float))
        return WidgetPropertyType.Float;
      else if (type == typeof(double))
        return WidgetPropertyType.Real;
      else if (type == typeof(bool))
        return WidgetPropertyType.Boolean;
      else if (type == typeof(Vector2))
        return WidgetPropertyType.Point;
      else if (type == typeof(Vector3))
        return WidgetPropertyType.Vector;
      else if (type == typeof(RectF))
        return WidgetPropertyType.Rect;
      else if (type == typeof(Margins))
        return WidgetPropertyType.Margins;
      else if (type == typeof(uint))
        return WidgetPropertyType.Color;
      else if (type == typeof(ColorPair))
        return WidgetPropertyType.ColorPair;
      else if (type == typeof(ColorRect))
        return WidgetPropertyType.ColorRect;
      else if (type.IsEnum)
        return WidgetPropertyType.Enumeration;
      else if (type == typeof(FontParameters))
        return WidgetPropertyType.Font;
      else if (type == typeof(string))
        return WidgetPropertyType.String;
      else
        return WidgetPropertyType.None;
    }

    /// Determines size of the given property type.
    private static int DeterminePropertySize(WidgetPropertyType type)
    {
      switch (type)
      {
        case WidgetPropertyType.Integer:
        case WidgetPropertyType.Float:
        case WidgetPropertyType.Color:
          return 4;

        case WidgetPropertyType.Int64:
        case WidgetPropertyType.Real:
        case WidgetPropertyType.Point:
        case WidgetPropertyType.ColorPair:
          return 8;

        case WidgetPropertyType.Vector:
          return 12;

        case WidgetPropertyType.Rect:
        case WidgetPropertyType.Margins:
        case WidgetPropertyType.ColorRect:
          return 16;

        case WidgetPropertyType.Boolean:
        case WidgetPropertyType.Enumeration:
          return 1;

        case WidgetPropertyType.Font:
          return 184;

        default:
          return 0;
      }
    }

    /// Retrieves a value of an enumerated property.
    private static object RetrievePropertyValue(IntPtr widgetHandle, WidgetProperty property)
    {
      switch (property.Type)
      {
        case WidgetPropertyType.Integer:
          return BitConverter.ToInt32(property.DataBytes, 0);

        case WidgetPropertyType.Int64:
          return BitConverter.ToInt64(property.DataBytes, 0);

        case WidgetPropertyType.Float:
          return BitConverter.ToSingle(property.DataBytes, 0);

        case WidgetPropertyType.Real:
          return BitConverter.ToDouble(property.DataBytes, 0);

        case WidgetPropertyType.Boolean:
          return property.DataBytes[0] != 0;

        case WidgetPropertyType.Point:
          return new Vector2(BitConverter.ToSingle(property.DataBytes, 0),
            BitConverter.ToSingle(property.DataBytes, 4));

        case WidgetPropertyType.Vector:
          return new Vector3(BitConverter.ToSingle(property.DataBytes, 0),
            BitConverter.ToSingle(property.DataBytes, 4), BitConverter.ToSingle(property.DataBytes, 8));

        case WidgetPropertyType.Rect:
          return new RectF(
            BitConverter.ToSingle(property.DataBytes, 0), BitConverter.ToSingle(property.DataBytes, 4),
            BitConverter.ToSingle(property.DataBytes, 8), BitConverter.ToSingle(property.DataBytes, 12));

        case WidgetPropertyType.Margins:
          return new Margins(
            BitConverter.ToSingle(property.DataBytes, 0), BitConverter.ToSingle(property.DataBytes, 4),
            BitConverter.ToSingle(property.DataBytes, 8), BitConverter.ToSingle(property.DataBytes, 12));

        case WidgetPropertyType.Color:
          return BitConverter.ToUInt32(property.DataBytes, 0);

        case WidgetPropertyType.ColorPair:
          return new ColorPair(BitConverter.ToUInt32(property.DataBytes, 0),
            BitConverter.ToUInt32(property.DataBytes, 4));

        case WidgetPropertyType.ColorRect:
          return new ColorRect(
            BitConverter.ToUInt32(property.DataBytes, 0), BitConverter.ToUInt32(property.DataBytes, 4),
            BitConverter.ToUInt32(property.DataBytes, 8), BitConverter.ToUInt32(property.DataBytes, 12));

        case WidgetPropertyType.Enumeration:
          return property.DataBytes[0];

        case WidgetPropertyType.String:
          {
            if (property.DataBytes[0] == 255)
            {
              int resLength = 0;
              API.WidgetGetPropertyAsString(widgetHandle, property.NameBytes, IntPtr.Zero, ref resLength);
              byte[] res = ArrayPool<byte>.Shared.Rent(resLength);
              try
              {
                GCHandle pinned = GCHandle.Alloc(res, GCHandleType.Pinned);
                try
                {
                  API.WidgetGetPropertyAsString(widgetHandle, property.NameBytes,
                    pinned.AddrOfPinnedObject(), ref resLength);
                }
                finally
                {
                  pinned.Free();
                }
                return Encoding.UTF8.GetString(res, 0, resLength);
              }
              finally
              {
                ArrayPool<byte>.Shared.Return(res);
              }
            }
            else if (property.DataBytes[0] != 0)
              return Encoding.UTF8.GetString(property.DataBytes, 1, property.DataBytes[0]);
            else
              return string.Empty;
          }

        case WidgetPropertyType.Font:
          {
            GCHandle handle = GCHandle.Alloc(property.DataBytes, GCHandleType.Pinned);
            try
            {
              return Marshal.PtrToStructure<FontParameters>(handle.AddrOfPinnedObject());
            }
            finally
            {
              handle.Free();
            }
          }

        default:
          return null;
      }
    }

    /// Returns value of an existing property with the given name (case-insensitive).
    /// If such property is not found, returns \c null.
    public object Get(string propertyName)
    {
      if (string.IsNullOrEmpty(propertyName))
        throw new API.Exception("Cannot retrieve a property without name");

      WidgetProperty property = default;
      property.Name = propertyName;
      property.Location = 0;

      if (!API.WidgetPropertyIdentify(Handle, ref property))
        return null;

      return RetrievePropertyValue(Handle, property);
    }

    /// Changes contents of an existing property with the given name (case-insensitive) or creates a new
    /// property with the given value.
    public void Set(string propertyName, object value, bool invokeChanged = true)
    {
      if (string.IsNullOrEmpty(propertyName))
        throw new API.Exception("Cannot modify a property without name");

      byte[] propertyNameBytes = Encoding.UTF8.GetBytes(propertyName + '\0');

      if (value is null)
      {
        if (!API.WidgetSetProperty(Handle, propertyNameBytes, WidgetPropertyType.None, IntPtr.Zero,
          invokeChanged))
          throw new API.Exception($"Could not remove value of a property ({propertyName})");

        return;
      }
      WidgetPropertyType type = DeterminePropertyType(value.GetType());
      if (type == WidgetPropertyType.None)
        throw new API.Exception($"Cannot determine property ({propertyName}) type for the given value");

      if (type == WidgetPropertyType.String)
      { // Pass value as a C string.
        byte[] stringBytes = Encoding.UTF8.GetBytes((string)value + '\0');

        GCHandle stringPinned = GCHandle.Alloc(stringBytes, GCHandleType.Pinned);
        try
        {
          if (!API.WidgetSetProperty(Handle, Encoding.UTF8.GetBytes(propertyName + '\0'),
            WidgetPropertyType.String, stringPinned.AddrOfPinnedObject(), invokeChanged))
            throw new API.Exception($"Could not update a property ({propertyName}) as a string value");
        }
        finally
        {
          stringPinned.Free();
        }

        return;
      }
      int size = DeterminePropertySize(type);
      if (size == 0)
        throw new API.Exception($"Cannot determine property ({propertyName}) size for its type ({type})");

      IntPtr propertyData = Marshal.AllocHGlobal(size);
      try
      {
        switch (type)
        {
          case WidgetPropertyType.Integer:
            Marshal.WriteInt32(propertyData, (int)value);
            break;

          case WidgetPropertyType.Int64:
            Marshal.WriteInt64(propertyData, (long)value);
            break;

          case WidgetPropertyType.Float:
            Marshal.WriteInt32(propertyData, BitConverter.SingleToInt32Bits((float)value));
            break;

          case WidgetPropertyType.Real:
            Marshal.WriteInt64(propertyData, BitConverter.DoubleToInt64Bits((double)value));
            break;

          case WidgetPropertyType.Boolean:
            Marshal.WriteByte(propertyData, 0, (byte)((bool)value ? 1 : 0));
            break;

          case WidgetPropertyType.Point:
            Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(((Vector2)value).X));
            Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(((Vector2)value).Y));
            break;

          case WidgetPropertyType.Vector:
            Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(((Vector3)value).X));
            Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(((Vector3)value).Y));
            Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(((Vector3)value).Z));
            break;

          case WidgetPropertyType.Rect:
            Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(((RectF)value).Left));
            Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(((RectF)value).Top));
            Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(((RectF)value).Width));
            Marshal.WriteInt32(propertyData, 12, BitConverter.SingleToInt32Bits(((RectF)value).Height));
            break;

          case WidgetPropertyType.Margins:
            Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(((Margins)value).Left));
            Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(((Margins)value).Top));
            Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(((Margins)value).Right));
            Marshal.WriteInt32(propertyData, 12, BitConverter.SingleToInt32Bits(((Margins)value).Bottom));
            break;

          case WidgetPropertyType.Color:
            Marshal.WriteInt32(propertyData, (int)(uint)value);
            break;

          case WidgetPropertyType.ColorPair:
            Marshal.WriteInt32(propertyData, 0, (int)((ColorPair)value).First);
            Marshal.WriteInt32(propertyData, 4, (int)((ColorPair)value).Second);
            break;

          case WidgetPropertyType.ColorRect:
            Marshal.WriteInt32(propertyData, 0, (int)((ColorRect)value).TopLeft);
            Marshal.WriteInt32(propertyData, 4, (int)((ColorRect)value).TopRight);
            Marshal.WriteInt32(propertyData, 8, (int)((ColorRect)value).BottomRight);
            Marshal.WriteInt32(propertyData, 12, (int)((ColorRect)value).BottomLeft);
            break;

          case WidgetPropertyType.Enumeration:
            Marshal.WriteByte(propertyData, 0, (byte)Convert.ToInt32(value));
            break;

          case WidgetPropertyType.Font:
            Marshal.StructureToPtr((FontParameters)value, propertyData, false);
            break;

          default:
            throw new API.Exception($"Cannot marshal type ({type}) of a property ({propertyName})");
        }
        if (!API.WidgetSetProperty(Handle, Encoding.UTF8.GetBytes(propertyName + '\0'), type, propertyData,
          invokeChanged))
          throw new API.Exception($"Could not update a property ({propertyName}) with value type ({type})");
      }
      finally
      {
        Marshal.FreeHGlobal(propertyData);
      }
    }

    /// Returns value of an existing property with the given name (case-insensitive) as a specific type.
    public T Get<T>(string propertyName)
    {
      if (string.IsNullOrEmpty(propertyName))
        throw new API.Exception("Cannot access a property without name");

      WidgetPropertyType type = DeterminePropertyType(typeof(T));
      if (type == WidgetPropertyType.None)
        throw new API.Exception($"Cannot determine type of the property ({propertyName})");

      byte[] propertyNameBytes = Encoding.UTF8.GetBytes(propertyName + '\0');

      if (type == WidgetPropertyType.String)
      {
        int resLength = 0;
        API.WidgetGetPropertyAsString(Handle, propertyNameBytes, IntPtr.Zero, ref resLength);
        byte[] res = ArrayPool<byte>.Shared.Rent(resLength);
        try
        {
          GCHandle pinned = GCHandle.Alloc(res, GCHandleType.Pinned);
          try
          {
            API.WidgetGetPropertyAsString(Handle, propertyNameBytes, pinned.AddrOfPinnedObject(),
              ref resLength);
          }
          finally
          {
            pinned.Free();
          }
          return (T)(object)Encoding.UTF8.GetString(res, 0, resLength);
        }
        finally
        {
          ArrayPool<byte>.Shared.Return(res);
        }
      }
      int size = DeterminePropertySize(type);
      if (size == 0)
        throw new API.Exception($"Size for the type ({type}) of the " +
          $"property ({propertyName}) cannot be determined");

      IntPtr propertyData = Marshal.AllocHGlobal(size);
      try
      {
        if (!API.WidgetGetProperty(Handle, propertyNameBytes, type, propertyData))
          throw new API.Exception($"Could not retrieve a property ({propertyName}) as type ({type})");

        switch (type)
        {
          case WidgetPropertyType.Integer:
            return (T)(object)Marshal.ReadInt32(propertyData);

          case WidgetPropertyType.Int64:
            return (T)(object)Marshal.ReadInt64(propertyData);

          case WidgetPropertyType.Float:
            return (T)(object)BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData));

          case WidgetPropertyType.Real:
            return (T)(object)BitConverter.Int64BitsToDouble(Marshal.ReadInt64(propertyData));

          case WidgetPropertyType.Boolean:
            return (T)(object)(Marshal.ReadByte(propertyData) != 0);

          case WidgetPropertyType.Point:
            return (T)(object)(new Vector2(
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 0)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 4))));

          case WidgetPropertyType.Vector:
            return (T)(object)(new Vector3(
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 0)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 4)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 8))));

          case WidgetPropertyType.Rect:
            return (T)(object)(new RectF(
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 0)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 4)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 8)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 12))));

          case WidgetPropertyType.Margins:
            return (T)(object)(new Margins(
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 0)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 4)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 8)),
              BitConverter.Int32BitsToSingle(Marshal.ReadInt32(propertyData, 12))));

          case WidgetPropertyType.Color:
            return (T)(object)(uint)Marshal.ReadInt32(propertyData);

          case WidgetPropertyType.ColorPair:
            return (T)(object)(new ColorPair(
              (uint)Marshal.ReadInt32(propertyData, 0),
              (uint)Marshal.ReadInt32(propertyData, 4)));

          case WidgetPropertyType.ColorRect:
            return (T)(object)(new ColorRect(
              (uint)Marshal.ReadInt32(propertyData, 0),
              (uint)Marshal.ReadInt32(propertyData, 4),
              (uint)Marshal.ReadInt32(propertyData, 8),
              (uint)Marshal.ReadInt32(propertyData, 12)));

          case WidgetPropertyType.Enumeration:
            return (T)(object)Marshal.ReadByte(propertyData);

          case WidgetPropertyType.Font:
            return (T)(object)Marshal.PtrToStructure<FontParameters>(propertyData);

          default:
            throw new API.Exception($"Don't know how to marshal type ({type}) from the " +
              $"property ({propertyName})");
        }
      }
      finally
      {
        Marshal.FreeHGlobal(propertyData);
      }
    }

    /// Changes contents of an existing property with the given name (case-insensitive) or creates a new
    /// one. Raises an exception if the property cannot be updated.
    public void Set<T>(string propertyName, T value, bool invokeChanged = true)
    {
      if (string.IsNullOrEmpty(propertyName))
        throw new API.Exception("Cannot modify a property without name");

      WidgetPropertyType type = DeterminePropertyType(typeof(T));
      if (type == WidgetPropertyType.None)
        throw new API.Exception($"Cannot determine type of the property ({propertyName})");

      if (type == WidgetPropertyType.String)
      { // Pass value as a C string.
        string stringValue = (string)(object)value;

        if (string.IsNullOrEmpty(stringValue))
        {
          if (!API.WidgetSetProperty(Handle, Encoding.UTF8.GetBytes(propertyName + '\0'),
            WidgetPropertyType.String, IntPtr.Zero, invokeChanged))
            throw new API.Exception($"Could not update a property ({propertyName}) with an empty string");
        }
        else
        {
          byte[] stringBytes = Encoding.UTF8.GetBytes(stringValue + '\0');

          GCHandle stringPinned = GCHandle.Alloc(stringBytes, GCHandleType.Pinned);
          try
          {
            if (!API.WidgetSetProperty(Handle, Encoding.UTF8.GetBytes(propertyName + '\0'),
              WidgetPropertyType.String, stringPinned.AddrOfPinnedObject(), invokeChanged))
              throw new API.Exception($"Could not update a property ({propertyName}) with a string value");
          }
          finally
          {
            stringPinned.Free();
          }
        }
        return;
      }
      int size = DeterminePropertySize(type);
      if (size == 0)
        throw new API.Exception($"Size for the type ({type}) of " +
          $"property ({propertyName}) cannot be determined");

      IntPtr propertyData = Marshal.AllocHGlobal(size);
      try
      {
        switch (type)
        {
          case WidgetPropertyType.Integer:
            Marshal.WriteInt32(propertyData, (int)(object)value);
            break;

          case WidgetPropertyType.Int64:
            Marshal.WriteInt64(propertyData, (long)(object)value);
            break;

          case WidgetPropertyType.Float:
            Marshal.WriteInt32(propertyData, BitConverter.SingleToInt32Bits((float)(object)value));
            break;

          case WidgetPropertyType.Real:
            Marshal.WriteInt64(propertyData, BitConverter.DoubleToInt64Bits((double)(object)value));
            break;

          case WidgetPropertyType.Boolean:
            Marshal.WriteByte(propertyData, 0, (byte)((bool)(object)value ? 1 : 0));
            break;

          case WidgetPropertyType.Point:
            {
              Vector2 value1 = (Vector2)(object)value;
              Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(value1.X));
              Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(value1.Y));
            }
            break;

          case WidgetPropertyType.Vector:
            {
              Vector3 value1 = (Vector3)(object)value;
              Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(value1.X));
              Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(value1.Y));
              Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(value1.Z));
            }
            break;

          case WidgetPropertyType.Rect:
            {
              RectF value1 = (RectF)(object)value;
              Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(value1.Left));
              Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(value1.Top));
              Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(value1.Width));
              Marshal.WriteInt32(propertyData, 12, BitConverter.SingleToInt32Bits(value1.Height));
            }
            break;

          case WidgetPropertyType.Margins:
            {
              Margins value1 = (Margins)(object)value;
              Marshal.WriteInt32(propertyData, 0, BitConverter.SingleToInt32Bits(value1.Left));
              Marshal.WriteInt32(propertyData, 4, BitConverter.SingleToInt32Bits(value1.Top));
              Marshal.WriteInt32(propertyData, 8, BitConverter.SingleToInt32Bits(value1.Right));
              Marshal.WriteInt32(propertyData, 12, BitConverter.SingleToInt32Bits(value1.Bottom));
            }
            break;

          case WidgetPropertyType.Color:
            Marshal.WriteInt32(propertyData, (int)(uint)(object)value);
            break;

          case WidgetPropertyType.ColorPair:
            {
              ColorPair value1 = (ColorPair)(object)value;
              Marshal.WriteInt32(propertyData, 0, (int)value1.First);
              Marshal.WriteInt32(propertyData, 4, (int)value1.Second);
            }
            break;

          case WidgetPropertyType.ColorRect:
            {
              ColorRect value1 = (ColorRect)(object)value;
              Marshal.WriteInt32(propertyData, 0, (int)value1.TopLeft);
              Marshal.WriteInt32(propertyData, 4, (int)value1.TopRight);
              Marshal.WriteInt32(propertyData, 8, (int)value1.BottomRight);
              Marshal.WriteInt32(propertyData, 12, (int)value1.BottomLeft);
            }
            break;

          case WidgetPropertyType.Enumeration:
            Marshal.WriteByte(propertyData, 0, (byte)(object)value);
            break;

          case WidgetPropertyType.Font:
            Marshal.StructureToPtr((FontParameters)(object)value, propertyData, false);
            break;

          default:
            throw new API.Exception($"Cannot marshal type ({type}) of a property ({propertyName})");
        }

        if (!API.WidgetSetProperty(Handle, Encoding.UTF8.GetBytes(propertyName + '\0'), type, propertyData,
          invokeChanged))
          throw new API.Exception($"Could not update a property ({propertyName}) with a value type ({type})");
      }
      finally
      {
        Marshal.FreeHGlobal(propertyData);
      }
    }

    /// Returns widget's padding for a particular zone (spacing inside of widget).
    public Margins GetPadding(int zoneIndex)
    {
      SetTransitoryZone(zoneIndex);
      return Get<Margins>("Padding");
    }

    /// Changes widget's padding for a particular zone (spacing inside of widget).
    public void SetPadding(int zoneIndex, Margins margins)
    {
      SetTransitoryZone(zoneIndex);
      Set("Padding", margins);
    }

    /// Returns widget client area's rectangle. If widget does not support placement of child controls,
    /// then client rectangle should be its entire area.
    public RectF GetClientRect(int zoneIndex)
    {
      SetTransitoryZone(zoneIndex);
      return Get<RectF>("ClientRect");
    }

    /// Sets transitory zone that is used as parameter for accessing other properties.
    private void SetTransitoryZone(int transitoryZone) => Set("Transitory.Zone", transitoryZone);

    /// Creates enumerator for iterating through widget's properties.
    public IEnumerator<PropertyValue> GetEnumerator() => new Iterator(Handle);

    /// Creates enumerator for iterating through widget's properties.
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    /// Manager associated with the widget.
    public WidgetManager Manager
    {
      get
      {
        IntPtr handle = API.WidgetGetManager(Handle);
        if (handle == IntPtr.Zero)
          throw new API.Exception("Could not retrieve associated manager of the widget");

        return new WidgetManager(handle);
      }
    }

    /// Returns widget's payload.
    public IntPtr Payload => API.WidgetGetPayload(Handle);

    /// Retrieves or changes widget's parent.
    public Widget Parent
    {
      get
      {
        IntPtr handle = API.WidgetGetParent(Handle);
        return handle != IntPtr.Zero ? new Widget(handle) : null;
      }

      set
      {
        if (!API.WidgetSetParent(Handle, value?.Handle ?? IntPtr.Zero))
          throw new API.Exception("Could not change parent of the widget");

        if (value is not null)
          _destroy = null;
        else
          _destroy = new DestroyDelegate(API.WidgetDestroy);
      }
    }

    /// Number of existing widget's children.
    public int ChildCount => API.WidgetGetChildCount(Handle);

    /// Number of existing properties.
    public int PropertyCount => API.WidgetGetPropertyCount(Handle);

    /// Widget's external event callback.
    public ExternalEventFunc ExternalEvent
    {
      get => _externalEvent;

      set
      {
        if (_externalEvent is not null)
        {
          _externalEvent = value;

          if (_externalEvent is null)
          {
            API.WidgetSetExternalEvent(Handle, null, IntPtr.Zero);
            _externalEventWrapped = null;
          }
        }
        else
        {
          _externalEvent = value;

          if (_externalEvent is not null)
          {
            _externalEventWrapped = new WidgetExternalEvent(ExternalEventWrapper);
            API.WidgetSetExternalEvent(Handle, _externalEventWrapped, IntPtr.Zero);
          }
        }
      }
    }

    /// Wrapper function for widget's external event.
    private bool ExternalEventWrapper(IntPtr widget, string _event, IntPtr user)
    {
      return _externalEvent is not null && _externalEvent(new Widget(widget), _event);
    }

    /// External callback to be called when a widget event occurs.
    private ExternalEventFunc _externalEvent;

    /// Pinned delegate for widget's external event.
    private WidgetExternalEvent _externalEventWrapped;

    /// Widget's name (case-insensitive).
    public string Name
    {
      get => Get<string>(nameof(Name));
      set => Set(nameof(Name), value);
    }

    /// Widget's class name (case-insensitive).
    public string ClassName => Get<string>("Class");

    /// Widget's visual style.
    public string Style
    {
      get => Get<string>(nameof(Style));
      set => Set(nameof(Style), value);
    }

    /// Widget's alignment.
    public WidgetAlignment Alignment
    {
      get => Get<WidgetAlignment>(nameof(Alignment));
      set => Set(nameof(Alignment), value);
    }

    /// Widget's visibility.
    public bool Visible
    {
      get => Get<bool>(nameof(Visible));
      set => Set(nameof(Visible), value);
    }

    /// Indicates whether the widget is able to receive keyboard input.
    public bool Enabled
    {
      get => Get<bool>(nameof(Enabled));
      set => Set(nameof(Enabled), value);
    }

    /// Indicates whether the widget is focused or not.
    public bool Focused
    {
      get => Get<bool>(nameof(Focused));
      set => Set(nameof(Focused), value);
    }

    /// Returns number of supported client zones. If widget does not support placement of child controls,
    /// this function returns -1.
    public int ZoneCount => Get<int>(nameof(ZoneCount));

    /// Child's identifier that corresponds to certain parent's client area.
    public int ParentZone
    {
      get => Get<int>(nameof(ParentZone));
      set => Set(nameof(ParentZone), value);
    }

    /// Widget's margins (spacing outside of the widget).
    public Margins Margins
    {
      get => Get<Margins>(nameof(Margins));
      set => Set(nameof(Margins), value);
    }

    /// Widget's rectangle (in logical units).
    public RectF Rect
    {
      get => Get<RectF>(nameof(Rect));
      set => Set(nameof(Rect), value);
    }
  }

  /// A service module that contains and manages all other widgets.
  public sealed class WidgetManager : Widget
  {
    /// Creates a wrapped or uninitialized widget manager.
    internal WidgetManager(IntPtr handle) : base(null, handle) { }

    /// Creates a new instance of widget manager.
    public WidgetManager(Application application, TextRenderer textRenderer, string instanceName = null,
      IntPtr payload = new IntPtr(), PixelFormat format = PixelFormat.Unknown, int multisamples = 8,
      uint attributes = WidgetManagerAttribute.Composition)
    : base(new DestroyDelegate(API.WidgetDestroy), IntPtr.Zero)
    {
      _handle = API.WidgetManagerCreate(application?.Handle ?? IntPtr.Zero,
        textRenderer?.Handle ?? IntPtr.Zero, instanceName is not null ?
        Encoding.UTF8.GetBytes(instanceName + '\0') : null, payload, format, multisamples, attributes);
      if (_handle == IntPtr.Zero)
        throw new API.Exception($"Could not create a widget manager ({instanceName})");
    }

    /// Returns an existing widget with the given name (case-insensitive).
    public Widget GetWidget(string instanceName)
    {
      IntPtr handle = API.WidgetManagerGetWidgetByName(Handle, Encoding.UTF8.GetBytes(instanceName + '\0'));
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve widget with name ({instanceName})");

      return new Widget(handle);
    }

    /// Returns an existing widget with the given name (case-insensitive). If widget with the given name is
    /// not found, returns \c null instead of raising exception.
    public Widget TryGetWidget(string instanceName)
    {
      IntPtr handle = API.WidgetManagerGetWidgetByName(Handle, Encoding.UTF8.GetBytes(instanceName + '\0'));
      return handle != IntPtr.Zero ? new Widget(handle) : null;
    }

    /// Returns an existing widget with the given payload. If widget with the given payload is not found,
    /// returns \c null instead of raising exception.
    public Widget TryGetWidget(IntPtr payload)
    {
      IntPtr handle = API.WidgetManagerGetWidgetByPayload(Handle, payload);
      return handle != IntPtr.Zero ? new Widget(handle) : null;
    }

    /// Renders widget manager's area and all controls within.
    public void Present()
    {
      if (!API.WidgetManagerPresent(Handle))
        throw new API.Exception("Could not render widget manager's contents");
    }

    /// Returns one of textures used for the composition.
    public Texture GetTexture(WidgetManagerTextureType textureType)
    {
      IntPtr handle = API.WidgetManagerGetTexture(Handle, textureType);
      if (handle == IntPtr.Zero)
        throw new API.Exception($"Could not retrieve widget manager's texture ({textureType})");

      return new Texture(handle);
    }

    /// Returns application associated with the widget manager.
    public Application Application
    {
      get
      {
        IntPtr handle = API.WidgetManagerGetApplication(Handle);
        return handle != IntPtr.Zero ? new Application(handle) : null;
      }
    }

    /// Returns application associated with the widget manager.
    public TextRenderer TextRenderer
    {
      get
      {
        IntPtr handle = API.WidgetManagerGetTextRenderer(Handle);
        return handle != IntPtr.Zero ? new TextRenderer(handle) : null;
      }
    }

    /// Returns total number of canvas batches during UI rendering.
    public int BatchCount => API.WidgetManagerBatchCount(Handle);

    /// Returns pixel format of composition textures.
    public PixelFormat Format => API.WidgetManagerGetFormat(Handle);

    /// Returns multisamples of rendering composition texture.
    public int Multisamples => API.WidgetManagerGetMultisamples(Handle);

    /// Returns attributes used when the manager was created.
    public uint Attributes => API.WidgetManagerGetAttributes(Handle);

    /// General scale of widget manager and all widgets parented to it.
    public double Scale
    {
      get => Get<double>(nameof(Scale));
      set => Set(nameof(Scale), value);
    }

    /// Currently active application cursor.
    public AppCursor Cursor => (AppCursor)Get<int>(nameof(Cursor));
  }

  /// Pseudo-random number generator (PRNG) utility module.
  public struct RandomSequence
  {
    /// Initializes random number generator state using local time.
    public RandomSequence() { API.RandomSequenceInit(out _state); }

    /// Initializes random number generator state with a given seed to generate a reproducible sequence of
    /// random numbers.
    public RandomSequence(ulong seed) => API.RandomSequenceInitBySeed(out _state, seed);

    /// Generates a random value in range [0, Range) using relatively linear distribution.
    public int Ranged(int range) => API.RandomSequenceGenerateRanged(ref _state, range);

    /// Generates next random number in the sequence from the specified context.
    public uint Raw => API.RandomSequenceGenerate(ref _state);

    /// Returns 64-bit random number by generating two 32-bit numbers.
    public ulong Raw64 => API.RandomSequenceGenerate64(ref _state);

    /// Generates a random value in range [0, 1) with 23 bits of precision, using relatively linear
    /// distribution.
    public float Value => API.RandomSequenceGenerateFloat(ref _state);

    /// Generates a random value in range [0, 1) with 53 bits of precision, using relatively linear
    /// distribution.
    public double ValueDouble => API.RandomSequenceGenerateDouble(ref _state);

    /// Generates a random value using Gaussian distribution with mean 0 and standard deviation of 1.
    public float Gaussian => API.RandomSequenceGenerateGaussian(ref _state);

    /// Returns current PRNG state.
    public readonly ulong State => _state;

    /// Current PRNG state.
    private ulong _state;
  }

  /// Shared library service functions.
  public partial struct Library
  {
    /// Returns current version of the shared library.
    public static bool GetVersion(out int major, out int minor, out uint build)
    {
      return API.LibraryGetVersion(out major, out minor, out build);
    }

    /// Specifies a business serial key for the shared library.
    public static void SerialCode(string code)
    {
      byte[] codeBytes = Encoding.UTF8.GetBytes(code + '\0');
      API.LibrarySerialCode(codeBytes, codeBytes.Length);
    }
  }

  /// A structure that defines a ray in 3D space.
  public struct Ray
  {
    /// Creates a new ray with the given values.
    public Ray(Vector3 origin, Vector3 direction)
    {
      Origin = origin;
      Direction = direction;
    }

    /// Creates a new ray based on 2D position, screen size, inverse view and projection matrices.
    public Ray(Vector2 position, Vector2 surfaceSize, Matrix4x4 viewInverse, Matrix4x4 projection)
    {
      API.RayCreate(out Origin, out Direction, ref position, ref surfaceSize, ref viewInverse,
        ref projection);
    }

    /// Tests whether the current ray intersects with the given front or back facing triangle and if so,
    /// calculates the resulting barycentric coordinates and distance from origin to triangle.
    public bool IntersectTriangle(Vector3 vertex1, Vector3 vertex2, Vector3 vertex3, out Vector2 intersection,
      out float distance, bool backFacing = true)
    {
      return API.RayIntersectTriangle(ref Origin, ref Direction, ref vertex1, ref vertex2, ref vertex3,
        backFacing, out intersection, out distance);
    }

    /// Tests whether the current ray intersects with one of triangles of minimalistic cube volume, whose
    /// vertices are first transformed by the given world matrix, returning distance to intersection point.
    public bool IntersectCubeVolume(Matrix4x4 world, out float distance)
    {
      return API.RayIntersectCubeVolume(ref Origin, ref Direction, ref world, out distance);
    }

    /// Tests whether the current ray intersects with a plane specified by a point on the plane and its
    /// surface normal, returning the actual point of intersection and distance from ray's origin.
    public bool IntersectPlane(Vector3 planePoint, Vector3 planeNormal, out Vector3 intersection,
      out float distance)
    {
      return API.RayIntersectPlane(ref Origin, ref Direction, ref planePoint, ref planeNormal,
        out intersection, out distance);
    }

    /// Origin of the ray.
    public Vector3 Origin;

    /// Direction of the ray.
    public Vector3 Direction;
  }

  public partial struct Volume
  {
    /// Calculates near and far planes for a bounding box transformed by the given world/view matrix.
    public static void CalculateNearFarPlanes(Matrix4x4 worldView, out float nearPlane, out float farPlane)
    {
      API.VolumeCalculateNearFarPlanes(ref worldView, out nearPlane, out farPlane);
    }

    /// Calculates visible surface frame for a bounding box transformed by the given world/view/projection
    /// matrix.
    public static RectF CalculateVisibleFrame(Matrix4x4 worldViewProjection, Vector2 surfaceSize)
    {
      API.VolumeCalculateVisibleFrame(ref worldViewProjection, ref surfaceSize, out RectF visibleFrame);
      return visibleFrame;
    }
  }

  public partial struct Utility
  {
    // Color functions.

    /// <summary>Creates 32-bit RGBA color with the specified color value, having its alpha-channel
    /// multiplied by the specified coefficient and divided by 255.</summary>
    /// <param name="color">32-bit RGBA color.</param>
    /// <param name="alpha">Alpha value in [0, 255] range.</param>
    /// <returns>The resulting color with its alpha-channel multiplied by the given alpha value and divided
    /// by 255.</returns>
    public static uint MakeColor(uint color, int alpha) => API.MakeColor(color, alpha);

    /// <summary>Creates 32-bit RGBA color with the specified color value, having its alpha-channel
    /// multiplied by the specified floating-point coefficient.</summary>
    /// <param name="color">32-bit RGBA color.</param>
    /// <param name="alpha">Alpha value in [0, 1] range.</param>
    /// <returns>The resulting color with its alpha-channel multiplied by the given alpha value.</returns>
    public static uint MakeColor(uint color, float alpha) => API.MakeColorF(color, alpha);

    //  \c gray and \c alpha must be in range of [0, 255].

    /// <summary>Creates 32-bit RGBA color where the original color value has its components multiplied by
    /// the given grayscale value and alpha-channel multiplied by the specified coefficient, and all
    /// components divided by 255.</summary>
    /// <param name="color">32-bit RGBA color.</param>
    /// <param name="gray">Grayscale value in [0, 255] range.</param>
    /// <param name="alpha">Alpha value in [0, 255] range.</param>
    /// <returns>The resulting color with its red, green and blue channels multiplied by the given grayscale
    /// value and its alpha-channel multiplied by the given alpha value, then all components divided by
    /// 255.</returns>
    public static uint MakeColorWithGray(uint color, int gray, int alpha) =>
      API.MakeColorWithGray(color, gray, alpha);

    /// <summary>Creates 32-bit RGBA color where the original color value has its components multiplied by
    /// the given grayscale value and alpha-channel multiplied by the specified coefficient.</summary>
    /// <param name="color">32-bit RGBA color.</param>
    /// <param name="gray">Grayscale value in [0, 1] range.</param>
    /// <param name="alpha">Alpha value in [0, 1] range.</param>
    /// <returns>The resulting color with its red, green and blue channels multiplied by the given grayscale
    /// value and its alpha-channel multiplied by the given alpha value.</returns>
    public static uint MakeColorWithGray(uint color, float gray, float alpha) =>
      API.MakeColorWithGrayF(color, gray, alpha);

    /// <summary>Creates 32-bit RGBA color using specified individual components.</summary>
    /// <param name="red">Red component value in [0, 255] range.</param>
    /// <param name="green">Green component value in [0, 255] range.</param>
    /// <param name="blue">Blue component value in [0, 255] range.</param>
    /// <param name="alpha">Alpha-channel value in [0, 255] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorRGB(int red, int green, int blue, int alpha) =>
      API.MakeColorRGB(red, green, blue, alpha);

    /// <summary>Creates 32-bit RGBA color using specified individual components.</summary>
    /// <param name="red">Red component value in [0, 1] range.</param>
    /// <param name="green">Green component value in [0, 1] range.</param>
    /// <param name="blue">Blue component value in [0, 1] range.</param>
    /// <param name="alpha">Alpha-channel value in [0, 1] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorRGB(float red, float green, float blue, float alpha) =>
      API.MakeColorRGBF(red, green, blue, alpha);

    /// <summary>Creates 32-bit RGBA color using specified grayscale and alpha values.</summary>
    /// <param name="gray">Grayscale value in [0, 255] range.</param>
    /// <param name="alpha">Alpha-channel value in [0, 255] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorGray(int gray, int alpha) => API.MakeColorGray(gray, alpha);

    /// <summary>Creates 32-bit RGBA color using specified grayscale and alpha values.</summary>
    /// <param name="gray">Grayscale value in [0, 1] range.</param>
    /// <param name="alpha">Alpha-channel value in [0, 1] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorGray(float gray, float alpha) => API.MakeColorGrayF(gray, alpha);

    /// <summary>Creates 32-bit RGBA white color with the specified alpha-channel value.</summary>
    /// <param name="alpha">Alpha-channel value in [0, 255] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorAlpha(int alpha) => API.MakeColorAlpha(alpha);

    /// <summary>Creates 32-bit RGBA white color with the specified alpha-channel value.</summary>
    /// <param name="alpha">Alpha-channel value in [0, 1] range.</param>
    /// <returns>The resulting 32-bit RGBA color.</returns>
    public static uint MakeColorAlpha(float alpha) => API.MakeColorAlphaF(alpha);

    /// <summary>Returns alpha-channel from the given 32-bit RGBA color</summary>
    /// <param name="color">The source 32-bit RGBA color.</param>
    /// <returns>Alpha-channel of the source color in [0, 255] range.</returns>
    public static int GetColorAlpha(uint color) => API.GetColorAlpha(color);

    /// <summary>Returns alpha-channel from the given 32-bit RGBA color</summary>
    /// <param name="color">The source 32-bit RGBA color.</param>
    /// <returns>Alpha-channel of the source color in [0, 1] range.</returns>
    public static float GetColorAlphaF(uint color) => API.GetColorAlphaF(color);

    /// Takes 32-bit RGBA color with unpremultiplied alpha and multiplies each of red, green, and blue
    /// components by its alpha-channel, resulting in premultiplied alpha color.
    public static uint PremultiplyAlpha(uint color) => API.PremultiplyAlpha(color);

    /// Takes 32-bit RGBA color with premultiplied alpha channel and divides each of its red, green, and blue
    /// components by alpha, resulting in unpremultiplied alpha color.
    public static uint UnpremultiplyAlpha(uint color) => API.UnpremultiplyAlpha(color);

    /// Switches red and blue channels in 32-bit RGBA color value.
    public static uint DisplaceRB(uint color) => API.DisplaceRB(color);

    /// Inverts each of the components in the color, including alpha-channel.
    public static uint InvertColor(uint color) => API.InvertColor(color);

    /// Adds two 32-bit RGBA color values together clamping the resulting values.
    public static uint AddColors(uint color1, uint color2) => API.AddColors(color1, color2);

    /// Subtracts two 32-bit RGBA color values clamping the resulting values.
    public static uint SubtractColors(uint color1, uint color2) => API.SubtractColors(color1, color2);

    /// Multiplies two 32-bit RGBA color values together.
    public static uint MultiplyColors(uint color1, uint color2) => API.MultiplyColors(color1, color2);

    /// Computes average of two given 32-bit RGBA color values.
    public static uint AverageColors(uint color1, uint color2) => API.AverageColors(color1, color2);

    /// Computes average of four given 32-bit RGBA color values.
    public static uint AverageFourColors(uint color1, uint color2, uint color3, uint color4) =>
      API.AverageFourColors(color1, color2, color3, color4);

    /// Computes average of six given 32-bit RGBA color values.
    public static uint AverageSixColors(uint color1, uint color2, uint color3, uint color4, uint color5,
      uint color6) => API.AverageSixColors(color1, color2, color3, color4, color5, color6);

    /// <summary>Computes alpha-blending for a pair of 32-bit RGBA colors values.</summary>
    /// <param name="color1">Source color for blending operation.</param>
    /// <param name="color2">Destination color for blending operation.</param>
    /// <param name="alpha">Blending coefficient in [0, 255] range.</param>
    /// <returns>The resulting color.</returns>
    public static uint BlendColors(uint color1, uint color2, int alpha) =>
      API.BlendColors(color1, color2, alpha);

    /// Computes resulting alpha-blended value between four 32-bit RGBA colors using linear interpolation.
    public static uint BlendFourColors(uint topLeft, uint topRight, uint bottomRight, uint bottomLeft,
      int alphaX, int alphaY, bool horizontal = true) => API.BlendFourColors(topLeft, topRight, bottomRight,
        bottomLeft, alphaX, alphaY, horizontal);

    /// Composes source color onto destination color using source and destination color alphas. This
    /// essentially means alpha-blending source color onto destination using source's alpha, but with
    /// appropriate alpha composition.
    public static uint ComposeColors(uint color1, uint color2) => API.ComposeColors(color1, color2);

    /// Returns grayscale value in range of [0..255] from the given 32-bit RGBA color value. The resulting
    /// value can be considered the color's "Luma". The alpha-channel is ignored.
    public static int ColorToGray(uint color) => API.ColorToGray(color);

    /// Returns grayscale value in range of [0, 1] from the given 32-bit RGBA color value. The resulting
    /// value can be considered the color's "Luma". The alpha-channel is ignored.
    public static float ColorToGrayF(uint color) => API.ColorToGrayF(color);

    // Math utility functions.

    /// Transforms value in range of [0, 1] by using sine wave (kind of "accelerate then decelerate").
    public static float SineTransform(float value) => API.SineTransform(value);

    /// Transforms value in range of [0, 1] using "accelerating" sine wave. The curve starts at zero with
    /// almost no "velocity", while in the end incrementing almost linearly.
    public static float SineAccelerate(float value) => API.SineAccelerate(value);

    /// Transforms value in range of [0, 1] using "decelerating" sine wave. The curve starts at zero,
    /// incrementing almost linearily, while in the end going to almost a complete stop.
    public static float SineDecelerate(float value) => API.SineDecelerate(value);

    /// Transforms value in range of [0, 1] to go through full sine curve in range [0 -> 1 -> 0].
    /// Note that the curve goes from zero to one and then back to zero.
    public static float SineCycle(float value) => API.SineCycle(value);

    /// Transforms value in range of [0, 1] to go full cycle through sine function (0 -> 1 -> 0 -> -1 -> 0).
    public static float SineTwoCycle(float value) => API.SineTwoCycle(value);

    /// Transforms value in range of [0, 1] with a power function, adding bias to either start of the curve
    /// or its end. Bias value of 0.25 would be similar to "SineAccelerate", whereas value of 0.75 would be
    /// similar to that of "SineDecelerate".
    public static float BiasTransform(float value, float bias) => API.BiasTransform(value, bias);

    /// Transforms value in range of [0, 1] with a power function, adding gain at start and end of the
    /// curve, similar to "SineTransform". Values of "gain" closer to zero produce stronger distortions,
    /// whereas values close to zero produce almost linear curves.
    public static float GainTransform(float value, float bias) => API.GainTransform(value, bias);

    // Vertex elements functions.

    /// Makes an estimation of pitch for the given channel based on existing element offsets.
    public static uint VertexElementsEstimatePitch(VertexElement[] elements, uint channel = 0)
    {
      return API.VertexElementsEstimatePitch(channel, elements, elements.Length);
    }

    // System functions.

    /// Retrieves number of microseconds that passed since application startup.
    public static ulong SystemTicks => API.GetSystemTicks();

    // Payload functions.

    /// Assigns an object as payload and returns an IntPtr handle.
    public static IntPtr AssignPayload<T>(T obj) where T : class
    {
      ArgumentNullException.ThrowIfNull(obj);

      GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Normal);
      return GCHandle.ToIntPtr(handle);
    }

    /// Retrieves an object from a payload pointer.
    public static T RetrievePayload<T>(IntPtr payload) where T : class
    {
      if (payload == IntPtr.Zero)
        return null;

      GCHandle handle = GCHandle.FromIntPtr(payload);
      return handle.Target as T;
    }

    /// Releases a GC handle associated with the payload pointer.
    public static void FreePayload(IntPtr payload)
    {
      if (payload == IntPtr.Zero)
        return;

      GCHandle handle = GCHandle.FromIntPtr(payload);
      if (handle.IsAllocated)
        handle.Free();
    }
  }
}