From eee4453993ba38f3f11c8f89c56cefa50eaaf74b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 Jan 2023 09:26:47 +0100 Subject: [PATCH] Revert of 971f2a08e2758ea1d91e75af9b1443d14b41f895 - Fix mipmap of compressed textures on AMD Radeon graphics cards by forcing the use of squared power of two textures Changed nanosvg library from https://github.com/memononen/nanosvg to https://github.com/fltk/nanosvg which contains the definition of the new function nsvgRasterizeXY() --- deps/NanoSVG/NanoSVG.cmake | 9 +++- src/slic3r/GUI/GLTexture.cpp | 72 ++++++++++++++++++++------------ src/slic3r/GUI/OpenGLManager.cpp | 16 ++++--- src/slic3r/GUI/OpenGLManager.hpp | 4 +- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/deps/NanoSVG/NanoSVG.cmake b/deps/NanoSVG/NanoSVG.cmake index 9623d3226..1b0fb9960 100644 --- a/deps/NanoSVG/NanoSVG.cmake +++ b/deps/NanoSVG/NanoSVG.cmake @@ -1,4 +1,9 @@ +# In PrusaSlicer 2.6.0 we switched from https://github.com/memononen/nanosvg to its fork https://github.com/fltk/nanosvg +# because this last implements the new function nsvgRasterizeXY() which we now use in GLTexture::load_from_svg() +# for rasterizing svg files from their original size to a squared power of two texture on Windows systems using +# AMD Radeon graphics cards + prusaslicer_add_cmake_project(NanoSVG - URL https://github.com/memononen/nanosvg/archive/4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148.zip - URL_HASH SHA256=584e084af1a75bf633f79753ce2f6f6ec8686002ca27f35f1037c25675fecfb6 + URL https://github.com/fltk/nanosvg/archive/abcd277ea45e9098bed752cf9c6875b533c0892f.zip + URL_HASH SHA256=e859938fbaee4b351bd8a8b3d3c7a75b40c36885ce00b73faa1ce0b98aa0ad34 ) \ No newline at end of file diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index ef9a8ef20..93e96a642 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -375,10 +375,29 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glDisable(GL_BLEND)); } +static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) +{ + auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; + auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; + + int new_w = std::max(w, h); + if (!is_power_of_two(new_w)) + new_w = upper_power_of_two(new_w); + + while (new_w > max_size_px) { + new_w /= 2; + } + + const int new_h = new_w; + const bool ret = (new_w != w || new_h != h); + w = new_w; + h = new_h; + return ret; +} + bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); - const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); // Load a PNG with an alpha channel. wxImage image; @@ -392,6 +411,11 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool requires_rescale = false; + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) { + if (to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), OpenGLManager::get_gl_info().get_max_tex_size(), m_width, m_height)) + requires_rescale = true; + } + if (compression_enabled && compression_type == MultiThreaded) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -448,7 +472,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo } if (compression_enabled) { - if (compression_type == SingleThreaded || !use_compressor) + if (compression_type == SingleThreaded) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else { // initializes the texture on GPU @@ -460,7 +484,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -507,10 +531,6 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } } - else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -522,7 +542,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo m_source = filename; - if (use_compressor && compression_type == MultiThreaded) + if (compression_type == MultiThreaded) // start asynchronous compression m_compressor.start_compressing(); @@ -532,7 +552,6 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); - const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); NSVGimage* image = BitmapCache::nsvgParseFromFileWithReplace(filename.c_str(), "px", 96.0f, {}); if (image == nullptr) { @@ -540,11 +559,17 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo return false; } - float scale = (float)max_size_px / std::max(image->width, image->height); + const float scale = (float)max_size_px / std::max(image->width, image->height); m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) + to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), max_size_px, m_width, m_height); + + float scale_w = (float)m_width / image->width; + float scale_h = (float)m_height / image->height; + if (compression_enabled) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -574,7 +599,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo // creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps std::vector data(n_pixels * 4, 0); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), m_width, m_height, m_width * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), m_width, m_height, m_width * 4); // sends data to gpu glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); @@ -588,19 +613,15 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo } if (compression_enabled) { - if (use_compressor) { - // initializes the texture on GPU - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - // and send the uncompressed data to the compressor - m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); - } - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -610,11 +631,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo lod_w = std::max(lod_w / 2, 1); lod_h = std::max(lod_h / 2, 1); - scale /= 2.0f; + scale_w /= 2.0f; + scale_h /= 2.0f; data.resize(lod_w * lod_h * 4); - nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); + nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), lod_w, lod_h, lod_w * 4); if (compression_enabled) { // initializes the texture on GPU glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); @@ -630,10 +652,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } } - else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -645,7 +663,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo m_source = filename; - if (use_compressor) + if (compression_enabled) // start asynchronous compression m_compressor.start_compressing(); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 8959553e6..ecf0c5790 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -200,7 +200,6 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; out << b_start << "Textures compression: " << b_end << (are_compressed_textures_supported() ? "Enabled" : "Disabled") << line_end; - out << b_start << "Textures mipmap generation: " << b_end << (use_manually_generated_mipmaps() ? "Manual" : "Automatic") << line_end; { #if ENABLE_GL_CORE_PROFILE @@ -257,7 +256,7 @@ std::vector OpenGLManager::GLInfo::get_extensions_list() const OpenGLManager::GLInfo OpenGLManager::s_gl_info; bool OpenGLManager::s_compressed_textures_supported = false; -bool OpenGLManager::s_use_manually_generated_mipmaps = true; +bool OpenGLManager::s_force_power_of_two_textures = false; OpenGLManager::EMultisampleState OpenGLManager::s_multisample = OpenGLManager::EMultisampleState::Unknown; OpenGLManager::EFramebufferType OpenGLManager::s_framebuffers_type = OpenGLManager::EFramebufferType::Unknown; @@ -415,17 +414,16 @@ bool OpenGLManager::init_gl() // texture of the bed (see: https://github.com/prusa3d/PrusaSlicer/issues/8417). // It seems that this issue only triggers when mipmaps are generated manually // (combined with a texture compression) with texture size not being power of two. - // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine. + // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine, + // but the mipmap generation is quite slow on some machines. // There is no an easy way to detect the driver version without using Win32 API because the strings returned by OpenGL // have no standardized format, only some of them contain the driver version. - // Until we do not know that driver will be fixed (if ever) we force the use of glGenerateMipmap() on all cards + // Until we do not know that driver will be fixed (if ever) we force the use of power of two textures on all cards // containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER) const auto& gl_info = OpenGLManager::get_gl_info(); - if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && boost::contains(gl_info.get_renderer(), "Radeon")) { - s_use_manually_generated_mipmaps = false; - BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; - } -#endif + if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && boost::contains(gl_info.get_renderer(), "Radeon")) + s_force_power_of_two_textures = true; +#endif // _WIN32 } return true; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 6b947deb5..5e2bdc316 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -110,7 +110,7 @@ private: static OSInfo s_os_info; #endif //__APPLE__ static bool s_compressed_textures_supported; - static bool s_use_manually_generated_mipmaps; + static bool s_force_power_of_two_textures; static EMultisampleState s_multisample; static EFramebufferType s_framebuffers_type; @@ -139,7 +139,7 @@ public: static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow& parent); static const GLInfo& get_gl_info() { return s_gl_info; } - static bool use_manually_generated_mipmaps() { return s_use_manually_generated_mipmaps; } + static bool force_power_of_two_textures() { return s_force_power_of_two_textures; } private: #if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES