diff --git a/drape/drape_tests/font_texture_tests.cpp b/drape/drape_tests/font_texture_tests.cpp index 1b4e4bd18e..81384b0616 100644 --- a/drape/drape_tests/font_texture_tests.cpp +++ b/drape/drape_tests/font_texture_tests.cpp @@ -97,6 +97,7 @@ UNIT_TEST(UploadingGlyphs) index.MapResource(GlyphKey(0x58)); index.MapResource(GlyphKey(0x59)); index.MapResource(GlyphKey(0x61)); + while(index.HasAsyncRoutines()); DummyTexture tex; tex.Create(64, 64, dp::ALPHA, nullptr); @@ -109,6 +110,8 @@ UNIT_TEST(UploadingGlyphs) index.MapResource(GlyphKey(0x65)); index.MapResource(GlyphKey(0x400)); index.MapResource(GlyphKey(0x401)); + while(index.HasAsyncRoutines()); + EXPECTGL(glTexSubImage2D(_, _, _, _, _, _, _)).WillOnce(Invoke(&r, &UploadedRender::glMemoryToQImage)) .WillOnce(Invoke(&r, &UploadedRender::glMemoryToQImage)); index.UploadResources(make_ref(&tex)); diff --git a/drape/drape_tests/glyph_mng_tests.cpp b/drape/drape_tests/glyph_mng_tests.cpp index d976529d40..601eb423db 100644 --- a/drape/drape_tests/glyph_mng_tests.cpp +++ b/drape/drape_tests/glyph_mng_tests.cpp @@ -37,9 +37,16 @@ namespace void RenderGlyphs(QPaintDevice * device) { vector glyphs; - glyphs.push_back(m_mng->GetGlyph(0xC0)); - glyphs.push_back(m_mng->GetGlyph(0x79)); - glyphs.push_back(m_mng->GetGlyph(0x122)); + auto generateGlyph = [this, &glyphs](strings::UniChar c) + { + dp::GlyphManager::Glyph g = m_mng->GetGlyph(c); + glyphs.push_back(m_mng->GenerateGlyph(g)); + g.m_image.Destroy(); + }; + + generateGlyph(0xC0); + generateGlyph(0x79); + generateGlyph(0x122); QPainter painter(device); painter.fillRect(QRectF(0.0, 0.0, device->width(), device->height()), Qt::white); diff --git a/drape/font_texture.cpp b/drape/font_texture.cpp index 441c9dbd0b..10f5093463 100644 --- a/drape/font_texture.cpp +++ b/drape/font_texture.cpp @@ -9,6 +9,7 @@ #include "base/string_utils.hpp" #include "base/stl_add.hpp" +#include "std/chrono.hpp" #include "std/string.hpp" #include "std/vector.hpp" #include "std/map.hpp" @@ -76,14 +77,91 @@ m2::RectF GlyphPacker::MapTextureCoords(const m2::RectU & pixelRect) const bool GlyphPacker::IsFull() const { return m_isFull; } +GlyphGenerator::GlyphGenerator(ref_ptr mng, TCompletionHandler const & completionHandler) + : m_mng(mng) + , m_completionHandler(completionHandler) + , m_isRunning(true) + , m_isSuspended(false) + , m_thread(&GlyphGenerator::Routine, this) +{ + ASSERT(m_completionHandler != nullptr, ()); +} + +GlyphGenerator::~GlyphGenerator() +{ + m_isRunning = false; + { + lock_guard lock(m_queueLock); + WakeUp(); + } + m_thread.join(); + m_completionHandler = nullptr; +} + +void GlyphGenerator::WaitForGlyph() +{ + unique_lock lock(m_queueLock); + if (m_queue.empty()) + { + m_isSuspended = true; + m_condition.wait(lock, [this] { return !m_queue.empty() || !m_isRunning; }); + m_isSuspended = false; + } +} + +void GlyphGenerator::WakeUp() +{ + if (m_isSuspended) + { + m_isSuspended = false; + m_condition.notify_one(); + } +} + +bool GlyphGenerator::IsSuspended() const +{ + lock_guard lock(m_queueLock); + return m_isSuspended; +} + +void GlyphGenerator::Routine(GlyphGenerator * generator) +{ + while(generator->m_isRunning) + { + generator->WaitForGlyph(); + + // generate glyphs + list queue; + { + lock_guard lock(generator->m_queueLock); + queue.swap(generator->m_queue); + } + + for (GlyphGenerationData & data : queue) + { + GlyphManager::Glyph glyph = generator->m_mng->GenerateGlyph(data.m_glyph); + data.m_glyph.m_image.Destroy(); + generator->m_completionHandler(data.m_rect, glyph); + } + } +} + +void GlyphGenerator::GenerateGlyph(m2::RectU const & rect, GlyphManager::Glyph const & glyph) +{ + lock_guard lock(m_queueLock); + m_queue.emplace_back(rect, glyph); + WakeUp(); +} + GlyphIndex::GlyphIndex(m2::PointU size, ref_ptr mng) : m_packer(size) , m_mng(mng) -{ -} + , m_generator(new GlyphGenerator(mng, bind(&GlyphIndex::OnGlyphGenerationCompletion, this, _1, _2))) +{} GlyphIndex::~GlyphIndex() { + m_generator.reset(); { threads::MutexGuard g(m_lock); for_each(m_pendingNodes.begin(), m_pendingNodes.end(), [](TPendingNode & node) @@ -112,16 +190,27 @@ ref_ptr GlyphIndex::MapResource(GlyphKey const & key, boo return nullptr; } - { - threads::MutexGuard g(m_lock); - m_pendingNodes.emplace_back(r, glyph); - } + m_generator->GenerateGlyph(r, glyph); auto res = m_index.emplace(uniChar, GlyphInfo(m_packer.MapTextureCoords(r), glyph.m_metrics)); ASSERT(res.second, ()); return make_ref(&res.first->second); } +bool GlyphIndex::HasAsyncRoutines() const +{ + return !m_generator->IsSuspended(); +} + +void GlyphIndex::OnGlyphGenerationCompletion(m2::RectU const & rect, GlyphManager::Glyph const & glyph) +{ + if (glyph.m_image.m_data == nullptr) + return; + + threads::MutexGuard g(m_lock); + m_pendingNodes.emplace_back(rect, glyph); +} + void GlyphIndex::UploadResources(ref_ptr texture) { if (m_pendingNodes.empty()) diff --git a/drape/font_texture.hpp b/drape/font_texture.hpp index 8b9edf55e7..c06dee57ef 100644 --- a/drape/font_texture.hpp +++ b/drape/font_texture.hpp @@ -5,9 +5,13 @@ #include "drape/glyph_manager.hpp" #include "drape/dynamic_texture.hpp" +#include "std/atomic.hpp" +#include "std/condition_variable.hpp" +#include "std/list.hpp" #include "std/map.hpp" #include "std/vector.hpp" #include "std/string.hpp" +#include "std/thread.hpp" namespace dp { @@ -47,8 +51,7 @@ public: GlyphInfo(m2::RectF const & texRect, GlyphManager::GlyphMetrics const & metrics) : TBase(texRect) , m_metrics(metrics) - { - } + {} virtual Texture::ResourceType GetType() const { return Texture::Glyph; } GlyphManager::GlyphMetrics const & GetMetrics() const { return m_metrics; } @@ -57,6 +60,43 @@ private: GlyphManager::GlyphMetrics m_metrics; }; +class GlyphGenerator +{ +public: + using TCompletionHandler = function; + struct GlyphGenerationData + { + m2::RectU m_rect; + GlyphManager::Glyph m_glyph; + GlyphGenerationData(m2::RectU const & rect, GlyphManager::Glyph const & glyph) + : m_rect(rect), m_glyph(glyph) + {} + }; + + GlyphGenerator(ref_ptr mng, TCompletionHandler const & completionHandler); + ~GlyphGenerator(); + + void GenerateGlyph(m2::RectU const & rect, GlyphManager::Glyph const & glyph); + + bool IsSuspended() const; + +private: + static void Routine(GlyphGenerator * generator); + void WakeUp(); + void WaitForGlyph(); + + ref_ptr m_mng; + TCompletionHandler m_completionHandler; + + list m_queue; + mutable mutex m_queueLock; + + atomic m_isRunning; + condition_variable m_condition; + bool m_isSuspended; + thread m_thread; +}; + class GlyphIndex { public: @@ -70,9 +110,14 @@ public: glConst GetMinFilter() const { return gl_const::GLLinear; } glConst GetMagFilter() const { return gl_const::GLLinear; } + bool HasAsyncRoutines() const; + + void OnGlyphGenerationCompletion(m2::RectU const & rect, GlyphManager::Glyph const & glyph); + private: GlyphPacker m_packer; ref_ptr m_mng; + unique_ptr m_generator; typedef map TResourceMapping; typedef pair TPendingNode; @@ -102,6 +147,11 @@ public: ~FontTexture() { TBase::Reset(); } + bool HasAsyncRoutines() const override + { + return m_index.HasAsyncRoutines(); + } + private: GlyphIndex m_index; }; diff --git a/drape/glyph_manager.cpp b/drape/glyph_manager.cpp index a7f8d6c779..2b62af4975 100644 --- a/drape/glyph_manager.cpp +++ b/drape/glyph_manager.cpp @@ -172,39 +172,36 @@ public: FT_Bitmap bitmap = m_fontFace->glyph->bitmap; + float const scale = 1.0f / SDF_SCALE_FACTOR; + SharedBufferManager::shared_buffer_ptr_t data; - size_t bufferSize = 0; - int imgWidth = bitmap.width; - int imgHeigh = bitmap.rows; + int imageWidth = bitmap.width; + int imageHeight = bitmap.rows; if (bitmap.buffer != nullptr) { SdfImage img(bitmap.rows, bitmap.pitch, bitmap.buffer, SDF_BORDER); - img.GenerateSDF(1.0f / (float)SDF_SCALE_FACTOR); + imageWidth = img.GetWidth() * scale; + imageHeight = img.GetHeight() * scale; - imgWidth = img.GetWidth(); - imgHeigh = img.GetHeight(); - - size_t byteSize = imgWidth * imgHeigh; - bufferSize = my::NextPowOf2(byteSize); + size_t bufferSize = bitmap.rows * bitmap.pitch; data = SharedBufferManager::instance().reserveSharedBuffer(bufferSize); - - img.GetData(*data); + memcpy(&(*data)[0], bitmap.buffer, bufferSize); } GlyphManager::Glyph result; result.m_image = GlyphManager::GlyphImage { - imgWidth, - imgHeigh, + imageWidth, imageHeight, + bitmap.rows, bitmap.pitch, data }; result.m_metrics = GlyphManager::GlyphMetrics { - static_cast(glyph->advance.x >> 16) / SDF_SCALE_FACTOR, - static_cast(glyph->advance.y >> 16) / SDF_SCALE_FACTOR, - static_cast(bbox.xMin) / SDF_SCALE_FACTOR, - static_cast(bbox.yMin) / SDF_SCALE_FACTOR, + static_cast(glyph->advance.x >> 16) * scale, + static_cast(glyph->advance.y >> 16) * scale, + static_cast(bbox.xMin) * scale, + static_cast(bbox.yMin) * scale, true }; @@ -213,6 +210,35 @@ public: return result; } + GlyphManager::Glyph GenerateGlyph(GlyphManager::Glyph const & glyph) const + { + if (glyph.m_image.m_data != nullptr) + { + GlyphManager::Glyph resultGlyph; + resultGlyph.m_metrics = glyph.m_metrics; + resultGlyph.m_fontIndex = glyph.m_fontIndex; + + SdfImage img(glyph.m_image.m_bitmapRows, glyph.m_image.m_bitmapPitch, + glyph.m_image.m_data->data(), SDF_BORDER); + + img.GenerateSDF(1.0f / (float)SDF_SCALE_FACTOR); + + ASSERT(img.GetWidth() == glyph.m_image.m_width, ()); + ASSERT(img.GetHeight() == glyph.m_image.m_height, ()); + + size_t bufferSize = my::NextPowOf2(glyph.m_image.m_width * glyph.m_image.m_height); + resultGlyph.m_image.m_width = glyph.m_image.m_width; + resultGlyph.m_image.m_height = glyph.m_image.m_height; + resultGlyph.m_image.m_bitmapRows = 0; + resultGlyph.m_image.m_bitmapPitch = 0; + resultGlyph.m_image.m_data = SharedBufferManager::instance().reserveSharedBuffer(bufferSize); + + img.GetData(*resultGlyph.m_image.m_data); + return resultGlyph; + } + return glyph; + } + void GetCharcodes(vector & charcodes) { FT_UInt gindex; @@ -467,7 +493,11 @@ GlyphManager::Glyph GlyphManager::GetGlyph(strings::UniChar unicodePoint) ASSERT_LESS(fontIndex, m_impl->m_fonts.size(), ()); Font const & f = m_impl->m_fonts[fontIndex]; if (f.HasGlyph(unicodePoint)) - return f.GetGlyph(unicodePoint, m_impl->m_baseGlyphHeight); + { + Glyph glyph = f.GetGlyph(unicodePoint, m_impl->m_baseGlyphHeight); + glyph.m_fontIndex = fontIndex; + return glyph; + } } fontIndex = block.GetFontOffset(fontIndex); @@ -476,6 +506,14 @@ GlyphManager::Glyph GlyphManager::GetGlyph(strings::UniChar unicodePoint) return GetInvalidGlyph(); } +GlyphManager::Glyph GlyphManager::GenerateGlyph(Glyph const & glyph) const +{ + ASSERT(glyph.m_fontIndex != -1, ()); + ASSERT_LESS(glyph.m_fontIndex, m_impl->m_fonts.size(), ()); + Font const & f = m_impl->m_fonts[glyph.m_fontIndex]; + return f.GenerateGlyph(glyph); +} + void GlyphManager::ForEachUnicodeBlock(GlyphManager::TUniBlockCallback const & fn) const { for (UnicodeBlock const & uni : m_impl->m_blocks) @@ -492,6 +530,7 @@ GlyphManager::Glyph GlyphManager::GetInvalidGlyph() const ASSERT(!m_impl->m_fonts.empty(), ()); s_glyph = m_impl->m_fonts[0].GetGlyph(0x9, m_impl->m_baseGlyphHeight); s_glyph.m_metrics.m_isValid = false; + s_glyph.m_fontIndex = 0; s_inited = true; } diff --git a/drape/glyph_manager.hpp b/drape/glyph_manager.hpp index f1fdfaf8ee..32c71800ab 100644 --- a/drape/glyph_manager.hpp +++ b/drape/glyph_manager.hpp @@ -44,12 +44,19 @@ public: void Destroy() { - SharedBufferManager::instance().freeSharedBuffer(m_data->size(), m_data); + if (m_data != nullptr) + { + SharedBufferManager::instance().freeSharedBuffer(m_data->size(), m_data); + m_data = nullptr; + } } int m_width; int m_height; + int m_bitmapRows; + int m_bitmapPitch; + SharedBufferManager::shared_buffer_ptr_t m_data; }; @@ -57,12 +64,14 @@ public: { GlyphMetrics m_metrics; GlyphImage m_image; + int m_fontIndex; }; GlyphManager(Params const & params); ~GlyphManager(); Glyph GetGlyph(strings::UniChar unicodePoints); + Glyph GenerateGlyph(Glyph const & glyph) const; typedef function TUniBlockCallback; void ForEachUnicodeBlock(TUniBlockCallback const & fn) const; diff --git a/drape/texture.hpp b/drape/texture.hpp index 4bac27856c..405c5373c6 100644 --- a/drape/texture.hpp +++ b/drape/texture.hpp @@ -57,6 +57,7 @@ public: virtual ref_ptr FindResource(Key const & key, bool & newResource) = 0; virtual void UpdateState() {} + virtual bool HasAsyncRoutines() const { return false; } TextureFormat GetFormat() const; uint32_t GetWidth() const; diff --git a/drape/texture_manager.cpp b/drape/texture_manager.cpp index 23570d25a3..6e367db085 100644 --- a/drape/texture_manager.cpp +++ b/drape/texture_manager.cpp @@ -138,16 +138,20 @@ void TextureManager::Release() m_glyphTextures.clear(); } -void TextureManager::UpdateDynamicTextures() +bool TextureManager::UpdateDynamicTextures() { - if (m_nothingToUpload.test_and_set()) - return; + bool const asyncRoutines = HasAsyncRoutines(m_glyphGroups) || HasAsyncRoutines(m_hybridGlyphGroups); + + if (!asyncRoutines && m_nothingToUpload.test_and_set()) + return false; m_colorTexture->UpdateState(); m_stipplePenTexture->UpdateState(); UpdateGlyphTextures(m_glyphGroups); UpdateGlyphTextures(m_hybridGlyphGroups); + + return true; } ref_ptr TextureManager::AllocateGlyphTexture() diff --git a/drape/texture_manager.hpp b/drape/texture_manager.hpp index 9ba687ec8f..c6bda89f61 100644 --- a/drape/texture_manager.hpp +++ b/drape/texture_manager.hpp @@ -89,7 +89,7 @@ public: /// On some devices OpenGL driver can't resolve situation when we upload on texture from one thread /// and use this texture to render on other thread. By this we move UpdateDynamicTextures call into render thread /// If you implement some kind of dynamic texture, you must synchronyze UploadData and index creation operations - void UpdateDynamicTextures(); + bool UpdateDynamicTextures(); private: struct GlyphGroup @@ -196,6 +196,16 @@ private: g.m_texture->UpdateState(); } + template + bool HasAsyncRoutines(TGlyphGroups & groups) + { + for (auto & g : groups) + if (g.m_texture != nullptr && g.m_texture->HasAsyncRoutines()) + return true; + + return false; + } + static constexpr size_t GetInvalidGlyphGroup(); private: diff --git a/drape_frontend/frontend_renderer.cpp b/drape_frontend/frontend_renderer.cpp index 87ae70e092..9e1beecea6 100755 --- a/drape_frontend/frontend_renderer.cpp +++ b/drape_frontend/frontend_renderer.cpp @@ -740,12 +740,12 @@ void FrontendRenderer::Routine::Do() while (!IsCancelled()) { context->setDefaultFramebuffer(); - m_renderer.m_texMng->UpdateDynamicTextures(); + bool const hasAsyncRoutines = m_renderer.m_texMng->UpdateDynamicTextures(); m_renderer.RenderScene(modelView); bool const animActive = InterpolationHolder::Instance().Advance(frameTime); modelView = m_renderer.UpdateScene(viewChanged); - if (!viewChanged && m_renderer.IsQueueEmpty() && !animActive) + if (!viewChanged && m_renderer.IsQueueEmpty() && !animActive && !hasAsyncRoutines) ++inactiveFrameCount; else inactiveFrameCount = 0;