1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "text_system.h"
17
18 #include <ComponentTools/component_query.h>
19 #if defined(TEXT3D_ENABLE_EXTRUDING) && (TEXT3D_ENABLE_EXTRUDING)
20 #include <tesselator.h>
21 #endif
22
23 #include <3d/ecs/components/material_component.h>
24 #include <3d/ecs/components/mesh_component.h>
25 #include <3d/ecs/components/render_handle_component.h>
26 #include <3d/ecs/components/render_mesh_component.h>
27 #include <3d/implementation_uids.h>
28 #include <3d/render/default_material_constants.h>
29 #include <3d/util/intf_mesh_builder.h>
30 #include <base/math/matrix_util.h>
31 #include <base/util/utf8_decode.h>
32 #include <core/ecs/intf_ecs.h>
33 #include <core/intf_engine.h>
34 #include <core/plugin/intf_class_factory.h>
35 #include <core/plugin/intf_class_register.h>
36 #include <core/property_tools/property_api_impl.inl>
37 #include <font/implementation_uids.h>
38 #include <font/intf_font_manager.h>
39 #include <render/device/intf_shader_manager.h>
40 #include <render/implementation_uids.h>
41 #include <render/intf_render_context.h>
42 #include <text_3d/ecs/components/text_component.h>
43
44 TEXT3D_BEGIN_NAMESPACE()
45 using namespace BASE_NS;
46 using namespace CORE_NS;
47 using namespace RENDER_NS;
48 namespace {
49 struct MeshData {
50 BASE_NS::vector<BASE_NS::Math::Vec3> positions;
51 BASE_NS::vector<BASE_NS::Math::Vec2> uvs;
52 BASE_NS::vector<BASE_NS::Math::Vec3> normals;
53 BASE_NS::vector<uint16_t> indices;
54 RENDER_NS::RenderHandleReference atlas;
55 };
56
57 struct PathVertex {
58 float x;
59 float y;
60 };
61
GenerateMeshData(FONT_NS::IFont & font,BASE_NS::string_view text)62 MeshData GenerateMeshData(FONT_NS::IFont& font, BASE_NS::string_view text)
63 {
64 // should really go through all glyphs and group based on atlas texture.
65 // could also go through all the TextComponents and create one mesh for all the glyphs, submesh per atlas.
66 MeshData data;
67
68 float posX = 0.f;
69 const char* str = text.data();
70 while (const uint32_t codepoint = BASE_NS::GetCharUtf8(&str)) {
71 const uint32_t glyphIndex = font.GetGlyphIndex(codepoint);
72 const auto& metrics = font.GetGlyphMetrics(glyphIndex);
73 const auto width = metrics.right - metrics.left;
74 const auto bl = BASE_NS::Math::Vec3(posX + metrics.left - metrics.leftBearing, metrics.bottom, 0.f);
75 const auto br = BASE_NS::Math::Vec3(bl.x + width, bl.y, 0.f);
76 const auto tr = BASE_NS::Math::Vec3(br.x, metrics.top, 0.f);
77 const auto tl = BASE_NS::Math::Vec3(bl.x, tr.y, 0.f);
78 posX += metrics.advance;
79 const auto i0 = static_cast<uint16_t>(data.positions.size());
80 data.positions.push_back(bl);
81 data.positions.push_back(br);
82 data.positions.push_back(tr);
83 data.positions.push_back(tl);
84
85 const auto& info = font.GetGlyphInfo(glyphIndex);
86 data.atlas = info.atlas;
87 data.uvs.emplace_back(info.tl.x, info.br.y);
88 data.uvs.push_back(info.br);
89 data.uvs.emplace_back(info.br.x, info.tl.y);
90 data.uvs.push_back(info.tl);
91
92 data.indices.push_back(i0);
93 data.indices.push_back(i0 + 2U);
94 data.indices.push_back(i0 + 3U);
95
96 data.indices.push_back(i0);
97 data.indices.push_back(i0 + 1U);
98 data.indices.push_back(i0 + 2U);
99 }
100 // +z facing normal for each vertex
101 data.normals.append(data.positions.size(), { 0.f, 0.f, 1.f });
102 return data;
103 }
104
105 #if defined(TEXT3D_ENABLE_EXTRUDING) && (TEXT3D_ENABLE_EXTRUDING)
GenerateFrontBackFaces(const size_t vertexCount,const size_t elementCount,const size_t vertexOffset,const float depth,const TESSreal * vertices,const float posX,const Font::GlyphInfo & info,const TESSindex * elements,const Font::GlyphMetrics & metrics,int & elementOffset,MeshData & data)106 void GenerateFrontBackFaces(const size_t vertexCount, const size_t elementCount, const size_t vertexOffset,
107 const float depth, const TESSreal* vertices, const float posX, const Font::GlyphInfo& info,
108 const TESSindex* elements, const Font::GlyphMetrics& metrics, int& elementOffset, MeshData& data)
109 {
110 // front face vertices
111 for (size_t ii = 0; ii < vertexCount; ii++) {
112 const BASE_NS::Math::Vec3 pos(
113 vertices[2 * ii] + (posX + metrics.left - metrics.leftBearing), vertices[2 * ii + 1], 0.f);
114
115 const BASE_NS::Math::Vec2 uv(
116 info.tl.x + (vertices[2 * ii] / (metrics.right - metrics.left)) * (info.br.x - info.tl.x),
117 info.br.y + (vertices[2 * ii + 1] / (metrics.top - metrics.bottom)) * (info.tl.y - info.br.y));
118
119 data.positions.push_back(pos);
120 data.uvs.push_back(uv);
121 data.normals.push_back({ 0.f, 0.f, 1.f });
122 elementOffset++;
123 }
124
125 // front face indices
126 for (size_t ii = 0; ii < elementCount * 3; ii += 3) {
127 const uint16_t v0 = static_cast<uint16_t>(elements[ii] + vertexOffset);
128 const uint16_t v1 = static_cast<uint16_t>(elements[ii + 1] + vertexOffset);
129 const uint16_t v2 = static_cast<uint16_t>(elements[ii + 2] + vertexOffset);
130
131 data.indices.push_back(v2);
132 data.indices.push_back(v1);
133 data.indices.push_back(v0);
134 }
135
136 // back face vertices
137 for (size_t ii = 0; ii < vertexCount; ii++) {
138 const BASE_NS::Math::Vec3 pos(
139 vertices[2 * ii] + (posX + metrics.left - metrics.leftBearing), vertices[2 * ii + 1], -depth);
140
141 const BASE_NS::Math::Vec2 uv = data.uvs[ii];
142
143 data.positions.push_back(pos);
144 data.uvs.push_back(uv);
145 data.normals.push_back({ 0.f, 0.f, -1.f });
146 elementOffset++;
147 }
148
149 // back face indices
150 for (size_t ii = 0; ii < elementCount * 3; ii += 3) {
151 const uint16_t v0 = static_cast<uint16_t>(elements[ii] + vertexOffset + vertexCount);
152 const uint16_t v1 = static_cast<uint16_t>(elements[ii + 1] + vertexOffset + vertexCount);
153 const uint16_t v2 = static_cast<uint16_t>(elements[ii + 2] + vertexOffset + vertexCount);
154
155 data.indices.push_back(v0);
156 data.indices.push_back(v1);
157 data.indices.push_back(v2);
158 }
159 }
160
GenerateSideFaces(const float depth,const float posX,const Font::GlyphInfo & info,const BASE_NS::vector<Font::GlyphContour> & contours,const Font::GlyphMetrics & metrics,int & elementOffset,MeshData & data)161 void GenerateSideFaces(const float depth, const float posX, const Font::GlyphInfo& info,
162 const BASE_NS::vector<Font::GlyphContour>& contours, const Font::GlyphMetrics& metrics, int& elementOffset,
163 MeshData& data)
164 {
165 // side faces
166 for (const auto& contour : contours) {
167 const size_t contourSize = contour.points.size();
168 for (size_t ptIdx = 0; ptIdx < contourSize; ptIdx++) {
169 const size_t nextPtIdx = (ptIdx + 1) % contourSize;
170 const BASE_NS::Math::Vec2& pt = contour.points[ptIdx];
171 const BASE_NS::Math::Vec2& nextPt = contour.points[nextPtIdx];
172
173 // side face verticies
174 const BASE_NS::Math::Vec3 frontFacePos1(pt.x + (posX + metrics.left - metrics.leftBearing), pt.y, 0.f);
175 const BASE_NS::Math::Vec3 frontFacePos2(
176 nextPt.x + (posX + metrics.left - metrics.leftBearing), nextPt.y, 0.f);
177 const BASE_NS::Math::Vec3 backFacePos1(pt.x + (posX + metrics.left - metrics.leftBearing), pt.y, -depth);
178 const BASE_NS::Math::Vec3 backFacePos2(
179 nextPt.x + (posX + metrics.left - metrics.leftBearing), nextPt.y, -depth);
180
181 data.positions.push_back(frontFacePos1);
182 data.positions.push_back(backFacePos1);
183 data.positions.push_back(frontFacePos2);
184 data.positions.push_back(backFacePos2);
185
186 const float u1 = info.tl.x + (pt.x / (metrics.right - metrics.left)) * (info.br.x - info.tl.x);
187 const float u2 = info.tl.x + (nextPt.x / (metrics.right - metrics.left)) * (info.br.x - info.tl.x);
188
189 data.uvs.emplace_back(u1, info.br.y);
190 data.uvs.emplace_back(u1, info.tl.y);
191 data.uvs.emplace_back(u2, info.br.y);
192 data.uvs.emplace_back(u2, info.tl.y);
193
194 elementOffset += 4;
195
196 const BASE_NS::Math::Vec3 edge1 = backFacePos1 - frontFacePos1;
197 const BASE_NS::Math::Vec3 edge2 = frontFacePos2 - frontFacePos1;
198 const BASE_NS::Math::Vec3 normal = Math::Normalize(Math::Cross(edge1, edge2));
199
200 data.normals.push_back(-normal);
201 data.normals.push_back(-normal);
202 data.normals.push_back(-normal);
203 data.normals.push_back(-normal);
204
205 const size_t sideVertOffset = data.positions.size() - 4;
206
207 // side face indices
208 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 2));
209 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 1));
210 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 0));
211
212 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 3));
213 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 1));
214 data.indices.push_back(static_cast<uint16_t>(sideVertOffset + 2));
215 }
216 }
217 }
218
GenerateMeshData3D(FONT_NS::IFont & font,BASE_NS::string_view text,const float depth)219 MeshData GenerateMeshData3D(FONT_NS::IFont& font, BASE_NS::string_view text, const float depth)
220 {
221 MeshData data;
222 float posX = 0.f;
223 size_t vertexOffset = 0;
224 TESStesselator* tessellator = tessNewTess(nullptr);
225 tessSetOption(tessellator, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1);
226
227 const char* str = text.data();
228 while (const uint32_t codepoint = BASE_NS::GetCharUtf8(&str)) {
229 const uint32_t glyphIndex = font.GetGlyphIndex(codepoint);
230 const auto& metrics = font.GetGlyphMetrics(glyphIndex);
231 const auto& info = font.GetGlyphInfo(glyphIndex);
232
233 const auto contours = font.GetGlyphContours(glyphIndex);
234
235 for (const auto& contour : contours) {
236 tessAddContour(tessellator, 2, contour.points.data(), sizeof(BASE_NS::Math::Vec2),
237 static_cast<int>(contour.points.size()));
238 }
239
240 if (tessTesselate(tessellator, TESS_WINDING_ODD, TESS_POLYGONS, 3, 2, nullptr)) {
241 const size_t vertexCount = static_cast<size_t>(tessGetVertexCount(tessellator));
242 const TESSreal* vertices = tessGetVertices(tessellator);
243 const size_t elementCount = static_cast<size_t>(tessGetElementCount(tessellator));
244 const TESSindex* elements = tessGetElements(tessellator);
245
246 int elementOffset = 0;
247
248 GenerateFrontBackFaces(vertexCount, elementCount, vertexOffset, depth, vertices, posX, info, elements,
249 metrics, elementOffset, data);
250
251 GenerateSideFaces(depth, posX, info, contours, metrics, elementOffset, data);
252
253 vertexOffset += elementOffset;
254 }
255 posX += metrics.advance;
256 }
257
258 tessDeleteTess(tessellator);
259 return data;
260 }
261 #endif
262
263 template<typename T>
FillData(array_view<const T> c)264 constexpr inline IMeshBuilder::DataBuffer FillData(array_view<const T> c) noexcept
265 {
266 Format format = BASE_FORMAT_UNDEFINED;
267 if constexpr (is_same_v<T, Math::Vec2>) {
268 format = BASE_FORMAT_R32G32_SFLOAT;
269 } else if constexpr (is_same_v<T, Math::Vec3>) {
270 format = BASE_FORMAT_R32G32B32_SFLOAT;
271 } else if constexpr (is_same_v<T, Math::Vec4>) {
272 format = BASE_FORMAT_R32G32B32A32_SFLOAT;
273 } else if constexpr (is_same_v<T, uint16_t>) {
274 format = BASE_FORMAT_R16_UINT;
275 } else if constexpr (is_same_v<T, uint32_t>) {
276 format = BASE_FORMAT_R32_UINT;
277 }
278 return IMeshBuilder::DataBuffer { format, sizeof(T),
279 { reinterpret_cast<const uint8_t*>(c.data()), c.size() * sizeof(T) } };
280 }
281
282 template<typename T>
FillData(const vector<T> & c)283 constexpr inline IMeshBuilder::DataBuffer FillData(const vector<T>& c) noexcept
284 {
285 return FillData(array_view<const T> { c });
286 }
287
GetVertexInputDeclaration(IRenderContext & renderContext)288 VertexInputDeclarationView GetVertexInputDeclaration(IRenderContext& renderContext)
289 {
290 IShaderManager& shaderManager = renderContext.GetDevice().GetShaderManager();
291 return shaderManager.GetVertexInputDeclarationView(shaderManager.GetVertexInputDeclarationHandle(
292 DefaultMaterialShaderConstants::VERTEX_INPUT_DECLARATION_FORWARD));
293 }
294 } // namespace
295
296 struct TextSystem::TextData {
297 uint32_t generation;
298 BASE_NS::string text;
299 BASE_NS::string fontFamily;
300 BASE_NS::string fontStyle;
301 float fontSize;
302 float font3DThickness { 10.0f };
303 TextComponent::FontMethod fontMethod { TextComponent::FontMethod::RASTER };
304 FONT_NS::IFont::Ptr font;
305 CORE_NS::EntityReference mesh;
306 };
307
SetActive(bool state)308 void TextSystem::SetActive(bool state)
309 {
310 active_ = (state) && (fontManager_);
311 }
312
IsActive() const313 bool TextSystem::IsActive() const
314 {
315 return active_;
316 }
317
TextSystem(IEcs & ecs)318 TextSystem::TextSystem(IEcs& ecs)
319 : ecs_(ecs), meshManager_(GetManager<IMeshComponentManager>(ecs)),
320 renderHandleManager_(GetManager<IRenderHandleComponentManager>(ecs)),
321 renderMeshManager_(GetManager<IRenderMeshComponentManager>(ecs)),
322 textManager_(GetManager<ITextComponentManager>(ecs))
323 {
324 if (auto* engineClassRegister = ecs_.GetClassFactory().GetInterface<IClassRegister>()) {
325 if (auto* renderClassRegister =
326 CORE_NS::GetInstance<CORE_NS::IClassRegister>(*engineClassRegister, RENDER_NS::UID_RENDER_CONTEXT)) {
327 fontManager_ = CORE_NS::GetInstance<FONT_NS::IFontManager>(*renderClassRegister, FONT_NS::UID_FONT_MANAGER);
328 active_ = static_cast<bool>(fontManager_);
329 }
330 }
331 }
332
333 TextSystem::~TextSystem() = default;
334
GetName() const335 string_view TextSystem::GetName() const
336 {
337 return CORE3D_NS::GetName(this);
338 }
339
GetUid() const340 Uid TextSystem::GetUid() const
341 {
342 return UID;
343 }
344
GetProperties() const345 const IPropertyHandle* TextSystem::GetProperties() const
346 {
347 return nullptr;
348 }
349
GetProperties()350 IPropertyHandle* TextSystem::GetProperties()
351 {
352 return nullptr;
353 }
354
SetProperties(const IPropertyHandle &)355 void TextSystem::SetProperties(const IPropertyHandle&) {}
356
GetECS() const357 const IEcs& TextSystem::GetECS() const
358 {
359 return ecs_;
360 }
361
Initialize()362 void TextSystem::Initialize()
363 {
364 if (auto* engineClassRegister = ecs_.GetClassFactory().GetInterface<IClassRegister>()) {
365 if (auto* renderContext =
366 CORE_NS::GetInstance<RENDER_NS::IRenderContext>(*engineClassRegister, RENDER_NS::UID_RENDER_CONTEXT)) {
367 RENDER_NS::IShaderManager& shaderManager = renderContext->GetDevice().GetShaderManager();
368
369 const auto renderSlot = DefaultMaterialShaderConstants::RENDER_SLOT_FORWARD_TRANSLUCENT;
370 const uint32_t renderSlotId = shaderManager.GetRenderSlotId(renderSlot);
371 const RENDER_NS::IShaderManager::RenderSlotData rsd = shaderManager.GetRenderSlotData(renderSlotId);
372
373 rasterShader_.shader = rsd.shader;
374 rasterShader_.graphicsState = rsd.graphicsState;
375 rasterShader_.depthShader =
376 shaderManager.GetShaderHandle("3dshaders://shader/core3d_dm_depth_alpha.shader");
377
378 sdfShader_.shader = shaderManager.GetShaderHandle("text3dshaders://shader/plugin_core3d_dm_fw_sdf.shader");
379 sdfShader_.graphicsState = rsd.graphicsState;
380 sdfShader_.depthShader =
381 shaderManager.GetShaderHandle("text3dshaders://shader/plugin_core3d_dm_depth_alpha_sdf.shader");
382 }
383 }
384 }
385
Update(bool frameRenderingQueued,uint64_t,uint64_t)386 bool TextSystem::Update(bool frameRenderingQueued, uint64_t, uint64_t)
387 {
388 if (!active_) {
389 return false;
390 }
391
392 const auto counter = textManager_->GetGenerationCounter();
393 if (textManagerGeneration_ == counter) {
394 return false;
395 }
396 textManagerGeneration_ = counter;
397
398 const auto components = textManager_->GetComponentCount();
399 for (IComponentManager::ComponentId id = 0U; id < components; ++id) {
400 const auto entity = textManager_->GetEntity(id);
401 auto pos = textCache_.find(entity);
402 if (pos != textCache_.end()) {
403 const auto generation = textManager_->GetComponentGeneration(id);
404 if (pos->second.generation == generation) {
405 continue;
406 }
407 } else {
408 auto result = textCache_.insert({ entity, {} });
409 pos = result.first;
410 }
411 pos->second.generation = textManager_->GetComponentGeneration(id);
412
413 auto textHandle = textManager_->Read(id);
414 if ((pos->second.fontFamily != textHandle->fontFamily) || (pos->second.fontStyle != textHandle->fontStyle)) {
415 pos->second.fontFamily = textHandle->fontFamily;
416 pos->second.fontStyle = textHandle->fontStyle;
417 if (auto* typeface = fontManager_->GetTypeFace(textHandle->fontFamily, textHandle->fontStyle)) {
418 pos->second.font = fontManager_->CreateFont(*typeface);
419 }
420 }
421 if (!pos->second.font) {
422 CORE_LOG_ONCE_E(to_hex(entity.id), "Failed to create font with '%s' '%s'", textHandle->fontFamily.data(),
423 textHandle->fontStyle.data());
424 continue;
425 }
426 if (pos->second.fontSize != textHandle->fontSize) {
427 pos->second.fontSize = textHandle->fontSize;
428 pos->second.font->SetSize(textHandle->fontSize);
429 }
430 if (pos->second.fontMethod != textHandle->fontMethod) {
431 pos->second.fontMethod = textHandle->fontMethod;
432 pos->second.font->SetMethod((textHandle->fontMethod == TextComponent::FontMethod::SDF)
433 ? Font::FontMethod::SDF
434 : Font::FontMethod::RASTER);
435 }
436 pos->second.text = textHandle->text;
437 GenerateMesh(*textHandle, pos->first, pos->second);
438 }
439 fontManager_->UploadPending();
440
441 return true;
442 }
443
GenerateMesh(const TextComponent & textComponent,CORE_NS::Entity entity,struct TextData & cached)444 void TextSystem::GenerateMesh(const TextComponent& textComponent, CORE_NS::Entity entity, struct TextData& cached)
445 {
446 if (!cached.font) {
447 return;
448 }
449
450 auto* engineClassRegister = ecs_.GetClassFactory().GetInterface<IClassRegister>();
451 if (!engineClassRegister) {
452 return;
453 }
454 auto* renderContext = GetInstance<IRenderContext>(*engineClassRegister, UID_RENDER_CONTEXT);
455 if (!renderContext) {
456 return;
457 }
458 auto* renderClassFactory = renderContext->GetInterface<IClassFactory>();
459 if (!renderClassFactory) {
460 return;
461 }
462
463 #if defined(TEXT3D_ENABLE_EXTRUDING) && (TEXT3D_ENABLE_EXTRUDING)
464 const bool extrudedGeometry = (textComponent.fontMethod == TextComponent::FontMethod::TEXT3D);
465 const auto data = extrudedGeometry ? GenerateMeshData3D(*cached.font, textComponent.text, cached.font3DThickness)
466 : GenerateMeshData(*cached.font, textComponent.text);
467 #else
468 const bool extrudedGeometry = false;
469 const auto data = GenerateMeshData(*cached.font, textComponent.text);
470 #endif
471 auto builder = CreateInstance<IMeshBuilder>(*renderClassFactory, UID_MESH_BUILDER);
472 builder->Initialize(GetVertexInputDeclaration(*renderContext), 1U);
473 builder->AddSubmesh(IMeshBuilder::Submesh { static_cast<uint32_t>(data.positions.size()),
474 static_cast<uint32_t>(data.indices.size()), 1U, 0U, IndexType::CORE_INDEX_TYPE_UINT16, entity, false, false,
475 false });
476 builder->Allocate();
477
478 static constexpr auto empty = IMeshBuilder::DataBuffer {};
479 auto positionData = FillData(data.positions);
480 builder->SetVertexData(0U, positionData, FillData(data.normals), FillData(data.uvs), empty, empty, empty);
481 builder->CalculateAABB(0U, positionData);
482 builder->SetIndexData(0U, FillData(data.indices));
483
484 auto meshEntity = builder->CreateMesh(ecs_);
485 renderMeshManager_->Create(entity);
486 renderMeshManager_->Write(entity)->mesh = meshEntity;
487
488 cached.mesh = ecs_.GetEntityManager().GetReferenceCounted(meshEntity);
489
490 // NOTE: ATM the materials created in the system are not available current frame
491 // The create-event happens after the system are processed
492 if (data.atlas || extrudedGeometry) {
493 auto* materialManager = GetManager<IMaterialComponentManager>(ecs_);
494 auto id = materialManager->GetComponentId(entity);
495 if (id == IComponentManager::INVALID_COMPONENT_ID) {
496 materialManager->Create(entity);
497 id = materialManager->GetComponentId(entity);
498 }
499 auto materialHandle = materialManager->Write(id);
500 if (!materialHandle) {
501 return;
502 }
503 materialHandle->textures[0U].image =
504 GetOrCreateEntityReference(ecs_.GetEntityManager(), *renderHandleManager_, data.atlas);
505
506 if (!extrudedGeometry) {
507 const auto& shaderData =
508 (textComponent.fontMethod == TextComponent::FontMethod::SDF) ? sdfShader_ : rasterShader_;
509 materialHandle->materialShader.shader =
510 GetOrCreateEntityReference(ecs_.GetEntityManager(), *renderHandleManager_, shaderData.shader);
511 materialHandle->materialShader.graphicsState =
512 GetOrCreateEntityReference(ecs_.GetEntityManager(), *renderHandleManager_, shaderData.graphicsState);
513 materialHandle->depthShader.shader =
514 GetOrCreateEntityReference(ecs_.GetEntityManager(), *renderHandleManager_, shaderData.depthShader);
515 }
516 }
517 }
518
Uninitialize()519 void TextSystem::Uninitialize() {}
520
TextSystemInstance(IEcs & ecs)521 ISystem* TextSystemInstance(IEcs& ecs)
522 {
523 return new TextSystem(ecs);
524 }
525
TextSystemDestroy(ISystem * instance)526 void TextSystemDestroy(ISystem* instance)
527 {
528 delete static_cast<TextSystem*>(instance);
529 }
530 TEXT3D_END_NAMESPACE()
531