2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.h>
22 #include <dali/dali.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
28 #include <dali-toolkit/public-api/controls/control-depth-index-ranges.h>
29 #include <dali-toolkit/internal/text/rendering/atlas/atlas-glyph-manager.h>
32 using namespace Dali::Toolkit;
33 using namespace Dali::Toolkit::Text;
37 #if defined(DEBUG_ENABLED)
38 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_RENDERING");
41 const float ZERO( 0.0f );
42 const float HALF( 0.5f );
43 const float ONE( 1.0f );
44 const float TWO( 2.0f );
45 const uint32_t DEFAULT_ATLAS_WIDTH = 512u;
46 const uint32_t DEFAULT_ATLAS_HEIGHT = 512u;
48 struct AtlasRenderer::Impl : public ConnectionTracker
61 AtlasManager::Mesh2D mMesh;
62 FrameBufferImage mBuffer;
71 float mUnderlinePosition;
72 float mUnderlineThickness;
73 uint32_t mMeshRecordIndex;
79 Text::GlyphIndex mIndex;
85 uint32_t mNeededBlockWidth;
86 uint32_t mNeededBlockHeight;
91 mGlyphManager = AtlasGlyphManager::Get();
92 mFontClient = TextAbstraction::FontClient::Get();
94 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
95 mQuadVertexFormat[ "aTexCoord" ] = Property::VECTOR2;
96 mQuadIndexFormat[ "indices" ] = Property::UNSIGNED_INTEGER;
99 void AddGlyphs( const std::vector<Vector2>& positions,
100 const Vector<GlyphInfo>& glyphs,
101 const Vector4& textColor,
102 const Vector2& shadowOffset,
103 const Vector4& shadowColor,
104 bool underlineEnabled,
105 const Vector4& underlineColor,
106 float underlineHeight )
108 AtlasManager::AtlasSlot slot;
109 std::vector< MeshRecord > meshContainer;
110 Vector< Extent > extents;
112 float currentUnderlinePosition = ZERO;
113 float currentUnderlineThickness = underlineHeight;
114 uint32_t currentBlockSize = 0;
115 FontId lastFontId = 0;
116 Style style = STYLE_NORMAL;
118 if ( fabsf( shadowOffset.x ) > Math::MACHINE_EPSILON_1 || fabsf( shadowOffset.y ) > Math::MACHINE_EPSILON_1 )
120 style = STYLE_DROP_SHADOW;
123 if ( mImageIds.Size() )
125 // Unreference any currently used glyphs
129 CalculateBlocksSize( glyphs );
131 for( uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i )
133 const GlyphInfo& glyph = glyphs[ i ];
135 // No operation for white space
136 if ( glyph.width && glyph.height )
138 // Are we still using the same fontId as previous
139 if ( glyph.fontId != lastFontId )
141 // We need to fetch fresh font underline metrics
142 FontMetrics fontMetrics;
143 mFontClient.GetFontMetrics( glyph.fontId, fontMetrics );
144 currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) );
145 float descender = ceil( fabsf( fontMetrics.descender ) );
147 if ( underlineHeight == ZERO )
149 currentUnderlineThickness = fontMetrics.underlineThickness;
151 // Ensure underline will be at least a pixel high
152 if ( currentUnderlineThickness < ONE )
154 currentUnderlineThickness = ONE;
158 currentUnderlineThickness = ceil( currentUnderlineThickness );
162 // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
163 if ( currentUnderlinePosition > descender )
165 currentUnderlinePosition = descender;
167 if ( ZERO == currentUnderlinePosition )
169 // Move offset down by one ( EFL behavior )
170 currentUnderlinePosition = ONE;
174 const Vector2& position = positions[ i ];
175 AtlasManager::Mesh2D newMesh;
176 mGlyphManager.Cached( glyph.fontId, glyph.index, slot );
180 // This glyph already exists so generate mesh data plugging in our supplied position
181 mGlyphManager.GenerateMeshData( slot.mImageId, position, newMesh );
182 mImageIds.PushBack( slot.mImageId );
187 // Select correct size for new atlas if needed....?
188 if ( lastFontId != glyph.fontId )
190 for ( uint32_t j = 0; j < mBlockSizes.size(); ++j )
192 if ( mBlockSizes[ j ].mFontId == glyph.fontId )
194 currentBlockSize = j;
195 mGlyphManager.SetNewAtlasSize( DEFAULT_ATLAS_WIDTH,
196 DEFAULT_ATLAS_HEIGHT,
197 mBlockSizes[ j ].mNeededBlockWidth,
198 mBlockSizes[ j ].mNeededBlockHeight );
203 // Create a new image for the glyph
204 BufferImage bitmap = mFontClient.CreateBitmap( glyph.fontId, glyph.index );
207 // Ensure that the next image will fit into the current block size
208 bool setSize = false;
209 if ( bitmap.GetWidth() > mBlockSizes[ currentBlockSize ].mNeededBlockWidth )
212 mBlockSizes[ currentBlockSize ].mNeededBlockWidth = bitmap.GetWidth();
214 if ( bitmap.GetHeight() > mBlockSizes[ currentBlockSize ].mNeededBlockHeight )
217 mBlockSizes[ currentBlockSize ].mNeededBlockHeight = bitmap.GetHeight();
222 mGlyphManager.SetNewAtlasSize( DEFAULT_ATLAS_WIDTH,
223 DEFAULT_ATLAS_HEIGHT,
224 mBlockSizes[ currentBlockSize ].mNeededBlockWidth,
225 mBlockSizes[ currentBlockSize ].mNeededBlockHeight );
228 // Locate a new slot for our glyph
229 mGlyphManager.Add( glyph, bitmap, slot );
231 // Generate mesh data for this quad, plugging in our supplied position
234 mGlyphManager.GenerateMeshData( slot.mImageId, position, newMesh );
235 mImageIds.PushBack( slot.mImageId );
239 // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas)
240 StitchTextMesh( meshContainer,
244 position.y + glyph.yBearing,
245 currentUnderlinePosition,
246 currentUnderlineThickness,
248 lastFontId = glyph.fontId;
252 if ( underlineEnabled )
254 // Check to see if any of the text needs an underline
255 GenerateUnderlines( meshContainer, extents, underlineColor, textColor );
258 // For each MeshData object, create a mesh actor and add to the renderable actor
259 if ( meshContainer.size() )
261 for ( std::vector< MeshRecord >::iterator mIt = meshContainer.begin(); mIt != meshContainer.end(); ++mIt )
263 Actor actor = CreateMeshActor( *mIt );
265 // Create an effect if necessary
266 if ( style == STYLE_DROP_SHADOW )
268 actor.Add( GenerateShadow( *mIt, shadowOffset, shadowColor ) );
280 mActor.OffStageSignal().Connect( this, &AtlasRenderer::Impl::OffStageDisconnect );
282 #if defined(DEBUG_ENABLED)
283 Toolkit::AtlasGlyphManager::Metrics metrics = mGlyphManager.GetMetrics();
284 DALI_LOG_INFO( gLogFilter, Debug::General, "TextAtlasRenderer::GlyphManager::GlyphCount: %i, AtlasCount: %i, TextureMemoryUse: %iK\n",
286 metrics.mAtlasMetrics.mAtlasCount,
287 metrics.mAtlasMetrics.mTextureMemoryUsed / 1024 );
288 for ( uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i )
290 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Atlas [%i] %sPixels: %s Size: %ix%i, BlockSize: %ix%i, BlocksUsed: %i/%i\n",
291 i + 1, i > 8 ? "" : " ",
292 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mPixelFormat == Pixel::L8 ? "L8 " : "BGRA",
293 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mWidth,
294 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mHeight,
295 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mBlockWidth,
296 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mBlockHeight,
297 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mBlocksUsed,
298 metrics.mAtlasMetrics.mAtlasMetrics[ i ].mTotalBlocks );
303 Actor CreateMeshActor( const MeshRecord& meshRecord )
305 PropertyBuffer quadVertices = PropertyBuffer::New( mQuadVertexFormat, meshRecord.mMesh.mVertices.Size() );
306 PropertyBuffer quadIndices = PropertyBuffer::New( mQuadIndexFormat, meshRecord.mMesh.mIndices.Size() );
307 quadVertices.SetData( const_cast< AtlasManager::Vertex2D* >( &meshRecord.mMesh.mVertices[ 0 ] ) );
308 quadIndices.SetData( const_cast< unsigned int* >( &meshRecord.mMesh.mIndices[ 0 ] ) );
310 Geometry quadGeometry = Geometry::New();
311 quadGeometry.AddVertexBuffer( quadVertices );
312 quadGeometry.SetIndexBuffer( quadIndices );
314 Material material = mGlyphManager.GetMaterial( meshRecord.mAtlasId );
315 Dali::Renderer renderer = Dali::Renderer::New( quadGeometry, material );
316 Actor actor = Actor::New();
317 actor.AddRenderer( renderer );
318 actor.SetSize( 1.0f, 1.0f );
319 actor.SetColor( meshRecord.mColor );
321 if ( meshRecord.mIsUnderline )
323 actor.SetColorMode( USE_OWN_COLOR );
327 actor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR );
332 void StitchTextMesh( std::vector< MeshRecord >& meshContainer,
333 AtlasManager::Mesh2D& newMesh,
334 Vector< Extent >& extents,
335 const Vector4& color,
337 float underlinePosition,
338 float underlineThickness,
339 AtlasManager::AtlasSlot& slot )
343 float left = newMesh.mVertices[ 0 ].mPosition.x;
344 float right = newMesh.mVertices[ 1 ].mPosition.x;
346 // Check to see if there's a mesh data object that references the same atlas ?
348 for ( std::vector< MeshRecord >::iterator mIt = meshContainer.begin(); mIt != meshContainer.end(); ++mIt, ++index )
350 if ( slot.mAtlasId == mIt->mAtlasId && color == mIt->mColor )
352 // Stitch the mesh to the existing mesh and adjust any extents
353 mGlyphManager.StitchMesh( mIt->mMesh, newMesh );
354 AdjustExtents( extents,
361 underlineThickness );
366 // No mesh data object currently exists that references this atlas, so create a new one
367 MeshRecord meshRecord;
368 meshRecord.mAtlasId = slot.mAtlasId;
369 meshRecord.mMesh = newMesh;
370 meshRecord.mColor = color;
371 meshRecord.mIsUnderline = false;
372 meshContainer.push_back( meshRecord );
374 // Adjust extents for this new meshrecord
375 AdjustExtents( extents,
377 meshContainer.size() - 1u,
382 underlineThickness );
387 void AdjustExtents( Vector< Extent >& extents,
388 std::vector< MeshRecord>& meshRecords,
393 float underlinePosition,
394 float underlineThickness )
396 bool foundExtent = false;
397 for ( Vector< Extent >::Iterator eIt = extents.Begin(); eIt != extents.End(); ++eIt )
399 if ( Equals( baseLine, eIt->mBaseLine ) )
402 if ( left < eIt->mLeft )
406 if ( right > eIt->mRight )
411 if ( underlinePosition > eIt->mUnderlinePosition )
413 eIt->mUnderlinePosition = underlinePosition;
415 if ( underlineThickness > eIt->mUnderlineThickness )
417 eIt->mUnderlineThickness = underlineThickness;
425 extent.mRight = right;
426 extent.mBaseLine = baseLine;
427 extent.mUnderlinePosition = underlinePosition;
428 extent.mUnderlineThickness = underlineThickness;
429 extent.mMeshRecordIndex = index;
430 extents.PushBack( extent );
434 // Unreference any glyphs that were used with this actor
435 void OffStageDisconnect( Dali::Actor actor )
442 for ( uint32_t i = 0; i < mImageIds.Size(); ++i )
444 mGlyphManager.Remove( mImageIds[ i ] );
446 mImageIds.Resize( 0 );
449 void CalculateBlocksSize( const Vector<GlyphInfo>& glyphs )
451 MaxBlockSize maxBlockSize;
452 for ( uint32_t i = 0; i < glyphs.Size(); ++i )
454 FontId fontId = glyphs[ i ].fontId;
455 bool foundFont = false;
456 for ( uint32_t j = 0; j < mBlockSizes.size(); ++j )
458 if ( mBlockSizes[ j ].mFontId == fontId )
465 FontMetrics fontMetrics;
466 mFontClient.GetFontMetrics( fontId, fontMetrics );
467 maxBlockSize.mNeededBlockWidth = static_cast< uint32_t >( fontMetrics.height );
468 maxBlockSize.mNeededBlockHeight = static_cast< uint32_t >( fontMetrics.height );
469 maxBlockSize.mFontId = fontId;
470 mBlockSizes.push_back( maxBlockSize );
475 void GenerateUnderlines( std::vector< MeshRecord>& meshRecords,
476 Vector< Extent >& extents,
477 const Vector4& underlineColor,
478 const Vector4& textColor )
480 AtlasManager::Mesh2D newMesh;
481 unsigned short faceIndex = 0;
482 for ( Vector< Extent >::ConstIterator eIt = extents.Begin(); eIt != extents.End(); ++eIt )
484 AtlasManager::Vertex2D vert;
485 uint32_t index = eIt->mMeshRecordIndex;
486 Vector2 uv = mGlyphManager.GetAtlasSize( meshRecords[ index ].mAtlasId );
488 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
489 float u = HALF / uv.x;
490 float v = HALF / uv.y;
491 float thickness = eIt->mUnderlineThickness;
492 float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ( thickness * HALF );
493 float tlx = eIt->mLeft;
494 float brx = eIt->mRight;
496 vert.mPosition.x = tlx;
497 vert.mPosition.y = baseLine;
498 vert.mTexCoords.x = ZERO;
499 vert.mTexCoords.y = ZERO;
500 newMesh.mVertices.PushBack( vert );
502 vert.mPosition.x = brx;
503 vert.mPosition.y = baseLine;
504 vert.mTexCoords.x = u;
505 newMesh.mVertices.PushBack( vert );
507 vert.mPosition.x = tlx;
508 vert.mPosition.y = baseLine + thickness;
509 vert.mTexCoords.x = ZERO;
510 vert.mTexCoords.y = v;
511 newMesh.mVertices.PushBack( vert );
513 vert.mPosition.x = brx;
514 vert.mPosition.y = baseLine + thickness;
515 vert.mTexCoords.x = u;
516 newMesh.mVertices.PushBack( vert );
518 // Six indices in counter clockwise winding
519 newMesh.mIndices.PushBack( faceIndex + 1u );
520 newMesh.mIndices.PushBack( faceIndex );
521 newMesh.mIndices.PushBack( faceIndex + 2u );
522 newMesh.mIndices.PushBack( faceIndex + 2u );
523 newMesh.mIndices.PushBack( faceIndex + 3u );
524 newMesh.mIndices.PushBack( faceIndex + 1u );
527 if ( underlineColor == textColor )
529 mGlyphManager.StitchMesh( meshRecords[ index ].mMesh, newMesh );
534 record.mMesh = newMesh;
535 record.mAtlasId = meshRecords[ index ].mAtlasId;
536 record.mColor = underlineColor;
537 record.mIsUnderline = true;
538 meshRecords.push_back( record );
543 Actor GenerateShadow( MeshRecord& meshRecord,
544 const Vector2& shadowOffset,
545 const Vector4& shadowColor )
547 // Scan vertex buffer to determine width and height of effect buffer needed
548 const Vector< AtlasManager::Vertex2D >& verts = meshRecord.mMesh.mVertices;
549 float tlx = verts[ 0 ].mPosition.x;
550 float tly = verts[ 0 ].mPosition.y;
554 for ( uint32_t i = 0; i < verts.Size(); ++i )
556 if ( verts[ i ].mPosition.x < tlx )
558 tlx = verts[ i ].mPosition.x;
560 if ( verts[ i ].mPosition.y < tly )
562 tly = verts[ i ].mPosition.y;
564 if ( verts[ i ].mPosition.x > brx )
566 brx = verts[ i ].mPosition.x;
568 if ( verts[ i ].mPosition.y > bry )
570 bry = verts[ i ].mPosition.y;
574 float width = brx - tlx;
575 float height = bry - tly;
576 float divWidth = TWO / width;
577 float divHeight = TWO / height;
579 // Create a buffer to render to
580 meshRecord.mBuffer = FrameBufferImage::New( width, height );
582 // We will render a quad into this buffer
583 unsigned int indices[ 6 ] = { 1, 0, 2, 2, 3, 1 };
584 PropertyBuffer quadVertices = PropertyBuffer::New( mQuadVertexFormat, 4u );
585 PropertyBuffer quadIndices = PropertyBuffer::New( mQuadIndexFormat, sizeof(indices)/sizeof(indices[0]) );
587 AtlasManager::Vertex2D vertices[ 4 ] = {
588 { Vector2( tlx + shadowOffset.x, tly + shadowOffset.y ), Vector2( ZERO, ZERO ) },
589 { Vector2( brx + shadowOffset.x, tly + shadowOffset.y ), Vector2( ONE, ZERO ) },
590 { Vector2( tlx + shadowOffset.x, bry + shadowOffset.y ), Vector2( ZERO, ONE ) },
591 { Vector2( brx + shadowOffset.x, bry + shadowOffset.y ), Vector2( ONE, ONE ) } };
593 quadVertices.SetData( vertices );
594 quadIndices.SetData( indices );
596 Geometry quadGeometry = Geometry::New();
597 quadGeometry.AddVertexBuffer( quadVertices );
598 quadGeometry.SetIndexBuffer( quadIndices );
600 Sampler sampler = Sampler::New( meshRecord.mBuffer, "sTexture" );
601 Material material = Material::New( mGlyphManager.GetEffectBufferShader() );
602 material.AddSampler( sampler );
604 Dali::Renderer renderer = Dali::Renderer::New( quadGeometry, material );
606 // Ensure shadow is behind the text...
607 renderer.SetDepthIndex( CONTENT_DEPTH_INDEX - 1 );
608 Actor actor = Actor::New();
609 actor.AddRenderer( renderer );
610 actor.SetSize( 1.0f, 1.0f );
612 // Create a sub actor to render the source with normalized vertex positions
613 Vector< AtlasManager::Vertex2D > normVertexList;
614 for ( uint32_t i = 0; i < verts.Size(); ++i )
616 AtlasManager::Vertex2D vertex = verts[ i ];
617 vertex.mPosition.x = ( ( vertex.mPosition.x - tlx ) * divWidth ) - ONE;
618 vertex.mPosition.y = ( ( vertex.mPosition.y - tly ) * divHeight ) - ONE;
619 normVertexList.PushBack( vertex );
622 PropertyBuffer normVertices = PropertyBuffer::New( mQuadVertexFormat, normVertexList.Size() );
623 PropertyBuffer normIndices = PropertyBuffer::New( mQuadIndexFormat, meshRecord.mMesh.mIndices.Size() );
624 normVertices.SetData( const_cast< AtlasManager::Vertex2D* >( &normVertexList[ 0 ] ) );
625 normIndices.SetData( const_cast< unsigned int* >( &meshRecord.mMesh.mIndices[ 0 ] ) );
627 Geometry normGeometry = Geometry::New();
628 normGeometry.AddVertexBuffer( normVertices );
629 normGeometry.SetIndexBuffer( normIndices );
631 Material normMaterial = Material::New( mGlyphManager.GetGlyphShadowShader() );
632 Sampler normSampler = mGlyphManager.GetSampler( meshRecord.mAtlasId );
633 normMaterial.AddSampler( normSampler );
634 Dali::Renderer normRenderer = Dali::Renderer::New( normGeometry, normMaterial );
635 Actor subActor = Actor::New();
636 subActor.AddRenderer( normRenderer );
637 subActor.SetSize( 1.0f, 1.0f );
638 subActor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR );
639 subActor.SetColor( shadowColor );
641 // Create a render task to render the effect
642 RenderTask task = Stage::GetCurrent().GetRenderTaskList().CreateTask();
643 task.SetTargetFrameBuffer( meshRecord.mBuffer );
644 task.SetSourceActor( subActor );
645 task.SetClearEnabled( true );
646 task.SetClearColor( Vector4::ZERO );
647 task.SetExclusive( true );
648 task.SetRefreshRate( RenderTask::REFRESH_ONCE );
649 task.FinishedSignal().Connect( this, &AtlasRenderer::Impl::RenderComplete );
650 actor.Add( subActor );
655 void RenderComplete( RenderTask& renderTask )
657 // Disconnect and remove this single shot render task
658 renderTask.FinishedSignal().Disconnect( this, &AtlasRenderer::Impl::RenderComplete );
659 Stage::GetCurrent().GetRenderTaskList().RemoveTask( renderTask );
661 // Get the actor used for render to buffer and remove it from the parent
662 Actor renderActor = renderTask.GetSourceActor();
665 Actor parent = renderActor.GetParent();
668 parent.Remove( renderActor );
673 Actor mActor; ///< The actor parent which renders the text
674 AtlasGlyphManager mGlyphManager; ///< Glyph Manager to handle upload and caching
675 Vector< uint32_t > mImageIds; ///< A list of imageIDs used by the renderer
676 TextAbstraction::FontClient mFontClient; ///> The font client used to supply glyph information
677 std::vector< MaxBlockSize > mBlockSizes; ///> Maximum size needed to contain a glyph in a block within a new atlas
678 std::vector< uint32_t > mFace; ///> Face indices for a quad
679 Property::Map mQuadVertexFormat;
680 Property::Map mQuadIndexFormat;
683 Text::RendererPtr AtlasRenderer::New()
685 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Text::AtlasRenderer::New()\n" );
687 return Text::RendererPtr( new AtlasRenderer() );
690 Actor AtlasRenderer::Render( Text::ViewInterface& view )
692 UnparentAndReset( mImpl->mActor );
694 Length numberOfGlyphs = view.GetNumberOfGlyphs();
696 if( numberOfGlyphs > 0u )
698 Vector<GlyphInfo> glyphs;
699 glyphs.Resize( numberOfGlyphs );
701 std::vector<Vector2> positions;
702 positions.resize( numberOfGlyphs );
704 numberOfGlyphs = view.GetGlyphs( glyphs.Begin(),
708 glyphs.Resize( numberOfGlyphs );
709 positions.resize( numberOfGlyphs );
711 mImpl->AddGlyphs( positions,
714 view.GetShadowOffset(),
715 view.GetShadowColor(),
716 view.IsUnderlineEnabled(),
717 view.GetUnderlineColor(),
718 view.GetUnderlineHeight() );
721 return mImpl->mActor;
724 AtlasRenderer::AtlasRenderer()
730 AtlasRenderer::~AtlasRenderer()