//################################################################################################## // // Custom Visualization Core library // Copyright (C) 2011-2012 Ceetron AS // // This library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at <> // for more details. // //################################################################################################## #include "cvfBase.h" #include "cvfTexture.h" #include "cvfTextureImage.h" #include "cvfOpenGL.h" #include "cvfSampler.h" #include "cvfOpenGLResourceManager.h" #include "cvfOglRc.h" #include "cvfOpenGLCapabilities.h" namespace cvf { //================================================================================================== /// /// \class cvf::Texture /// \ingroup Render /// /// Encapsulates an OpenGL texture object. Currently only supports 2D textures. /// /// Stores the OpenGL id (name) of the texture and sets it up with glTexImage2D(). /// Can be created either with an Image for traditional texture mapping, or based on an internal format /// and texture type for using textures as render targets in \link FrameBufferObject Frame buffer objects \endlink /// /// \warning Requires OpenGL2 support /// //================================================================================================== //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Texture::Texture(TextureType textureType, InternalFormat internalFormat) : m_textureType(textureType), m_internalFormat(internalFormat), m_enableMipmapGeneration(false), m_hasMipmaps(false), m_width(0), m_height(0), m_versionTick(1) { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Texture::Texture(TextureImage* image) : m_textureType(TEXTURE_2D), m_internalFormat(RGBA), m_enableMipmapGeneration(false), m_hasMipmaps(false), m_width(0), m_height(0), m_versionTick(0) { setFromImage(image); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Texture::~Texture() { // Just release our reference CVF_ASSERT(OglRc::isSafeToRelease(m_oglRcTexture.p())); m_oglRcTexture = NULL; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::setFromImage(TextureImage* image) { CVF_ASSERT(textureType() == TEXTURE_2D || textureType() == TEXTURE_RECTANGLE); CVF_ASSERT(m_cubeMapImages.size() == 0); CVF_ASSERT(image); forgetCurrentOglTexture(); m_image = image; m_width = image->width(); m_height = image->height(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::setCubeMapImage(CubeMapFace face, TextureImage* cubeMapImage) { CVF_ASSERT(textureType() == TEXTURE_CUBE_MAP); CVF_ASSERT(m_image.isNull()); CVF_ASSERT(cubeMapImage); forgetCurrentOglTexture(); m_image = NULL; if (m_cubeMapImages.size() == 0) { m_cubeMapImages.resize(6); m_width = cubeMapImage->width(); m_height = cubeMapImage->height(); } else { CVF_ASSERT(m_width == cubeMapImage->width() && m_height == cubeMapImage->height()); } m_cubeMapImages[face] = cubeMapImage; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TextureImage* Texture::image() { return m_image.p(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TextureImage* Texture::cubeMapImage(CubeMapFace face) { CVF_ASSERT(m_cubeMapImages.size() == 6); return m_cubeMapImages[face].p(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::setSize(uint width, uint height) { CVF_ASSERT(m_image.isNull()); CVF_ASSERT(m_cubeMapImages.size() == 0); forgetCurrentOglTexture(); clearImages(); m_width = width; m_height = height; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- uint Texture::width() const { return m_width; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- uint Texture::height() const { return m_height; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Texture::TextureType Texture::textureType() const { return m_textureType; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- Texture::InternalFormat Texture::internalFormat() const { return m_internalFormat; } //-------------------------------------------------------------------------------------------------- /// Do setup of the texture /// /// \warning Requires at least OpenGL2 capability. Will assert if this condition is not met. /// \warning Default unpack alignment (GL_UNPACK_ALIGNMENT) is 4 and 4 byte values is preferred, thus /// we should always use RGBA when having byte textures as GPUs are optimized to 32 bit values //-------------------------------------------------------------------------------------------------- bool Texture::setupTexture(OpenGLContext* oglContext) { CVF_CALLSITE_OPENGL(oglContext); CVF_ASSERT(OglRc::safeOglId(m_oglRcTexture.p()) == 0); const OpenGLCapabilities* oglCaps = oglContext->capabilities(); CVF_ASSERT(oglCaps->supportsOpenGL2()); CVF_CLEAR_OGL_ERROR(oglContext); // Is manual generation of mipmaps through glGenerateMipmap() supported? bool supportsGenerateMipmapFunc = oglCaps->hasCapability(OpenGLCapabilities::GENERATE_MIPMAP_FUNC); m_hasMipmaps = false; m_oglRcTexture = oglContext->resourceManager()->createOglRcTexture(oglContext); bind(oglContext); CVF_ASSERT(m_image.isNull() || (m_image->width() == m_width && m_image->height() == m_height)); switch (m_textureType) { case TEXTURE_2D: { if (m_internalFormat == DEPTH_COMPONENT16 || m_internalFormat == DEPTH_COMPONENT24 || m_internalFormat == DEPTH_COMPONENT32) { CVF_ASSERT(m_image.isNull()); CVF_ASSERT(!m_enableMipmapGeneration); glTexImage2D(GL_TEXTURE_2D, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); } else if (m_internalFormat == DEPTH24_STENCIL8) { #ifndef CVF_OPENGL_ES CVF_ASSERT(m_image.isNull()); CVF_ASSERT(!m_enableMipmapGeneration); glTexImage2D(GL_TEXTURE_2D, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0); #else CVF_FAIL_MSG("Not supported on IOS"); #endif } else { #ifndef CVF_OPENGL_ES if (!supportsGenerateMipmapFunc) { // Explicit mipmap generation not supported so must configure before specifying texture image if (m_enableMipmapGeneration && m_image.notNull()) { glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); m_hasMipmaps = true; } else { glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE); } } #endif glTexImage2D(GL_TEXTURE_2D, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_image.notNull() ? m_image->ptr() : 0); if (supportsGenerateMipmapFunc && m_enableMipmapGeneration && m_image.notNull()) { glGenerateMipmap(GL_TEXTURE_2D); m_hasMipmaps = true; } } break; } case TEXTURE_RECTANGLE: { #ifndef CVF_OPENGL_ES CVF_ASSERT(!m_enableMipmapGeneration); if (m_internalFormat == DEPTH_COMPONENT16 || m_internalFormat == DEPTH_COMPONENT24 || m_internalFormat == DEPTH_COMPONENT32) { CVF_ASSERT(m_image.isNull()); glTexImage2D(GL_TEXTURE_RECTANGLE, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); } else { glTexImage2D(GL_TEXTURE_RECTANGLE, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_image.notNull() ? m_image->ptr() : 0); } #else CVF_FAIL_MSG("Not supported on iOS"); #endif break; } case TEXTURE_CUBE_MAP: { if (m_cubeMapImages.size() == 0) { uint i; for (i = 0; i < 6; i++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); } } else { #ifndef CVF_OPENGL_ES if (!supportsGenerateMipmapFunc) { if (m_enableMipmapGeneration) { glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE); m_hasMipmaps = true; } else { glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_FALSE); } } #endif CVF_ASSERT(m_cubeMapImages.size() == 6); uint i; for (i = 0; i < 6; i++) { ref img = m_cubeMapImages[i]; CVF_ASSERT(img.notNull()); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormatOpenGL(), static_cast(m_width), static_cast(m_height), 0, GL_RGBA, GL_UNSIGNED_BYTE, img->ptr()); } if (supportsGenerateMipmapFunc && m_enableMipmapGeneration) { glGenerateMipmap(GL_TEXTURE_CUBE_MAP); m_hasMipmaps = true; } } break; } } if (CVF_TEST_AND_REPORT_OPENGL_ERROR(oglContext, "Setup texture")) { deleteTexture(oglContext); return false; } m_versionTick++; return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::bind(OpenGLContext* /*oglContext*/) const { CVF_ASSERT(OglRc::safeOglId(m_oglRcTexture.p()) != 0); glBindTexture(textureTypeOpenGL(), m_oglRcTexture->oglId()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool Texture::isBound(OpenGLContext* /*oglContext*/) const { GLint currentTextureBinding = 0; switch (m_textureType) { case TEXTURE_2D: glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤tTextureBinding); break; case TEXTURE_RECTANGLE: #ifndef CVF_OPENGL_ES glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, ¤tTextureBinding); break; #else CVF_FAIL_MSG("Not supported on iOS"); break; #endif case TEXTURE_CUBE_MAP: glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, ¤tTextureBinding); break; } if (currentTextureBinding != 0) { if (static_cast(currentTextureBinding) == OglRc::safeOglId(m_oglRcTexture.p())) { return true; } } return false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::setupTextureParamsFromSampler(OpenGLContext* oglContext, const Sampler& sampler) const { CVF_ASSERT(oglContext); CVF_ASSERT(isBound(oglContext)); cvfGLint oglWrapS = GL_CLAMP_TO_EDGE; cvfGLint oglWrapT = GL_CLAMP_TO_EDGE; cvfGLint oglMinFilter = GL_NEAREST; cvfGLint oglMagFilter = GL_NEAREST; switch(sampler.wrapModeS()) { case Sampler::CLAMP_TO_EDGE: oglWrapS = GL_CLAMP_TO_EDGE; break; case Sampler::REPEAT: oglWrapS = GL_REPEAT; break; case Sampler::CLAMP_TO_BORDER: #ifdef CVF_OPENGL_ES CVF_FAIL_MSG("CLAMP_TO_BORDER not supported"); break; #else oglWrapS = GL_CLAMP_TO_BORDER; break; #endif case Sampler::MIRRORED_REPEAT: oglWrapS = GL_MIRRORED_REPEAT; break; } switch(sampler.wrapModeT()) { case Sampler::CLAMP_TO_EDGE: oglWrapT = GL_CLAMP_TO_EDGE; break; case Sampler::REPEAT: oglWrapT = GL_REPEAT; break; #ifdef CVF_OPENGL_ES case Sampler::CLAMP_TO_BORDER: CVF_FAIL_MSG("CLAMP_TO_BORDER not supported"); break; #else case Sampler::CLAMP_TO_BORDER: oglWrapT = GL_CLAMP_TO_BORDER; break; #endif case Sampler::MIRRORED_REPEAT: oglWrapT = GL_MIRRORED_REPEAT; break; } switch(sampler.minFilter()) { case Sampler::NEAREST: oglMinFilter = GL_NEAREST; break; case Sampler::LINEAR: oglMinFilter = GL_LINEAR; break; case Sampler::NEAREST_MIPMAP_NEAREST: oglMinFilter = GL_NEAREST_MIPMAP_NEAREST; break; case Sampler::NEAREST_MIPMAP_LINEAR: oglMinFilter = GL_NEAREST_MIPMAP_LINEAR; break; case Sampler::LINEAR_MIPMAP_NEAREST: oglMinFilter = GL_LINEAR_MIPMAP_NEAREST; break; case Sampler::LINEAR_MIPMAP_LINEAR: oglMinFilter = GL_LINEAR_MIPMAP_LINEAR; break; } switch(sampler.magFilter()) { case Sampler::NEAREST: oglMagFilter = GL_NEAREST; break; case Sampler::LINEAR: oglMagFilter = GL_LINEAR; break; case Sampler::NEAREST_MIPMAP_NEAREST: case Sampler::NEAREST_MIPMAP_LINEAR: case Sampler::LINEAR_MIPMAP_NEAREST: case Sampler::LINEAR_MIPMAP_LINEAR: CVF_FAIL_MSG("Illegal mag format"); break; } cvfGLenum textureType = textureTypeOpenGL(); glTexParameteri(textureType, GL_TEXTURE_WRAP_S, oglWrapS); glTexParameteri(textureType, GL_TEXTURE_WRAP_T, oglWrapT); glTexParameteri(textureType, GL_TEXTURE_MIN_FILTER, oglMinFilter); glTexParameteri(textureType, GL_TEXTURE_MAG_FILTER, oglMagFilter); // HACK #ifndef CVF_OPENGL_ES if (m_internalFormat == DEPTH_COMPONENT16 || m_internalFormat == DEPTH_COMPONENT24 || m_internalFormat == DEPTH_COMPONENT32) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); } #endif // Set these to openGL // TODO TEXTURE_WRAP_R, TEXTURE_BORDER_COLOR, TEXTURE_MIN_LOD, TEXTURE_MAX_LOD, TEXTURE_LOD_BIAS, // TEXTURE_COMPARE_MODE, TEXTURE_COMPARE_FUNC CVF_CHECK_OGL(oglContext); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::deleteTexture(OpenGLContext* oglContext) { CVF_ASSERT(oglContext); if (m_oglRcTexture.notNull()) { m_oglRcTexture->deleteResource(oglContext); CVF_ASSERT(OglRc::isSafeToRelease(m_oglRcTexture.p())); m_oglRcTexture = NULL; } m_hasMipmaps = false; m_versionTick++; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::forgetCurrentOglTexture() { // Just release our reference CVF_ASSERT(OglRc::isSafeToRelease(m_oglRcTexture.p())); m_oglRcTexture = NULL; m_hasMipmaps = false; m_versionTick++; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- OglId Texture::textureOglId() const { return OglRc::safeOglId(m_oglRcTexture.p()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- uint Texture::versionTick() const { return m_versionTick; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool Texture::supportedOpenGL(OpenGLContext* oglContext) { // We'll require baseline OpenGL2 support even if the actual requirement may be lower return oglContext->capabilities()->supportsOpenGL2(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::clearImages() { m_image = NULL; m_cubeMapImages.clear(); m_versionTick++; } //-------------------------------------------------------------------------------------------------- /// Enable or disable mipmap generation for this texture /// /// If enabled, mipmaps will be generated during texture setup. /// /// \warning Mipmap generation requires at least OpenGL version 1.4 //-------------------------------------------------------------------------------------------------- void Texture::enableMipmapGeneration(bool enableMipmapGeneration) { CVF_ASSERT(m_textureType == TEXTURE_2D || m_textureType == TEXTURE_CUBE_MAP); if (m_enableMipmapGeneration != enableMipmapGeneration) { m_enableMipmapGeneration = enableMipmapGeneration; // To force re-setup of texture forgetCurrentOglTexture(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool Texture::isMipmapGenerationEnabled() const { return m_enableMipmapGeneration; } //-------------------------------------------------------------------------------------------------- /// Explicitly generate mipmaps /// /// \warning Requires the GENERATE_MIPMAP_FUNC capability. Will assert if this requirement is not met. //-------------------------------------------------------------------------------------------------- void Texture::generateMipmap(OpenGLContext* oglContext) { CVF_CALLSITE_OPENGL(oglContext); CVF_ASSERT(oglContext->capabilities()->hasCapability(OpenGLCapabilities::GENERATE_MIPMAP_FUNC)); bind(oglContext); if (m_textureType == TEXTURE_2D) { glGenerateMipmap(GL_TEXTURE_2D); m_hasMipmaps = true; } else if (m_textureType == TEXTURE_CUBE_MAP) { glGenerateMipmap(GL_TEXTURE_CUBE_MAP); m_hasMipmaps = true; } else { CVF_FAIL_MSG("Mipmap generation not supported for this texture type"); } CVF_CHECK_OGL(oglContext); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool Texture::hasMipmap() const { return m_hasMipmaps; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void Texture::removeMipmap() { // No way to actually delete mipmaps, just flag them as not present m_hasMipmaps = false; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvfGLint Texture::internalFormatOpenGL() const { switch(m_internalFormat) { case RGBA: return GL_RGBA; case DEPTH_COMPONENT16: return GL_DEPTH_COMPONENT16; #ifndef CVF_OPENGL_ES case RGBA32F: return GL_RGBA32F; case R32F: return GL_R32F; case DEPTH_COMPONENT24: return GL_DEPTH_COMPONENT24; case DEPTH_COMPONENT32: return GL_DEPTH_COMPONENT32; case DEPTH24_STENCIL8: return GL_DEPTH24_STENCIL8; #endif } CVF_FAIL_MSG("Illegal texture internal format"); return GL_RGBA; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvfGLenum Texture::textureTypeOpenGL() const { switch(m_textureType) { case TEXTURE_2D: return GL_TEXTURE_2D; #ifndef CVF_OPENGL_ES case TEXTURE_RECTANGLE: return GL_TEXTURE_RECTANGLE; #endif case TEXTURE_CUBE_MAP: return GL_TEXTURE_CUBE_MAP; } CVF_FAIL_MSG("Illegal texture type"); return GL_TEXTURE_2D; } } // namespace cvf