diff --git a/src/AbstractTexture.cpp b/src/AbstractTexture.cpp index 75530006a..4c034ef00 100644 --- a/src/AbstractTexture.cpp +++ b/src/AbstractTexture.cpp @@ -33,6 +33,14 @@ AbstractTexture::ParameterfvImplementation AbstractTexture::parameterfvImplement AbstractTexture::MipmapImplementation AbstractTexture::mipmapImplementation = &AbstractTexture::mipmapImplementationDefault; #ifndef MAGNUM_TARGET_GLES +AbstractTexture::Storage1DImplementation AbstractTexture::storage1DImplementation = + &AbstractTexture::storageImplementationDefault; +#endif +AbstractTexture::Storage2DImplementation AbstractTexture::storage2DImplementation = + &AbstractTexture::storageImplementationDefault; +AbstractTexture::Storage3DImplementation AbstractTexture::storage3DImplementation = + &AbstractTexture::storageImplementationDefault; +#ifndef MAGNUM_TARGET_GLES AbstractTexture::Image1DImplementation AbstractTexture::image1DImplementation = &AbstractTexture::imageImplementationDefault; #endif @@ -209,6 +217,9 @@ void AbstractTexture::initializeContextBasedFunctionality(Context* context) { parameterfImplementation = &AbstractTexture::parameterImplementationDSA; parameterfvImplementation = &AbstractTexture::parameterImplementationDSA; mipmapImplementation = &AbstractTexture::mipmapImplementationDSA; + storage1DImplementation = &AbstractTexture::storageImplementationDSA; + storage2DImplementation = &AbstractTexture::storageImplementationDSA; + storage3DImplementation = &AbstractTexture::storageImplementationDSA; image1DImplementation = &AbstractTexture::imageImplementationDSA; image2DImplementation = &AbstractTexture::imageImplementationDSA; image3DImplementation = &AbstractTexture::imageImplementationDSA; @@ -258,6 +269,64 @@ void AbstractTexture::parameterImplementationDSA(GLenum parameter, const GLfloat glTextureParameterfvEXT(_id, _target, parameter, values); } +void AbstractTexture::storageImplementationDefault(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Math::Vector< 1, GLsizei >& size) { + bindInternal(); + /** @todo Re-enable when extension wrangler is available for ES2 */ + #ifndef MAGNUM_TARGET_GLES2 + glTexStorage1D(target, levels, GLenum(internalFormat), size[0]); + #else + //glTexStorage2DEXT(target, levels, GLenum(internalFormat), size.x(), size.y()); + static_cast(target); + static_cast(levels); + static_cast(internalFormat); + static_cast(size); + #endif +} + +void AbstractTexture::storageImplementationDSA(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Math::Vector< 1, GLsizei >& size) { + glTextureStorage1DEXT(_id, target, levels, GLenum(internalFormat), size[0]); +} +#endif + +void AbstractTexture::storageImplementationDefault(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Vector2i& size) { + bindInternal(); + /** @todo Re-enable when extension wrangler is available for ES2 */ + #ifndef MAGNUM_TARGET_GLES2 + glTexStorage2D(target, levels, GLenum(internalFormat), size.x(), size.y()); + #else + //glTexStorage2DEXT(target, levels, GLenum(internalFormat), size.x(), size.y()); + static_cast(target); + static_cast(levels); + static_cast(internalFormat); + static_cast(size); + #endif +} + +#ifndef MAGNUM_TARGET_GLES +void AbstractTexture::storageImplementationDSA(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Vector2i& size) { + glTextureStorage2DEXT(_id, target, levels, GLenum(internalFormat), size.x(), size.y()); +} +#endif + +void AbstractTexture::storageImplementationDefault(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Vector3i& size) { + bindInternal(); + /** @todo Re-enable when extension wrangler is available for ES2 */ + #ifndef MAGNUM_TARGET_GLES2 + glTexStorage3D(target, levels, GLenum(internalFormat), size.x(), size.y(), size.z()); + #else + //glTexStorage3DEXT(target, levels, GLenum(internalFormat), size.x(), size.y(), size.z()); + static_cast(target); + static_cast(levels); + static_cast(internalFormat); + static_cast(size); + #endif +} + +#ifndef MAGNUM_TARGET_GLES +void AbstractTexture::storageImplementationDSA(GLenum target, GLsizei levels, AbstractTexture::InternalFormat internalFormat, const Vector3i& size) { + glTextureStorage3DEXT(_id, target, levels, GLenum(internalFormat), size.x(), size.y(), size.z()); +} + void AbstractTexture::imageImplementationDefault(GLenum target, GLint level, InternalFormat internalFormat, const Math::Vector<1, GLsizei>& size, AbstractImage::Format format, AbstractImage::Type type, const GLvoid* data) { bindInternal(); glTexImage1D(target, level, static_cast(internalFormat), size[0], 0, static_cast(format), static_cast(type), data); diff --git a/src/AbstractTexture.h b/src/AbstractTexture.h index afedd74b9..b2c875343 100644 --- a/src/AbstractTexture.h +++ b/src/AbstractTexture.h @@ -42,16 +42,17 @@ repeated @fn_gl{Get} calls. If extension @extension{EXT,direct_state_access} is available, bind() uses DSA function to avoid unnecessary calls to @fn_gl{ActiveTexture}. Also all texture -configuration functions use DSA functions to avoid unnecessary calls to -@fn_gl{ActiveTexture} and @fn_gl{BindTexture}. See respective function -documentation for more information. +configuration and data updating functions use DSA functions to avoid +unnecessary calls to @fn_gl{ActiveTexture} and @fn_gl{BindTexture}. See +respective function documentation for more information. To achieve least state changes, fully configure each texture in one run -- method chaining comes in handy -- and try to have often used textures in -dedicated layers, not occupied by other textures. - -Always fully configure the texture before setting the texture data, so OpenGL -can optimize the data to match your settings. +dedicated layers, not occupied by other textures. First configure the texture +and *then* set the data, so OpenGL can optimize them to match the settings. To +avoid redundant consistency checks and memory reallocations when updating +texture data, set texture storage at once using @ref Texture::setStorage() "setStorage()" +and then set data using @ref Texture::setSubImage() "setSubImage()". You can use functions invalidateImage() and @ref Texture::invalidateSubImage() "invalidateSubImage()" if you don't need texture data anymore to avoid unnecessary memory operations @@ -1187,6 +1188,27 @@ class MAGNUM_EXPORT AbstractTexture { #endif static MAGNUM_LOCAL MipmapImplementation mipmapImplementation; + #ifndef MAGNUM_TARGET_GLES + typedef void(AbstractTexture::*Storage1DImplementation)(GLenum, GLsizei, InternalFormat, const Math::Vector<1, GLsizei>&); + void MAGNUM_LOCAL storageImplementationDefault(GLenum target, GLsizei levels, InternalFormat internalFormat, const Math::Vector<1, GLsizei>& size); + void MAGNUM_LOCAL storageImplementationDSA(GLenum target, GLsizei levels, InternalFormat internalFormat, const Math::Vector<1, GLsizei>& size); + static Storage1DImplementation storage1DImplementation; + #endif + + typedef void(AbstractTexture::*Storage2DImplementation)(GLenum, GLsizei, InternalFormat, const Vector2i&); + void MAGNUM_LOCAL storageImplementationDefault(GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector2i& size); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL storageImplementationDSA(GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector2i& size); + #endif + static Storage2DImplementation storage2DImplementation; + + typedef void(AbstractTexture::*Storage3DImplementation)(GLenum, GLsizei, InternalFormat, const Vector3i&); + void MAGNUM_LOCAL storageImplementationDefault(GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector3i& size); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL storageImplementationDSA(GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector3i& size); + #endif + static Storage3DImplementation storage3DImplementation; + #ifndef MAGNUM_TARGET_GLES typedef void(AbstractTexture::*Image1DImplementation)(GLenum, GLint, InternalFormat, const Math::Vector<1, GLsizei>&, AbstractImage::Format, AbstractImage::Type, const GLvoid*); void MAGNUM_LOCAL imageImplementationDefault(GLenum target, GLint level, InternalFormat internalFormat, const Math::Vector<1, GLsizei>& size, AbstractImage::Format format, AbstractImage::Type type, const GLvoid* data); @@ -1262,6 +1284,10 @@ template<> struct AbstractTexture::DataHelper<1> { (texture->*parameteriImplementation)(GL_TEXTURE_WRAP_S, static_cast(wrapping.x())); } + inline static void setStorage(AbstractTexture* texture, GLenum target, GLsizei levels, InternalFormat internalFormat, const Math::Vector<1, GLsizei>& size) { + (texture->*storage1DImplementation)(target, levels, internalFormat, size); + } + template inline static typename std::enable_if::type set(AbstractTexture* texture, GLenum target, GLint level, InternalFormat internalFormat, Image* image) { (texture->*image1DImplementation)(target, level, internalFormat, image->size(), image->format(), image->type(), image->data()); } @@ -1289,6 +1315,10 @@ template<> struct MAGNUM_EXPORT AbstractTexture::DataHelper<2> { static void setWrapping(AbstractTexture* texture, const Array2D& wrapping); + inline static void setStorage(AbstractTexture* texture, GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector2i& size) { + (texture->*storage2DImplementation)(target, levels, internalFormat, size); + } + template inline static typename std::enable_if::type set(AbstractTexture* texture, GLenum target, GLint level, InternalFormat internalFormat, Image* image) { (texture->*image2DImplementation)(target, level, internalFormat, image->size(), image->format(), image->type(), image->data()); } @@ -1319,6 +1349,10 @@ template<> struct MAGNUM_EXPORT AbstractTexture::DataHelper<3> { static void setWrapping(AbstractTexture* texture, const Array3D& wrapping); + inline static void setStorage(AbstractTexture* texture, GLenum target, GLsizei levels, InternalFormat internalFormat, const Vector3i& size) { + (texture->*storage3DImplementation)(target, levels, internalFormat, size); + } + template inline static typename std::enable_if::type set(AbstractTexture* texture, GLenum target, GLint level, InternalFormat internalFormat, Image* image) { (texture->*image3DImplementation)(target, level, internalFormat, image->size(), image->format(), image->type(), image->data()); } diff --git a/src/CubeMapTexture.h b/src/CubeMapTexture.h index 6e8f9da46..61dcd3b77 100644 --- a/src/CubeMapTexture.h +++ b/src/CubeMapTexture.h @@ -51,8 +51,9 @@ Image2D positiveX({256, 256}, Image2D::Components::RGBA, Image2D::ComponentType: CubeMapTexture texture; texture.setMagnificationFilter(Texture2D::Filter::Linear) // ... - ->setImage(CubeMapTexture::Coordinate::PositiveX, 0, Texture2D::Format::RGBA8, &positiveX) - ->setImage(CubeMapTexture::Coordinate::NegativeX, 0, Texture2D::Format::RGBA8, &negativeX) + ->setStorage(Math::log2(256)+1, Texture2D::Format::RGBA8, {256, 256}) + ->setSubImage(CubeMapTexture::Coordinate::PositiveX, 0, {}, &positiveX) + ->setSubImage(CubeMapTexture::Coordinate::NegativeX, 0, {}, &negativeX) // ... @endcode @@ -109,6 +110,16 @@ class CubeMapTexture: public AbstractTexture { return this; } + /** + * @brief Set storage + * + * See Texture::setStorage() for more information. + */ + inline CubeMapTexture* setStorage(GLsizei levels, InternalFormat internalFormat, const Vector2i& size) { + DataHelper<2>::setStorage(this, _target, levels, internalFormat, size); + return this; + } + /** * @brief Set image data * @param coordinate Coordinate diff --git a/src/CubeMapTextureArray.h b/src/CubeMapTextureArray.h index 00f3e574f..28c2feea5 100644 --- a/src/CubeMapTextureArray.h +++ b/src/CubeMapTextureArray.h @@ -34,24 +34,24 @@ See CubeMapTexture documentation for introduction. @section CubeMapTextureArray-usage Usage Common usage is to specify each layer and face separately using setSubImage(). -You have to allocate the memory for all layers and faces first, possibly by -passing properly sized empty Image to setImage(). Example: array with 16 -layers of cube map faces, each face consisting of six 64x64 images: +You have to allocate the memory for all layers and faces first either by +calling setStorage() or by passing properly sized empty Image to setImage(). +Example: array with 16 layers of cube map faces, each face consisting of six +64x64 images: @code Image3D dummy({64, 64, 16*6}, Image3D::Components::RGBA, Image3D::ComponentType::UnsignedByte, nullptr); CubeMapTextureArray texture; texture.setMagnificationFilter(CubeMapTextureArray::Filter::Linear) // ... - ->setImage(0, CubeMapTextureArray::Format::RGBA8, &dummy); + ->setStorage(Math::log2(64)+1, CubeMapTextureArray::Format::RGBA8, {64, 64, 16}); for(std::size_t i = 0; i != 16; ++i) { void* dataPositiveX = ...; Image2D imagePositiveX({64, 64}, Image3D::Components::RGBA, Image3D::ComponentType::UnsignedByte, imagePositiveX); // ... - - texture->setSubImage(i, CubeMapTextureArray::Coordinate::PositiveX, 0, imagePositiveX); - texture->setSubImage(i, CubeMapTextureArray::Coordinate::NegativeX, 0, imageNegativeX); + texture->setSubImage(i, CubeMapTextureArray::Coordinate::PositiveX, 0, {}, imagePositiveX); + texture->setSubImage(i, CubeMapTextureArray::Coordinate::NegativeX, 0, {}, imageNegativeX); // ... } @@ -99,6 +99,16 @@ class CubeMapTextureArray: public AbstractTexture { return this; } + /** + * @brief Set storage + * + * See Texture::setStorage() for more information. + */ + inline CubeMapTextureArray* setStorage(GLsizei levels, InternalFormat internalFormat, const Vector3i& size) { + DataHelper<3>::setStorage(this, _target, levels, internalFormat, size); + return this; + } + /** * @brief Set image data * @param level Mip level diff --git a/src/Texture.h b/src/Texture.h index 8b3738609..0913bd083 100644 --- a/src/Texture.h +++ b/src/Texture.h @@ -44,17 +44,18 @@ texture.setMagnificationFilter(Texture2D::Filter::Linear) ->setMinificationFilter(Texture2D::Filter::Linear, Texture2D::Mipmap::Linear) ->setWrapping(Texture2D::Wrapping::ClampToEdge) ->setMaxAnisotropy(Texture2D::maxSupportedAnisotropy) - ->setImage(0, Texture2D::Format::RGBA8, &image) + ->setStorage(Math::log2(4096)+1, Texture2D::Format::RGBA8, {4096, 4096}) + ->setSubImage(0, {}, &image) ->generateMipmap(); @endcode @attention Don't forget to fully configure the texture before use. Note that default configuration (if setMinificationFilter() is not called with another value) is to use mipmaps, so be sure to either call setMinificationFilter(), - explicitly set all mip levels or call generateMipmap(). If using rectangle - texture, you must also call setWrapping(), because the initial value is - not supported on rectangle textures. See also setMagnificationFilter() and - setBorderColor(). + explicitly specify all mip levels with setStorage() and setImage() or call + generateMipmap(). If using rectangle texture, you must also call + setWrapping(), because the initial value is not supported on rectangle + textures. See also setMagnificationFilter() and setBorderColor(). The texture is bound to layer specified by shader via bind(). In shader, the texture is used via `sampler1D`, `sampler2D` or `sampler3D` depending on @@ -67,16 +68,14 @@ You can create texture arrays by passing @ref Texture::Target "Texture2D::Target or @ref Texture::Target "Texture3D::Target::Texture2DArray" to constructor. It is possible to specify each layer separately using setSubImage(), but you -have to allocate the memory for all layers first, possibly by passing properly -sized empty Image to setImage(). Example: 2D texture array with 16 layers of -64x64 images: +have to allocate the memory for all layers first either by calling setStorage() +or by passing properly sized empty Image to setImage(). Example: 2D texture +array with 16 layers of 64x64 images: @code -Image3D dummy({64, 64, 16}, Image3D::Components::RGBA, Image3D::ComponentType::UnsignedByte, nullptr); - Texture3D texture(Texture3D::Target::Texture2DArray); texture.setMagnificationFilter(Texture2D::Filter::Linear) // ... - ->setImage(0, Texture2D::Format::RGBA8, &dummy); + ->setStorage(levels, Texture2D::Format::RGBA8, {64, 64,16}); for(std::size_t i = 0; i != 16; ++i) { void* data = ...; @@ -198,6 +197,33 @@ template class Texture: public AbstractTexture { return this; } + /** + * @brief Set storage + * @param levels Mip level count + * @param internalFormat Internal format + * @param size Size of largest mip level + * @return Pointer to self (for method chaining) + * + * Specifies entire structure of a texture at once, removing the need + * for additional consistency checks and memory reallocations when + * updating the data later. After calling this function the texture + * is immutable and calling setStorage() or setImage() is not allowed. + * + * If @extension{EXT,direct_state_access} is not available, the + * texture is bound to some layer before the operation. + * @see @fn_gl{ActiveTexture}, @fn_gl{BindTexture} and + * @fn_gl{TexStorage1D}/@fn_gl{TexStorage2D}/@fn_gl{TexStorage3D} + * or @fn_gl_extension{TextureStorage1D,EXT,direct_state_access}/ + * @fn_gl_extension{TextureStorage2D,EXT,direct_state_access}/ + * @fn_gl_extension{TextureStorage3D,EXT,direct_state_access} + * @requires_gl42 %Extension @extension{ARB,texture_storage} + * @requires_gles30 %Extension @es_extension{EXT,texture_storage} + */ + inline Texture* setStorage(GLsizei levels, InternalFormat internalFormat, const typename DimensionTraits::VectorType& size) { + DataHelper::setStorage(this, _target, levels, internalFormat, size); + return this; + } + /** * @brief Set image data * @param level Mip level @@ -208,9 +234,13 @@ template class Texture: public AbstractTexture { * * The image is not deleted afterwards. * + * For better performance when generating mipmaps using + * generateMipmap() or calling setImage() more than once use + * setStorage() and setSubImage() instead. + * * If @extension{EXT,direct_state_access} is not available, the * texture is bound to some layer before the operation. - * @see setSubImage(), @fn_gl{ActiveTexture}, @fn_gl{BindTexture} and + * @see @fn_gl{ActiveTexture}, @fn_gl{BindTexture} and * @fn_gl{TexImage1D}/@fn_gl{TexImage2D}/@fn_gl{TexImage3D} or * @fn_gl_extension{TextureImage1D,EXT,direct_state_access}/ * @fn_gl_extension{TextureImage2D,EXT,direct_state_access}/ @@ -240,8 +270,8 @@ template class Texture: public AbstractTexture { * * If @extension{EXT,direct_state_access} is not available, the * texture is bound to some layer before the operation. - * @see setImage(), @fn_gl{ActiveTexture}, @fn_gl{BindTexture} and - * @fn_gl{TexSubImage1D}/@fn_gl{TexSubImage2D}/@fn_gl{TexSubImage3D} + * @see setStorage(), setImage(), @fn_gl{ActiveTexture}, @fn_gl{BindTexture} + * and @fn_gl{TexSubImage1D}/@fn_gl{TexSubImage2D}/@fn_gl{TexSubImage3D} * or @fn_gl_extension{TextureSubImage1D,EXT,direct_state_access}/ * @fn_gl_extension{TextureSubImage2D,EXT,direct_state_access}/ * @fn_gl_extension{TextureSubImage3D,EXT,direct_state_access}