1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Resources/BsBuiltinResourcesHelper.h"
4#include "FileSystem/BsFileSystem.h"
5#include "Importer/BsImporter.h"
6#include "Resources/BsResources.h"
7#include "Importer/BsShaderImportOptions.h"
8#include "Importer/BsTextureImportOptions.h"
9#include "Renderer/BsRendererMaterialManager.h"
10#include "Renderer/BsRendererMaterial.h"
11#include "Text/BsFontImportOptions.h"
12#include "Image/BsSpriteTexture.h"
13#include "Image/BsTexture.h"
14#include "Reflection/BsRTTIType.h"
15#include "FileSystem/BsDataStream.h"
16#include "Resources/BsResourceManifest.h"
17#include "FileSystem/BsFileSystem.h"
18#include "CoreThread/BsCoreThread.h"
19#include "Utility/BsUUID.h"
20#include "Material/BsShader.h"
21#include "Material/BsPass.h"
22#include "RenderAPI/BsGpuProgram.h"
23
24using json = nlohmann::json;
25
26namespace bs
27{
28 void BuiltinResourcesHelper::importAssets(const nlohmann::json& entries, const Vector<bool>& importFlags,
29 const Path& inputFolder, const Path& outputFolder, const SPtr<ResourceManifest>& manifest, AssetType mode,
30 nlohmann::json* dependencies, bool compress, bool mipmap)
31 {
32 if (!FileSystem::exists(inputFolder))
33 return;
34
35 bool outputExists = FileSystem::exists(outputFolder);
36 if(!outputExists)
37 FileSystem::createDir(outputFolder);
38
39 Path spriteOutputFolder = outputFolder + "/Sprites/";
40 if(mode == AssetType::Sprite)
41 FileSystem::createDir(spriteOutputFolder);
42
43 struct QueuedImportOp
44 {
45 QueuedImportOp(const TAsyncOp<HResource>& op, const Path& outputPath, const nlohmann::json& jsonEntry)
46 :op(op), outputPath(outputPath), jsonEntry(jsonEntry)
47 { }
48
49 TAsyncOp<HResource> op;
50 Path outputPath;
51 const nlohmann::json& jsonEntry;
52 };
53
54 List<QueuedImportOp> queuedOps;
55 auto importResource = [&](const nlohmann::json& entry)
56 {
57 std::string name = entry["Path"];
58 std::string uuidStr;
59
60 if (mode == AssetType::Normal)
61 uuidStr = entry["UUID"];
62 else if (mode == AssetType::Sprite)
63 uuidStr = entry["TextureUUID"];
64
65 String fileName = name.c_str();
66 UUID UUID(uuidStr.c_str());
67
68 Path filePath = inputFolder + fileName;
69
70 Path relativePath = fileName;
71 Path relativeAssetPath = fileName;
72 relativeAssetPath.setFilename(relativeAssetPath.getFilename() + u8".asset");
73
74 SPtr<ImportOptions> importOptions = gImporter().createImportOptions(filePath);
75 if (importOptions != nullptr)
76 {
77 if (rtti_is_of_type<TextureImportOptions>(importOptions))
78 {
79 SPtr<TextureImportOptions> texImportOptions =
80 std::static_pointer_cast<TextureImportOptions>(importOptions);
81
82 texImportOptions->generateMips = mipmap;
83 }
84 else if (rtti_is_of_type<ShaderImportOptions>(importOptions))
85 {
86 ShaderDefines defines = RendererMaterialManager::_getDefines(relativePath);
87
88 SPtr<ShaderImportOptions> shaderImportOptions =
89 std::static_pointer_cast<ShaderImportOptions>(importOptions);
90
91 UnorderedMap<String, String> allDefines = defines.getAll();
92 for(auto& define : allDefines)
93 shaderImportOptions->setDefine(define.first, define.second);
94 }
95 }
96
97 Path outputPath = outputFolder + relativeAssetPath;
98
99 TAsyncOp<HResource> op = gImporter().importAsync(filePath, importOptions, UUID);
100 queuedOps.emplace_back(op, outputPath, entry);
101 };
102
103 auto generateSprite = [&](const HTexture& texture, const String& fileName, const UUID& UUID)
104 {
105 Path relativePath = fileName;
106 Path outputPath = spriteOutputFolder + relativePath;
107
108 outputPath.setFilename("sprite_" + fileName + ".asset");
109
110 SPtr<SpriteTexture> spriteTexPtr = SpriteTexture::_createPtr(texture);
111 HResource spriteTex = gResources()._createResourceHandle(spriteTexPtr, UUID);
112
113 Resources::instance().save(spriteTex, outputPath, true, compress);
114 manifest->registerResource(spriteTex.getUUID(), outputPath);
115 };
116
117 auto generateAnimatedSprite = [&](const HTexture& texture, const String& fileName, const UUID& UUID,
118 SpriteAnimationPlayback playback, const SpriteSheetGridAnimation& animation)
119 {
120 Path relativePath = fileName;
121 Path outputPath = spriteOutputFolder + relativePath;
122
123 outputPath.setFilename("sprite_" + fileName + ".asset");
124
125 SPtr<SpriteTexture> spriteTexPtr = SpriteTexture::_createPtr(texture);
126 spriteTexPtr->setAnimation(animation);
127 spriteTexPtr->setAnimationPlayback(playback);
128
129 HResource spriteTex = gResources()._createResourceHandle(spriteTexPtr, UUID);
130
131 Resources::instance().save(spriteTex, outputPath, true, compress);
132 manifest->registerResource(spriteTex.getUUID(), outputPath);
133 };
134
135 // Start async import for all resources
136 int idx = 0;
137 for(auto& entry : entries)
138 {
139 if(!importFlags[idx])
140 {
141 idx++;
142 continue;
143 }
144
145 importResource(entry);
146 idx++;
147 }
148
149 struct IconData
150 {
151 String name;
152 HTexture source;
153 SPtr<PixelData> srcData;
154 std::string TextureUUIDs[3];
155 std::string SpriteUUIDs[3];
156 };
157
158 Vector<IconData> iconsToGenerate;
159 while(!queuedOps.empty())
160 {
161 for(auto iter = queuedOps.begin(); iter != queuedOps.end();)
162 {
163 QueuedImportOp& importOp = *iter;
164 if(!importOp.op.hasCompleted())
165 {
166 ++iter;
167 continue;
168 }
169
170 HResource outputRes = importOp.op.getReturnValue();
171 if (outputRes != nullptr)
172 {
173 Resources::instance().save(outputRes, importOp.outputPath, true, compress);
174 manifest->registerResource(outputRes.getUUID(), importOp.outputPath);
175
176 const nlohmann::json& entry = importOp.jsonEntry;
177
178 std::string name = entry["Path"];
179
180 bool isIcon = false;
181 if (mode == AssetType::Normal)
182 isIcon = entry.find("UUID16") != entry.end();
183 else if (mode == AssetType::Sprite)
184 isIcon = entry.find("TextureUUID16") != entry.end();
185
186 if (rtti_is_of_type<Shader>(outputRes.get()))
187 {
188 HShader shader = static_resource_cast<Shader>(outputRes);
189 if (!verifyAndReportShader(shader))
190 {
191 iter = queuedOps.erase(iter);
192 continue;
193 }
194
195 if (dependencies != nullptr)
196 {
197 SPtr<ShaderMetaData> shaderMetaData = std::static_pointer_cast<ShaderMetaData>(shader->getMetaData());
198
199 nlohmann::json dependencyEntries;
200 if (shaderMetaData != nullptr && shaderMetaData->includes.size() > 0)
201 {
202 for (auto& include : shaderMetaData->includes)
203 {
204 Path includePath = include.c_str();
205 if (include.substr(0, 8) == "$ENGINE$" || include.substr(0, 8) == "$EDITOR$")
206 {
207 if (include.size() > 8)
208 includePath = include.substr(9, include.size() - 9);
209 }
210
211 nlohmann::json newDependencyEntry =
212 {
213 { "Path", includePath.toString().c_str() }
214 };
215
216 dependencyEntries.push_back(newDependencyEntry);
217 }
218 }
219
220 (*dependencies)[name] = dependencyEntries;
221 }
222 }
223
224 if (mode == AssetType::Sprite)
225 {
226 HTexture tex = static_resource_cast<Texture>(outputRes);
227 std::string spriteUUID = entry["SpriteUUID"];
228
229 bool isAnimated = entry.find("Animation") != entry.end();
230 if(isAnimated)
231 {
232 auto& jsonAnimation = entry["Animation"];
233
234 SpriteSheetGridAnimation animation;
235 animation.numRows = jsonAnimation["NumRows"].get<UINT32>();
236 animation.numColumns = jsonAnimation["NumColumns"].get<UINT32>();
237 animation.count = jsonAnimation["Count"].get<UINT32>();
238 animation.fps = jsonAnimation["FPS"].get<UINT32>();
239
240 generateAnimatedSprite(tex, name.c_str(), UUID(spriteUUID.c_str()),
241 SpriteAnimationPlayback::Loop, animation);
242 }
243 else
244 generateSprite(tex, name.c_str(), UUID(spriteUUID.c_str()));
245
246 }
247
248 if (isIcon)
249 {
250 IconData iconData;
251 iconData.source = static_resource_cast<Texture>(outputRes);
252 iconData.name = name.c_str();
253
254 if (mode == AssetType::Normal)
255 {
256 iconData.TextureUUIDs[0] = entry["UUID48"];
257 iconData.TextureUUIDs[1] = entry["UUID32"];
258 iconData.TextureUUIDs[2] = entry["UUID16"];
259 }
260 else if (mode == AssetType::Sprite)
261 {
262 iconData.TextureUUIDs[0] = entry["TextureUUID48"];
263 iconData.TextureUUIDs[1] = entry["TextureUUID32"];
264 iconData.TextureUUIDs[2] = entry["TextureUUID16"];
265
266 iconData.SpriteUUIDs[0] = entry["SpriteUUID48"];
267 iconData.SpriteUUIDs[1] = entry["SpriteUUID32"];
268 iconData.SpriteUUIDs[2] = entry["SpriteUUID16"];
269 }
270
271 iconsToGenerate.push_back(iconData);
272 }
273 }
274
275 iter = queuedOps.erase(iter);
276 }
277 }
278
279 for(UINT32 i = 0; i < (UINT32)iconsToGenerate.size(); i++)
280 {
281 IconData& data = iconsToGenerate[i];
282
283 data.srcData = data.source->getProperties().allocBuffer(0, 0);
284 data.source->readData(data.srcData);
285 }
286
287 gCoreThread().submit(true);
288
289 auto saveTexture = [&](auto& pixelData, auto& path, std::string& uuid)
290 {
291 SPtr<Texture> texturePtr = Texture::_createPtr(pixelData);
292 HResource texture = gResources()._createResourceHandle(texturePtr, UUID(uuid.c_str()));
293
294 Resources::instance().save(texture, path, true, compress);
295 manifest->registerResource(texture.getUUID(), path);
296
297 return static_resource_cast<Texture>(texture);
298 };
299
300 for (UINT32 i = 0; i < (UINT32)iconsToGenerate.size(); i++)
301 {
302 SPtr<PixelData> src = iconsToGenerate[i].srcData;
303
304 SPtr<PixelData> scaled48 = PixelData::create(48, 48, 1, src->getFormat());
305 PixelUtil::scale(*src, *scaled48);
306
307 SPtr<PixelData> scaled32 = PixelData::create(32, 32, 1, src->getFormat());
308 PixelUtil::scale(*scaled48, *scaled32);
309
310 SPtr<PixelData> scaled16 = PixelData::create(16, 16, 1, src->getFormat());
311 PixelUtil::scale(*scaled32, *scaled16);
312
313 Path outputPath48 = outputFolder + (iconsToGenerate[i].name + "48.asset");
314 Path outputPath32 = outputFolder + (iconsToGenerate[i].name + "32.asset");
315 Path outputPath16 = outputFolder + (iconsToGenerate[i].name + "16.asset");
316
317 HTexture tex48 = saveTexture(scaled48, outputPath48, iconsToGenerate[i].TextureUUIDs[0]);
318 HTexture tex32 = saveTexture(scaled32, outputPath32, iconsToGenerate[i].TextureUUIDs[1]);
319 HTexture tex16 = saveTexture(scaled16, outputPath16, iconsToGenerate[i].TextureUUIDs[2]);
320
321 if (mode == AssetType::Sprite)
322 {
323 generateSprite(tex48, iconsToGenerate[i].name + "48", UUID(iconsToGenerate[i].SpriteUUIDs[0].c_str()));
324 generateSprite(tex32, iconsToGenerate[i].name + "32", UUID(iconsToGenerate[i].SpriteUUIDs[1].c_str()));
325 generateSprite(tex16, iconsToGenerate[i].name + "16", UUID(iconsToGenerate[i].SpriteUUIDs[2].c_str()));
326 }
327 }
328 }
329
330 void BuiltinResourcesHelper::importFont(const Path& inputFile, const String& outputName, const Path& outputFolder,
331 const Vector<UINT32>& fontSizes, bool antialiasing, const UUID& UUID, const SPtr<ResourceManifest>& manifest)
332 {
333 SPtr<ImportOptions> fontImportOptions = Importer::instance().createImportOptions(inputFile);
334 if (rtti_is_of_type<FontImportOptions>(fontImportOptions))
335 {
336 FontImportOptions* importOptions = static_cast<FontImportOptions*>(fontImportOptions.get());
337
338 importOptions->fontSizes = { fontSizes };
339 importOptions->renderMode = antialiasing ? FontRenderMode::HintedSmooth : FontRenderMode::HintedRaster;
340 }
341 else
342 return;
343
344 HFont font = Importer::instance().import<Font>(inputFile, fontImportOptions, UUID);
345
346 String fontName = outputName;
347 Path outputPath = outputFolder + fontName;
348 outputPath.setFilename(outputPath.getFilename() + u8".asset");
349
350 Resources::instance().save(font, outputPath, true);
351 manifest->registerResource(font.getUUID(), outputPath);
352
353 // Save font texture pages as well. TODO - Later maybe figure out a more automatic way to do this
354 for (auto& size : fontSizes)
355 {
356 SPtr<const FontBitmap> fontData = font->getBitmap(size);
357
358 Path texPageOutputPath = outputFolder;
359
360 UINT32 pageIdx = 0;
361 for (auto tex : fontData->texturePages)
362 {
363 texPageOutputPath.setFilename(fontName + u8"_" + toString(size) + u8"_texpage_" +
364 toString(pageIdx) + u8".asset");
365
366 Resources::instance().save(tex, texPageOutputPath, true);
367 manifest->registerResource(tex.getUUID(), texPageOutputPath);
368
369 pageIdx++;
370 }
371 }
372 }
373
374 Vector<bool> BuiltinResourcesHelper::generateImportFlags(const nlohmann::json& entries, const Path& inputFolder,
375 time_t lastUpdateTime, bool forceImport, const nlohmann::json* dependencies, const Path& dependencyFolder)
376 {
377 Vector<bool> output(entries.size());
378 UINT32 idx = 0;
379 for (auto& entry : entries)
380 {
381 std::string name = entry["Path"];
382
383 if (forceImport)
384 output[idx] = true;
385 else
386 {
387 Path filePath = inputFolder + Path(name.c_str());
388
389 // Check timestamp
390 time_t lastModifiedSrc = FileSystem::getLastModifiedTime(filePath);
391 if (lastModifiedSrc > lastUpdateTime)
392 output[idx] = true;
393 else if (dependencies != nullptr) // Check dependencies
394 {
395 bool anyDepModified = false;
396 auto iterFind = dependencies->find(name);
397 if(iterFind != dependencies->end())
398 {
399 for(auto& dependency : *iterFind)
400 {
401 std::string dependencyName = dependency["Path"];
402 Path dependencyPath = dependencyFolder + Path(dependencyName.c_str());
403
404 time_t lastModifiedDep = FileSystem::getLastModifiedTime(dependencyPath);
405 if(lastModifiedDep > lastUpdateTime)
406 {
407 anyDepModified = true;
408 break;
409 }
410 }
411 }
412
413 output[idx] = anyDepModified;
414 }
415 else
416 output[idx] = false;
417 }
418
419 idx++;
420 }
421
422 return output;
423 }
424
425 bool BuiltinResourcesHelper::updateJSON(const Path& folder, AssetType type, nlohmann::json& entries)
426 {
427 UnorderedSet<Path> existingEntries;
428 for(auto& entry : entries)
429 {
430 std::string strPath = entry["Path"];
431 Path path = strPath.c_str();
432
433 existingEntries.insert(path);
434 }
435
436 bool foundChanges = false;
437 auto checkForChanges = [&](const Path& filePath)
438 {
439 Path relativePath = filePath.getRelative(folder);
440
441 auto iterFind = existingEntries.find(relativePath);
442 if(iterFind == existingEntries.end())
443 {
444 if(type == AssetType::Normal)
445 {
446 String uuid = UUIDGenerator::generateRandom().toString();
447 nlohmann::json newEntry =
448 {
449 { "Path", relativePath.toString().c_str() },
450 { "UUID", uuid.c_str() }
451 };
452
453 entries.push_back(newEntry);
454 }
455 else // Sprite
456 {
457 String texUuid = UUIDGenerator::generateRandom().toString();
458 String spriteUuid = UUIDGenerator::generateRandom().toString();
459 nlohmann::json newEntry =
460 {
461 { "Path", relativePath.toString().c_str() },
462 { "SpriteUUID", spriteUuid.c_str() },
463 { "TextureUUID", texUuid.c_str() }
464 };
465
466 entries.push_back(newEntry);
467 }
468
469 foundChanges = true;
470 }
471
472 return true;
473 };
474
475 FileSystem::iterate(folder, checkForChanges, nullptr, false);
476
477 // Prune deleted entries
478 auto iter = entries.begin();
479 while(iter != entries.end())
480 {
481 std::string strPath = (*iter)["Path"];
482 Path path = strPath.c_str();
483 path = path.getAbsolute(folder);
484
485 if (!FileSystem::exists(path))
486 {
487 iter = entries.erase(iter);
488 foundChanges = true;
489 }
490 else
491 ++iter;
492 }
493
494 return foundChanges;
495 }
496
497 void BuiltinResourcesHelper::updateManifest(const Path& folder, const nlohmann::json& entries,
498 const SPtr<ResourceManifest>& manifest, AssetType type)
499 {
500 for (auto& entry : entries)
501 {
502 std::string name = entry["Path"];
503 std::string uuid;
504
505 bool isIcon = false;
506 if (type == AssetType::Normal)
507 {
508 uuid = entry["UUID"];
509 isIcon = entry.find("UUID16") != entry.end();
510 }
511 else if (type == AssetType::Sprite)
512 {
513 uuid = entry["TextureUUID"];
514 isIcon = entry.find("TextureUUID16") != entry.end();
515 }
516
517 Path path = folder + name.c_str();
518 path.setFilename(path.getFilename() + u8".asset");
519
520 manifest->registerResource(UUID(uuid.c_str()), path);
521
522 if (type == AssetType::Sprite)
523 {
524 std::string spriteUUID = entry["SpriteUUID"];
525
526 Path spritePath = folder + "/Sprites/";
527 spritePath.setFilename(String("sprite_") + name.c_str() + ".asset");
528
529 manifest->registerResource(UUID(spriteUUID.c_str()), spritePath);
530 }
531
532 if (isIcon)
533 {
534 std::string texUUIDs[3];
535
536 if (type == AssetType::Normal)
537 {
538 texUUIDs[0] = entry["UUID48"];
539 texUUIDs[1] = entry["UUID32"];
540 texUUIDs[2] = entry["UUID16"];
541 }
542 else if (type == AssetType::Sprite)
543 {
544 texUUIDs[0] = entry["TextureUUID48"];
545 texUUIDs[1] = entry["TextureUUID32"];
546 texUUIDs[2] = entry["TextureUUID16"];
547 }
548
549 Path texPath = folder + name.c_str();
550
551 texPath.setFilename(texPath.getFilename() + u8"48.asset");
552 manifest->registerResource(UUID(texUUIDs[0].c_str()), texPath);
553
554 texPath.setFilename(texPath.getFilename() + u8"32.asset");
555 manifest->registerResource(UUID(texUUIDs[1].c_str()), texPath);
556
557 texPath.setFilename(texPath.getFilename() + u8"16.asset");
558 manifest->registerResource(UUID(texUUIDs[2].c_str()), texPath);
559
560 if(type == AssetType::Sprite)
561 {
562 std::string spriteUUIDs[3];
563
564 spriteUUIDs[0] = entry["SpriteUUID48"];
565 spriteUUIDs[1] = entry["SpriteUUID32"];
566 spriteUUIDs[2] = entry["SpriteUUID16"];
567
568 Path spritePath = folder + "/Sprites/";
569
570 spritePath.setFilename(String("sprite_") + name.c_str() + "48.asset");
571 manifest->registerResource(UUID(spriteUUIDs[0].c_str()), spritePath);
572
573 spritePath.setFilename(String("sprite_") + name.c_str() + "32.asset");
574 manifest->registerResource(UUID(spriteUUIDs[1].c_str()), spritePath);
575
576 spritePath.setFilename(String("sprite_") + name.c_str() + "16.asset");
577 manifest->registerResource(UUID(spriteUUIDs[2].c_str()), spritePath);
578 }
579 }
580 }
581 }
582
583 void BuiltinResourcesHelper::writeTimestamp(const Path& file)
584 {
585 SPtr<DataStream> fileStream = FileSystem::createAndOpenFile(file);
586
587 time_t currentTime = std::time(nullptr);
588 fileStream->write(&currentTime, sizeof(currentTime));
589 fileStream->close();
590 }
591
592 UINT32 BuiltinResourcesHelper::checkForModifications(const Path& folder, const Path& timeStampFile,
593 time_t& lastUpdateTime)
594 {
595 lastUpdateTime = 0;
596
597 if (!FileSystem::exists(timeStampFile))
598 return 2;
599
600 lastUpdateTime = FileSystem::getLastModifiedTime(timeStampFile);
601
602 bool upToDate = true;
603 auto checkUpToDate = [&](const Path& filePath)
604 {
605 time_t fileLastModified = FileSystem::getLastModifiedTime(filePath);
606
607 if (fileLastModified > lastUpdateTime)
608 {
609 upToDate = false;
610 return false;
611 }
612
613 return true;
614 };
615
616 FileSystem::iterate(folder, checkUpToDate, nullptr);
617
618 if (!upToDate)
619 return 1;
620
621 return 0;
622 }
623
624 bool BuiltinResourcesHelper::verifyAndReportShader(const HShader& shader)
625 {
626 if(!shader.isLoaded(false) || shader->getNumTechniques() == 0)
627 {
628#if BS_DEBUG_MODE
629 BS_EXCEPT(InvalidStateException, "Error occured while compiling a shader. Check earlier log messages for exact error.");
630#else
631 LOGERR("Error occured while compiling a shader. Check earlier log messages for exact error.")
632#endif
633 return false;
634 }
635
636 Vector<SPtr<Technique>> techniques = shader->getCompatibleTechniques();
637 for(auto& technique : techniques)
638 {
639 technique->compile();
640
641 UINT32 numPasses = technique->getNumPasses();
642 for(UINT32 i = 0; i < numPasses; i++)
643 {
644 SPtr<Pass> pass = technique->getPass(i);
645
646 std::array<SPtr<GpuProgram>, 6> gpuPrograms;
647
648 const SPtr<GraphicsPipelineState>& graphicsPipeline = pass->getGraphicsPipelineState();
649 if (graphicsPipeline)
650 {
651 gpuPrograms[0] = graphicsPipeline->getVertexProgram();
652 gpuPrograms[1] = graphicsPipeline->getFragmentProgram();
653 gpuPrograms[2] = graphicsPipeline->getGeometryProgram();
654 gpuPrograms[3] = graphicsPipeline->getHullProgram();
655 gpuPrograms[4] = graphicsPipeline->getDomainProgram();
656 }
657
658 const SPtr<ComputePipelineState>& computePipeline = pass->getComputePipelineState();
659 if (computePipeline)
660 gpuPrograms[5] = computePipeline->getProgram();
661
662 for(auto& program : gpuPrograms)
663 {
664 if (program == nullptr)
665 continue;
666
667 program->blockUntilCoreInitialized();
668 if(!program->isCompiled())
669 {
670 String errMsg = "Error occured while compiling a shader \"" + shader->getName()
671 + "\". Error message: " + program->getCompileErrorMessage();
672
673#if BS_DEBUG_MODE
674 BS_EXCEPT(InvalidStateException, errMsg);
675#else
676 LOGERR(errMsg)
677#endif
678 return false;
679 }
680 }
681 }
682 }
683
684 return true;
685 }
686
687 void BuiltinResourcesHelper::updateShaderBytecode(const Path& path)
688 {
689 HShader shader = gResources().load<Shader>(path, ResourceLoadFlag::KeepSourceData);
690 if (!shader)
691 return;
692
693 Vector<SPtr<Technique>> techniques = shader->getCompatibleTechniques();
694 bool hasBytecode = true;
695 for (auto& technique : techniques)
696 {
697 UINT32 numPasses = technique->getNumPasses();
698 for (UINT32 i = 0; i < numPasses; i++)
699 {
700 SPtr<Pass> pass = technique->getPass(i);
701
702 for (UINT32 j = 0; j < GPT_COUNT; j++)
703 {
704 const GPU_PROGRAM_DESC& desc = pass->getProgramDesc((GpuProgramType)j);
705 if (desc.source.empty())
706 continue;
707
708 if (!desc.bytecode)
709 {
710 hasBytecode = false;
711 break;
712 }
713 }
714
715 if (!hasBytecode)
716 break;
717 }
718
719 if (!hasBytecode)
720 break;
721 }
722
723 if (hasBytecode)
724 return;
725
726 for (auto& technique : techniques)
727 technique->compile();
728
729 gResources().save(shader, path, true, true);
730 }
731
732 GUIElementStyle BuiltinResourcesHelper::loadGUIStyleFromJSON(const nlohmann::json& entry,
733 const GUIElementStyleLoader& loader)
734 {
735 GUIElementStyle style;
736
737 if(entry.count("font") > 0)
738 {
739 std::string font = entry["font"];
740 style.font = loader.loadFont(font.c_str());
741 }
742
743 if(entry.count("fontSize") > 0)
744 style.fontSize = entry["fontSize"];
745
746 if(entry.count("textHorzAlign") > 0)
747 style.textHorzAlign = entry["textHorzAlign"];
748
749 if(entry.count("textVertAlign") > 0)
750 style.textVertAlign = entry["textVertAlign"];
751
752 if(entry.count("imagePosition") > 0)
753 style.imagePosition = entry["imagePosition"];
754
755 if(entry.count("wordWrap") > 0)
756 style.wordWrap = entry["wordWrap"];
757
758 const auto loadState = [&loader, &entry](const char* name, GUIElementStateStyle& state)
759 {
760 if (entry.count(name) == 0)
761 return false;
762
763 nlohmann::json subEntry = entry[name];
764
765 if(subEntry.count("texture") > 0)
766 {
767 std::string texture = subEntry["texture"];
768 state.texture = loader.loadTexture(texture.c_str());
769 }
770
771 if(subEntry.count("textColor") > 0)
772 {
773 nlohmann::json colorEntry = subEntry["textColor"];
774
775 state.textColor.r = colorEntry["r"];
776 state.textColor.g = colorEntry["g"];
777 state.textColor.b = colorEntry["b"];
778 state.textColor.a = colorEntry["a"];
779 }
780
781 return true;
782 };
783
784 loadState("normal", style.normal);
785
786 const bool hasHover = loadState("hover", style.hover);
787 if(!hasHover)
788 style.hover = style.normal;
789
790 if(!loadState("active", style.active))
791 style.active = style.normal;
792
793 if(!loadState("focused", style.focused))
794 style.focused = style.normal;
795
796 if(!loadState("focusedHover", style.focusedHover))
797 {
798 if(hasHover)
799 style.focusedHover = style.hover;
800 else
801 style.focusedHover = style.normal;
802 }
803
804 loadState("normalOn", style.normalOn);
805
806 const bool hasHoverOn = loadState("hoverOn", style.hoverOn);
807 if(!hasHoverOn)
808 style.hoverOn = style.normalOn;
809
810 if(!loadState("activeOn", style.activeOn))
811 style.activeOn = style.normalOn;
812
813 if(!loadState("focusedOn", style.focusedOn))
814 style.focusedOn = style.normalOn;
815
816 if(!loadState("focusedHoverOn", style.focusedHoverOn))
817 {
818 if(hasHoverOn)
819 style.focusedHoverOn = style.hoverOn;
820 else
821 style.focusedHoverOn = style.normalOn;
822 }
823
824 const auto loadRectOffset = [entry](const char* name, RectOffset& state)
825 {
826 if (entry.count(name) == 0)
827 return;
828
829 nlohmann::json subEntry = entry[name];
830 state.left = subEntry["left"];
831 state.right = subEntry["right"];
832 state.top = subEntry["top"];
833 state.bottom = subEntry["bottom"];
834 };
835
836 loadRectOffset("border", style.border);
837 loadRectOffset("margins", style.margins);
838 loadRectOffset("contentOffset", style.contentOffset);
839 loadRectOffset("padding", style.padding);
840
841 if(entry.count("width") > 0)
842 style.width = entry["width"];
843
844 if(entry.count("height") > 0)
845 style.height = entry["height"];
846
847 if(entry.count("minWidth") > 0)
848 style.minWidth = entry["minWidth"];
849
850 if(entry.count("maxWidth") > 0)
851 style.maxWidth = entry["maxWidth"];
852
853 if(entry.count("minHeight") > 0)
854 style.minHeight = entry["minHeight"];
855
856 if(entry.count("maxHeight") > 0)
857 style.maxHeight = entry["maxHeight"];
858
859 if(entry.count("fixedWidth") > 0)
860 style.fixedWidth = entry["fixedWidth"];
861
862 if(entry.count("fixedHeight") > 0)
863 style.fixedHeight = entry["fixedHeight"];
864
865 if(entry.count("subStyles") > 0)
866 {
867 nlohmann::json subStyles = entry["subStyles"];
868 for (auto& subStyle : subStyles)
869 {
870 std::string name = subStyle["name"];
871 std::string styleName = subStyle["style"];
872
873 style.subStyles.insert(std::make_pair(name.c_str(), styleName.c_str()));
874 }
875 }
876
877 return style;
878 }
879
880 BuiltinResourceGUIElementStyleLoader::BuiltinResourceGUIElementStyleLoader(const Path& fontPath, const Path& texturePath)
881 :mFontPath(fontPath), mTexturePath(texturePath)
882 { }
883
884
885 HSpriteTexture BuiltinResourceGUIElementStyleLoader::loadTexture(const String& name) const
886 {
887 Path texturePath = mTexturePath;
888 texturePath.append(u8"sprite_" + name + u8".asset");
889
890 return gResources().load<SpriteTexture>(texturePath);
891 }
892
893 HFont BuiltinResourceGUIElementStyleLoader::loadFont(const String& name) const
894 {
895 Path fontPath = mFontPath;
896 fontPath.append(name + u8".asset");
897
898 return gResources().load<Font>(fontPath);
899 }
900}