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 #if defined(_IRR_COMPILE_WITH_OPENGL_) || defined(_IRR_COMPILE_WITH_OGLES1_) || defined(_IRR_COMPILE_WITH_OGLES2_)
\r
11 #include "irrArray.h"
\r
12 #include "SMaterialLayer.h"
\r
13 #include "ITexture.h"
\r
14 #include "EDriverFeatures.h"
\r
17 #include "CColorConverter.h"
\r
19 // Check if GL version we compile with should have the glGenerateMipmap function.
\r
20 #if defined(GL_VERSION_3_0) || defined(GL_ES_VERSION_2_0)
\r
21 #define IRR_OPENGL_HAS_glGenerateMipmap
\r
29 template <class TOpenGLDriver>
\r
30 class COpenGLCoreTexture : public ITexture
\r
35 SStatesCache() : WrapU(ETC_REPEAT), WrapV(ETC_REPEAT), WrapW(ETC_REPEAT),
\r
36 LODBias(0), AnisotropicFilter(0), BilinearFilter(false), TrilinearFilter(false),
\r
37 MipMapStatus(false), IsCached(false)
\r
45 u8 AnisotropicFilter;
\r
46 bool BilinearFilter;
\r
47 bool TrilinearFilter;
\r
52 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
53 TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0),
\r
54 KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false)
\r
56 _IRR_DEBUG_BREAK_IF(images.size() == 0)
\r
58 DriverType = Driver->getDriverType();
\r
59 TextureType = TextureTypeIrrToGL(Type);
\r
60 HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS);
\r
61 KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY);
\r
63 getImageValues(images[0]);
\r
65 const core::array<IImage*>* tmpImages = &images;
\r
67 if (KeepImage || OriginalSize != Size || OriginalColorFormat != ColorFormat)
\r
69 Images.set_used(images.size());
\r
71 for (u32 i = 0; i < images.size(); ++i)
\r
73 Images[i] = Driver->createImage(ColorFormat, Size);
\r
75 if (images[i]->getDimension() == Size)
\r
76 images[i]->copyTo(Images[i]);
\r
78 images[i]->copyToScaling(Images[i]);
\r
80 if ( images[i]->getMipMapsData() )
\r
82 if ( OriginalSize == Size && OriginalColorFormat == ColorFormat )
\r
84 Images[i]->setMipMapsData( images[i]->getMipMapsData(), false);
\r
88 // TODO: handle at least mipmap with changing color format
\r
89 os::Printer::log("COpenGLCoreTexture: Can't handle format changes for mipmap data. Mipmap data dropped", ELL_WARNING);
\r
94 tmpImages = &Images;
\r
97 glGenTextures(1, &TextureName);
\r
99 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
100 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
102 glTexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
\r
103 glTexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
\r
105 #ifdef GL_GENERATE_MIPMAP_HINT
\r
108 if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
109 glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
\r
110 else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
\r
111 glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
\r
113 glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
\r
117 #if !defined(IRR_OPENGL_HAS_glGenerateMipmap) && defined(GL_GENERATE_MIPMAP)
\r
120 LegacyAutoGenerateMipMaps = Driver->getTextureCreationFlag(ETCF_AUTO_GENERATE_MIP_MAPS) &&
\r
121 Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE);
\r
122 glTexParameteri(TextureType, GL_GENERATE_MIPMAP, LegacyAutoGenerateMipMaps ? GL_TRUE : GL_FALSE);
\r
126 for (u32 i = 0; i < (*tmpImages).size(); ++i)
\r
127 uploadTexture(true, i, 0, (*tmpImages)[i]->getData());
\r
129 if (HasMipMaps && !LegacyAutoGenerateMipMaps)
\r
131 // Create mipmaps (either from image mipmaps or generate them)
\r
132 for (u32 i = 0; i < (*tmpImages).size(); ++i)
\r
134 void* mipmapsData = (*tmpImages)[i]->getMipMapsData();
\r
135 regenerateMipMapLevels(mipmapsData, i);
\r
141 for (u32 i = 0; i < Images.size(); ++i)
\r
148 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
150 Driver->testGLError(__LINE__);
\r
153 COpenGLCoreTexture(const io::path& name, const core::dimension2d<u32>& size, E_TEXTURE_TYPE type, ECOLOR_FORMAT format, TOpenGLDriver* driver)
\r
154 : ITexture(name, type),
\r
155 Driver(driver), TextureType(GL_TEXTURE_2D),
\r
156 TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false),
\r
157 MipLevelStored(0), LegacyAutoGenerateMipMaps(false)
\r
159 DriverType = Driver->getDriverType();
\r
160 TextureType = TextureTypeIrrToGL(Type);
\r
161 HasMipMaps = false;
\r
162 IsRenderTarget = true;
\r
164 OriginalColorFormat = format;
\r
166 if (ECF_UNKNOWN == OriginalColorFormat)
\r
167 ColorFormat = getBestColorFormat(Driver->getColorFormat());
\r
169 ColorFormat = OriginalColorFormat;
\r
171 OriginalSize = size;
\r
172 Size = OriginalSize;
\r
174 Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8;
\r
176 if ( !Driver->getColorFormatParameters(ColorFormat, InternalFormat, PixelFormat, PixelType, &Converter) )
\r
178 os::Printer::log("COpenGLCoreTexture: Color format is not supported", ColorFormatNames[ColorFormat < ECF_UNKNOWN?ColorFormat:ECF_UNKNOWN], ELL_ERROR);
\r
181 glGenTextures(1, &TextureName);
\r
183 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
184 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
187 glTexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
\r
188 glTexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
\r
189 glTexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
\r
190 glTexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
\r
192 #if defined(GL_VERSION_1_2)
\r
193 glTexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
\r
196 StatesCache.WrapU = ETC_CLAMP_TO_EDGE;
\r
197 StatesCache.WrapV = ETC_CLAMP_TO_EDGE;
\r
198 StatesCache.WrapW = ETC_CLAMP_TO_EDGE;
\r
203 glTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
206 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
207 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
208 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
209 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
210 glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
211 glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0);
\r
215 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
216 if ( Driver->testGLError(__LINE__) )
\r
219 snprintf_irr(msg, 256, "COpenGLCoreTexture: InternalFormat:0x%04x PixelFormat:0x%04x", (int)InternalFormat, (int)PixelFormat);
\r
220 os::Printer::log(msg, ELL_ERROR);
\r
224 virtual ~COpenGLCoreTexture()
\r
227 glDeleteTextures(1, &TextureName);
\r
232 for (u32 i = 0; i < Images.size(); ++i)
\r
236 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
239 return getLockImageData(MipLevelStored);
\r
241 if (IImage::isCompressedFormat(ColorFormat))
\r
244 LockReadOnly |= (mode == ETLM_READ_ONLY);
\r
246 MipLevelStored = mipmapLevel;
\r
250 _IRR_DEBUG_BREAK_IF(LockLayer > Images.size())
\r
252 if ( mipmapLevel == 0 || (Images[LockLayer] && Images[LockLayer]->getMipMapsData(mipmapLevel)) )
\r
254 LockImage = Images[LockLayer];
\r
261 core::dimension2d<u32> lockImageSize( IImage::getMipMapsSize(Size, MipLevelStored));
\r
263 // note: we save mipmap data also in the image because IImage doesn't allow saving single mipmap levels to the mipmap data
\r
264 LockImage = Driver->createImage(ColorFormat, lockImageSize);
\r
266 if (LockImage && mode != ETLM_WRITE_ONLY)
\r
268 bool passed = true;
\r
270 #ifdef IRR_COMPILE_GL_COMMON
\r
271 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
273 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
274 Driver->testGLError(__LINE__);
\r
276 GLenum tmpTextureType = TextureType;
\r
278 if (tmpTextureType == GL_TEXTURE_CUBE_MAP)
\r
280 _IRR_DEBUG_BREAK_IF(layer > 5)
\r
282 tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
\r
285 glGetTexImage(tmpTextureType, MipLevelStored, PixelFormat, PixelType, tmpImage->getData());
\r
286 Driver->testGLError(__LINE__);
\r
288 if (IsRenderTarget && lockFlags == ETLF_FLIP_Y_UP_RTT)
\r
290 const s32 pitch = tmpImage->getPitch();
\r
292 u8* srcA = static_cast<u8*>(tmpImage->getData());
\r
293 u8* srcB = srcA + (tmpImage->getDimension().Height - 1) * pitch;
\r
295 u8* tmpBuffer = new u8[pitch];
\r
297 for (u32 i = 0; i < tmpImage->getDimension().Height; i += 2)
\r
299 memcpy(tmpBuffer, srcA, pitch);
\r
300 memcpy(srcA, srcB, pitch);
\r
301 memcpy(srcB, tmpBuffer, pitch);
\r
306 delete[] tmpBuffer;
\r
308 #elif (defined(IRR_COMPILE_GLES2_COMMON) || defined(IRR_COMPILE_GLES_COMMON))
\r
309 // TODO: on ES2 we can likely also work with glCopyTexImage2D instead of rendering which should be faster.
\r
310 COpenGLCoreTexture* tmpTexture = new COpenGLCoreTexture("OGL_CORE_LOCK_TEXTURE", Size, ETT_2D, ColorFormat, Driver);
\r
313 Driver->irrGlGenFramebuffers(1, &tmpFBO);
\r
315 GLint prevViewportX = 0;
\r
316 GLint prevViewportY = 0;
\r
317 GLsizei prevViewportWidth = 0;
\r
318 GLsizei prevViewportHeight = 0;
\r
319 Driver->getCacheHandler()->getViewport(prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight);
\r
320 Driver->getCacheHandler()->setViewport(0, 0, Size.Width, Size.Height);
\r
322 GLuint prevFBO = 0;
\r
323 Driver->getCacheHandler()->getFBO(prevFBO);
\r
324 Driver->getCacheHandler()->setFBO(tmpFBO);
\r
326 Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tmpTexture->getOpenGLTextureName(), 0);
\r
328 glClear(GL_COLOR_BUFFER_BIT);
\r
330 Driver->draw2DImage(this, layer, true);
\r
332 IImage* tmpImage = Driver->createImage(ECF_A8R8G8B8, Size);
\r
333 glReadPixels(0, 0, Size.Width, Size.Height, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->getData());
\r
335 Driver->getCacheHandler()->setFBO(prevFBO);
\r
336 Driver->getCacheHandler()->setViewport(prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight);
\r
338 Driver->irrGlDeleteFramebuffers(1, &tmpFBO);
\r
341 void* src = tmpImage->getData();
\r
342 void* dest = LockImage->getData();
\r
344 switch (ColorFormat)
\r
347 CColorConverter::convert_A8R8G8B8toA1B5G5R5(src, tmpImage->getDimension().getArea(), dest);
\r
350 CColorConverter::convert_A8R8G8B8toR5G6B5(src, tmpImage->getDimension().getArea(), dest);
\r
353 CColorConverter::convert_A8R8G8B8toB8G8R8(src, tmpImage->getDimension().getArea(), dest);
\r
356 CColorConverter::convert_A8R8G8B8toA8B8G8R8(src, tmpImage->getDimension().getArea(), dest);
\r
372 Driver->testGLError(__LINE__);
\r
375 return (LockImage) ? getLockImageData(MipLevelStored) : 0;
\r
378 void unlock() override
\r
385 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
386 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
388 uploadTexture(false, LockLayer, MipLevelStored, getLockImageData(MipLevelStored));
\r
390 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
395 LockReadOnly = false;
\r
400 void regenerateMipMapLevels(void* data = 0, u32 layer = 0) override
\r
402 if (!HasMipMaps || LegacyAutoGenerateMipMaps || (Size.Width <= 1 && Size.Height <= 1))
\r
405 const COpenGLCoreTexture* prevTexture = Driver->getCacheHandler()->getTextureCache().get(0);
\r
406 Driver->getCacheHandler()->getTextureCache().set(0, this);
\r
410 u32 width = Size.Width;
\r
411 u32 height = Size.Height;
\r
412 u8* tmpData = static_cast<u8*>(data);
\r
424 dataSize = IImage::getDataSizeFromFormat(ColorFormat, width, height);
\r
427 uploadTexture(true, layer, level, tmpData);
\r
429 tmpData += dataSize;
\r
431 while (width != 1 || height != 1);
\r
435 #ifdef IRR_OPENGL_HAS_glGenerateMipmap
\r
436 #if !defined(IRR_COMPILE_GLES2_COMMON)
\r
437 glEnable(GL_TEXTURE_2D); // Hack some ATI cards need this glEnable according to https://www.khronos.org/opengl/wiki/Common_Mistakes
\r
439 Driver->irrGlGenerateMipmap(TextureType);
\r
443 Driver->getCacheHandler()->getTextureCache().set(0, prevTexture);
\r
446 GLenum getOpenGLTextureType() const
\r
448 return TextureType;
\r
451 GLuint getOpenGLTextureName() const
\r
453 return TextureName;
\r
456 SStatesCache& getStatesCache() const
\r
458 return StatesCache;
\r
463 void * getLockImageData(irr::u32 miplevel) const
\r
465 if ( KeepImage && MipLevelStored > 0
\r
466 && LockImage->getMipMapsData(MipLevelStored) )
\r
468 return LockImage->getMipMapsData(MipLevelStored);
\r
470 return LockImage->getData();
\r
473 ECOLOR_FORMAT getBestColorFormat(ECOLOR_FORMAT format)
\r
475 // We only try for to adapt "simple" formats
\r
476 ECOLOR_FORMAT destFormat = (format <= ECF_A8R8G8B8) ? ECF_A8R8G8B8 : format;
\r
481 if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
\r
482 destFormat = ECF_A1R5G5B5;
\r
485 if (!Driver->getTextureCreationFlag(ETCF_ALWAYS_32_BIT))
\r
486 destFormat = ECF_R5G6B5;
\r
489 if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) ||
\r
490 Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
491 destFormat = ECF_A1R5G5B5;
\r
494 // Note: Using ECF_A8R8G8B8 even when ETCF_ALWAYS_32_BIT is not set as 24 bit textures fail with too many cards
\r
495 if (Driver->getTextureCreationFlag(ETCF_ALWAYS_16_BIT) || Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
\r
496 destFormat = ECF_A1R5G5B5;
\r
501 if (Driver->getTextureCreationFlag(ETCF_NO_ALPHA_CHANNEL))
\r
503 switch (destFormat)
\r
506 destFormat = ECF_R5G6B5;
\r
509 destFormat = ECF_R8G8B8;
\r
519 void getImageValues(const IImage* image)
\r
521 OriginalColorFormat = image->getColorFormat();
\r
522 ColorFormat = getBestColorFormat(OriginalColorFormat);
\r
524 if ( !Driver->getColorFormatParameters(ColorFormat, InternalFormat, PixelFormat, PixelType, &Converter) )
\r
526 os::Printer::log("getImageValues: Color format is not supported", ColorFormatNames[ColorFormat < ECF_UNKNOWN?ColorFormat:ECF_UNKNOWN], ELL_ERROR);
\r
527 // not quitting as it will use some alternative internal format
\r
530 if (IImage::isCompressedFormat(image->getColorFormat()))
\r
535 OriginalSize = image->getDimension();
\r
536 Size = OriginalSize;
\r
538 if (Size.Width == 0 || Size.Height == 0)
\r
540 os::Printer::log("Invalid size of image for texture.", ELL_ERROR);
\r
544 const f32 ratio = (f32)Size.Width / (f32)Size.Height;
\r
546 if ((Size.Width > Driver->MaxTextureSize) && (ratio >= 1.f))
\r
548 Size.Width = Driver->MaxTextureSize;
\r
549 Size.Height = (u32)(Driver->MaxTextureSize / ratio);
\r
551 else if (Size.Height > Driver->MaxTextureSize)
\r
553 Size.Height = Driver->MaxTextureSize;
\r
554 Size.Width = (u32)(Driver->MaxTextureSize * ratio);
\r
557 bool needSquare = (!Driver->queryFeature(EVDF_TEXTURE_NSQUARE) || Type == ETT_CUBEMAP);
\r
559 Size = Size.getOptimalSize(!Driver->queryFeature(EVDF_TEXTURE_NPOT), needSquare, true, Driver->MaxTextureSize);
\r
561 Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8;
\r
564 void uploadTexture(bool initTexture, u32 layer, u32 level, void* data)
\r
569 u32 width = Size.Width >> level;
\r
570 u32 height = Size.Height >> level;
\r
572 GLenum tmpTextureType = TextureType;
\r
574 if (tmpTextureType == GL_TEXTURE_CUBE_MAP)
\r
576 _IRR_DEBUG_BREAK_IF(layer > 5)
\r
578 tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
\r
581 if (!IImage::isCompressedFormat(ColorFormat))
\r
583 CImage* tmpImage = 0;
\r
584 void* tmpData = data;
\r
588 const core::dimension2d<u32> tmpImageSize(width, height);
\r
590 tmpImage = new CImage(ColorFormat, tmpImageSize);
\r
591 tmpData = tmpImage->getData();
\r
593 Converter(data, tmpImageSize.getArea(), tmpData);
\r
596 switch (TextureType)
\r
598 case GL_TEXTURE_2D:
\r
599 case GL_TEXTURE_CUBE_MAP:
\r
601 glTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, PixelFormat, PixelType, tmpData);
\r
603 glTexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, PixelType, tmpData);
\r
604 Driver->testGLError(__LINE__);
\r
614 u32 dataSize = IImage::getDataSizeFromFormat(ColorFormat, width, height);
\r
616 switch (TextureType)
\r
618 case GL_TEXTURE_2D:
\r
619 case GL_TEXTURE_CUBE_MAP:
\r
621 Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data);
\r
623 Driver->irrGlCompressedTexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, dataSize, data);
\r
624 Driver->testGLError(__LINE__);
\r
632 GLenum TextureTypeIrrToGL(E_TEXTURE_TYPE type) const
\r
637 return GL_TEXTURE_2D;
\r
639 return GL_TEXTURE_CUBE_MAP;
\r
642 os::Printer::log("COpenGLCoreTexture::TextureTypeIrrToGL unknown texture type", ELL_WARNING);
\r
643 return GL_TEXTURE_2D;
\r
646 TOpenGLDriver* Driver;
\r
648 GLenum TextureType;
\r
649 GLuint TextureName;
\r
650 GLint InternalFormat;
\r
651 GLenum PixelFormat;
\r
653 void (*Converter)(const void*, s32, void*);
\r
660 core::array<IImage*> Images;
\r
663 bool LegacyAutoGenerateMipMaps;
\r
665 mutable SStatesCache StatesCache;
\r