From 081328e63d96fbfb5941d4be14192bda093dbefc Mon Sep 17 00:00:00 2001 From: frank Date: Wed, 16 Aug 2023 18:57:35 -0400 Subject: [PATCH] store size and format of texture object, only regenerate texture and reallocate memory if size and format are changing --- src/Text.cpp | 3 +- src/Texture.cpp | 54 ++++++++++++++++++++++---------- src/Texture.hpp | 83 ++++++++++++++++++++++++++++--------------------- src/Timer.hpp | 2 +- 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/src/Text.cpp b/src/Text.cpp index 0faaa75..1001aed 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -105,7 +105,8 @@ void Text::refresh() else { /* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object will handle - * destroying the previous texture. */ + * destroying the previous texture. The texture object is optimized so that it won't reallocate pixel memory if it was previously + * generated at the same size. */ texture().generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality); texture().load(flipped.get()); } diff --git a/src/Texture.cpp b/src/Texture.cpp index 2bcc489..6e0834c 100644 --- a/src/Texture.cpp +++ b/src/Texture.cpp @@ -30,12 +30,27 @@ void Texture::generate() void Texture::generate(glm::vec2 size, GLenum format, GLint filter) { - generate(); - bind(); - glTexStorage2D(GL_TEXTURE_2D, 1, format, size.x, size.y); + /* Only generate a new texture ID and reallocate memory if the current texture ID hasn't been registered by this object as having identically + * sized memory with the same format. */ + if (!generated() || !_size.has_value() || !_format.has_value() || _size != size || _format != format) + { + generate(); + bind(); + glTexStorage2D(GL_TEXTURE_2D, 1, format, size.x, size.y); + } + else + { + bind(); + } + + /* Set the resizing algorithm of this texture */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + sb::Log::gl_errors(); + + /* Store a copy of size */ + _size = size; } void Texture::load() @@ -146,7 +161,7 @@ void Texture::load(void* pixels, glm::vec2 size, GLenum format, GLenum type) } else { - message << "Cannot load pixels with zero or negative size in either dimension"; + message << "Cannot load pixels with zero or negative size in either dimension."; message_level = sb::Log::WARN; } sb::Log::log(message, message_level); @@ -171,8 +186,6 @@ void Texture::bind() const } } -/* glGetTexlevelparameteriv is not available in OpenGL ES 3.0 */ -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID) void Texture::load(void* pixels, GLenum format, GLenum type) { if (generated()) @@ -181,29 +194,36 @@ void Texture::load(void* pixels, GLenum format, GLenum type) } else { - throw std::invalid_argument("This texture has not been generated and has no size property, so a size must be provided"); + std::ostringstream message; + message << "Texture"; + if (!path.empty()) + { + message << " with path " << path; + } + message << " has not been generated and has no size property, so a size must be provided to the load function."; + throw std::invalid_argument(message.str()); } } glm::vec2 Texture::size() const { - if (generated()) + if (_size.has_value()) { - bind(); - int width, height; - int miplevel = 0; - glGetTexLevelParameteriv(GL_TEXTURE_2D, miplevel, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, miplevel, GL_TEXTURE_HEIGHT, &height); - return {width, height}; + return *_size; } else { - return {0, 0}; + std::ostringstream message; + message << "Texture"; + if (!path.empty()) + { + message << " with path " << path; + } + message << " has not been generated or loaded with a size given, so it currently has no size."; + throw std::runtime_error(message.str()); } } -#endif -/* Textures are considered equal if they have the same ID */ bool Texture::operator==(const Texture& texture) const { return id() == texture.id(); diff --git a/src/Texture.hpp b/src/Texture.hpp index 539fe2a..795542f 100644 --- a/src/Texture.hpp +++ b/src/Texture.hpp @@ -1,29 +1,18 @@ -/*!
- *        /\         +------------------------------------------------------+ 
- *   ____/  \____   /| - Open source game framework licensed to freely use, |
- *   \          /  / |   copy, modify and sell without restriction          |
- * +--\ ^__^   /--+  |                                                      |
- * | ~/        \~ |  | - created for              |
- * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
- * | SPACE ~~~~~  | /
- * |  ~~~~~~~ BOX |/
- * +--------------+                                                    
- * - * Texture - * ======= - * - * The Texture class abstracts the file opening, data loading and binding steps of Open GL - * texture creation. Currently it only supports loading GL_TEXTURE_2D with GL_RGB8 pixels - * and automatic GL_NEAREST mipmapping. Support may be added for users to pass in their own - * pixel data, to customize mipmapping, and more. The class can also be used in a custom - * way by passing the Texture ID to GL functions instead of using the class's member - * functions. - */ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ #pragma once #include #include +#include #include "glm/vec2.hpp" #include "SDL.h" @@ -37,13 +26,24 @@ namespace sb { + /*! + * The Texture class abstracts the image file opening, data loading and binding steps of Open GL texture creation. Currently it supports only + * the GL_TEXTURE_2D target with one mipmap level. Support for other types of textures may be added in the future. + * + * The texture object's ID can be used to pass this texture directly to OpenGL functions. + */ class Texture : public GLObject { private: + /* An image file path can be set with Texture::allocate(fs::path) and later loaded with Texture::load(). */ fs::path path = ""; + /* Size and format are unset until memory for pixel data is allocated. */ + std::optional _size; + std::optional _format; + public: /*! @@ -59,8 +59,8 @@ namespace sb Texture(fs::path path); /*! - * Store an image path as a member variable for loading later. Each texture should have one image - * path (support for multiple mipmap levels may be added later). + * Store an image path as a member variable for loading later. Each texture can have only one image + * path (support for multiple paths to enable multiple texture mipmap levels may be added later). * * @param path path to an image that can be loaded by the SDL image library */ @@ -75,8 +75,8 @@ namespace sb * Generate a GL_TEXTURE_2D texture ID and allocate the specified format storage for the given size. * * @param size Width and height of the texture in texels - * @param format Sized internal format to be used to store texture data (for example, GL_RGBA8, GL_RGB8) - * @param filter Resize function to use (see glTexParameter) + * @param format Sized internal format to be used to store texture data (for example, `GL_RGBA8`, `GL_RGB8`) + * @param filter Resize function to use (see `glTexParameter`) */ void generate(glm::vec2 size, GLenum format = GL_RGBA8, GLint filter = GL_NEAREST); @@ -131,14 +131,10 @@ namespace sb void load(void* pixels, glm::vec2 size, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE); - /* glGetTexlevelparameteriv is not available in OpenGL ES 3.0 */ -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID) /*! * @overload load(void* pixels, glm::vec2 size, GLenum format, GLenum type) * - * The texture must have been previously generated with a size to use this generic pixel data load function. The size is - * determined with `glGetTexLevelParameter`, which is only available to OpenGL ES 3.1+, so this overload is not available - * for Emscripten or Android builds. + * The texture must have been previously generated with a size to use this generic pixel data load function. * * see OpenGL's `glTexSubImage2D` * @@ -149,17 +145,32 @@ namespace sb void load(void* pixels, GLenum format = GL_RGBA, GLenum type = GL_UNSIGNED_BYTE); /*! - * Return the size in pixels of mipmap level 0 (the only mipmap level supported by this class). If the texture hasn't been, - * generated, return {0, 0}. `glGetTexLevelParameter` is only available in OpenGL ES 3.1+, so this function is removed from - * Emscripten and Android builds. + * Return the size in pixels of mipmap level 0 (the only mipmap level supported by this class). If the texture hasn't been + * generated or loaded with a size given, an exception will be thrown because it currently has no size. * * @return glm::vec2 A vector consisting of {TEXTURE_MIPMAP_WIDTH, TEXTURE_MIPMAP_HEIGHT} */ glm::vec2 size() const; -#endif - + + /*! + * Call `glBindTexture`, binding this texture's ID to the target `GL_TEXTURE_2D`. The texture must have previously been + * generated, using Texture::generate() or Texture::generate(glm::vec2, GLenum, GLint), otherwise an exception will be + * thrown. + * + * From the OpenGL manual: + * + * > While a texture is bound, GL operations on the target to which it is bound affect the bound texture, and queries of + * > the target to which it is bound return state from the bound texture. In effect, the texture targets become aliases for + * > the textures currently bound to them. + */ void bind() const override; - bool operator==(const Texture&) const; + + /*! + * Textures are considered equal if they have the same ID. + * + * @param texture texture to compare this texture to + */ + bool operator==(const Texture& texture) const; }; diff --git a/src/Timer.hpp b/src/Timer.hpp index 921c609..25f0bb0 100644 --- a/src/Timer.hpp +++ b/src/Timer.hpp @@ -16,7 +16,7 @@ namespace sb { /*! - * Timer in seconds which can be paused arbitrarily. + * Timer in seconds which can be paused and resumed. * * It must be updated every frame with the timestamp passed to Game::update, regardless of whether it is actively timing or not. */