Merge pull request #2780 from Noisyfox:bugfix/amd-png

Fix PNG build plate texture not rendering on AMD GPUs
This commit is contained in:
SoftFever 2023-11-19 19:08:04 +08:00 committed by GitHub
commit 93f62a47f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 360 additions and 168 deletions

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Hejl @hejllukas, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
//BBS:add i18n
#include "I18N.hpp"
//BBS: add fstream for debug output
@ -416,7 +420,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::stri
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (compress && GLEW_EXT_texture_compression_s3tc)
if (compress && OpenGLManager::are_compressed_textures_supported())
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
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
@ -534,7 +538,7 @@ bool GLTexture::generate_from_text(const std::string &text_str, wxFont &font, wx
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
if (GLEW_EXT_texture_compression_s3tc)
if (OpenGLManager::are_compressed_textures_supported())
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
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
@ -635,7 +639,7 @@ bool GLTexture::generate_texture_from_text(const std::string& text_str, wxFont&
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
if (GLEW_EXT_texture_compression_s3tc)
if (OpenGLManager::are_compressed_textures_supported())
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
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
@ -695,9 +699,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)
{
bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc;
const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported();
// Load a PNG with an alpha channel.
wxImage image;
@ -711,6 +735,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;
@ -837,7 +866,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo
m_source = filename;
if (compression_enabled && compression_type == MultiThreaded)
if (compression_type == MultiThreaded)
// start asynchronous compression
m_compressor.start_compressing();
@ -846,7 +875,7 @@ 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)
{
bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc;
const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported();
NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f);
if (image == nullptr) {
@ -854,11 +883,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;
@ -871,7 +906,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
m_height += (4 - height_rem);
}
int n_pixels = m_width * m_height;
const int n_pixels = m_width * m_height;
if (n_pixels <= 0) {
reset();
@ -888,7 +923,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<unsigned char> 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));
@ -910,7 +945,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
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;
@ -920,11 +955,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));
@ -939,9 +975,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
} else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) {
glGenerateMipmap(GL_TEXTURE_2D);
} else {
}
else {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
}

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "libslic3r/libslic3r.h"
#include "OpenGLManager.hpp"
@ -212,7 +216,7 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const
OpenGLManager::GLInfo OpenGLManager::s_gl_info;
bool OpenGLManager::s_compressed_textures_supported = false;
bool OpenGLManager::m_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;
@ -295,31 +299,22 @@ bool OpenGLManager::init_gl(bool popup_error)
#ifdef _WIN32
// Since AMD driver version 22.7.1, there is probably some bug in the driver that causes the issue with the missing
// texture of the bed. It seems that this issue only triggers when mipmaps are generated manually
// (combined with a texture compression) and when mipmaps are generated through OpenGL glGenerateMipmap is working.
// So, for newer drivers than 22.6.1, the last working driver version, we use mipmaps generated through OpenGL.
if (const auto gl_info = OpenGLManager::get_gl_info(); boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.")) {
// WHQL drivers seem to have one more version number at the end besides non-WHQL drivers.
// WHQL: 4.6.14800 Compatibility Profile Context 22.6.1 30.0.21023.1015
// Non-WHQL: 4.6.0 Compatibility Profile Context 22.8.1.220810
std::regex version_rgx(R"(Compatibility\sProfile\sContext\s(\d+)\.(\d+)\.(\d+))");
if (std::smatch matches; std::regex_search(gl_info.get_version(), matches, version_rgx) && matches.size() == 4) {
int version_major = std::stoi(matches[1].str());
int version_minor = std::stoi(matches[2].str());
int version_patch = std::stoi(matches[3].str());
BOOST_LOG_TRIVIAL(debug) << "Found AMD driver version: " << version_major << "." << version_minor << "." << version_patch;
if (version_major > 22 || (version_major == 22 && version_minor > 6) || (version_major == 22 && version_minor == 6 && version_patch > 1)) {
m_use_manually_generated_mipmaps = false;
BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled.";
}
} else {
BOOST_LOG_TRIVIAL(error) << "Not recognized format of version.";
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "not AMD driver.";
}
#endif
// 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,
// 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 power of two textures on all cards
// 1) containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER)
// 2) containing the string 'Custom' 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") ||
boost::contains(gl_info.get_renderer(), "Custom")))
s_force_power_of_two_textures = true;
#endif // _WIN32
}
return true;

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_OpenGLManager_hpp_
#define slic3r_OpenGLManager_hpp_
@ -83,10 +87,11 @@ private:
static OSInfo s_os_info;
#endif //__APPLE__
static bool s_compressed_textures_supported;
static bool s_force_power_of_two_textures;
static EMultisampleState s_multisample;
static EFramebufferType s_framebuffers_type;
static bool m_use_manually_generated_mipmaps;
public:
OpenGLManager() = default;
~OpenGLManager();
@ -103,7 +108,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 m_use_manually_generated_mipmaps; }
static bool force_power_of_two_textures() { return s_force_power_of_two_textures; }
private:
static void detect_multisample(int* attribList);