From 259811d59c7aedc3b5b477685dfd027fb9f961b4 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Wed, 23 Sep 2015 17:05:39 +0200 Subject: [PATCH] Implemented support for explicit mipmap generation in sf::Texture and sf::RenderTexture. (#123) --- examples/opengl/OpenGL.cpp | 64 ++++++++++++++--------- include/SFML/Graphics/RenderTexture.hpp | 16 ++++++ include/SFML/Graphics/Texture.hpp | 35 +++++++++++++ src/SFML/Graphics/GLExtensions.hpp | 2 + src/SFML/Graphics/RenderTexture.cpp | 8 +++ src/SFML/Graphics/Texture.cpp | 67 ++++++++++++++++++++++++- 6 files changed, 168 insertions(+), 24 deletions(-) diff --git a/examples/opengl/OpenGL.cpp b/examples/opengl/OpenGL.cpp index f8079f9f9..20af113ce 100644 --- a/examples/opengl/OpenGL.cpp +++ b/examples/opengl/OpenGL.cpp @@ -21,7 +21,7 @@ int main() bool exit = false; bool sRgb = false; - while(!exit) + while (!exit) { // Request a 24-bits depth buffer when creating the window sf::ContextSettings contextSettings; @@ -44,26 +44,24 @@ int main() if (!font.loadFromFile("resources/sansation.ttf")) return EXIT_FAILURE; sf::Text text("SFML / OpenGL demo", font); - sf::Text instructions("Press space to toggle sRGB conversion", font); + sf::Text sRgbInstructions("Press space to toggle sRGB conversion", font); + sf::Text mipmapInstructions("Press return to toggle mipmapping", font); text.setFillColor(sf::Color(255, 255, 255, 170)); - instructions.setFillColor(sf::Color(255, 255, 255, 170)); + sRgbInstructions.setFillColor(sf::Color(255, 255, 255, 170)); + mipmapInstructions.setFillColor(sf::Color(255, 255, 255, 170)); text.setPosition(250.f, 450.f); - instructions.setPosition(150.f, 500.f); + sRgbInstructions.setPosition(150.f, 500.f); + mipmapInstructions.setPosition(180.f, 550.f); - // Load an OpenGL texture. - // We could directly use a sf::Texture as an OpenGL texture (with its Bind() member function), - // but here we want more control on it (generate mipmaps, ...) so we create a new one from an image - GLuint texture = 0; - { - sf::Image image; - if (!image.loadFromFile("resources/texture.jpg")) - return EXIT_FAILURE; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, sRgb ? GL_SRGB8_ALPHA8 : GL_RGBA, image.getSize().x, image.getSize().y, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.getPixelsPtr()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } + // Load a texture to apply to our 3D cube + sf::Texture texture; + if (!texture.loadFromFile("resources/texture.jpg")) + return EXIT_FAILURE; + + // Attempt to generate a mipmap for our cube texture + // We don't check the return value here since + // mipmapping is purely optional in this example + texture.generateMipmap(); // Enable Z-buffer read and write glEnable(GL_DEPTH_TEST); @@ -84,7 +82,7 @@ int main() // Bind the texture glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, texture); + sf::Texture::bind(&texture); // Define a 3D cube (6 faces made of 2 triangles composed by 3 vertices) static const GLfloat cube[] = @@ -146,6 +144,9 @@ int main() // Create a clock for measuring the time elapsed sf::Clock clock; + // Flag to track whether mipmapping is currently enabled + bool mipmapEnabled = true; + // Start game loop while (window.isOpen()) { @@ -167,6 +168,25 @@ int main() window.close(); } + // Return key: toggle mipmapping + if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Return)) + { + if (mipmapEnabled) + { + // We simply reload the texture to disable mipmapping + if (!texture.loadFromFile("resources/texture.jpg")) + return EXIT_FAILURE; + + mipmapEnabled = false; + } + else + { + texture.generateMipmap(); + + mipmapEnabled = true; + } + } + // Space key: toggle sRGB conversion if ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Space)) { @@ -205,15 +225,13 @@ int main() // Draw some text on top of our OpenGL object window.pushGLStates(); window.draw(text); - window.draw(instructions); + window.draw(sRgbInstructions); + window.draw(mipmapInstructions); window.popGLStates(); // Finally, display the rendered frame on screen window.display(); } - - // Don't forget to destroy our texture - glDeleteTextures(1, &texture); } return EXIT_SUCCESS; diff --git a/include/SFML/Graphics/RenderTexture.hpp b/include/SFML/Graphics/RenderTexture.hpp index e06740cd9..5fc6d2176 100644 --- a/include/SFML/Graphics/RenderTexture.hpp +++ b/include/SFML/Graphics/RenderTexture.hpp @@ -131,6 +131,22 @@ public: //////////////////////////////////////////////////////////// bool isRepeated() const; + //////////////////////////////////////////////////////////// + /// \brief Generate a mipmap using the current texture data + /// + /// This function is similar to Texture::generateMipmap and operates + /// on the texture used as the target for drawing. + /// Be aware that any draw operation may modify the base level image data. + /// For this reason, calling this function only makes sense after all + /// drawing is completed and display has been called. Not calling display + /// after subsequent drawing will lead to undefined behavior if a mipmap + /// had been previously generated. + /// + /// \return True if mipmap generation was successful, false if unsuccessful + /// + //////////////////////////////////////////////////////////// + bool generateMipmap(); + //////////////////////////////////////////////////////////// /// \brief Activate or deactivate the render-texture for rendering /// diff --git a/include/SFML/Graphics/Texture.hpp b/include/SFML/Graphics/Texture.hpp index e9e97afe6..e43ca6b9a 100644 --- a/include/SFML/Graphics/Texture.hpp +++ b/include/SFML/Graphics/Texture.hpp @@ -445,6 +445,31 @@ public: //////////////////////////////////////////////////////////// bool isRepeated() const; + //////////////////////////////////////////////////////////// + /// \brief Generate a mipmap using the current texture data + /// + /// Mipmaps are pre-computed chains of optimized textures. Each + /// level of texture in a mipmap is generated by halving each of + /// the previous level's dimensions. This is done until the final + /// level has the size of 1x1. The textures generated in this process may + /// make use of more advanced filters which might improve the visual quality + /// of textures when they are applied to objects much smaller than they are. + /// This is known as minification. Because fewer texels (texture elements) + /// have to be sampled from when heavily minified, usage of mipmaps + /// can also improve rendering performance in certain scenarios. + /// + /// Mipmap generation relies on the necessary OpenGL extension being + /// available. If it is unavailable or generation fails due to another + /// reason, this function will return false. Mipmap data is only valid from + /// the time it is generated until the next time the base level image is + /// modified, at which point this function will have to be called again to + /// regenerate it. + /// + /// \return True if mipmap generation was successful, false if unsuccessful + /// + //////////////////////////////////////////////////////////// + bool generateMipmap(); + //////////////////////////////////////////////////////////// /// \brief Overload of assignment operator /// @@ -532,6 +557,15 @@ private: //////////////////////////////////////////////////////////// static unsigned int getValidSize(unsigned int size); + //////////////////////////////////////////////////////////// + /// \brief Invalidate the mipmap if one exists + /// + /// This also resets the texture's minifying function. + /// This function is mainly for internal use by RenderTexture. + /// + //////////////////////////////////////////////////////////// + void invalidateMipmap(); + //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// @@ -543,6 +577,7 @@ private: bool m_isRepeated; ///< Is the texture in repeat mode? mutable bool m_pixelsFlipped; ///< To work around the inconsistency in Y orientation bool m_fboAttachment; ///< Is this texture owned by a framebuffer object? + bool m_hasMipmap; ///< Has the mipmap been generated? Uint64 m_cacheId; ///< Unique number that identifies the texture to the render target's cache }; diff --git a/src/SFML/Graphics/GLExtensions.hpp b/src/SFML/Graphics/GLExtensions.hpp index b63ea9993..c3333b735 100644 --- a/src/SFML/Graphics/GLExtensions.hpp +++ b/src/SFML/Graphics/GLExtensions.hpp @@ -101,6 +101,7 @@ #define GLEXT_glCheckFramebufferStatus glCheckFramebufferStatusOES #define GLEXT_glFramebufferTexture2D glFramebufferTexture2DOES #define GLEXT_glFramebufferRenderbuffer glFramebufferRenderbufferOES + #define GLEXT_glGenerateMipmap glGenerateMipmapOES #define GLEXT_GL_FRAMEBUFFER GL_FRAMEBUFFER_OES #define GLEXT_GL_RENDERBUFFER GL_RENDERBUFFER_OES #define GLEXT_GL_DEPTH_COMPONENT GL_DEPTH_COMPONENT16_OES @@ -228,6 +229,7 @@ #define GLEXT_glCheckFramebufferStatus glCheckFramebufferStatusEXT #define GLEXT_glFramebufferTexture2D glFramebufferTexture2DEXT #define GLEXT_glFramebufferRenderbuffer glFramebufferRenderbufferEXT + #define GLEXT_glGenerateMipmap glGenerateMipmapEXT #define GLEXT_GL_FRAMEBUFFER GL_FRAMEBUFFER_EXT #define GLEXT_GL_RENDERBUFFER GL_RENDERBUFFER_EXT #define GLEXT_GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT diff --git a/src/SFML/Graphics/RenderTexture.cpp b/src/SFML/Graphics/RenderTexture.cpp index 4602beb31..3678c8082 100644 --- a/src/SFML/Graphics/RenderTexture.cpp +++ b/src/SFML/Graphics/RenderTexture.cpp @@ -116,6 +116,13 @@ bool RenderTexture::isRepeated() const } +//////////////////////////////////////////////////////////// +bool RenderTexture::generateMipmap() +{ + return m_texture.generateMipmap(); +} + + //////////////////////////////////////////////////////////// bool RenderTexture::setActive(bool active) { @@ -131,6 +138,7 @@ void RenderTexture::display() { m_impl->updateTexture(m_texture.m_texture); m_texture.m_pixelsFlipped = true; + m_texture.invalidateMipmap(); } } diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp index ac610db1c..0e4ae2eb8 100644 --- a/src/SFML/Graphics/Texture.cpp +++ b/src/SFML/Graphics/Texture.cpp @@ -88,6 +88,7 @@ m_sRgb (false), m_isRepeated (false), m_pixelsFlipped(false), m_fboAttachment(false), +m_hasMipmap (false), m_cacheId (getUniqueId()) { } @@ -103,6 +104,7 @@ m_sRgb (copy.m_sRgb), m_isRepeated (copy.m_isRepeated), m_pixelsFlipped(false), m_fboAttachment(false), +m_hasMipmap (false), m_cacheId (getUniqueId()) { if (copy.m_texture) @@ -217,6 +219,8 @@ bool Texture::create(unsigned int width, unsigned int height) glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); m_cacheId = getUniqueId(); + m_hasMipmap = false; + return true; } @@ -298,6 +302,9 @@ bool Texture::loadFromImage(const Image& image, const IntRect& area) pixels += 4 * width; } + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + m_hasMipmap = false; + // Force an OpenGL flush, so that the texture will appear updated // in all contexts immediately (solves problems in multi-threaded apps) glCheck(glFlush()); @@ -425,6 +432,8 @@ void Texture::update(const Uint8* pixels, unsigned int width, unsigned int heigh // Copy pixels from the given array to the texture glCheck(glBindTexture(GL_TEXTURE_2D, m_texture)); glCheck(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + m_hasMipmap = false; m_pixelsFlipped = false; m_cacheId = getUniqueId(); } @@ -467,6 +476,8 @@ void Texture::update(const Window& window, unsigned int x, unsigned int y) // Copy pixels from the back-buffer to the texture glCheck(glBindTexture(GL_TEXTURE_2D, m_texture)); glCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x, y, 0, 0, window.getSize().x, window.getSize().y)); + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + m_hasMipmap = false; m_pixelsFlipped = true; m_cacheId = getUniqueId(); } @@ -489,7 +500,15 @@ void Texture::setSmooth(bool smooth) glCheck(glBindTexture(GL_TEXTURE_2D, m_texture)); glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); - glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + + if (m_hasMipmap) + { + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR)); + } + else + { + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + } } } } @@ -561,6 +580,51 @@ bool Texture::isRepeated() const } +//////////////////////////////////////////////////////////// +bool Texture::generateMipmap() +{ + if (!m_texture) + return false; + + ensureGlContext(); + + // Make sure that extensions are initialized + priv::ensureExtensionsInit(); + + if (!GLEXT_framebuffer_object) + return false; + + // Make sure that the current texture binding will be preserved + priv::TextureSaver save; + + glCheck(glBindTexture(GL_TEXTURE_2D, m_texture)); + glCheck(GLEXT_glGenerateMipmap(GL_TEXTURE_2D)); + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR)); + + m_hasMipmap = true; + + return true; +} + + +//////////////////////////////////////////////////////////// +void Texture::invalidateMipmap() +{ + if (!m_hasMipmap) + return; + + ensureGlContext(); + + // Make sure that the current texture binding will be preserved + priv::TextureSaver save; + + glCheck(glBindTexture(GL_TEXTURE_2D, m_texture)); + glCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isSmooth ? GL_LINEAR : GL_NEAREST)); + + m_hasMipmap = false; +} + + //////////////////////////////////////////////////////////// void Texture::bind(const Texture* texture, CoordinateType coordinateType) { @@ -641,6 +705,7 @@ Texture& Texture::operator =(const Texture& right) std::swap(m_isRepeated, temp.m_isRepeated); std::swap(m_pixelsFlipped, temp.m_pixelsFlipped); std::swap(m_fboAttachment, temp.m_fboAttachment); + std::swap(m_hasMipmap, temp.m_hasMipmap); m_cacheId = getUniqueId(); return *this;