License conversion from Flora to Apache 2.0
[platform/core/uifw/dali-core.git] / dali / internal / event / text / resource / glyph-resource-manager.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18
19 // CLASS HEADER
20 #include "glyph-resource-manager.h"
21
22 // INTERNAL INCLUDES
23 #include <dali/integration-api/debug.h>
24 #include <dali/integration-api/platform-abstraction.h>
25 #include <dali/internal/event/common/thread-local-storage.h>
26 #include <dali/internal/event/resources/resource-client.h>
27 #include <dali/public-api/images/pixel.h>
28 #include <dali/internal/event/text/resource/debug/glyph-resource-debug.h>
29
30 using namespace Dali::Integration;
31
32 namespace Dali
33 {
34
35 namespace Internal
36 {
37
38 namespace
39 {
40 /**
41  * Convert the quality level into a loaded status
42  */
43 GlyphResourceObserver::Quality GetGlyphStatus( unsigned int quality )
44 {
45   if( quality == Integration::GlyphMetrics::LOW_QUALITY)
46   {
47     return GlyphResourceObserver::LOW_QUALITY_LOADED;
48   }
49   else
50   {
51     return GlyphResourceObserver::HIGH_QUALITY_LOADED;
52   }
53 }
54 } // un-named namespace
55
56 GlyphResourceManager::GlyphResourceManager( const FontLookupInterface& fontLookup )
57 :mFontLookup( fontLookup ),
58  mResourceClient( ThreadLocalStorage::Get().GetResourceClient() )
59 {
60 }
61
62 GlyphResourceManager::~GlyphResourceManager()
63 {
64
65 }
66
67 unsigned int GlyphResourceManager::CreateTexture(unsigned int size )
68 {
69   // create a new texture. Using Alpha 8 = 1 byte per pixel
70   ResourceTicketPtr ticket = mResourceClient.AllocateTexture( size, size, Pixel::A8 );
71
72   mTextureTickets.push_back( ticket );
73
74   // return the texture id
75   return ticket->GetId();
76 }
77
78 void GlyphResourceManager::AddObserver( GlyphResourceObserver& observer)
79 {
80   DALI_ASSERT_DEBUG( ( mObservers.find( &observer ) == mObservers.end() ) && "Observer already exists");
81   mObservers.insert( &observer );
82 }
83
84 void GlyphResourceManager::RemoveObserver( GlyphResourceObserver& observer)
85 {
86   DALI_ASSERT_DEBUG( ( mObservers.find( &observer ) != mObservers.end() ) && "Observer not found");
87   mObservers.erase( &observer );
88 }
89
90 void GlyphResourceManager::AddTextObserver( TextObserver& observer)
91 {
92   DALI_ASSERT_DEBUG( ( mTextObservers.find( &observer ) == mTextObservers.end() ) && "Observer already exists");
93   mTextObservers.insert( &observer );
94 }
95
96 void GlyphResourceManager::RemoveTextObserver( TextObserver& observer)
97 {
98   DALI_ASSERT_DEBUG( ( mTextObservers.find( &observer ) != mTextObservers.end() ) && "Observer doesn't exists");
99   mTextObservers.erase( &observer );
100 }
101
102
103 void GlyphResourceManager::AddRequests( const GlyphRequestList& requestList,
104                                         GlyphResourceObserver& observer,
105                                         ResourceId atlasTextureId)
106 {
107   // each entry in the request list is for a specific font,
108   // style, quality and a list of characters
109
110   for( std::size_t n = 0, size = requestList.size(); n < size ; ++n )
111   {
112     const GlyphResourceRequest& request( requestList[n] );
113     SendRequests( request, observer, atlasTextureId );
114   }
115 }
116
117 void GlyphResourceManager::GlyphsLoaded( Integration::ResourceId id, const Integration::GlyphSet& glyphSet, LoadStatus loadStatus )
118 {
119   // Get the the observer
120   GlyphResourceObserver* observer = GetObserver( id );
121
122   DALI_LOG_INFO( gTextLogFilter, Debug::General,
123                  "GlyphResourceManager::GlyphsLoaded: id:%d, status:%s textureId:%d observer:%p\n",
124                  id,
125                  loadStatus==RESOURCE_LOADING?"LOADING":loadStatus==RESOURCE_PARTIALLY_LOADED?"PARTIAL":"COMPLETED",
126                  glyphSet.GetAtlasResourceId(),
127                  observer);
128
129   DALI_LOG_INFO( gTextLogFilter, Debug::Verbose, "GlyphResourceManager::GlyphsLoaded: %s\n", DebugCharacterString(glyphSet).c_str() );
130
131   if( observer )
132   {
133     FontId fontId = glyphSet.mFontHash;
134
135     // Stage 1. Inform the observer of the characters that have been loaded
136     UpdateObserver( observer, fontId, glyphSet, GLYPH_LOADED_FROM_FILE );
137
138     // Stage 2. Tell the observers the glyphs have been uploaded to GL (Resource manager is
139     // responsible for this now)
140     //
141     // @todo If there is an issue with the timing of the text-loaded signal from text-actor
142     // we can set this status after the upload instead of before (using uploaded callback on texture ticket).
143     UpdateObserver( observer, fontId, glyphSet, GLYPH_UPLOADED_TO_GL );
144
145     // Stage 3. Tell the text-observers some text has been loaded.
146     // They can then query if the text they are using has been uploaded
147     NotifyTextObservers();
148
149     // Only remove the ticket when all the responses have been received
150     if( loadStatus == RESOURCE_COMPLETELY_LOADED )
151     {
152       mGlyphLoadTickets.erase( id );
153     }
154   }
155   else
156   {
157     // The observer has been deleted after the resource request was sent.
158     // Note, we may still get responses that are already in the system - in this case,
159     // ignore them.
160     mGlyphLoadTickets.erase( id );
161   }
162 }
163
164 void GlyphResourceManager::SendRequests( const GlyphResourceRequest& request, GlyphResourceObserver& observer, ResourceId atlasTextureId )
165 {
166   Integration::PlatformAbstraction& platform = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
167
168   // get the font information from the lookup, using the font id
169   std::string family, style;
170   float maxGlyphWidth, maxGlyphHeight;
171   FontId fontId = request.GetFontId();
172   mFontLookup.GetFontInformation( fontId, family, style, maxGlyphWidth, maxGlyphHeight );
173
174   // List of requested characters
175   const Integration::TextResourceType::CharacterList& requestedCharacters = request.GetCharacterList();
176   const size_t requestedCharacterCount = requestedCharacters.size();
177
178   DALI_LOG_INFO( gTextLogFilter, Debug::Verbose, "GlyphResourceManager::SendRequests() - requested character list: %s\n",
179                  DebugCharacterString(requestedCharacters).c_str() );
180
181   // create a new resource request for the characters
182   Integration::TextResourceType resourceType( fontId, style, requestedCharacters, atlasTextureId,
183                                               Integration::TextResourceType::TextQualityHigh, // TODO: Remove
184                                               Vector2 (maxGlyphWidth, maxGlyphHeight),
185                                               Integration::TextResourceType::GLYPH_CACHE_WRITE );
186
187   // Try to synchronously load cached versions of the glyph bitmaps
188   Integration::GlyphSetPointer cachedGlyphs = platform.GetCachedGlyphData( resourceType, family );
189   const GlyphSet::CharacterList& cachedCharacters = cachedGlyphs->GetCharacterList();  // list of cached glyphs
190   const size_t cachedCharacterCount = cachedCharacters.size();                         // number of cached glyphs
191
192   // Any glyphs loaded from cache?
193   if( 0u != cachedCharacterCount )
194   {
195     // yes..Upload cached bitmaps to texture
196     UploadGlyphsToTexture( &observer, fontId, *(cachedGlyphs.Get()) );
197     UpdateObserver( &observer, fontId, *(cachedGlyphs.Get()), GLYPH_UPLOADED_TO_GL );
198     NotifyTextObservers();
199   }
200
201   // Any glyphs still missing?
202   if( requestedCharacterCount != cachedCharacterCount )
203   {
204     // create a list of uncached/missing glyphs
205     Integration::TextResourceType::CharacterList uncachedCharacters;
206     for( size_t i = 0; i < requestedCharacterCount; ++i )
207     {
208       uint32_t charCode = requestedCharacters[ i ].character;
209       bool isCached = false;
210       for( size_t j = 0; j < cachedCharacterCount; ++j )
211       {
212         if( cachedCharacters[ j ].second.code == charCode )
213         {
214           isCached = true;
215           break;
216         }
217       }
218       if( !isCached )
219       {
220         uncachedCharacters.push_back( requestedCharacters[ i ] );
221       }
222     }
223
224     // replace requested character list with missing character list for resource request
225     resourceType.mCharacterList.assign( uncachedCharacters.begin(), uncachedCharacters.end() );
226
227     // Make asynchronous request for the missing glyphs
228     ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, family );
229
230     // store the ticket
231     mGlyphLoadTickets[ ticket->GetId() ] = ( ObserverTicketPair(ticket, &observer) );
232
233     DALI_LOG_INFO( gTextLogFilter, Debug::General, "GlyphResourceManager::SendRequests() - id:%d observer:%p\n",
234                    ticket->GetId(), &observer );
235
236     DALI_LOG_INFO( gTextLogFilter, Debug::Verbose, "GlyphResourceManager::SendRequests() - uncached character list:%s\n",
237                    DebugCharacterString(uncachedCharacters).c_str() );
238
239     // Also synchronously load low quality version of glyphs
240     resourceType.mQuality = Integration::TextResourceType::TextQualityLow;
241     Integration::GlyphSetPointer lowQualityGlyphPointer = platform.GetGlyphData( resourceType, family, true );
242     Integration::GlyphSet& lowQualityGlyphs = *(lowQualityGlyphPointer.Get());
243     size_t lowQualityCharacterCount = lowQualityGlyphs.GetCharacterList().size();
244
245     // Any low quality glyphs loaded?
246     if( 0u != lowQualityCharacterCount )
247     {
248       // yes..Upload cached bitmaps to texture
249       UploadGlyphsToTexture( &observer, fontId, lowQualityGlyphs );
250       // Update atlas load status in update thread
251       mResourceClient.UpdateAtlasStatus( ticket->GetId(), resourceType.mTextureAtlasId, RESOURCE_PARTIALLY_LOADED );
252       // Notify observers and text observers that a partial load has occured
253       GlyphsLoaded( ticket->GetId(), lowQualityGlyphs, RESOURCE_PARTIALLY_LOADED );
254     }
255   }
256 }
257
258 void GlyphResourceManager::UploadGlyphsToTexture( GlyphResourceObserver* observer,
259                                                   FontId fontId,
260                                                   const Integration::GlyphSet& glyphSet )
261 {
262   // the glyphset contains an array of bitmap / characters .
263   // The function uploads the bitmaps to a texture
264   const Integration::GlyphSet::CharacterList& charList( glyphSet.GetCharacterList() );
265   BitmapUploadArray uploadArray;
266   for(std::size_t i = 0, count = charList.size() ; i < count; i++ )
267   {
268     const Integration::GlyphSet::Character& character( charList[i] );
269     uint32_t charCode = character.second.code;
270     unsigned int xPos,yPos;
271     // ask the observer (atlas) where the bitmap should be uploaded to
272     bool inUse = observer->GetGlyphTexturePosition( charCode, fontId, xPos, yPos );
273     if( !inUse )
274     {
275       // if it's no longer used, don't upload
276       continue;
277     }
278     // grab a pointer to the bitmap
279     Integration::Bitmap* bitmap( charList[ i ].first.Get() );
280     unsigned int bitmapWidth  = bitmap->GetImageWidth();
281     unsigned int bitmapHeight = bitmap->GetImageHeight();
282     Bitmap::PackedPixelsProfile* packedBitmap = bitmap->GetPackedPixelsProfile();
283     if( NULL != packedBitmap )
284     {
285       bitmapWidth  = packedBitmap->GetBufferWidth();
286       bitmapHeight = packedBitmap->GetBufferHeight();
287     }
288
289     // create a bitmap upload object, then add it to the array
290     BitmapUpload upload( bitmap->ReleaseBuffer(), // Inform the bitmap we're taking ownership of it's pixel buffer.
291                          xPos,          // x position in the texture to upload the bitmap to
292                          yPos,          // y position in the texture to upload the bitmap to
293                          bitmapWidth,   // bitmap width
294                          bitmapHeight,  // bitmap height
295                          BitmapUpload::DISCARD_PIXEL_DATA ); // tell the the texture to delete the bitmap pixel buffer when it's done
296     uploadArray.push_back( upload );
297   }
298   // update the texture
299   mResourceClient.UpdateTexture( observer->GetTextureId(), uploadArray );
300 }
301
302 void GlyphResourceManager::NotifyTextObservers()
303 {
304   // copy this list so, the observers can remove themselves during the call back
305   TextObserverList observerList( mTextObservers );
306
307   TextObserverList::iterator iter( observerList.begin() );
308   TextObserverList::const_iterator endIter( observerList.end() );
309   for( ; iter != endIter; ++iter )
310   {
311     TextObserver* observer((*iter));
312     observer->TextLoaded();
313   }
314 }
315
316 void GlyphResourceManager::DeleteOldTextures( GlyphResourceObserver* observer )
317 {
318   // see if the observer is doing a texture-resize operation
319   GlyphResourceObserver::TextureState textureState = observer->GetTextureState();
320
321   if( textureState == GlyphResourceObserver::TEXTURE_RESIZED )
322   {
323     unsigned int newTexture;
324     TextureIdList oldTextures;
325
326     observer->GetNewTextureId( oldTextures, newTexture );
327
328     // the old texture(s) can be deleted,
329     // this is done automatically when we release the ticket
330     for( std::size_t i = 0; i< oldTextures.size(); i++ )
331     {
332       DeleteTextureTicket( oldTextures[i] );
333     }
334   }
335 }
336
337 void GlyphResourceManager::UpdateObserver( GlyphResourceObserver* observer,
338                                            FontId fontId,
339                                            const Integration::GlyphSet& glyphSet,
340                                            GlyphUpdateType updateType)
341 {
342   const Integration::GlyphSet::CharacterList& charList( glyphSet.GetCharacterList() );
343
344   for(std::size_t i = 0, count = charList.size() ; i < count; i++ )
345   {
346     const Integration::GlyphSet::Character& character( charList[i] );
347
348     uint32_t charCode = character.second.code;
349     uint32_t quality = character.second.quality;
350
351     if( updateType == GLYPH_LOADED_FROM_FILE)
352     {
353       observer->GlyphLoadedFromFile( charCode, fontId,  GetGlyphStatus(  quality ) );
354     }
355     else // updateType == GLYPH_UPLOADED_TO_GL
356     {
357       observer->GlyphUpLoadedToTexture( charCode, fontId );
358     }
359   }
360
361   if( updateType == GLYPH_UPLOADED_TO_GL )
362   {
363     DeleteOldTextures(observer);
364   }
365 }
366
367 GlyphResourceObserver* GlyphResourceManager::GetObserver( Integration::ResourceId id )
368 {
369   GlyphResourceObserver* observer = NULL;
370
371   // Get the observer for a resource
372   TicketList::iterator iter = mGlyphLoadTickets.find( id );
373
374   if( iter != mGlyphLoadTickets.end() ) // Only check for observers if the ticket is still alive
375   {
376     ObserverTicketPair& observerTicket ( (*iter).second );
377     observer = observerTicket.second;
378
379     // check if the atlas is still alive and in the observer list
380     if( mObservers.find( observer ) == mObservers.end() )
381     {
382       observer = NULL;
383     }
384   }
385
386   return observer;
387 }
388
389 void GlyphResourceManager::DeleteTextureTicket(unsigned int id )
390 {
391   TextureTickets::iterator endIter;
392
393   for( TextureTickets::iterator iter = mTextureTickets.begin();  iter != endIter; ++iter )
394   {
395     ResourceTicketPtr ticket = (*iter);
396     if( ticket->GetId() == id )
397     {
398       mTextureTickets.erase( iter );
399       return;
400     }
401   }
402 }
403
404 Integration::TextResourceType::TextQuality GlyphResourceManager::GetQuality( GlyphResourceRequest::GlyphQuality quality )
405 {
406   if( quality == GlyphResourceRequest::LOW_QUALITY)
407   {
408     return Integration::TextResourceType::TextQualityLow;
409   }
410   return Integration::TextResourceType::TextQualityHigh;
411 }
412
413 } // namespace Internal
414
415 } // namespace Dali