| Description | uses | Classes, Interfaces, Objects and Records | Functions and Procedures | Types | Constants | Variables |
Using images in OpenGL (as textures and as normal images).
This unit implements various OpenGL utilities relates to handling images in TImage classes. This includes
Loading TImage instance as OpenGL texture. Wrapper around glTexImage2D and other texture-related operations. See LoadGLTexture.
Drawing TImage instance in OpenGL buffer. Wrapper around glDrawPixels and related things. See ImageDraw.
Screen saving, that is saving OpenGL buffer contents to TImage instance. Wrapper around glReadPixels and related things. See TGLWindow.SaveScreen, based on SaveScreen_noflush in this unit.
See Images unit for functions to load, save, process images. Images unit is the non-OpenGL-related helper of this unit.
This unit hides from you some specifics of OpenGL images handling :
Don't worry about pixel store alignment, this unit handles it for you.
Since internally our image formats have no alignment, we call something like
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
when appropriate. Actually, we use Before/After Pack/Unpack NotAlignedImage procedures from KambiGLUtils unit.
Don't worry about texture size being power of 2, or about maximum texture size.
This unit checks OpenGL capabilities, and if needed will scale your texture to have power of 2 sizes that fit within OpenGL GL_MAX_TEXTURE_SIZE limit.
Notes about power-of-two constraint: Newer OpenGL allows using non-power-of-two textures when extension ARB_texture_non_power_of_two is supported (or version is >= 2.0). We can use this — the code is ready, just uncomment it in TextureNonPowerOfTwo. But by default we don't use this, that is: we will scale textures to be power of 2, even if we have ARB_texture_non_power_of_two or new OpenGL.
Reason: well, looks like every vendor screwed it up. Embarassing.
On Mesa, small artifacts occur (strange cracks appears on non-power-of-2 texture, see kambi_vrml_test_suite/inlined_textures.wrl). That's the best result compared to other vendors, see below.
On ATI (fglrx on Linux on Mac Book Pro), the extension is not present and OpenGL 2.0 entry points are not fully present. (Although GL_VERSION claims 2.1 version, glBlendEquationSeparate entry point from GL 2.0 is not present.) For safety, I just assume that ATI is not able to make OpenGL 2.0, so no extension and no 2.0 means textures must be power of two.
Just for kicks, I tried anyway to pass texture 4x3 (after all, GL_VERSION claims 2.1 so this should be supported), and... uhh... libGL segfaulted. Congrats, ATI.
I thought: Mesa is always buggy and Radeon should not be treated as OpenGL 2.0, so I can live with this. Now let's try great NVidia, they will for sure do this right. Well, yes, it works correctly on NVidia (GeForce FX 5200)... but the slowdown is enormous. For trivial box with 4x3 texture (see kambi_vrml_test_suite/inlined_textures.wrl), that normally runs with virtually infinite speed, suddenly the speed becomes like 1 frame per second ! Other example when the slowdown is enormous: castle/levels/castle_hall.wrl
You can test yourself (with view3dscene using kambi_vrml_test_suite/inlined_textures.wrl model; just compile view3dscene to use non-power-of-2 textures; contact me if you want a binary compiled as such for testing.)
Such slowdown is not acceptable, I prefer to loose texture quality by scaling them to powers of 2 in this case...
Internally, this unit depends on the knowledge on how pixels are stored in TRGBImage and similar classes. For example we know that TRGBImage stores image in format that OpenGL would call "GL_RGB using GL_UNSIGNED_BYTE, without any alignment". Which means that Image.RGBPixels is a pointer to array like packed array[0..Image.Height - 1, 0..Image.Width - 1] of TVector3Byte. So we have rows of TVector3Byte structures, stored from lowest row to highest row.
function ImageGLFormat(const Img: TImage): TGLenum; |
function ImageGLType(const Img: TImage): TGLenum; |
function LoadImageToDisplayList(const FileName: string; const LoadAsClass: array of TImageClass; const LoadForbiddenConvs: TImageLoadConversions; const ResizeToX, ResizeToY: Cardinal): TGLuint; overload; |
procedure ImageDraw(const Image: TImage); |
procedure ImageDrawRows(const Image: TImage; Row0, RowsCount: integer); |
procedure ImageDrawCutted(const image: TImage; cutx, cuty: cardinal); |
function ImageDrawToDisplayList(const img: TImage): TGLuint; |
procedure SaveScreen_noflush(const FileName: string; ReadBuffer: TGLenum); overload; |
function SaveScreen_noflush(ReadBuffer: TGLenum): TRGBImage; overload; |
function SaveScreen_noflush(xpos, ypos, width, height: integer; ReadBuffer: TGLenum): TRGBImage; overload; |
function SaveScreenToDisplayList_noflush(ReadBuffer: TGLenum): TGLuint; overload; |
function SaveScreenToDisplayList_noflush(xpos, ypos, width, height: integer; ReadBuffer: TGLenum): TGLuint; overload; |
procedure ResizeForTextureSize(var r: TImage); |
function ResizeToTextureSize(const r: TImage): TImage; |
function IsTextureSized(const r: TImage): boolean; |
function LoadGLTexture(const image: TImage; minFilter, magFilter: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
function LoadGLTexture(const image: TImage; minFilter, magFilter, WrapS, WrapT: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
function LoadGLTexture(const FileName: string; minFilter, magFilter, WrapS, WrapT: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
procedure LoadGLGeneratedTexture(texnum: TGLuint; const image: TImage; minFilter, magFilter, wrapS, wrapT: TGLenum; GrayscaleIsAlpha: boolean = false); overload; |
procedure LoadGLGeneratedTexture(texnum: TGLuint; const image: TImage; minFilter, magFilter: TGLenum; GrayscaleIsAlpha: boolean = false); overload; |
function LoadGLTextureModulated(const Image: TImage; MinFilter, MagFilter, WrapS, WrapT: TGLenum; ColorModulatorByte: TColorModulatorByteFunc): TGLuint; |
GLImageClasses: array [0..2] of TImageClass = (
TRGBImage, TAlphaImage, TGrayscaleImage); |
function ImageGLFormat(const Img: TImage): TGLenum; |
|
These functions return appropriate GL_xxx format and type for given TImage descendant. If you will pass here Img that is not a descendant of one of GLImageClasses, they will return GL_INVALID_ENUM. Note that OpenGL does not guarantee that GL_INVALID_ENUM <> GL_RGB, GL_RGBA etc. (even if every OpenGL implementation has constants defined that in a way that satisfies this). So better to not assume that instead of checking InImageClasses(MyImage, GLImageClasses) you can simply check (But this fact can be used to make routines in this unit like ImageDraw work faster, because I don't guarantee anywhere that ImageDraw will check at runtime that passed Image has class in GLImageClasses. So ImageDraw simply passes to OpenGL values returned by So this way ImageDraw does not do any checks using GLImageFormats, even when compiled with -dDEBUG. Everything is done in OpenGL. And, in practice, current OpenGL implementations *will* signal errors so things are checked.). |
function ImageGLType(const Img: TImage): TGLenum; |
function LoadImageToDisplayList(const FileName: string; const LoadAsClass: array of TImageClass; const LoadForbiddenConvs: TImageLoadConversions; const ResizeToX, ResizeToY: Cardinal): TGLuint; overload; |
|
This calls Images.LoadImage and creates a display-list with an ImageDraw call for this image. Image will be loaded with AllowedImageClasses = LoadAsClass and ForbiddenConvs = LoadForbiddenConvs, see Images.LoadImage for description what these parameters mean. LoadAsClass may contain only classes present in GLImageClasses. |
procedure ImageDraw(const Image: TImage); |
|
Draws the image as 2D on screen. This calls OpenGL glDrawPixels command on this image. Don't worry about OpenGL's UNPACK_ALIGNMENT, we will take care here about this (changing it and restoring to previous value if necessary). |
procedure ImageDrawRows(const Image: TImage; Row0, RowsCount: integer); |
|
Same as ImageDraw, but will draw only RowsCount rows starting from Row0. |
procedure ImageDrawCutted(const image: TImage; cutx, cuty: cardinal); |
|
Same as ImageDraw, but will omit 1st CutX columns (1st from the left) and 1st CutY rows (1st from down). This is implemented using glPixelStorei(GL_UNPACK_ROW_LENGTH / GL_UNPACK_SKIP_PIXELS / GL_UNPACK_SKIP_ROWS). So it works fast. Don't worry, we will take care here of changing (and later restoring to previous values) such unpack settings, just like GL_UNPACK_ALIGNMENT. |
function ImageDrawToDisplayList(const img: TImage): TGLuint; |
|
This creates new display list with a call to ImageDraw(Img) inside. |
procedure SaveScreen_noflush(const FileName: string; ReadBuffer: TGLenum); overload; |
|
Saves the current color buffer contents to an image file or to TRGBImage object. Sidenote: useful function to generate image filename for game screenshots is FnameAutoInc in KambiUtils unit. It does glReadBuffer(ReadBuffer) and then glReadPixels with appropriate parameters. In case of overloaded version that takes a FileName, it then saves image to file using SaveImage. It has such strange name (_noflush) to remind you that this function does not do anything like TGLWindow.FlushRedisplay but you should usually take care of doing something like that before saving contents of OpenGL front buffer. In other words, remember that this function saves the *current* contents of color buffer – so be sure that it contains what you want before using this function. Note that you can pass here any ReadBuffer value allowed by glReadBuffer OpenGL function). |
function SaveScreen_noflush(ReadBuffer: TGLenum): TRGBImage; overload; |
function SaveScreen_noflush(xpos, ypos, width, height: integer; ReadBuffer: TGLenum): TRGBImage; overload; |
function SaveScreenToDisplayList_noflush(ReadBuffer: TGLenum): TGLuint; overload; |
|
Saves the current color buffer (captured like SaveScreen_noflush) into the display list to redraw it. That is, it returns newly created display list that contains call to ImageDraw on a captured image. |
function SaveScreenToDisplayList_noflush(xpos, ypos, width, height: integer; ReadBuffer: TGLenum): TGLuint; overload; |
procedure ResizeForTextureSize(var r: TImage); |
|
Resizes the image to a size accepted as texture size for OpenGL. It tries to resize to larger size, not smaller, to avoid losing image information. Usually you don't have to call this, LoadGLTexture* functions call it automatically when needed. |
function ResizeToTextureSize(const r: TImage): TImage; |
function IsTextureSized(const r: TImage): boolean; |
|
Tests if texture has proper size for OpenGL, that is for passing it to glTexImage2d. This checks glGet(GL_MAX_TEXTURE_SIZE), so requires initialized OpenGL context. |
function LoadGLTexture(const image: TImage; minFilter, magFilter: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
|
Load new texture. It generates new texture number by glGenTextures. This takes care of UNPACK_ALIGNMENT (if needed, we'll change it and later revert back, so that the texture is correctly loaded). If you omit WrapS / WrapT parameters then they will not be set (so default OpenGL values will be used, since we always initialize new texture here). Changes currently bound texture to this one (returned). GrayscaleIsAlpha is meaningful only if the image is TGrayscaleImage class. If GrayscaleIsAlpha is |
function LoadGLTexture(const image: TImage; minFilter, magFilter, WrapS, WrapT: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
function LoadGLTexture(const FileName: string; minFilter, magFilter, WrapS, WrapT: TGLenum; GrayscaleIsAlpha: boolean = false): TGLuint; overload; |
procedure LoadGLGeneratedTexture(texnum: TGLuint; const image: TImage; minFilter, magFilter, wrapS, wrapT: TGLenum; GrayscaleIsAlpha: boolean = false); overload; |
|
Load texture into already reserved texture number. Besides this, works exactly like LoadGLTexture. If you omit WrapS / WrapT parameters then they will not be set. Changes currently bound texture to TexNum. You can use this to set "default unnamed OpenGL texture" parameters by passing TexNum = 0. |
procedure LoadGLGeneratedTexture(texnum: TGLuint; const image: TImage; minFilter, magFilter: TGLenum; GrayscaleIsAlpha: boolean = false); overload; |
function LoadGLTextureModulated(const Image: TImage; MinFilter, MagFilter, WrapS, WrapT: TGLenum; ColorModulatorByte: TColorModulatorByteFunc): TGLuint; |
|
As LoadGLTexture, but the texture will be modified using ColorModulatorByte. If not Assigned(ColorModulatorByte) then this will simply return LoadGLTexture(Image, MinFilter, MagFilter, WrapS, WrapT). Else it will return LoadGLTexture(ImageModulated(Image), MinFilter, MagFilter, WrapS, WrapT) (without introducing any memoty leaks). |
GLImageClasses: array [0..2] of TImageClass = (
TRGBImage, TAlphaImage, TGrayscaleImage); |
|
All routines in this unit that take TImage paramater accept only TImage descendants enumerated here. Note that *not everywhere* this is checked (especially if you compile with -dRELEASE) so just be sure that you're always passing only TImage instances of correct class (e.g. using InImageClasses(MyImage, |