字体渲染过程及合批研究
2022-06-11 20:13 kk20161206 阅读(622) 评论(0) 收藏 举报---恢复内容开始---
FSlateFontTextureRHI
FSlateFontTextureRHI
if (FontAtlasTexture == nullptr || GlyphAtlasData.TextureIndex != FontTextureIndex)
{
// Font has a new texture for this glyph. Refresh the batch we use and the index we are currently using
FontTextureIndex = GlyphAtlasData.TextureIndex;
FontAtlasTexture = FontCache.GetSlateTextureResource(FontTextureIndex);
check(FontAtlasTexture);
FontShaderResource = ResourceManager.GetFontShaderResource(FontTextureIndex, FontAtlasTexture, FontMaterial);
check(FontShaderResource);
ElementBatch = &FindBatchForElement(InLayer, FShaderParams(), FontShaderResource, ESlateDrawPrimitive::TriangleList, ESlateShader::Font, InDrawEffects, ESlateBatchDrawFlag::None, DrawElement.GetClippingIndex(), DrawElement.GetSceneIndex());
FSlateDataPayload
FSlateFontTextureRHIResource
FontCache.cpp
bool FSlateFontCache::AddNewEntry( const FCharacterRenderData InRenderData, uint8& OutTextureIndex, uint16& OutGlyphX, uint16& OutGlyphY, uint16& OutGlyphWidth, uint16& OutGlyphHeight )
{
const uint64 LastFlushRequestFrameDelta = GFrameCounter - FrameCounterLastFlushRequest;
……
}
将renderData拷贝到Slot
void FSlateTextureAtlas::CopyDataIntoSlot( const FAtlasedTextureSlot* SlotToCopyTo, const TArray<uint8>& Data )
{
// Copy pixel data to the texture
uint8* Start = &AtlasData[SlotToCopyTo->Y*AtlasWidth*BytesPerPixel + SlotToCopyTo->X*BytesPerPixel];
// Account for same padding on each sides
const uint32 Padding = GetPaddingAmount();
const uint32 AllPadding = Padding*2;
// Make sure the actual slot is not zero-area (otherwise padding could corrupt other images in the atlas)
check(SlotToCopyTo->Width > AllPadding);
check(SlotToCopyTo->Height > AllPadding);
// The width of the source texture without padding (actual width)
const uint32 SourceWidth = SlotToCopyTo->Width - AllPadding;
const uint32 SourceHeight = SlotToCopyTo->Height - AllPadding;
……
}

void FSlateElementBatcher::AddElementsInternal(const TArray<FSlateDrawElement>& DrawElements, const FVector2D& ViewportSize)
{
checkSlow(DrawLayer);
for ( int32 DrawElementIndex = 0; DrawElementIndex < DrawElements.Num(); ++DrawElementIndex )
{
const FSlateDrawElement& DrawElement = DrawElements[DrawElementIndex];
// Determine what type of element to add
switch ( DrawElement.GetElementType() )
{
case FSlateDrawElement::ET_Box:
ElmementStat_Boxes++;
DrawElement.IsPixelSnapped() ? AddBoxElement<ESlateVertexRounding::Enabled>(DrawElement) : AddBoxElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Border:
ElmementStat_Borders++;
DrawElement.IsPixelSnapped() ? AddBorderElement<ESlateVertexRounding::Enabled>(DrawElement) : AddBorderElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Text:
ElmementStat_Text++;
DrawElement.IsPixelSnapped() ? AddTextElement<ESlateVertexRounding::Enabled>(DrawElement) : AddTextElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_ShapedText:
ElmementStat_ShapedText++;
DrawElement.IsPixelSnapped() ? AddShapedTextElement<ESlateVertexRounding::Enabled>(DrawElement) : AddShapedTextElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Line:
ElmementStat_Line++;
DrawElement.IsPixelSnapped() ? AddLineElement<ESlateVertexRounding::Enabled>(DrawElement) : AddLineElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_DebugQuad:
ElmementStat_Other++;
DrawElement.IsPixelSnapped() ? AddQuadElement<ESlateVertexRounding::Enabled>(DrawElement) : AddQuadElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Spline:
// Note that we ignore pixel snapping here; see implementation for more info.
ElmementStat_Other++;
AddSplineElement(DrawElement);
break;
case FSlateDrawElement::ET_Gradient:
ElmementStat_Other++;
DrawElement.IsPixelSnapped() ? AddGradientElement<ESlateVertexRounding::Enabled>(DrawElement) : AddGradientElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Viewport:
ElmementStat_Other++;
DrawElement.IsPixelSnapped() ? AddViewportElement<ESlateVertexRounding::Enabled>(DrawElement) : AddViewportElement<ESlateVertexRounding::Disabled>(DrawElement);
break;
case FSlateDrawElement::ET_Custom:
ElmementStat_Other++;
AddCustomElement(DrawElement);
break;
case FSlateDrawElement::ET_CustomVerts:
ElmementStat_Other++;
AddCustomVerts(DrawElement);
break;
case FSlateDrawElement::ET_Layer:
ElmementStat_Other++;
AddLayer(DrawElement);
break;
case FSlateDrawElement::ET_CachedBuffer:
ElmementStat_CachedBuffer++;
AddCachedBuffer(DrawElement);
break;
case FSlateDrawElement::ET_PostProcessPass:
ElmementStat_Other++;
AddPostProcessPass(DrawElement, ViewportSize);
break;
default:
checkf(0, TEXT("Invalid element type"));
break;
}
}
}
这个函数根据类型DrawElement类型调用不同的函数,当类型为text时,调用AddTextElement函数;
当类型为ShapedTextElement时,调用AddShapedTextElement,(void FSlateElementBatcher::AddShapedTextElement( const FSlateDrawElement& DrawElement ))它里面的
auto BuildFontGeometry = [&](const FFontOutlineSettings& InOutlineSettings, const FColor& InTint, const UObject* FontMaterial, int32 InLayer, int32 InHorizontalOffset)
{
FVector2D TopLeft(0, 0);
const float PosX = TopLeft.X+InHorizontalOffset;
float PosY = TopLeft.Y;
float LineX = PosX;
float LineY = PosY;
int32 FontTextureIndex = -1;
FSlateShaderResource* FontAtlasTexture = nullptr;
FSlateShaderResource* FontShaderResource = nullptr;
FSlateElementBatch* ElementBatch = nullptr;
FSlateVertexArray* BatchVertices = nullptr;
FSlateIndexArray* BatchIndices = nullptr;
uint32 VertexOffset = 0;
uint32 IndexOffset = 0;
float InvTextureSizeX = 0;
float InvTextureSizeY = 0;
const bool bIsFontMaterial = FontMaterial != nullptr;
// Optimize by culling
bool bEnableCulling = false;
float LocalClipBoundingBoxLeft = 0;
float LocalClipBoundingBoxRight = 0;
if (GlyphsToRender.Num() > 200)
{
int16 ClippingIndex = DrawElement.GetClippingIndex();
if (ClippingStates && ClippingStates->IsValidIndex(ClippingIndex))
{
const FSlateClippingState& ClippingState = (*ClippingStates)[ClippingIndex];
if (ClippingState.ScissorRect.IsSet() && ClippingState.ScissorRect->IsAxisAligned() && RenderTransform.GetMatrix().IsIdentity())
{
bEnableCulling = true;
const FSlateRect LocalClipBoundingBox = TransformRect(RenderTransform.Inverse(), ClippingState.ScissorRect->GetBoundingBox());
LocalClipBoundingBoxLeft = LocalClipBoundingBox.Left;
LocalClipBoundingBoxRight = LocalClipBoundingBox.Right;
}
}
}
const int32 NumGlyphs = GlyphsToRender.Num();
for (int32 GlyphIndex = 0; GlyphIndex < NumGlyphs; ++GlyphIndex)
{
const FShapedGlyphEntry& GlyphToRender = GlyphsToRender[GlyphIndex];
if (GlyphToRender.bIsVisible)
{
const FShapedGlyphFontAtlasData GlyphAtlasData = FontCache.GetShapedGlyphFontAtlasData(GlyphToRender, InOutlineSettings);
……
//
}
里面调用了FontCache.GetShapedGlyphFontAtlasData(GlyphToRender, InOutlineSettings)
许多地方都可能调到这个地方,比如:

bool FSlateFontCache::AddNewEntry(const FShapedGlyphEntry& InShapedGlyph, const FFontOutlineSettings& InOutlineSettings, FShapedGlyphFontAtlasData& OutAtlasData)
{
// Render the glyph
FCharacterRenderData RenderData;
const bool bDidRender = FontRenderer->GetRenderData(InShapedGlyph, InOutlineSettings, RenderData);
OutAtlasData.Valid = bDidRender && AddNewEntry(RenderData, OutAtlasData.TextureIndex, OutAtlasData.StartU, OutAtlasData.StartV, OutAtlasData.USize, OutAtlasData.VSize);
if (OutAtlasData.Valid)
{
OutAtlasData.VerticalOffset = RenderData.MeasureInfo.VerticalOffset;
OutAtlasData.HorizontalOffset = RenderData.MeasureInfo.HorizontalOffset;
}
return OutAtlasData.Valid;
}
里面的GetRenderData函数
bool FSlateFontRenderer::GetRenderData(const FShapedGlyphEntry& InShapedGlyph, const FFontOutlineSettings& InOutlineSettings, FCharacterRenderData& OutRenderData) const
{
#if WITH_FREETYPE
SCOPE_CYCLE_COUNTER(STAT_FreetypeRenderGlyph);
FFreeTypeFaceGlyphData FaceGlyphData;
FaceGlyphData.FaceAndMemory = InShapedGlyph.FontFaceData->FontFace.Pin();
FaceGlyphData.GlyphIndex = InShapedGlyph.GlyphIndex;
FaceGlyphData.GlyphFlags = InShapedGlyph.FontFaceData->GlyphFlags;
if (FaceGlyphData.FaceAndMemory.IsValid())
{
check(FaceGlyphData.FaceAndMemory->IsValid());
FT_Error Error = FreeTypeUtils::LoadGlyph(FaceGlyphData.FaceAndMemory->GetFace(), FaceGlyphData.GlyphIndex, FaceGlyphData.GlyphFlags, InShapedGlyph.FontFaceData->FontSize, InShapedGlyph.FontFaceData->FontScale);
check(Error == 0);
OutRenderData.Char = 0;
return GetRenderDataInternal(FaceGlyphData, InShapedGlyph.FontFaceData->FontScale, InOutlineSettings, OutRenderData);
}
#endif // WITH_FREETYPE
return false;
}
LoadGlyph获得字体的大小,scale矩阵。GetRenderDataInternal获得要渲染的像素长宽,填充像素。
然后AddNewEntry函数又调用了另一个重载的AddNewEntry函数,这个函数主要作用是:根据上面求出的RenderData的数据,将Character加到AtlasedTextureSlot里。然后将这个图集贴图Slot加到图集里。得到这个字母的x, y, 宽,高值。
bool FSlateFontCache::AddNewEntry( const FCharacterRenderData InRenderData, uint8& OutTextureIndex, uint16& OutGlyphX, uint16& OutGlyphY, uint16& OutGlyphWidth, uint16& OutGlyphHeight )
{
const uint64 LastFlushRequestFrameDelta = GFrameCounter - FrameCounterLastFlushRequest;
// Will this entry fit within any atlas texture?
if (InRenderData.MeasureInfo.SizeX > FontAtlasFactory->GetAtlasSize().X || InRenderData.MeasureInfo.SizeY > FontAtlasFactory->GetAtlasSize().Y)
{
TSharedPtr<ISlateFontTexture> NonAtlasedTexture = FontAtlasFactory->CreateNonAtlasedTexture(InRenderData.MeasureInfo.SizeX, InRenderData.MeasureInfo.SizeY, InRenderData.RawPixels);
if (NonAtlasedTexture.IsValid())
{
INC_DWORD_STAT_BY(STAT_SlateNumFontNonAtlasedTextures, 1);
UE_LOG(LogSlate, Warning, TEXT("SlateFontCache - Glyph texture is too large to store in the font atlas, so we're falling back to a non-atlased texture for this glyph. This may have SERIOUS performance implications. Atlas page size: { %d, %d }. Glyph render size: { %d, %d }"),
FontAtlasFactory->GetAtlasSize().X, FontAtlasFactory->GetAtlasSize().Y,
InRenderData.MeasureInfo.SizeX, InRenderData.MeasureInfo.SizeY
);
NonAtlasedTextures.Add(NonAtlasedTexture.ToSharedRef());
OutTextureIndex = AllFontTextures.Add(NonAtlasedTexture.ToSharedRef());
OutGlyphX = 0;
OutGlyphY = 0;
OutGlyphWidth = InRenderData.MeasureInfo.SizeX;
OutGlyphHeight = InRenderData.MeasureInfo.SizeY;
if (NonAtlasedTextures.Num() > CurrentMaxNonAtlasedTexturesBeforeFlushRequest && !bFlushRequested)
{
// The InitialMaxNonAtlasedTexturesBeforeFlushRequest may have changed since last flush,
// so double check that we're below the current max or initial, if we're under it,
// update the current to the initial.
if (NonAtlasedTextures.Num() <= InitialMaxNonAtlasedTexturesBeforeFlushRequest)
{
CurrentMaxNonAtlasedTexturesBeforeFlushRequest = InitialMaxNonAtlasedTexturesBeforeFlushRequest;
}
// If we grew back up to this number of non-atlased textures within the same or next frame of the previous flush request, then we likely legitimately have
// a lot of font data cached. We should update CurrentMaxNonAtlasedTexturesBeforeFlushRequest to give us a bit more flexibility before the next flush request
else if (LastFlushRequestFrameDelta <= GrowFontNonAtlasFrameWindow)
{
CurrentMaxNonAtlasedTexturesBeforeFlushRequest = NonAtlasedTextures.Num();
UE_LOG(LogSlate, Warning, TEXT("SlateFontCache - Setting the threshold to trigger a flush to %d non-atlased textures as there is a lot of font data being cached."), CurrentMaxNonAtlasedTexturesBeforeFlushRequest);
}
else
{
// We've grown beyond our current stable limit - try and request a flush
RequestFlushCache(FString::Printf(TEXT("Large glyph font atlases out of space; %d/%d Textures; frames since last flush: %llu"), NonAtlasedTextures.Num(), CurrentMaxNonAtlasedTexturesBeforeFlushRequest, LastFlushRequestFrameDelta));
}
}
return true;
}
UE_LOG(LogSlate, Warning, TEXT("SlateFontCache - Glyph texture is too large to store in the font atlas, but we cannot support rendering such a large texture. Atlas page size: { %d, %d }. Glyph render size: { %d, %d }"),
FontAtlasFactory->GetAtlasSize().X, FontAtlasFactory->GetAtlasSize().Y,
InRenderData.MeasureInfo.SizeX, InRenderData.MeasureInfo.SizeY
);
return false;
}
auto FillOutputParamsFromAtlasedTextureSlot = [&](const FAtlasedTextureSlot& AtlasedTextureSlot)
{
OutGlyphX = AtlasedTextureSlot.X + AtlasedTextureSlot.Padding;
OutGlyphY = AtlasedTextureSlot.Y + AtlasedTextureSlot.Padding;
OutGlyphWidth = AtlasedTextureSlot.Width - (2 * AtlasedTextureSlot.Padding);
OutGlyphHeight = AtlasedTextureSlot.Height - (2 * AtlasedTextureSlot.Padding);
};
for( OutTextureIndex = 0; OutTextureIndex < FontAtlases.Num(); ++OutTextureIndex )
{
// Add the character to the texture
const FAtlasedTextureSlot* NewSlot = FontAtlases[OutTextureIndex]->AddCharacter(InRenderData);
if( NewSlot )
{
FillOutputParamsFromAtlasedTextureSlot(*NewSlot);
return true;
}
}
TSharedRef<FSlateFontAtlas> FontAtlas = FontAtlasFactory->CreateFontAtlas();
// Add the character to the texture
const FAtlasedTextureSlot* NewSlot = FontAtlas->AddCharacter(InRenderData);
if( NewSlot )
{
FillOutputParamsFromAtlasedTextureSlot(*NewSlot);
}
FontAtlases.Add( FontAtlas );
OutTextureIndex = AllFontTextures.Add(FontAtlas);
INC_DWORD_STAT_BY( STAT_SlateNumFontAtlases, 1 );
if( FontAtlases.Num() > CurrentMaxAtlasPagesBeforeFlushRequest && !bFlushRequested )
{
// The InitialMaxNonAtlasedTexturesBeforeFlushRequest may have changed since last flush,
// so double check that we're below the current max or initial, if we're under it,
// update the current to the initial.
if (FontAtlases.Num() <= InitialMaxAtlasPagesBeforeFlushRequest)
{
CurrentMaxAtlasPagesBeforeFlushRequest = InitialMaxAtlasPagesBeforeFlushRequest;
}
// If we grew back up to this number of atlas pages within the same or next frame of the previous flush request, then we likely legitimately have
// a lot of font data cached. We should update MaxAtlasPagesBeforeFlushRequest to give us a bit more flexibility before the next flush request
else if (LastFlushRequestFrameDelta <= GrowFontAtlasFrameWindow)
{
CurrentMaxAtlasPagesBeforeFlushRequest = FontAtlases.Num();
UE_LOG(LogSlate, Warning, TEXT("SlateFontCache - Setting the threshold to trigger a flush to %d atlas pages as there is a lot of font data being cached."), CurrentMaxAtlasPagesBeforeFlushRequest);
}
else
{
// We've grown beyond our current stable limit - try and request a flush
RequestFlushCache(FString::Printf(TEXT("Font Atlases Full; %d/%d Pages; frames since last flush: %llu"), FontAtlases.Num(), CurrentMaxAtlasPagesBeforeFlushRequest, LastFlushRequestFrameDelta));
}
}
return NewSlot != nullptr;
}
通过这个AddMewEntry,就把这个字母相关的渲染数据写入到图集贴图里了。
后面在ElementBatcher.cpp文件中,AddShapedTextElement函数中,BuildFontGeometry函数就会用到图集信息
if (bOutlineFont)
{
// Build geometry for the outline
BuildFontGeometry(OutlineSettings, PackVertexColor(DrawElementPayload.GetOutlineTint()), OutlineFontMaterial, Layer, 0);
//The fill area was measured without an outline so it must be shifted by the scaled outline size
float HorizontalOffset = FMath::RoundToFloat(OutlineSize * FontScale);
// Build geometry for the base font which is always rendered on top of the outline
BuildFontGeometry(FFontOutlineSettings::NoOutline, BaseTint, BaseFontMaterial, Layer+1, HorizontalOffset);
}
else
{
// No outline
BuildFontGeometry(FFontOutlineSettings::NoOutline, BaseTint, BaseFontMaterial, Layer, 0);
}
shapedText的 AddShapedTextElement里的BuildFontGeometry函数:
auto BuildFontGeometry = [&](const FFontOutlineSettings& InOutlineSettings, const FColor& InTint, const UObject* FontMaterial, int32 InLayer, int32 InHorizontalOffset)
{
FVector2D TopLeft(0, 0);
const float PosX = TopLeft.X+InHorizontalOffset;
float PosY = TopLeft.Y;
float LineX = PosX;
float LineY = PosY;
int32 FontTextureIndex = -1;
FSlateShaderResource* FontAtlasTexture = nullptr;
FSlateShaderResource* FontShaderResource = nullptr;
FSlateElementBatch* ElementBatch = nullptr;
FSlateVertexArray* BatchVertices = nullptr;
FSlateIndexArray* BatchIndices = nullptr;
uint32 VertexOffset = 0;
uint32 IndexOffset = 0;
float InvTextureSizeX = 0;
float InvTextureSizeY = 0;
const bool bIsFontMaterial = FontMaterial != nullptr;
// Optimize by culling
bool bEnableCulling = false;
float LocalClipBoundingBoxLeft = 0;
float LocalClipBoundingBoxRight = 0;
if (GlyphsToRender.Num() > 200)
{
int16 ClippingIndex = DrawElement.GetClippingIndex();
if (ClippingStates && ClippingStates->IsValidIndex(ClippingIndex))
{
const FSlateClippingState& ClippingState = (*ClippingStates)[ClippingIndex];
if (ClippingState.ScissorRect.IsSet() && ClippingState.ScissorRect->IsAxisAligned() && RenderTransform.GetMatrix().IsIdentity())
{
bEnableCulling = true;
const FSlateRect LocalClipBoundingBox = TransformRect(RenderTransform.Inverse(), ClippingState.ScissorRect->GetBoundingBox());
LocalClipBoundingBoxLeft = LocalClipBoundingBox.Left;
LocalClipBoundingBoxRight = LocalClipBoundingBox.Right;
}
}
}
const int32 NumGlyphs = GlyphsToRender.Num();
for (int32 GlyphIndex = 0; GlyphIndex < NumGlyphs; ++GlyphIndex)
{
const FShapedGlyphEntry& GlyphToRender = GlyphsToRender[GlyphIndex];
if (GlyphToRender.bIsVisible)
{
const FShapedGlyphFontAtlasData GlyphAtlasData = FontCache.GetShapedGlyphFontAtlasData(GlyphToRender, InOutlineSettings);
if (GlyphAtlasData.Valid)
{
const float X = LineX + GlyphAtlasData.HorizontalOffset + GlyphToRender.XOffset;
// Note PosX,PosY is the upper left corner of the bounding box representing the string. This computes the Y position of the baseline where text will sit
if (bEnableCulling)
{
if (X + GlyphAtlasData.USize < LocalClipBoundingBoxLeft)
{
LineX += GlyphToRender.XAdvance;
LineY += GlyphToRender.YAdvance;
continue;
}
else if (X > LocalClipBoundingBoxRight)
{
break;
}
}
if (FontAtlasTexture == nullptr || GlyphAtlasData.TextureIndex != FontTextureIndex)
{
// Font has a new texture for this glyph. Refresh the batch we use and the index we are currently using
FontTextureIndex = GlyphAtlasData.TextureIndex;
FontAtlasTexture = FontCache.GetSlateTextureResource(FontTextureIndex);
check(FontAtlasTexture);
FontShaderResource = ResourceManager.GetFontShaderResource(FontTextureIndex, FontAtlasTexture, FontMaterial);
check(FontShaderResource);
ElementBatch = &FindBatchForElement(InLayer, FShaderParams(), FontShaderResource, ESlateDrawPrimitive::TriangleList, ESlateShader::Font, InDrawEffects, ESlateBatchDrawFlag::None, DrawElement.GetClippingIndex(), DrawElement.GetSceneIndex());
BatchVertices = &BatchData->GetBatchVertexList(*ElementBatch);
BatchIndices = &BatchData->GetBatchIndexList(*ElementBatch);
VertexOffset = BatchVertices->Num();
IndexOffset = BatchIndices->Num();
InvTextureSizeX = 1.0f / FontAtlasTexture->GetWidth();
InvTextureSizeY = 1.0f / FontAtlasTexture->GetHeight();
}
const float Y = LineY - GlyphAtlasData.VerticalOffset + GlyphToRender.YOffset + MaxHeight + TextBaseline;
const float U = GlyphAtlasData.StartU * InvTextureSizeX;
const float V = GlyphAtlasData.StartV * InvTextureSizeY;
const float SizeX = GlyphAtlasData.USize;
const float SizeY = GlyphAtlasData.VSize;
const float SizeU = GlyphAtlasData.USize * InvTextureSizeX;
const float SizeV = GlyphAtlasData.VSize * InvTextureSizeY;
{
FSlateVertexArray& BatchVerticesRef = *BatchVertices;
FSlateIndexArray& BatchIndicesRef = *BatchIndices;
const FVector2D UpperLeft(X, Y);
const FVector2D UpperRight(X + SizeX, Y);
const FVector2D LowerLeft(X, Y + SizeY);
const FVector2D LowerRight(X + SizeX, Y + SizeY);
// Add four vertices for this quad
BatchVerticesRef.AddUninitialized(4);
// Add six indices for this quad
BatchIndicesRef.AddUninitialized(6);
// The start index of these vertices in the index buffer
uint32 IndexStart = VertexOffset;
float Ut = 0.0f, Vt = 0.0f, UtMax = 0.0f, VtMax = 0.0f;
if (bIsFontMaterial)
{
float DistAlpha = (float)GlyphIndex / NumGlyphs;
float DistAlphaNext = (float)(GlyphIndex + 1) / NumGlyphs;
// This creates a set of UV's that goes from 0-1, from left to right of the string in U and 0-1 baseline to baseline top to bottom in V
Ut = FMath::Lerp(0.0f, 1.0f, DistAlpha);
Vt = FMath::Lerp(0.0f, 1.0f, UpperLeft.Y / MaxHeight);
UtMax = FMath::Lerp(0.0f, 1.0f, DistAlphaNext);
VtMax = FMath::Lerp(0.0f, 1.0f, LowerLeft.Y / MaxHeight);
}
// Add four vertices to the list of verts to be added to the vertex buffer
BatchVerticesRef[VertexOffset++] = FSlateVertex::Make<Rounding>(RenderTransform, UpperLeft, FVector4(U, V, Ut, Vt), FVector2D(0.0f, 0.0f), InTint );
BatchVerticesRef[VertexOffset++] = FSlateVertex::Make<Rounding>(RenderTransform, FVector2D(LowerRight.X, UpperLeft.Y), FVector4(U + SizeU, V, UtMax, Vt), FVector2D(1.0f, 0.0f), InTint );
BatchVerticesRef[VertexOffset++] = FSlateVertex::Make<Rounding>(RenderTransform, FVector2D(UpperLeft.X, LowerRight.Y), FVector4(U, V + SizeV, Ut, VtMax), FVector2D(0.0f, 1.0f), InTint );
BatchVerticesRef[VertexOffset++] = FSlateVertex::Make<Rounding>(RenderTransform, LowerRight, FVector4(U + SizeU, V + SizeV, UtMax, VtMax), FVector2D(1.0f, 1.0f), InTint );
BatchIndicesRef[IndexOffset++] = IndexStart + 0;
BatchIndicesRef[IndexOffset++] = IndexStart + 1;
BatchIndicesRef[IndexOffset++] = IndexStart + 2;
BatchIndicesRef[IndexOffset++] = IndexStart + 1;
BatchIndicesRef[IndexOffset++] = IndexStart + 3;
BatchIndicesRef[IndexOffset++] = IndexStart + 2;
}
}
}
LineX += GlyphToRender.XAdvance;
LineY += GlyphToRender.YAdvance;
}
};
普通text的AddTextElement,跟ShapedText不同。 根据characterList得到characterEntry,这个entry上有textureindex,从而获得图集贴图,联合材质球构成shaderResource。然后获得顶点和索引,uv。
两者跟outline相关的地方:
FCharacterList& CharacterList = FontCache.GetCharacterList(DrawElementPayload.GetFontInfo(), FontScale, InOutlineSettings);
const FShapedGlyphFontAtlasData GlyphAtlasData = FontCache.GetShapedGlyphFontAtlasData(GlyphToRender, InOutlineSettings);
先看第二个,返回的是FShapedGlyphFontAtlasData数据,从AddNewEntry(InShapedGlyph, InOutlineSettings, *NewAtlasData);获取到的。上面说过了。
然后看看顶点属性
template<ESlateVertexRounding Rounding>
static FSlateVertex Make(const FSlateRenderTransform& RenderTransform, const FVector2D& InLocalPosition, const FVector4& InTexCoords, const FVector2D& InMaterialTexCoords, const FColor& InColor)
{
FSlateVertex Vertex;
Vertex.TexCoords[0] = InTexCoords.X;
Vertex.TexCoords[1] = InTexCoords.Y;
Vertex.TexCoords[2] = InTexCoords.Z;
Vertex.TexCoords[3] = InTexCoords.W;
Vertex.MaterialTexCoords = InMaterialTexCoords;
Vertex.InitCommon<Rounding>(RenderTransform, InLocalPosition, InColor);
return Vertex;
}
因为看到祖龙的龙族幻想里面,同样坐标的顶点出现了两次,只有InAttribute3这个属性不一样。如下
VTX IDX in_ATTRIBUTE0.x in_ATTRIBUTE0.y in_ATTRIBUTE0.z in_ATTRIBUTE0.w in_ATTRIBUTE1.x in_ATTRIBUTE1.y in_ATTRIBUTE2.x in_ATTRIBUTE2.y in_ATTRIBUTE2.z in_ATTRIBUTE3.x in_ATTRIBUTE3.y in_ATTRIBUTE3.z in_ATTRIBUTE3.w 2 2 0.16113 0.06152 0 0 0 1 1962.7439 208.84578 0.99947 0 0 0 0.4 104 70 0.25684 0.06152 0 0 0 1 1962.7439 208.84578 0.99947 0.83922 0.94118 0.60392 1
输出的,颜色和var_texcoord1不一样。
half3 sRGBToLinear( half3 Color )
{
Color = max(6.10352e-5, Color); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
return Color > 0.04045 ? pow( Color * (1.0 / 1.055) + 0.0521327, 2.4 ) : Color * (1.0 / 12.92);
}
half3 sRGBToLinear( half3 Color )
{
Color = max(6.10352e-5, Color); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
return pow( Color * (1.0 / 1.055) + 0.0521327, 2.4 );
}
FindBatchForElement这个函数会查找,是否有能跟当前text合批的text,首先查找这个layer,找到了,则将这个element加到batch。如果没有,则创建一个新的batch:
FSlateElementBatch& FSlateElementBatcher::FindBatchForElement(
uint32 Layer,
const FShaderParams& ShaderParams,
const FSlateShaderResource* InTexture,
ESlateDrawPrimitive::Type PrimitiveType,
ESlateShader::Type ShaderType,
ESlateDrawEffect DrawEffects,
ESlateBatchDrawFlag DrawFlags,
int32 ClippingIndex,
int32 SceneIndex)
{
SCOPE_CYCLE_COUNTER( STAT_SlateFindBatchForElement );
FElementBatchMap& LayerToElementBatches = DrawLayer->GetElementBatchMap();
// See if the layer already exists.
TUniqueObj<FElementBatchArray>* ElementBatches = LayerToElementBatches.Find( Layer );
if( !ElementBatches )
{
// The layer doesn't exist so make it now
ElementBatches = &LayerToElementBatches.Add( Layer );
}
checkSlow( ElementBatches );
// Create a temp batch so we can use it as our key to find if the same batch already exists
FSlateElementBatch TempBatch( InTexture, ShaderParams, ShaderType, PrimitiveType, DrawEffects, DrawFlags, ClippingIndex, *ClippingStates, 0, 0, nullptr, SceneIndex );
FSlateElementBatch* ElementBatch = (*ElementBatches)->FindByKey( TempBatch );
if( !ElementBatch )
{
// No batch with the specified parameter exists. Create it from the temp batch.
int32 Index = (*ElementBatches)->Add( TempBatch );
ElementBatch = &(**ElementBatches)[Index];
BatchData->AssignVertexArrayToBatch(*ElementBatch);
BatchData->AssignIndexArrayToBatch(*ElementBatch);
}
check( ElementBatch );
// Increment the number of elements in the batch.
++ElementBatch->NumElementsInBatch;
return *ElementBatch;
}
浙公网安备 33010602011771号