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 | |
24 | using json = nlohmann::json; |
25 | |
26 | namespace bs |
27 | { |
28 | void BuiltinResourcesHelper::(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::(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::(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::(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(¤tTime, 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::(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 | } |