Code: Select all
// Copyright (C) 2002-2010 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_OPENGL_
#include "irrTypes.h"
#include "COpenGLTexture.h"
#include "COpenGLDriver.h"
#include "os.h"
#include "CImage.h"
#include "CColorConverter.h"
#include <GL/glut.h>
#include "glext.h"
#include "irrString.h"
namespace irr
{
namespace video
{
//! constructor for usual textures
COpenGLTexture::COpenGLTexture(IImage* origImage, const io::path& name, void* mipmapData, COpenGLDriver* driver)
: ITexture(name), ColorFormat(ECF_A8R8G8B8), Driver(driver), Image(0), MipImage(0),
TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_BGRA_EXT),
PixelType(GL_UNSIGNED_BYTE),
IsRenderTarget(false), AutomaticMipmapUpdate(false),
ReadOnlyLock(false), KeepImage(true), isPixelBufferObject(false)
{
#ifdef _DEBUG
setDebugName("COpenGLTexture");
#endif
HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS);
getImageValues(origImage);
glGenTextures(1, &TextureName);
if (ImageSize==TextureSize)
{
Image = new CImage(ColorFormat, ImageSize);
origImage->copyTo(Image);
}
else
{
Image = new CImage(ColorFormat, TextureSize);
// scale texture
origImage->copyToScaling(Image);
}
uploadTexture(true, mipmapData);
if (!KeepImage)
{
Image->drop();
Image=0;
}
}
//! constructor for basic setup (only for derived classes)
COpenGLTexture::COpenGLTexture(const io::path& name, COpenGLDriver* driver)
: ITexture(name), ColorFormat(ECF_A8R8G8B8), Driver(driver), Image(0), MipImage(0),
TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_BGRA_EXT),
PixelType(GL_UNSIGNED_BYTE),
HasMipMaps(true), IsRenderTarget(false), AutomaticMipmapUpdate(false),
ReadOnlyLock(false), KeepImage(true)
{
#ifdef _DEBUG
setDebugName("COpenGLTexture");
#endif
}
//! destructor
COpenGLTexture::~COpenGLTexture()
{
if (TextureName)
glDeleteTextures(1, &TextureName);
if (Image)
Image->drop();
if(isPixelBufferObject)
glDeleteBuffersARB(1, &pboId);
}
//! Choose best matching color format, based on texture creation flags
ECOLOR_FORMAT COpenGLTexture::getBestColorFormat(ECOLOR_FORMAT format)
{
ECOLOR_FORMAT destFormat = ECF_A8R8G8B8;
switch (format)
{
case ECF_A1R5G5B5:
if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
destFormat = ECF_A1R5G5B5;
break;
case ECF_R5G6B5:
if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
destFormat = ECF_A1R5G5B5;
break;
case ECF_A8R8G8B8:
if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) ||
Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
destFormat = ECF_A1R5G5B5;
break;
case ECF_R8G8B8:
if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) ||
Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
destFormat = ECF_A1R5G5B5;
default:
break;
}
if (Driver->getTextureCreationFlag(ETCF_NO_ALPHA_CHANNEL))
{
switch (destFormat)
{
case ECF_A1R5G5B5:
destFormat = ECF_R5G6B5;
break;
case ECF_A8R8G8B8:
destFormat = ECF_R8G8B8;
break;
default:
break;
}
}
return destFormat;
}
//! Get opengl values for the GPU texture storage
GLint COpenGLTexture::getOpenGLFormatAndParametersFromColorFormat(ECOLOR_FORMAT format,
GLint& filtering,
GLenum& colorformat,
GLenum& type)
{
// default
filtering = GL_LINEAR;
colorformat = GL_RGBA;
type = GL_UNSIGNED_BYTE;
switch(format)
{
case ECF_A1R5G5B5:
colorformat=GL_BGRA_EXT;
type=GL_UNSIGNED_SHORT_1_5_5_5_REV;
return GL_RGBA;
case ECF_R5G6B5:
colorformat=GL_BGR;
type=GL_UNSIGNED_SHORT_5_6_5_REV;
return GL_RGB;
case ECF_R8G8B8:
colorformat=GL_BGR;
type=GL_UNSIGNED_BYTE;
return GL_RGB;
case ECF_A8R8G8B8:
colorformat=GL_BGRA_EXT;
if (Driver->Version > 101)
type=GL_UNSIGNED_INT_8_8_8_8_REV;
return GL_RGBA;
// Floating Point texture formats. Thanks to Patryk "Nadro" Nadrowski.
case ECF_R16F:
{
#ifdef GL_ARB_texture_rg
filtering = GL_NEAREST;
colorformat = GL_RED;
type = GL_FLOAT;
return GL_R16F;
#else
return GL_RGB8;
#endif
}
case ECF_G16R16F:
{
#ifdef GL_ARB_texture_rg
filtering = GL_NEAREST;
colorformat = GL_RG;
type = GL_FLOAT;
return GL_RG16F;
#else
return GL_RGB8;
#endif
}
case ECF_A16B16G16R16F:
{
#ifdef GL_ARB_texture_rg
filtering = GL_NEAREST;
colorformat = GL_RGBA;
type = GL_FLOAT;
return GL_RGBA16F_ARB;
#else
return GL_RGBA8;
#endif
}
case ECF_R32F:
{
#ifdef GL_ARB_texture_rg
filtering = GL_NEAREST;
colorformat = GL_RED;
type = GL_FLOAT;
return GL_R32F;
#else
return GL_RGB8;
#endif
}
case ECF_G32R32F:
{
#ifdef GL_ARB_texture_rg
filtering = GL_NEAREST;
colorformat = GL_RG;
type = GL_FLOAT;
return GL_RG32F;
#else
return GL_RGB8;
#endif
}
case ECF_A32B32G32R32F:
{
#ifdef GL_ARB_texture_float
filtering = GL_NEAREST;
colorformat = GL_RGBA;
type = GL_FLOAT;
return GL_RGBA32F_ARB;
#else
return GL_RGBA8;
#endif
}
default:
{
os::Printer::log("Unsupported texture format", ELL_ERROR);
return GL_RGBA8;
}
}
}
// prepare values ImageSize, TextureSize, and ColorFormat based on image
void COpenGLTexture::getImageValues(IImage* image)
{
if (!image)
{
os::Printer::log("No image for OpenGL texture.", ELL_ERROR);
return;
}
ImageSize = image->getDimension();
if ( !ImageSize.Width || !ImageSize.Height)
{
os::Printer::log("Invalid size of image for OpenGL Texture.", ELL_ERROR);
return;
}
const f32 ratio = (f32)ImageSize.Width/(f32)ImageSize.Height;
if ((ImageSize.Width>Driver->MaxTextureSize) && (ratio >= 1.0f))
{
ImageSize.Width = Driver->MaxTextureSize;
ImageSize.Height = (u32)(Driver->MaxTextureSize/ratio);
}
else if (ImageSize.Height>Driver->MaxTextureSize)
{
ImageSize.Height = Driver->MaxTextureSize;
ImageSize.Width = (u32)(Driver->MaxTextureSize*ratio);
}
TextureSize=ImageSize.getOptimalSize(!Driver->queryFeature(EVDF_TEXTURE_NPOT));
ColorFormat = getBestColorFormat(image->getColorFormat());
}
//! copies the the texture into an open gl texture.
void COpenGLTexture::uploadTexture(bool newTexture, void* mipmapData, u32 level)
{
// check which image needs to be uploaded
IImage* image = level?MipImage:Image;
if (!image)
{
os::Printer::log("No image for OpenGL texture to upload", ELL_ERROR);
return;
}
// get correct opengl color data values
GLenum oldInternalFormat = InternalFormat;
GLint filtering;
InternalFormat = getOpenGLFormatAndParametersFromColorFormat(ColorFormat, filtering, PixelFormat, PixelType);
// make sure we don't change the internal format of existing images
if (!newTexture)
InternalFormat=oldInternalFormat;
Driver->setActiveTexture(0, this);
if (Driver->testGLError())
os::Printer::log("Could not bind Texture", ELL_ERROR);
// mipmap handling for main texture
if (!level && newTexture)
{
#ifndef DISABLE_MIPMAPPING
#ifdef GL_SGIS_generate_mipmap
// auto generate if possible and no mipmap data is given
if (HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
{
if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_FASTEST);
else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);
else
glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_DONT_CARE);
// automatically generate and update mipmaps
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
AutomaticMipmapUpdate=true;
}
else
#endif
{
// Either generate manually due to missing capability
// or use predefined mipmap data
AutomaticMipmapUpdate=false;
regenerateMipMapLevels(mipmapData);
}
if (HasMipMaps) // might have changed in regenerateMipMapLevels
{
// enable bilinear mipmap filter
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
else
#else
HasMipMaps=false;
os::Printer::log("Did not create OpenGL texture mip maps.", ELL_INFORMATION);
#endif
{
// enable bilinear filter without mipmaps
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
}
// now get image data and upload to GPU
void* source = image->lock();
if (newTexture)
glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
image->getDimension().Height, 0, PixelFormat, PixelType, source);
else
{
if( isPixelBufferObject )
{
// bind the texture and PBO
//glBindTexture(GL_TEXTURE_2D, textureId);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboId);
// copy pixels from PBO to texture object
// Use offset instead of ponter.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, Image->getDimension().Width, Image->getDimension().Height, PixelFormat, PixelType, 0);
// bind PBO to update pixel values
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboId);
// map the buffer object into client's memory
// Note that glMapBufferARB() causes sync issue.
// If GPU is working with this buffer, glMapBufferARB() will wait(stall)
// for GPU to finish its job. To avoid waiting (stall), you can call
// first glBufferDataARB() with NULL pointer before glMapBufferARB().
// If you do that, the previous data in PBO will be discarded and
// glMapBufferARB() returns a new allocated pointer immediately
// even if GPU is still working with the previous data.
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, image->getImageDataSizeInBytes(), 0, GL_STREAM_DRAW_ARB);
GLubyte* ptr = (GLubyte*)glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB);
if(ptr)
{
// update data directly on the mapped buffer
memcpy( ptr, source, Image->getImageDataSizeInBytes() );
glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release pointer to mapping buffer
}
// it is good idea to release PBOs with ID 0 after use.
// Once bound with 0, all pixel operations behave normal ways.
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
}
else
{
glTexSubImage2D( GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
image->getDimension().Height, PixelFormat, PixelType, source);
}
}
image->unlock();
if (Driver->testGLError())
os::Printer::log("Could not glTexImage2D", ELL_ERROR);
}
//! lock function
void* COpenGLTexture::lock(bool readOnly, u32 mipmapLevel)
{
// store info about which image is locked
IImage* image = (mipmapLevel==0)?Image:MipImage;
ReadOnlyLock |= readOnly;
MipLevelStored = mipmapLevel;
// if data not available or might have changed on GPU download it
if (!image || IsRenderTarget)
{
// prepare the data storage if necessary
if (!image)
{
if (mipmapLevel)
{
u32 i=0;
u32 width = TextureSize.Width;
u32 height = TextureSize.Height;
do
{
if (width>1)
width>>=1;
if (height>1)
height>>=1;
++i;
}
while (i != mipmapLevel);
MipImage = image = new CImage(ECF_A8R8G8B8, core::dimension2du(width,height));
}
else
Image = image = new CImage(ECF_A8R8G8B8, ImageSize);
ColorFormat = ECF_A8R8G8B8;
}
if (!image)
return 0;
u8* pixels = static_cast<u8*>(image->lock());
if (!pixels)
return 0;
// we need to keep the correct texture bound later on
GLint tmpTexture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &tmpTexture);
glBindTexture(GL_TEXTURE_2D, TextureName);
// allows to read pixels in top-to-bottom order
#ifdef GL_MESA_pack_invert
if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert))
glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE);
#endif
// download GPU data as ARGB8 to pixels;
glGetTexImage(GL_TEXTURE_2D, mipmapLevel, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
#ifdef GL_MESA_pack_invert
if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert))
glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE);
else
#endif
{
// opengl images are horizontally flipped, so we have to fix that here.
const s32 pitch=image->getPitch();
u8* p2 = pixels + (image->getDimension().Height - 1) * pitch;
u8* tmpBuffer = new u8[pitch];
for (u32 i=0; i < image->getDimension().Height; i += 2)
{
memcpy(tmpBuffer, pixels, pitch);
memcpy(pixels, p2, pitch);
memcpy(p2, tmpBuffer, pitch);
pixels += pitch;
p2 -= pitch;
}
delete [] tmpBuffer;
}
image->unlock();
//reset old bound texture
glBindTexture(GL_TEXTURE_2D, tmpTexture);
}
return image->lock();
}
//! unlock function
void COpenGLTexture::unlock()
{
// test if miplevel or main texture was locked
IImage* image = MipImage?MipImage:Image;
if (!image)
return;
// unlock image to see changes
image->unlock();
// copy texture data to GPU
if (!ReadOnlyLock)
uploadTexture(false, 0, MipLevelStored);
ReadOnlyLock = false;
// cleanup local image
if (MipImage)
{
MipImage->drop();
MipImage=0;
}
else if (!KeepImage)
{
Image->drop();
Image=0;
}
// update information
if (Image)
ColorFormat=Image->getColorFormat();
else
ColorFormat=ECF_A8R8G8B8;
}
//! Returns size of the original image.
const core::dimension2d<u32>& COpenGLTexture::getOriginalSize() const
{
return ImageSize;
}
//! Returns size of the texture.
const core::dimension2d<u32>& COpenGLTexture::getSize() const
{
return TextureSize;
}
//! returns driver type of texture, i.e. the driver, which created the texture
E_DRIVER_TYPE COpenGLTexture::getDriverType() const
{
return EDT_OPENGL;
}
//! returns color format of texture
ECOLOR_FORMAT COpenGLTexture::getColorFormat() const
{
return ColorFormat;
}
//! returns pitch of texture (in bytes)
u32 COpenGLTexture::getPitch() const
{
if (Image)
return Image->getPitch();
else
return 0;
}
//! return open gl texture name
GLuint COpenGLTexture::getOpenGLTextureName() const
{
return TextureName;
}
//! Returns whether this texture has mipmaps
bool COpenGLTexture::hasMipMaps() const
{
return HasMipMaps;
}
//! Regenerates the mip map levels of the texture. Useful after locking and
//! modifying the texture
void COpenGLTexture::regenerateMipMapLevels(void* mipmapData)
{
if (AutomaticMipmapUpdate || !HasMipMaps || !Image)
return;
if ((Image->getDimension().Width==1) && (Image->getDimension().Height==1))
return;
// Manually create mipmaps or use prepared version
u32 width=Image->getDimension().Width;
u32 height=Image->getDimension().Height;
u32 i=0;
u8* target = static_cast<u8*>(mipmapData);
do
{
if (width>1)
width>>=1;
if (height>1)
height>>=1;
++i;
if (!target)
target = new u8[width*height*Image->getBytesPerPixel()];
// create scaled version if no mipdata available
if (!mipmapData)
Image->copyToScaling(target, width, height, Image->getColorFormat());
glTexImage2D(GL_TEXTURE_2D, i, InternalFormat, width, height,
0, PixelFormat, PixelType, target);
// get next prepared mipmap data if available
if (mipmapData)
{
mipmapData = static_cast<u8*>(mipmapData)+width*height*Image->getBytesPerPixel();
target = static_cast<u8*>(mipmapData);
}
}
while (width!=1 || height!=1);
// cleanup
if (!mipmapData)
delete [] target;
}
bool COpenGLTexture::isRenderTarget() const
{
return IsRenderTarget;
}
void COpenGLTexture::setIsRenderTarget(bool isTarget)
{
IsRenderTarget = isTarget;
}
bool COpenGLTexture::initPbo()
{
if( Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_ARB_pixel_buffer_object ) )
{
glGenBuffersARB(1, &pboId);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pboId);
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, Image->getDimension().Height * Image->getPitch(), 0, GL_STREAM_DRAW_ARB);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
isPixelBufferObject = true;
return true;
}
return false;
}
bool COpenGLTexture::isFrameBufferObject() const
{
return false;
}
//! Bind Render Target Texture
void COpenGLTexture::bindRTT()
{
}
//! Unbind Render Target Texture
void COpenGLTexture::unbindRTT()
{
Driver->setActiveTexture(0, this);
// Copy Our ViewPort To The Texture
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, getSize().Width, getSize().Height);
}
} // end namespace video
} // end namespace irr
#endif // _IRR_COMPILE_WITH_OPENGL_
Code: Select all
// Copyright (C) 2002-2010 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#ifndef __C_OPEN_GL_TEXTURE_H_INCLUDED__
#define __C_OPEN_GL_TEXTURE_H_INCLUDED__
#include "ITexture.h"
#include "IImage.h"
#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_OPENGL_
#if defined(_IRR_OPENGL_USE_EXTPOINTER_)
#define GL_GLEXT_LEGACY 1
#else
#define GL_GLEXT_PROTOTYPES 1
#endif
#ifdef _IRR_WINDOWS_API_
// include windows headers for HWND
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <GL/gl.h>
#ifdef _MSC_VER
#pragma comment(lib, "OpenGL32.lib")
#endif
#elif defined(_IRR_OSX_PLATFORM_)
#include <OpenGL/gl.h>
#elif defined(_IRR_COMPILE_WITH_SDL_DEVICE_)
#define NO_SDL_GLEXT
#include <SDL/SDL_video.h>
#include <SDL/SDL_opengl.h>
#else
#if defined(_IRR_OSX_PLATFORM_)
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#endif
namespace irr
{
namespace video
{
class COpenGLDriver;
//! OpenGL texture.
class COpenGLTexture : public ITexture
{
public:
//! constructor
COpenGLTexture(IImage* surface, const io::path& name, void* mipmapData=0, COpenGLDriver* driver=0);
//! destructor
virtual ~COpenGLTexture();
//! lock function
virtual void* lock(bool readOnly=false, u32 mipmapLevel=0);
//! unlock function
virtual void unlock();
//! Returns original size of the texture (image).
virtual const core::dimension2d<u32>& getOriginalSize() const;
//! Returns size of the texture.
virtual const core::dimension2d<u32>& getSize() const;
//! returns driver type of texture (=the driver, that created it)
virtual E_DRIVER_TYPE getDriverType() const;
//! returns color format of texture
virtual ECOLOR_FORMAT getColorFormat() const;
//! returns pitch of texture (in bytes)
virtual u32 getPitch() const;
//! return open gl texture name
GLuint getOpenGLTextureName() const;
//! return whether this texture has mipmaps
virtual bool hasMipMaps() const;
//! Regenerates the mip map levels of the texture.
/** Useful after locking and modifying the texture
\param mipmapData Pointer to raw mipmap data, including all necessary mip levels, in the same format as the main texture image. If not set the mipmaps are derived from the main image. */
virtual void regenerateMipMapLevels(void* mipmapData=0);
//! Is it a render target?
virtual bool isRenderTarget() const;
//! Is it a FrameBufferObject?
virtual bool isFrameBufferObject() const;
//! Bind RenderTargetTexture
virtual void bindRTT();
//! Unbind RenderTargetTexture
virtual void unbindRTT();
//! sets whether this texture is intended to be used as a render target.
void setIsRenderTarget(bool isTarget);
bool initPbo();
protected:
//! protected constructor with basic setup, no GL texture name created, for derived classes
COpenGLTexture(const io::path& name, COpenGLDriver* driver);
//! get the desired color format based on texture creation flags and the input format.
ECOLOR_FORMAT getBestColorFormat(ECOLOR_FORMAT format);
//! Get the OpenGL color format parameters based on the given Irrlicht color format
GLint getOpenGLFormatAndParametersFromColorFormat(
ECOLOR_FORMAT format, GLint& filtering, GLenum& colorformat, GLenum& type);
//! get important numbers of the image and hw texture
void getImageValues(IImage* image);
//! copies the texture into an OpenGL texture.
/** \param newTexture True if method is called for a newly created texture for the first time. Otherwise call with false to improve memory handling.
\param mipmapData Pointer to raw mipmap data, including all necessary mip levels, in the same format as the main texture image.
\param mipLevel If set to non-zero, only that specific miplevel is updated, using the MipImage member. */
void uploadTexture(bool newTexture=false, void* mipmapData=0, u32 mipLevel=0);
core::dimension2d<u32> ImageSize;
core::dimension2d<u32> TextureSize;
ECOLOR_FORMAT ColorFormat;
COpenGLDriver* Driver;
IImage* Image;
IImage* MipImage;
GLuint TextureName;
GLint InternalFormat;
GLenum PixelFormat;
GLenum PixelType;
u8 MipLevelStored;
bool HasMipMaps;
bool IsRenderTarget;
bool AutomaticMipmapUpdate;
bool ReadOnlyLock;
bool KeepImage;
bool isPixelBufferObject;
u32 pboId;
};
} // end namespace video
} // end namespace irr
#endif
#endif // _IRR_COMPILE_WITH_OPENGL_