{
  $Id: ImagingOpenGL.pas 165 2009-08-14 12:34:40Z galfar $
  Vampyre Imaging Library
  by Marek Mauder 
  http://imaginglib.sourceforge.net

  The contents of this file are used with permission, subject to the Mozilla
  Public License Version 1.1 (the "License"); you may not use this file except
  in compliance with the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/MPL-1.1.html

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  the specific language governing rights and limitations under the License.

  Alternatively, the contents of this file may be used under the terms of the
  GNU Lesser General Public License (the  "LGPL License"), in which case the
  provisions of the LGPL License are applicable instead of those above.
  If you wish to allow use of your version of this file only under the terms
  of the LGPL License and not to allow others to use your version of this file
  under the MPL, indicate your decision by deleting  the provisions above and
  replace  them with the notice and other provisions required by the LGPL
  License.  If you do not delete the provisions above, a recipient may use
  your version of this file under either the MPL or the LGPL License.

  For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
}

{ This unit contains functions for loading and saving OpenGL textures
  using Imaging and for converting images to textures and vice versa.}
unit ImagingOpenGL;

{$I ImagingOptions.inc}

{ Define this symbol if you want to use dglOpenGL header.}
{$DEFINE USE_DGL_HEADERS}
{ $DEFINE USE_GLSCENE_HEADERS}

interface

uses
  SysUtils, Classes, ImagingTypes, Imaging, ImagingFormats,
{$IF Defined(USE_DGL_HEADERS)}
  dglOpenGL,
{$ELSEIF Defined(USE_GLSCENE_HEADERS)}
  OpenGL1x,
{$ELSE}
  gl, glext,
{$IFEND}
 ImagingUtility;

type
  { Various texture capabilities of installed OpenGL driver.}
  TGLTextureCaps = record
    MaxTextureSize: LongInt;     // Max size of texture in pixels supported by HW
    NonPowerOfTwo: Boolean;      // HW has full support for NPOT textures
    DXTCompression: Boolean;     // HW supports S3TC/DXTC compressed textures
    ATI3DcCompression: Boolean;  // HW supports ATI 3Dc compressed textures (ATI2N)
    LATCCompression: Boolean;    // HW supports LATC/RGTC compressed textures (ATI1N+ATI2N)
    FloatTextures: Boolean;      // HW supports floating point textures
    MaxAnisotropy: LongInt;      // Max anisotropy for aniso texture filtering
    MaxSimultaneousTextures: LongInt; // Number of texture units
    ClampToEdge: Boolean;        // GL_EXT_texture_edge_clamp
    TextureLOD: Boolean;         // GL_SGIS_texture_lod
    VertexTextureUnits: Integer; // Texture units accessible in vertex programs
  end;

{ Returns texture capabilities of installed OpenGL driver.}
function GetGLTextureCaps(var Caps: TGLTextureCaps): Boolean;
{ Function which can be used to retrieve GL extension functions.}
function GetGLProcAddress(const ProcName: string): Pointer;
{ Returns True if the given GL extension is supported.}
function IsGLExtensionSupported(const Extension: string): Boolean;
{ Returns True if the given image format can be represented as GL texture
  format. GLFormat, GLType, and GLInternal are parameters for functions like
  glTexImage. Note that GLU functions like gluBuildMipmaps cannot handle some
  formats returned by this function (i.e. GL_UNSIGNED_SHORT_5_5_5_1 as GLType).
  If you are using compressed or floating-point images make sure that they are
  supported by hardware using GetGLTextureCaps, ImageFormatToGL does not
  check this.}
function ImageFormatToGL(Format: TImageFormat; var GLFormat: GLenum;
  var GLType: GLenum; var GLInternal: GLint; const Caps: TGLTextureCaps): Boolean;

{ All GL textures created by Imaging functions have default parameters set -
  that means that no glTexParameter calls are made so default filtering,
  wrapping, and other parameters are used. Created textures
  are left bound by glBindTexture when function is exited.}

{ Creates GL texture from image in file in format supported by Imaging.
  You can use CreatedWidth and Height parameters to query dimensions of created textures
  (it could differ from dimensions of source image).}
function LoadGLTextureFromFile(const FileName: string; CreatedWidth: PLongInt = nil;
  CreatedHeight: PLongInt = nil): GLuint;
{ Creates GL texture from image in stream in format supported by Imaging.
  You can use CreatedWidth and Height parameters to query dimensions of created textures
  (it could differ from dimensions of source image).}
function LoadGLTextureFromStream(Stream: TStream; CreatedWidth: PLongInt = nil;
  CreatedHeight: PLongInt = nil): GLuint;
{ Creates GL texture from image in memory in format supported by Imaging.
  You can use CreatedWidth and Height parameters to query dimensions of created textures
  (it could differ from dimensions of source image).}
function LoadGLTextureFromMemory(Data: Pointer; Size: LongInt;
  CreatedWidth: PLongInt = nil; CreatedHeight: PLongInt = nil): GLuint;

{ Converts TImageData structure to OpenGL texture.
  Input images is used as main mipmap level and additional requested
  levels are generated from this one. For the details on parameters
  look at CreateGLTextureFromMultiImage function.}
function CreateGLTextureFromImage(const Image: TImageData;
  Width: LongInt = 0; Height: LongInt = 0; MipMaps: Boolean = True;
  OverrideFormat: TImageFormat = ifUnknown; CreatedWidth: PLongInt = nil;
  CreatedHeight: PLongInt = nil): GLuint;
{ Converts images in TDymImageDataArray to one OpenGL texture.
  Image at index MainLevelIndex in the array is used as main mipmap level and
  additional images are used as subsequent levels. If there is not enough images
  in array missing levels are automatically generated (and if there is enough images
  but they have wrong dimensions or format then they are resized/converted).
  If driver supports only power of two sized textures images are resized.
  OverrideFormat can be used to convert image into specific format before
  it is passed to OpenGL, ifUnknown means no conversion.
  If desired texture format is not supported by hardware default
  A8R8G8B8 format is used instead for color images and ifGray8 is used
  for luminance images. DXTC (S3TC) compressed and floating point textures
  are created if supported by hardware.
  Width and Height can be used to set size of main mipmap level according
  to your needs, Width and Height of 0 mean use width and height of input
  image that will become main level mipmap.
  MipMaps set to True mean build all possible levels, False means use only level 0.
  You can use CreatedWidth and CreatedHeight parameters to query dimensions of
  created texture's largest mipmap level (it could differ from dimensions
  of source image).}
function CreateGLTextureFromMultiImage(const Images: TDynImageDataArray;
  Width: LongInt = 0; Height: LongInt = 0; MipMaps: Boolean = True;
  MainLevelIndex: LongInt = 0; OverrideFormat: TImageFormat = ifUnknown;
  CreatedWidth: PLongInt = nil; CreatedHeight: PLongInt = nil): GLuint;

{ Saves GL texture to file in one of formats supported by Imaging.
  Saves all present mipmap levels.}
function SaveGLTextureToFile(const FileName: string; const Texture: GLuint): Boolean;
{ Saves GL texture to stream in one of formats supported by Imaging.
  Saves all present mipmap levels.}
function SaveGLTextureToStream(const Ext: string; Stream: TStream; const Texture: GLuint): Boolean;
{ Saves GL texture to memory in one of formats supported by Imaging.
  Saves all present mipmap levels.}
function SaveGLTextureToMemory(const Ext: string; Data: Pointer; var Size: LongInt; const Texture: GLuint): Boolean;

{ Converts main level of the GL texture to TImageData strucrue. OverrideFormat
  can be used to convert output image to the specified format rather
  than use the format taken from GL texture, ifUnknown means no conversion.}
function CreateImageFromGLTexture(const Texture: GLuint;
  var Image: TImageData; OverrideFormat: TImageFormat = ifUnknown): Boolean;
{ Converts GL texture to TDynImageDataArray array of images. You can specify
  how many mipmap levels of the input texture you want to be converted
  (default is all levels). OverrideFormat can be used to convert output images to
  the specified format rather than use the format taken from GL texture,
  ifUnknown means no conversion.}
function CreateMultiImageFromGLTexture(const Texture: GLuint;
  var Images: TDynImageDataArray; MipLevels: LongInt = 0;
  OverrideFormat: TImageFormat = ifUnknown): Boolean;

var
  { Standard behaviour of image->texture functions like CreateGLTextureFrom(Multi)Image is:
    If graphic card supports non power of 2 textures and image is nonpow2 then
    texture is created directly from image.
    If graphic card does not support them input image is rescaled (bilinear)
    to power of 2 size.
    If you set PasteNonPow2ImagesIntoPow2 to True then instead of rescaling, a new
    pow2 texture is created and nonpow2 input image is pasted into it
    keeping its original size. This could be useful for some 2D stuff
    (and its faster than rescaling of course). Note that this is applied
    to all rescaling smaller->bigger operations that might ocurr during
    image->texture process (usually only pow2/nonpow2 stuff and when you
    set custom Width & Height in CreateGLTextureFrom(Multi)Image).}
  PasteNonPow2ImagesIntoPow2: Boolean = False;
  { Standard behaviur if GL_ARB_texture_non_power_of_two extension is not supported
    is to rescale image to power of 2 dimensions. NPOT extension is exposed only
    when HW has full support for NPOT textures but some cards
    (ATI Radeons, some other maybe) have partial NPOT support. Namely Radeons
    can use NPOT textures but not mipmapped. If you know what you are doing
    you can disable NPOT support check so the image won't be rescaled to POT
    by seting DisableNPOTSupportCheck to True.}
  DisableNPOTSupportCheck: Boolean = False;

implementation

const
  // cube map consts
  GL_TEXTURE_BINDING_CUBE_MAP       = $8514;
  GL_TEXTURE_CUBE_MAP_POSITIVE_X    = $8515;
  GL_TEXTURE_CUBE_MAP_NEGATIVE_X    = $8516;
  GL_TEXTURE_CUBE_MAP_POSITIVE_Y    = $8517;
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Y    = $8518;
  GL_TEXTURE_CUBE_MAP_POSITIVE_Z    = $8519;
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z    = $851A;

  // texture formats
  GL_COLOR_INDEX                    = $1900;
  GL_STENCIL_INDEX                  = $1901;
  GL_DEPTH_COMPONENT                = $1902;
  GL_RED                            = $1903;
  GL_GREEN                          = $1904;
  GL_BLUE                           = $1905;
  GL_ALPHA                          = $1906;
  GL_RGB                            = $1907;
  GL_RGBA                           = $1908;
  GL_LUMINANCE                      = $1909;
  GL_LUMINANCE_ALPHA                = $190A;
  GL_BGR_EXT                        = $80E0;
  GL_BGRA_EXT                       = $80E1;

  // texture internal formats
  GL_ALPHA4                         = $803B;
  GL_ALPHA8                         = $803C;
  GL_ALPHA12                        = $803D;
  GL_ALPHA16                        = $803E;
  GL_LUMINANCE4                     = $803F;
  GL_LUMINANCE8                     = $8040;
  GL_LUMINANCE12                    = $8041;
  GL_LUMINANCE16                    = $8042;
  GL_LUMINANCE4_ALPHA4              = $8043;
  GL_LUMINANCE6_ALPHA2              = $8044;
  GL_LUMINANCE8_ALPHA8              = $8045;
  GL_LUMINANCE12_ALPHA4             = $8046;
  GL_LUMINANCE12_ALPHA12            = $8047;
  GL_LUMINANCE16_ALPHA16            = $8048;
  GL_INTENSITY                      = $8049;
  GL_INTENSITY4                     = $804A;
  GL_INTENSITY8                     = $804B;
  GL_INTENSITY12                    = $804C;
  GL_INTENSITY16                    = $804D;
  GL_R3_G3_B2                       = $2A10;
  GL_RGB4                           = $804F;
  GL_RGB5                           = $8050;
  GL_RGB8                           = $8051;
  GL_RGB10                          = $8052;
  GL_RGB12                          = $8053;
  GL_RGB16                          = $8054;
  GL_RGBA2                          = $8055;
  GL_RGBA4                          = $8056;
  GL_RGB5_A1                        = $8057;
  GL_RGBA8                          = $8058;
  GL_RGB10_A2                       = $8059;
  GL_RGBA12                         = $805A;
  GL_RGBA16                         = $805B;

  // floating point texture formats
  GL_RGBA32F_ARB                    = $8814;
  GL_INTENSITY32F_ARB               = $8817;
  GL_LUMINANCE32F_ARB               = $8818;
  GL_RGBA16F_ARB                    = $881A;
  GL_INTENSITY16F_ARB               = $881D;
  GL_LUMINANCE16F_ARB               = $881E;

  // compressed texture formats
  GL_COMPRESSED_RGBA_S3TC_DXT1_EXT  = $83F1;
  GL_COMPRESSED_RGBA_S3TC_DXT3_EXT  = $83F2;
  GL_COMPRESSED_RGBA_S3TC_DXT5_EXT  = $83F3;
  GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI          = $8837;
  GL_COMPRESSED_LUMINANCE_LATC1_EXT              = $8C70;
  GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT       = $8C71;
  GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT        = $8C72;
  GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT = $8C73;

  // various GL extension constants
  GL_MAX_TEXTURE_UNITS              = $84E2;
  GL_TEXTURE_MAX_ANISOTROPY_EXT     = $84FE;
  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = $84FF;

  // texture source data formats
  GL_UNSIGNED_BYTE_3_3_2            = $8032;
  GL_UNSIGNED_SHORT_4_4_4_4         = $8033;
  GL_UNSIGNED_SHORT_5_5_5_1         = $8034;
  GL_UNSIGNED_INT_8_8_8_8           = $8035;
  GL_UNSIGNED_INT_10_10_10_2        = $8036;
  GL_UNSIGNED_BYTE_2_3_3_REV        = $8362;
  GL_UNSIGNED_SHORT_5_6_5           = $8363;
  GL_UNSIGNED_SHORT_5_6_5_REV       = $8364;
  GL_UNSIGNED_SHORT_4_4_4_4_REV     = $8365;
  GL_UNSIGNED_SHORT_1_5_5_5_REV     = $8366;
  GL_UNSIGNED_INT_8_8_8_8_REV       = $8367;
  GL_UNSIGNED_INT_2_10_10_10_REV    = $8368;
  GL_HALF_FLOAT_ARB                 = $140B;

  // Other GL constants
  GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = $8B4C;


{$IFDEF MSWINDOWS}
  GLLibName = 'opengl32.dll';
{$ENDIF}
{$IFDEF UNIX}
  GLLibName = 'libGL.so';
{$ENDIF}

type
  TglCompressedTexImage2D = procedure (Target: GLenum; Level: GLint;
    InternalFormat: GLenum; Width: GLsizei; Height: GLsizei; Border: GLint;
    ImageSize: GLsizei; const Data: PGLvoid);
    {$IFDEF MSWINDOWS}stdcall;{$ELSE}cdecl;{$ENDIF}
var
  glCompressedTexImage2D: TglCompressedTexImage2D = nil;
  ExtensionBuffer: string = '';

{$IFDEF MSWINDOWS}
function wglGetProcAddress(ProcName: PChar): Pointer; stdcall; external GLLibName;
{$ENDIF}
{$IFDEF UNIX}
function glXGetProcAddress(ProcName: PChar): Pointer; cdecl; external GLLibName;
{$ENDIF}

function IsGLExtensionSupported(const Extension: string): Boolean;
var
  ExtPos: LongInt;
begin
  if ExtensionBuffer = '' then
    ExtensionBuffer := glGetString(GL_EXTENSIONS);

  ExtPos := Pos(Extension, ExtensionBuffer);
  Result := ExtPos > 0;
  if Result then
  begin
    Result := ((ExtPos + Length(Extension) - 1) = Length(ExtensionBuffer)) or
      not (ExtensionBuffer[ExtPos + Length(Extension)] in ['_', 'A'..'Z', 'a'..'z']);
  end;
end;

function GetGLProcAddress(const ProcName: string): Pointer;
begin
{$IFDEF MSWINDOWS}
  Result := wglGetProcAddress(PChar(ProcName));
{$ENDIF}
{$IFDEF UNIX}
  Result := glXGetProcAddress(PChar(ProcName));
{$ENDIF}
end;

function GetGLTextureCaps(var Caps: TGLTextureCaps): Boolean;
begin
  // Check DXTC support and load extension functions if necesary
  Caps.DXTCompression := IsGLExtensionSupported('GL_ARB_texture_compression') and
    IsGLExtensionSupported('GL_EXT_texture_compression_s3tc');
  if Caps.DXTCompression then
    glCompressedTexImage2D := GetGLProcAddress('glCompressedTexImage2D');
  Caps.DXTCompression := Caps.DXTCompression and (@glCompressedTexImage2D <> nil);
  Caps.ATI3DcCompression := Caps.DXTCompression and
    IsGLExtensionSupported('GL_ATI_texture_compression_3dc');
  Caps.LATCCompression := Caps.DXTCompression and
    IsGLExtensionSupported('GL_EXT_texture_compression_latc');
  // Check non power of 2 textures
  Caps.NonPowerOfTwo := IsGLExtensionSupported('GL_ARB_texture_non_power_of_two');
  // Check for floating point textures support
  Caps.FloatTextures := IsGLExtensionSupported('GL_ARB_texture_float');
  // Get max texture size
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, @Caps.MaxTextureSize);
  // Get max anisotropy
  if IsGLExtensionSupported('GL_EXT_texture_filter_anisotropic') then
    glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, @Caps.MaxAnisotropy)
  else
    Caps.MaxAnisotropy := 0;
  // Get number of texture units
  if IsGLExtensionSupported('GL_ARB_multitexture') then
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, @Caps.MaxSimultaneousTextures)
  else
    Caps.MaxSimultaneousTextures := 1;
  // Get number of vertex texture units
  if IsGLExtensionSupported('GL_ARB_vertex_shader') then
    glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, @Caps.VertexTextureUnits)
  else
    Caps.VertexTextureUnits := 1;
  // Get max texture size
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, @Caps.MaxTextureSize);
  // Clamp texture to edge?
  Caps.ClampToEdge := IsGLExtensionSupported('GL_EXT_texture_edge_clamp');
  // Texture LOD extension?
  Caps.TextureLOD := IsGLExtensionSupported('GL_SGIS_texture_lod');

  Result := True;
end;

function ImageFormatToGL(Format: TImageFormat; var GLFormat: GLenum;
  var GLType: GLenum; var GLInternal: GLint; const Caps: TGLTextureCaps): Boolean;
begin
  GLFormat := 0;
  GLType := 0;
  GLInternal := 0;
  case Format of
    // Gray formats
    ifGray8, ifGray16:
      begin
        GLFormat   := GL_LUMINANCE;
        GLType     := Iff(Format = ifGray8, GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT);
        GLInternal := Iff(Format = ifGray8, GL_LUMINANCE8, GL_LUMINANCE16);
      end;
    ifA8Gray8, ifA16Gray16:
      begin
        GLFormat   := GL_LUMINANCE_ALPHA;
        GLType     := Iff(Format = ifA8Gray8, GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT);
        GLInternal := Iff(Format = ifA8Gray8, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE16_ALPHA16);
      end;
    // RGBA formats
    ifR3G3B2:
      begin
        GLFormat   := GL_RGB;
        GLType     := GL_UNSIGNED_BYTE_3_3_2;
        GLInternal := GL_R3_G3_B2;
      end;
    ifR5G6B5:
      begin
        GLFormat   := GL_RGB;
        GLType     := GL_UNSIGNED_SHORT_5_6_5;
        GLInternal := GL_RGB5;
      end;
    ifA1R5G5B5, ifX1R5G5B5:
      begin
        GLFormat   := GL_BGRA_EXT;
        GLType     := GL_UNSIGNED_SHORT_1_5_5_5_REV;
        GLInternal := Iff(Format = ifA1R5G5B5, GL_RGB5_A1, GL_RGB5);
      end;
    ifA4R4G4B4, ifX4R4G4B4:
      begin
        GLFormat   := GL_BGRA_EXT;
        GLType     := GL_UNSIGNED_SHORT_4_4_4_4_REV;
        GLInternal := Iff(Format = ifA4R4G4B4, GL_RGBA4, GL_RGB4);
      end;
    ifR8G8B8:
      begin
        GLFormat   := GL_BGR_EXT;
        GLType     := GL_UNSIGNED_BYTE;
        GLInternal := GL_RGB8;
      end;
    ifA8R8G8B8, ifX8R8G8B8:
      begin
        GLFormat   := GL_BGRA_EXT;
        GLType     := GL_UNSIGNED_BYTE;
        GLInternal := Iff(Format = ifA8R8G8B8, GL_RGBA8, GL_RGB8);
      end;
    ifR16G16B16, ifB16G16R16:
      begin
        GLFormat   := Iff(Format = ifR16G16B16, GL_BGR_EXT, GL_RGB);
        GLType     := GL_UNSIGNED_SHORT;
        GLInternal := GL_RGB16;
      end;
    ifA16R16G16B16, ifA16B16G16R16:
      begin
        GLFormat   := Iff(Format = ifA16R16G16B16, GL_BGRA_EXT, GL_RGBA);
        GLType     := GL_UNSIGNED_SHORT;
        GLInternal := GL_RGBA16;
      end;
    // Floating-Point formats
    ifR32F:
      begin
        GLFormat   := GL_RED;
        GLType     := GL_FLOAT;
        GLInternal := GL_LUMINANCE32F_ARB;
      end;
    ifA32R32G32B32F, ifA32B32G32R32F:
      begin
        GLFormat   := Iff(Format = ifA32R32G32B32F, GL_BGRA_EXT, GL_RGBA);
        GLType     := GL_FLOAT;
        GLInternal := GL_RGBA32F_ARB;
      end;
    ifR16F:
      begin
        GLFormat   := GL_RED;
        GLType     := GL_HALF_FLOAT_ARB;
        GLInternal := GL_LUMINANCE16F_ARB;
      end;
    ifA16R16G16B16F, ifA16B16G16R16F:
      begin
        GLFormat   := Iff(Format = ifA16R16G16B16F, GL_BGRA_EXT, GL_RGBA);
        GLType     := GL_HALF_FLOAT_ARB;
        GLInternal := GL_RGBA16F_ARB;
      end;
    // Special formats
    ifDXT1: GLInternal := GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
    ifDXT3: GLInternal := GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
    ifDXT5: GLInternal := GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
    ifATI1N: GLInternal := GL_COMPRESSED_LUMINANCE_LATC1_EXT;
    ifATI2N:
      begin
        GLInternal := GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
        if not Caps.LATCCompression and Caps.ATI3DcCompression then
          GLInternal := GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI;
      end;
  end;
  Result := GLInternal <> 0;
end;

function LoadGLTextureFromFile(const FileName: string; CreatedWidth, CreatedHeight: PLongInt): GLuint;
var
  Images: TDynImageDataArray;
begin
  if LoadMultiImageFromFile(FileName, Images) and (Length(Images) > 0) then
  begin
    Result := CreateGLTextureFromMultiImage(Images, Images[0].Width,
      Images[0].Height, True, 0, ifUnknown, CreatedWidth, CreatedHeight);
  end
  else
    Result := 0;
  FreeImagesInArray(Images);
end;

function LoadGLTextureFromStream(Stream: TStream; CreatedWidth, CreatedHeight: PLongInt): GLuint;
var
  Images: TDynImageDataArray;
begin
  if LoadMultiImageFromStream(Stream, Images) and (Length(Images) > 0) then
  begin
    Result := CreateGLTextureFromMultiImage(Images, Images[0].Width,
      Images[0].Height, True, 0, ifUnknown, CreatedWidth, CreatedHeight);
  end
  else
    Result := 0;
  FreeImagesInArray(Images);
end;

function LoadGLTextureFromMemory(Data: Pointer; Size: LongInt; CreatedWidth, CreatedHeight: PLongInt): GLuint;
var
  Images: TDynImageDataArray;
begin
  if LoadMultiImageFromMemory(Data, Size, Images)  and (Length(Images) > 0) then
  begin
    Result := CreateGLTextureFromMultiImage(Images, Images[0].Width,
      Images[0].Height, True, 0, ifUnknown, CreatedWidth, CreatedHeight);
  end
  else
    Result := 0;
  FreeImagesInArray(Images);
end;

function CreateGLTextureFromImage(const Image: TImageData;
  Width, Height: LongInt; MipMaps: Boolean; OverrideFormat: TImageFormat;
  CreatedWidth, CreatedHeight: PLongInt): GLuint;
var
  Arr: TDynImageDataArray;
begin
  // Just calls function operating on image arrays
  SetLength(Arr, 1);
  Arr[0] := Image;
  Result := CreateGLTextureFromMultiImage(Arr, Width, Height, MipMaps, 0,
    OverrideFormat, CreatedWidth, CreatedHeight);
end;

function CreateGLTextureFromMultiImage(const Images: TDynImageDataArray;
  Width, Height: LongInt; MipMaps: Boolean; MainLevelIndex: LongInt; OverrideFormat: TImageFormat;
  CreatedWidth, CreatedHeight: PLongInt): GLuint;
const
  BlockCompressedFormats: TImageFormats = [ifDXT1, ifDXT3, ifDXT5, ifATI1N, ifATI2N];
var
  I, MipLevels, PossibleLevels, ExistingLevels, CurrentWidth, CurrentHeight: LongInt;
  Caps: TGLTextureCaps;
  GLFormat: GLenum;
  GLType: GLenum;
  GLInternal: GLint;
  Desired, ConvTo: TImageFormat;
  Info: TImageFormatInfo;
  LevelsArray: TDynImageDataArray;
  NeedsResize, NeedsConvert: Boolean;
  UnpackAlignment, UnpackSkipRows, UnpackSkipPixels, UnpackRowLength: LongInt;

  procedure PasteImage(var Image: TImageData; Width, Height: LongInt);
  var
    Clone: TImageData;
  begin
    CloneImage(Image, Clone);
    NewImage(Width, Height, Clone.Format, Image);
    FillRect(Image, 0, 0, Width, Height, Clone.Bits);
    CopyRect(Clone, 0, 0, Clone.Width, Clone.Height, Image, 0, 0);
    FreeImage(Clone);
  end;

begin
  Result := 0;
  ExistingLevels := Length(Images);

  if GetGLTextureCaps(Caps) and (ExistingLevels > 0) then
  try
    // Check if requested main level is at valid index
    if (MainLevelIndex < 0) or (MainLevelIndex > High(Images)) then
      MainLevelIndex := 0;

    // First check desired size and modify it if necessary
    if Width <= 0 then Width := Images[MainLevelIndex].Width;
    if Height <= 0 then Height := Images[MainLevelIndex].Height;
    if not Caps.NonPowerOfTwo and not DisableNPOTSupportCheck then
    begin
      // If device supports only power of 2 texture sizes
      Width := NextPow2(Width);
      Height := NextPow2(Height);
    end;
    Width := ClampInt(Width, 1, Caps.MaxTextureSize);
    Height := ClampInt(Height, 1, Caps.MaxTextureSize);

    // Get various mipmap level counts and modify
    // desired MipLevels if its value is invalid
    PossibleLevels := GetNumMipMapLevels(Width, Height);
    if MipMaps then
      MipLevels := PossibleLevels
    else
      MipLevels := 1;

    // Prepare array for mipmap levels. Make it larger than necessary - that
    // way we can use the same index for input images and levels in the large loop below
    SetLength(LevelsArray, MipLevels + MainLevelIndex);

    // Now determine which image format will be used
    if OverrideFormat = ifUnknown then
      Desired := Images[MainLevelIndex].Format
    else
      Desired := OverrideFormat;

    // Check if the hardware supports floating point and compressed textures
    GetImageFormatInfo(Desired, Info);
    if Info.IsFloatingPoint and not Caps.FloatTextures then
      Desired := ifA8R8G8B8;
    if (Desired in [ifDXT1, ifDXT3, ifDXT5]) and not Caps.DXTCompression then
      Desired := ifA8R8G8B8;
    if (Desired = ifATI1N)  and not Caps.LATCCompression then
      Desired := ifGray8;
    if (Desired = ifATI2N) and not (Caps.ATI3DcCompression or Caps.LATCCompression) then
      Desired := ifA8Gray8;

    // Try to find GL format equivalent to image format and if it is not
    // found use one of default formats
    if not ImageFormatToGL(Desired, GLFormat, GLType, GLInternal, Caps) then
    begin
      GetImageFormatInfo(Desired, Info);
      if Info.HasGrayChannel then
        ConvTo := ifGray8
      else
        ConvTo := ifA8R8G8B8;
      if not ImageFormatToGL(ConvTo, GLFormat, GLType, GLInternal, Caps) then
        Exit;
    end
    else
      ConvTo := Desired;

    CurrentWidth := Width;
    CurrentHeight := Height;
    // If user is interested in width and height of created texture lets
    // give him that
    if CreatedWidth <> nil then CreatedWidth^ := CurrentWidth;
    if CreatedHeight <> nil then CreatedHeight^ := CurrentHeight;

    // Store old pixel unpacking settings
    glGetIntegerv(GL_UNPACK_ALIGNMENT, @UnpackAlignment);
    glGetIntegerv(GL_UNPACK_SKIP_ROWS, @UnpackSkipRows);
    glGetIntegerv(GL_UNPACK_SKIP_PIXELS, @UnpackSkipPixels);
    glGetIntegerv(GL_UNPACK_ROW_LENGTH, @UnpackRowLength);
    // Set new pixel unpacking settings
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

    // Generate new texture, bind it and set
    glGenTextures(1, @Result);
    glBindTexture(GL_TEXTURE_2D, Result);
    if Byte(glIsTexture(Result)) <> GL_TRUE then
      Exit;

    for I := MainLevelIndex to MipLevels - 1 + MainLevelIndex do
    begin
      // Check if we can use input image array as a source for this mipmap level
      if I < ExistingLevels then
      begin
        // Check if input image for this mipmap level has the right
        // size and format
        NeedsConvert := not (Images[I].Format = ConvTo);
        if ConvTo in BlockCompressedFormats then
        begin
          // Input images in DXTC will have min dimensions of 4, but we need
          // current Width and Height to be lesser (for glCompressedTexImage2D)
          NeedsResize := not ((Images[I].Width = Max(4, CurrentWidth)) and
            (Images[I].Height = Max(4, CurrentHeight)));
        end
        else
          NeedsResize := not ((Images[I].Width = CurrentWidth) and (Images[I].Height = CurrentHeight));

        if NeedsResize or NeedsConvert then
        begin
          // Input image must be resized or converted to different format
          // to become valid mipmap level
          CloneImage(Images[I], LevelsArray[I]);
          if NeedsConvert then
            ConvertImage(LevelsArray[I], ConvTo);
          if NeedsResize then
          begin
            if (not PasteNonPow2ImagesIntoPow2) or (LevelsArray[I].Width > CurrentWidth) or
              (LevelsArray[I].Height > CurrentHeight)then
            begin
              // If pasteNP2toP2 is disabled or if source is bigger than target
              // we rescale image, otherwise we paste it with the same size
              ResizeImage(LevelsArray[I], CurrentWidth, CurrentHeight, rfBilinear)
            end
            else
              PasteImage(LevelsArray[I], CurrentWidth, CurrentHeight);
          end;
        end
        else
          // Input image can be used without any changes
          LevelsArray[I] := Images[I];
      end
      else
      begin
        // This mipmap level is not present in the input image array
        // so we create a new level
        FillMipMapLevel(LevelsArray[I - 1], CurrentWidth, CurrentHeight, LevelsArray[I]);
      end;

      if ConvTo in BlockCompressedFormats then
      begin
        // Note: GL DXTC texture snaller than 4x4 must have width and height
        // as expected for non-DXTC texture (like 1x1 -  we cannot
        // use LevelsArray[I].Width and LevelsArray[I].Height - they are
        // at least 4 for DXTC images). But Bits and Size passed to
        // glCompressedTexImage2D must contain regular 4x4 DXTC block.
        glCompressedTexImage2D(GL_TEXTURE_2D, I - MainLevelIndex, GLInternal, CurrentWidth,
          CurrentHeight, 0, LevelsArray[I].Size, LevelsArray[I].Bits)
      end
      else
      begin
        glTexImage2D(GL_TEXTURE_2D, I - MainLevelIndex, GLInternal, CurrentWidth,
          CurrentHeight, 0, GLFormat, GLType, LevelsArray[I].Bits);
      end;

      // Calculate width and height of the next mipmap level
      CurrentWidth := ClampInt(CurrentWidth div 2, 1, CurrentWidth);
      CurrentHeight := ClampInt(CurrentHeight div 2, 1, CurrentHeight);
    end;

    // Restore old pixel unpacking settings
    glPixelStorei(GL_UNPACK_ALIGNMENT, UnpackAlignment);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, UnpackSkipRows);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, UnpackSkipPixels);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, UnpackRowLength);
  finally
    // Free local image copies
    for I := 0 to Length(LevelsArray) - 1 do
    begin
      if ((I < ExistingLevels) and (LevelsArray[I].Bits <> Images[I].Bits)) or
        (I >= ExistingLevels) then
        FreeImage(LevelsArray[I]);
    end;
  end;
end;

function SaveGLTextureToFile(const FileName: string; const Texture: GLuint): Boolean;
var
  Arr: TDynImageDataArray;
  Fmt: TImageFileFormat;
  IsDDS: Boolean;
begin
  Result := CreateMultiImageFromGLTexture(Texture, Arr);
  if Result then
  begin
    Fmt := FindImageFileFormatByName(FileName);
    if Fmt <> nil then
    begin
      IsDDS := SameText(Fmt.Extensions[0], 'dds');
      if IsDDS then
      begin
        PushOptions;
        SetOption(ImagingDDSSaveMipMapCount, Length(Arr));
      end;
      Result := SaveMultiImageToFile(FileName, Arr);
      if IsDDS then
        PopOptions;
    end;
    FreeImagesInArray(Arr);
  end;
end;

function SaveGLTextureToStream(const Ext: string; Stream: TStream; const Texture: GLuint): Boolean;
var
  Arr: TDynImageDataArray;
  Fmt: TImageFileFormat;
  IsDDS: Boolean;
begin
  Result := CreateMultiImageFromGLTexture(Texture, Arr);
  if Result then
  begin
    Fmt := FindImageFileFormatByExt(Ext);
    if Fmt <> nil then
    begin
      IsDDS := SameText(Fmt.Extensions[0], 'dds');
      if IsDDS then
      begin
        PushOptions;
        SetOption(ImagingDDSSaveMipMapCount, Length(Arr));
      end;
      Result := SaveMultiImageToStream(Ext, Stream, Arr);
      if IsDDS then
        PopOptions;
    end;
    FreeImagesInArray(Arr);
  end;
end;

function SaveGLTextureToMemory(const Ext: string; Data: Pointer; var Size: LongInt; const Texture: GLuint): Boolean;
var
  Arr: TDynImageDataArray;
  Fmt: TImageFileFormat;
  IsDDS: Boolean;
begin
  Result := CreateMultiImageFromGLTexture(Texture, Arr);
  if Result then
  begin
    Fmt := FindImageFileFormatByExt(Ext);
    if Fmt <> nil then
    begin
      IsDDS := SameText(Fmt.Extensions[0], 'dds');
      if IsDDS then
      begin
        PushOptions;
        SetOption(ImagingDDSSaveMipMapCount, Length(Arr));
      end;
      Result := SaveMultiImageToMemory(Ext, Data, Size, Arr);
      if IsDDS then
        PopOptions;
    end;
    FreeImagesInArray(Arr);
  end;
end;

function CreateImageFromGLTexture(const Texture: GLuint;
  var Image: TImageData; OverrideFormat: TImageFormat): Boolean;
var
  Arr: TDynImageDataArray;
begin
  // Just calls function operating on image arrays
  FreeImage(Image);
  SetLength(Arr, 1);
  Result := CreateMultiImageFromGLTexture(Texture, Arr, 1, OverrideFormat);
  Image := Arr[0];
end;

function CreateMultiImageFromGLTexture(const Texture: GLuint;
  var Images: TDynImageDataArray; MipLevels: LongInt; OverrideFormat: TImageFormat): Boolean;
var
  I, Width, Height, ExistingLevels: LongInt;
begin
  FreeImagesInArray(Images);
  SetLength(Images, 0);
  Result := False;
  if Byte(glIsTexture(Texture)) = GL_TRUE then
  begin
    // Check if desired mipmap level count is valid
    glBindTexture(GL_TEXTURE_2D, Texture);
    if MipLevels <= 0 then
      MipLevels := GetNumMipMapLevels(Width, Height);
    SetLength(Images, MipLevels);
    ExistingLevels := 0;

    for I := 0 to MipLevels - 1 do
    begin
      // Get the current level size
      glGetTexLevelParameteriv(GL_TEXTURE_2D, I, GL_TEXTURE_WIDTH, @Width);
      glGetTexLevelParameteriv(GL_TEXTURE_2D, I, GL_TEXTURE_HEIGHT, @Height);
      // Break when the mipmap chain is broken
      if (Width = 0) or (Height = 0) then
        Break;
      // Create new image and copy texture data
      NewImage(Width, Height, ifA8R8G8B8, Images[I]);
      glGetTexImage(GL_TEXTURE_2D, I, GL_BGRA_EXT, GL_UNSIGNED_BYTE, Images[I].Bits);
      Inc(ExistingLevels);
    end;
    // Resize mipmap array if necessary
    if MipLevels <> ExistingLevels then
      SetLength(Images, ExistingLevels);
    // Convert images to desired format if set
    if OverrideFormat <> ifUnknown then
      for I := 0 to Length(Images) - 1 do
        ConvertImage(Images[I], OverrideFormat);

    Result := True;
  end;
end;

initialization

{
  File Notes:

  -- TODOS ----------------------------------------------------
    - use internal format of texture in CreateMultiImageFromGLTexture
      not only A8R8G8B8
    - support for cube and 3D maps

  -- 0.26.1 Changes/Bug Fixes ---------------------------------
    - Added support for GLScene's OpenGL header.

  -- 0.25.0 Changes/Bug Fixes ---------------------------------
    - Added 3Dc compressed texture formats support.
    - Added detection of 3Dc formats to texture caps.

  -- 0.24.3 Changes/Bug Fixes ---------------------------------
    - Added DisableNPOTSupportCheck option and related functionality.
    - Added some new texture caps detection.

  -- 0.24.1 Changes/Bug Fixes ---------------------------------
    - Added PasteNonPow2ImagesIntoPow2 option and related functionality.
    - Better NeedsResize determination for small DXTC textures -
      avoids needless resizing.
    - Added MainLevelIndex to CreateMultiImageFromGLTexture.

  -- 0.21 Changes/Bug Fixes -----------------------------------
    - Added CreatedWidth and CreatedHeight parameters to most
      LoadGLTextureFromXXX/CreateGLTextureFromXXX functions.

  -- 0.19 Changes/Bug Fixes -----------------------------------
    - fixed bug in CreateGLTextureFromMultiImage which caused assert failure
      when creating mipmaps (using FillMipMapLevel) for DXTC formats
    - changed single channel floating point texture formats from
      GL_INTENSITY..._ARB to GL_LUMINANCE..._ARB
    - added support for half float texture formats (GL_RGBA16F_ARB etc.)   

  -- 0.17 Changes/Bug Fixes -----------------------------------
    - filtered mipmap creation
    - more texture caps added
    - fixed memory leaks in SaveGLTextureTo... functions

  -- 0.15 Changes/Bug Fixes -----------------------------------
    - unit created and initial stuff added
}

end.
