1 // Copyright (C) 2015 Patryk Nadrowski
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #ifndef __C_OGLCORE_TEXTURE_H_INCLUDED__
\r
6 #define __C_OGLCORE_TEXTURE_H_INCLUDED__
\r
9 #include "irrArray.h"
\r
10 #include "SMaterialLayer.h"
\r
11 #include "ITexture.h"
\r
12 #include "EDriverFeatures.h"
\r
15 #include "CColorConverter.h"
\r
17 // Check if GL version we compile with should have the glGenerateMipmap function.
\r
18 #if defined(GL_VERSION_3_0) || defined(GL_ES_VERSION_2_0)
\r
19 #define IRR_OPENGL_HAS_glGenerateMipmap
\r
27 template <class TOpenGLDriver>
\r
28 class COpenGLCoreTexture : public ITexture
\r
33 SStatesCache() : WrapU(ETC_REPEAT), WrapV(ETC_REPEAT), WrapW(ETC_REPEAT),
\r
34 LODBias(0), AnisotropicFilter(0), BilinearFilter(false), TrilinearFilter(false),
\r
35 MipMapStatus(false), IsCached(false)
\r
43 u8 AnisotropicFilter;
\r
44 bool BilinearFilter;
\r
45 bool TrilinearFilter;
\r
50 COpenGLCoreTexture(const io::path& name, const core::array<IImage*>& images, E_TEXTURE_TYPE type, TOpenGLDriver* driver) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D),
\r
51 TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0),
\r
52 KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false)
\r
54 _IRR_DEBUG_BREAK_IF(images.size() == 0)
\r
56 DriverType = Driver->getDriverType();
\r
57 TextureType = TextureTypeIrrToGL(Type);
\r
58 HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS);
\r
59 KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY);
\r
61 getImageValues(images[0]);
\r
63 const core::array<IImage*>* tmpImages = &images;
\r
65 if (KeepImage || OriginalSize != Size || OriginalColorFormat != ColorFormat)
\r
67 Images.set_used(images.size());
\r
69 for (u32 i = 0; i < images.size(); ++i)
\r
71 Images[i] = Driver->createImage(ColorFormat, Size);
\r
73 if (images[i]->getDimension() == Size)
\r
74 images[i]->copyTo(Images[i]);
\r
76 images[i]->copyToScaling(Images[i]);
\r
78 if ( images[i]->getMipMapsData() )
\r
80 if ( OriginalSize == Size && OriginalColorFormat == ColorFormat )
\r
82 Images[i]->setMipMapsData( images[i]->getMipMapsData(), false);
\r
86 // TODO: handle at least mipmap with changing color format
\r
87 os::Printer::log("COpenGLCoreTexture: Can't handle format changes for mipmap data. Mipmap data dropped", ELL_WARNING);
\r
92 tmpImages = &Images;
\r
95 glGenTextures(1, &TextureName);
\r
97 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
98 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
100 glTexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
\r
101 glTexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
\r
103 #ifdef GL_GENERATE_MIPMAP_HINT
\r
106 if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
107 glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
\r
108 else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
\r
109 glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
\r
111 glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
\r
115 #if !defined(IRR_OPENGL_HAS_glGenerateMipmap) && defined(GL_GENERATE_MIPMAP)
\r
118 LegacyAutoGenerateMipMaps = Driver->getTextureCreationFlag(ETCF_AUTO_GENERATE_MIP_MAPS) &&
\r
119 Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE);
\r
120 glTexParameteri(TextureType, GL_GENERATE_MIPMAP, LegacyAutoGenerateMipMaps ? GL_TRUE : GL_FALSE);
\r
124 for (u32 i = 0; i < (*tmpImages).size(); ++i)
\r
125 uploadTexture(true, i, 0, (*tmpImages)[i]->getData());
\r
127 if (HasMipMaps && !LegacyAutoGenerateMipMaps)
\r
129 // Create mipmaps (either from image mipmaps or generate them)
\r
130 for (u32 i = 0; i < (*tmpImages).size(); ++i)
\r
132 void* mipmapsData = (*tmpImages)[i]->getMipMapsData();
\r
133 regenerateMipMapLevels(mipmapsData, i);
\r
139 for (u32 i = 0; i < Images.size(); ++i)
\r
146 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
148 Driver->testGLError(__LINE__);
\r
151 COpenGLCoreTexture(const io::path& name, const core::dimension2d<u32>& size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver* driver)
\r
152 : ITexture(name, type),
\r
153 Driver(driver), TextureType(GL_TEXTURE_2D),
\r
154 TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false),
\r
155 MipLevelStored(0), LegacyAutoGenerateMipMaps(false)
\r
157 DriverType = Driver->getDriverType();
\r
158 TextureType = TextureTypeIrrToGL(Type);
\r
159 HasMipMaps = false;
\r
160 IsRenderTarget = true;
\r
162 OriginalColorFormat = format;
\r
164 if (ECF_UNKNOWN == OriginalColorFormat)
\r
165 ColorFormat = getBestColorFormat(Driver->getColorFormat());
\r
167 ColorFormat = OriginalColorFormat;
\r
169 OriginalSize = size;
\r
170 Size = OriginalSize;
\r
172 Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8;
\r
174 if ( !Driver->getColorFormatParameters(ColorFormat, InternalFormat, PixelFormat, PixelType, &Converter) )
\r
176 os::Printer::log("COpenGLCoreTexture: Color format is not supported", ColorFormatNames[ColorFormat < ECF_UNKNOWN?ColorFormat:ECF_UNKNOWN], ELL_ERROR);
\r
179 glGenTextures(1, &TextureName);
\r
181 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
182 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
185 glTexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
\r
186 glTexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
\r
187 glTexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
\r
188 glTexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
\r
190 #if defined(GL_VERSION_1_2)
\r
191 glTexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
\r
194 StatesCache.WrapU = ETC_CLAMP_TO_EDGE;
\r
195 StatesCache.WrapV = ETC_CLAMP_TO_EDGE;
\r
196 StatesCache.WrapW = ETC_CLAMP_TO_EDGE;
\r
201 glTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
204 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
205 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
206 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
207 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
208 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
209 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
213 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
214 if ( Driver->testGLError(__LINE__) )
\r
217 snprintf_irr(msg, 256, "COpenGLCoreTexture: InternalFormat:0x%04x PixelFormat:0x%04x", (int)InternalFormat, (int)PixelFormat);
\r
218 os::Printer::log(msg, ELL_ERROR);
\r
222 virtual ~COpenGLCoreTexture()
\r
225 glDeleteTextures(1, &TextureName);
\r
230 for (u32 i = 0; i < Images.size(); ++i)
\r
234 void* lock(E_TEXTURE_LOCK_MODE mode = ETLM_READ_WRITE, u32 mipmapLevel=0, u32 layer = 0, E_TEXTURE_LOCK_FLAGS lockFlags = ETLF_FLIP_Y_UP_RTT) override
\r
237 return getLockImageData(MipLevelStored);
\r
239 if (IImage::isCompressedFormat(ColorFormat))
\r
242 LockReadOnly |= (mode == ETLM_READ_ONLY);
\r
244 MipLevelStored = mipmapLevel;
\r
248 _IRR_DEBUG_BREAK_IF(LockLayer > Images.size())
\r
250 if ( mipmapLevel == 0 || (Images[LockLayer] && Images[LockLayer]->getMipMapsData(mipmapLevel)) )
\r
252 LockImage = Images[LockLayer];
\r
259 core::dimension2d<u32> lockImageSize( IImage::getMipMapsSize(Size, MipLevelStored));
\r
261 // note: we save mipmap data also in the image because IImage doesn't allow saving single mipmap levels to the mipmap data
\r
262 LockImage = Driver->createImage(ColorFormat, lockImageSize);
\r
264 if (LockImage && mode != ETLM_WRITE_ONLY)
\r
266 bool passed = true;
\r
268 #ifdef IRR_COMPILE_GL_COMMON
\r
269 IImage* tmpImage = LockImage; // not sure yet if the size required by glGetTexImage is always correct, if not we might have to allocate a different tmpImage and convert colors later on.
\r
271 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
272 Driver->testGLError(__LINE__);
\r
274 GLenum tmpTextureType = TextureType;
\r
276 if (tmpTextureType == GL_TEXTURE_CUBE_MAP)
\r
278 _IRR_DEBUG_BREAK_IF(layer > 5)
\r
280 tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
\r
283 glGetTexImage(tmpTextureType, MipLevelStored, PixelFormat, PixelType, tmpImage->getData());
\r
284 Driver->testGLError(__LINE__);
\r
286 if (IsRenderTarget && lockFlags == ETLF_FLIP_Y_UP_RTT)
\r
288 const s32 pitch = tmpImage->getPitch();
\r
290 u8* srcA = static_cast<u8*>(tmpImage->getData());
\r
291 u8* srcB = srcA + (tmpImage->getDimension().Height - 1) * pitch;
\r
293 u8* tmpBuffer = new u8[pitch];
\r
295 for (u32 i = 0; i < tmpImage->getDimension().Height; i += 2)
\r
297 memcpy(tmpBuffer, srcA, pitch);
\r
298 memcpy(srcA, srcB, pitch);
\r
299 memcpy(srcB, tmpBuffer, pitch);
\r
304 delete[] tmpBuffer;
\r
306 #elif (defined(IRR_COMPILE_GLES2_COMMON) || defined(IRR_COMPILE_GLES_COMMON))
\r
307 // TODO: on ES2 we can likely also work with glCopyTexImage2D instead of rendering which should be faster.
\r
308 COpenGLCoreTexture* tmpTexture = new COpenGLCoreTexture("OGL_CORE_LOCK_TEXTURE", Size, ETT_2D, ColorFormat, Driver);
\r
311 Driver->irrGlGenFramebuffers(1, &tmpFBO);
\r
313 GLint prevViewportX = 0;
\r
314 GLint prevViewportY = 0;
\r
315 GLsizei prevViewportWidth = 0;
\r
316 GLsizei prevViewportHeight = 0;
\r
317 Driver->getCacheHandler()->getViewport(prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight);
\r
318 Driver->getCacheHandler()->setViewport(0, 0, Size.Width, Size.Height);
\r
320 GLuint prevFBO = 0;
\r
321 Driver->getCacheHandler()->getFBO(prevFBO);
\r
322 Driver->getCacheHandler()->setFBO(tmpFBO);
\r
324 Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tmpTexture->getOpenGLTextureName(), 0);
\r
326 glClear(GL_COLOR_BUFFER_BIT);
\r
328 Driver->draw2DImage(this, layer, true);
\r
330 IImage* tmpImage = Driver->createImage(ECF_A8R8G8B8, Size);
\r
331 glReadPixels(0, 0, Size.Width, Size.Height, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->getData());
\r
333 Driver->getCacheHandler()->setFBO(prevFBO);
\r
334 Driver->getCacheHandler()->setViewport(prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight);
\r
336 Driver->irrGlDeleteFramebuffers(1, &tmpFBO);
\r
339 void* src = tmpImage->getData();
\r
340 void* dest = LockImage->getData();
\r
342 switch (ColorFormat)
\r
345 CColorConverter::convert_A8R8G8B8toA1B5G5R5(src, tmpImage->getDimension().getArea(), dest);
\r
348 CColorConverter::convert_A8R8G8B8toR5G6B5(src, tmpImage->getDimension().getArea(), dest);
\r
351 CColorConverter::convert_A8R8G8B8toB8G8R8(src, tmpImage->getDimension().getArea(), dest);
\r
354 CColorConverter::convert_A8R8G8B8toA8B8G8R8(src, tmpImage->getDimension().getArea(), dest);
\r
370 Driver->testGLError(__LINE__);
\r
373 return (LockImage) ? getLockImageData(MipLevelStored) : 0;
\r
376 void unlock() override
\r
383 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
384 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
386 uploadTexture(false, LockLayer, MipLevelStored, getLockImageData(MipLevelStored));
\r
388 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
393 LockReadOnly = false;
\r
398 void regenerateMipMapLevels(void* data = 0, u32 layer = 0) override
\r
400 if (!HasMipMaps || LegacyAutoGenerateMipMaps || (Size.Width <= 1 && Size.Height <= 1))
\r
403 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
404 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
408 u32 width = Size.Width;
\r
409 u32 height = Size.Height;
\r
410 u8* tmpData = static_cast<u8*>(data);
\r
422 dataSize = IImage::getDataSizeFromFormat(ColorFormat, width, height);
\r
425 uploadTexture(true, layer, level, tmpData);
\r
427 tmpData += dataSize;
\r
429 while (width != 1 || height != 1);
\r
433 #ifdef IRR_OPENGL_HAS_glGenerateMipmap
\r
434 Driver->irrGlGenerateMipmap(TextureType);
\r
438 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
441 GLenum getOpenGLTextureType() const
\r
443 return TextureType;
\r
446 GLuint getOpenGLTextureName() const
\r
448 return TextureName;
\r
451 SStatesCache& getStatesCache() const
\r
453 return StatesCache;
\r
458 void * getLockImageData(irr::u32 miplevel) const
\r
460 if ( KeepImage && MipLevelStored > 0
\r
461 && LockImage->getMipMapsData(MipLevelStored) )
\r
463 return LockImage->getMipMapsData(MipLevelStored);
\r
465 return LockImage->getData();
\r
468 ECOLOR_FORMAT getBestColorFormat(ECOLOR_FORMAT format)
\r
470 // We only try for to adapt "simple" formats
\r
471 ECOLOR_FORMAT destFormat = (format <= ECF_A8R8G8B8) ? ECF_A8R8G8B8 : format;
\r
476 if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
\r
477 destFormat = ECF_A1R5G5B5;
\r
480 if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
\r
481 destFormat = ECF_R5G6B5;
\r
484 if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) ||
\r
485 Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
486 destFormat = ECF_A1R5G5B5;
\r
489 // Note: Using ECF_A8R8G8B8 even when ETCF_ALWAYS_32_BIT is not set as 24 bit textures fail with too many cards
\r
490 if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) || Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
491 destFormat = ECF_A1R5G5B5;
\r
496 if (Driver->getTextureCreationFlag(ETCF_NO_ALPHA_CHANNEL))
\r
498 switch (destFormat)
\r
501 destFormat = ECF_R5G6B5;
\r
504 destFormat = ECF_R8G8B8;
\r
514 void getImageValues(const IImage* image)
\r
516 OriginalColorFormat = image->getColorFormat();
\r
517 ColorFormat = getBestColorFormat(OriginalColorFormat);
\r
519 if ( !Driver->getColorFormatParameters(ColorFormat, InternalFormat, PixelFormat, PixelType, &Converter) )
\r
521 os::Printer::log("getImageValues: Color format is not supported", ColorFormatNames[ColorFormat < ECF_UNKNOWN?ColorFormat:ECF_UNKNOWN], ELL_ERROR);
\r
522 // not quitting as it will use some alternative internal format
\r
525 if (IImage::isCompressedFormat(image->getColorFormat()))
\r
530 OriginalSize = image->getDimension();
\r
531 Size = OriginalSize;
\r
533 if (Size.Width == 0 || Size.Height == 0)
\r
535 os::Printer::log("Invalid size of image for texture.", ELL_ERROR);
\r
539 const f32 ratio = (f32)Size.Width / (f32)Size.Height;
\r
541 if ((Size.Width > Driver->MaxTextureSize) && (ratio >= 1.f))
\r
543 Size.Width = Driver->MaxTextureSize;
\r
544 Size.Height = (u32)(Driver->MaxTextureSize / ratio);
\r
546 else if (Size.Height > Driver->MaxTextureSize)
\r
548 Size.Height = Driver->MaxTextureSize;
\r
549 Size.Width = (u32)(Driver->MaxTextureSize * ratio);
\r
552 bool needSquare = (!Driver->queryFeature(EVDF_TEXTURE_NSQUARE) || Type == ETT_CUBEMAP);
\r
554 Size = Size.getOptimalSize(!Driver->queryFeature(EVDF_TEXTURE_NPOT), needSquare, true, Driver->MaxTextureSize);
\r
556 Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8;
\r
559 void uploadTexture(bool initTexture, u32 layer, u32 level, void* data)
\r
564 u32 width = Size.Width >> level;
\r
565 u32 height = Size.Height >> level;
\r
567 GLenum tmpTextureType = TextureType;
\r
569 if (tmpTextureType == GL_TEXTURE_CUBE_MAP)
\r
571 _IRR_DEBUG_BREAK_IF(layer > 5)
\r
573 tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
\r
576 if (!IImage::isCompressedFormat(ColorFormat))
\r
578 CImage* tmpImage = 0;
\r
579 void* tmpData = data;
\r
583 const core::dimension2d<u32> tmpImageSize(width, height);
\r
585 tmpImage = new CImage(ColorFormat, tmpImageSize);
\r
586 tmpData = tmpImage->getData();
\r
588 Converter(data, tmpImageSize.getArea(), tmpData);
\r
591 switch (TextureType)
\r
593 case GL_TEXTURE_2D:
\r
594 case GL_TEXTURE_CUBE_MAP:
\r
596 glTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, PixelFormat, PixelType, tmpData);
\r
598 glTexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, PixelType, tmpData);
\r
599 Driver->testGLError(__LINE__);
\r
609 u32 dataSize = IImage::getDataSizeFromFormat(ColorFormat, width, height);
\r
611 switch (TextureType)
\r
613 case GL_TEXTURE_2D:
\r
614 case GL_TEXTURE_CUBE_MAP:
\r
616 Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data);
\r
618 Driver->irrGlCompressedTexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, dataSize, data);
\r
619 Driver->testGLError(__LINE__);
\r
627 GLenum TextureTypeIrrToGL(E_TEXTURE_TYPE type) const
\r
632 return GL_TEXTURE_2D;
\r
634 return GL_TEXTURE_CUBE_MAP;
\r
637 os::Printer::log("COpenGLCoreTexture::TextureTypeIrrToGL unknown texture type", ELL_WARNING);
\r
638 return GL_TEXTURE_2D;
\r
641 TOpenGLDriver* Driver;
\r
643 GLenum TextureType;
\r
644 GLuint TextureName;
\r
645 GLint InternalFormat;
\r
646 GLenum PixelFormat;
\r
648 void (*Converter)(const void*, s32, void*);
\r
655 core::array<IImage*> Images;
\r
658 bool LegacyAutoGenerateMipMaps;
\r
660 mutable SStatesCache StatesCache;
\r