Fixed parallelization of texture compression:

Memory synchronization (memory barriers) are introduced using
std::atomic variables.
This commit is contained in:
bubnikv 2019-08-06 11:29:26 +02:00
parent ef0e323d1b
commit e1ff808f14
2 changed files with 47 additions and 46 deletions

View file

@ -27,49 +27,57 @@ namespace GUI {
void GLTexture::Compressor::reset() void GLTexture::Compressor::reset()
{ {
if (m_is_compressing) if (m_thread.joinable()) {
{
// force compression completion, if any
m_abort_compressing = true; m_abort_compressing = true;
// wait for compression completion, if any m_thread.join();
while (m_is_compressing) {} m_levels.clear();
} m_num_levels_compressed = 0;
m_abort_compressing = false;
m_levels.clear(); }
} assert(m_levels.empty());
assert(m_abort_compressing == false);
void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) assert(m_num_levels_compressed == 0);
{
m_levels.emplace_back(w, h, data);
} }
void GLTexture::Compressor::start_compressing() void GLTexture::Compressor::start_compressing()
{ {
std::thread t(&GLTexture::Compressor::compress, this); // The worker thread should be stopped already.
t.detach(); assert(! m_thread.joinable());
assert(! m_levels.empty());
assert(m_abort_compressing == false);
assert(m_num_levels_compressed == 0);
if (! m_levels.empty()) {
std::thread thrd(&GLTexture::Compressor::compress, this);
m_thread = std::move(thrd);
}
} }
bool GLTexture::Compressor::unsent_compressed_data_available() const bool GLTexture::Compressor::unsent_compressed_data_available() const
{ {
for (const Level& level : m_levels) if (m_levels.empty())
{ return false;
if (!level.sent_to_gpu && level.compressed) // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
unsigned int num_compressed = m_num_levels_compressed;
for (unsigned int i = 0; i < num_compressed; ++ i)
if (! m_levels[i].sent_to_gpu && ! m_levels[i].compressed_data.empty())
return true; return true;
}
return false; return false;
} }
void GLTexture::Compressor::send_compressed_data_to_gpu() void GLTexture::Compressor::send_compressed_data_to_gpu()
{ {
// this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed // this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed
if (m_levels.empty())
return;
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id));
for (int i = 0; i < (int)m_levels.size(); ++i) // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
int num_compressed = (int)m_num_levels_compressed;
for (int i = 0; i < num_compressed; ++ i)
{ {
Level& level = m_levels[i]; Level& level = m_levels[i];
if (!level.sent_to_gpu && level.compressed) if (! level.sent_to_gpu && ! level.compressed_data.empty())
{ {
glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i));
@ -77,29 +85,21 @@ void GLTexture::Compressor::send_compressed_data_to_gpu()
level.sent_to_gpu = true; level.sent_to_gpu = true;
// we are done with the compressed data, we can discard it // we are done with the compressed data, we can discard it
level.compressed_data.clear(); level.compressed_data.clear();
level.compressed = false;
} }
} }
glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
}
bool GLTexture::Compressor::all_compressed_data_sent_to_gpu() const if (num_compressed == (unsigned int)m_levels.size())
{ // Finalize the worker thread, close it.
for (const Level& level : m_levels) this->reset();
{
if (!level.sent_to_gpu)
return false;
}
return true;
} }
void GLTexture::Compressor::compress() void GLTexture::Compressor::compress()
{ {
// reference: https://github.com/Cyan4973/RygsDXTc // reference: https://github.com/Cyan4973/RygsDXTc
m_is_compressing = true; assert(m_num_levels_compressed == 0);
m_abort_compressing = false; assert(m_abort_compressing == false);
for (Level& level : m_levels) for (Level& level : m_levels)
{ {
@ -115,11 +115,8 @@ void GLTexture::Compressor::compress()
// we are done with the source data, we can discard it // we are done with the source data, we can discard it
level.src_data.clear(); level.src_data.clear();
level.compressed = true; ++ m_num_levels_compressed;
} }
m_is_compressing = false;
m_abort_compressing = false;
} }
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };

View file

@ -19,30 +19,34 @@ namespace GUI {
unsigned int h; unsigned int h;
std::vector<unsigned char> src_data; std::vector<unsigned char> src_data;
std::vector<unsigned char> compressed_data; std::vector<unsigned char> compressed_data;
bool compressed;
bool sent_to_gpu; bool sent_to_gpu;
Level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) : w(w), h(h), src_data(data), compressed(false), sent_to_gpu(false) {} Level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {}
}; };
GLTexture& m_texture; GLTexture& m_texture;
std::vector<Level> m_levels; std::vector<Level> m_levels;
bool m_is_compressing; std::thread m_thread;
bool m_abort_compressing; // Does the caller want the background thread to stop?
// This atomic also works as a memory barrier for synchronizing the cancel event with the worker thread.
std::atomic<bool> m_abort_compressing;
// How many levels were compressed since the start of the background processing thread?
// This atomic also works as a memory barrier for synchronizing results of the worker thread with the calling thread.
std::atomic<unsigned int> m_num_levels_compressed;
public: public:
explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {} explicit Compressor(GLTexture& texture) : m_texture(texture), m_abort_compressing(false), m_num_levels_compressed(0) {}
~Compressor() { reset(); } ~Compressor() { reset(); }
void reset(); void reset();
void add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data); void add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) { m_levels.emplace_back(w, h, data); }
void start_compressing(); void start_compressing();
bool unsent_compressed_data_available() const; bool unsent_compressed_data_available() const;
void send_compressed_data_to_gpu(); void send_compressed_data_to_gpu();
bool all_compressed_data_sent_to_gpu() const; bool all_compressed_data_sent_to_gpu() const { return m_levels.empty(); }
private: private:
void compress(); void compress();