ResInsight/Fwk/VizFwk/LibRender/cvfTexture.cpp

700 lines
24 KiB
C++

//##################################################################################################
//
// Custom Visualization Core library
// Copyright (C) 2011-2013 Ceetron AS
//
// This library may be used under the terms of either the GNU General Public License or
// the GNU Lesser General Public License as follows:
//
// GNU General Public License Usage
// 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 <<http://www.gnu.org/licenses/gpl.html>>
// for more details.
//
// GNU Lesser General Public License Usage
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 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 Lesser General Public License at <<http://www.gnu.org/licenses/lgpl-2.1.html>>
// 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<GLsizei>(m_width), static_cast<GLsizei>(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<GLsizei>(m_width), static_cast<GLsizei>(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<GLsizei>(m_width), static_cast<GLsizei>(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<GLsizei>(m_width), static_cast<GLsizei>(m_height), 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
}
else
{
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, internalFormatOpenGL(), static_cast<GLsizei>(m_width), static_cast<GLsizei>(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<GLsizei>(m_width), static_cast<GLsizei>(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<TextureImage> img = m_cubeMapImages[i];
CVF_ASSERT(img.notNull());
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormatOpenGL(), static_cast<GLsizei>(m_width), static_cast<GLsizei>(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, &currentTextureBinding); break;
case TEXTURE_RECTANGLE:
#ifndef CVF_OPENGL_ES
glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE, &currentTextureBinding); break;
#else
CVF_FAIL_MSG("Not supported on iOS"); break;
#endif
case TEXTURE_CUBE_MAP: glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &currentTextureBinding); break;
}
if (currentTextureBinding != 0)
{
if (static_cast<OglId>(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