CentrED/Imaging/ImagingOpenGL.pas

928 lines
36 KiB
Plaintext

{
$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.