-
Notifications
You must be signed in to change notification settings - Fork 7.1k
/
CompressedTextureFile.js
577 lines (497 loc) · 19.9 KB
/
CompressedTextureFile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2021 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var AtlasJSONFile = require('./AtlasJSONFile');
var BinaryFile = require('./BinaryFile');
var Class = require('../../utils/Class');
var FileTypesManager = require('../FileTypesManager');
var GetFastValue = require('../../utils/object/GetFastValue');
var ImageFile = require('./ImageFile');
var IsPlainObject = require('../../utils/object/IsPlainObject');
var JSONFile = require('./JSONFile');
var KTXParser = require('../../textures/parsers/KTXParser');
var Merge = require('../../utils/object/Merge');
var MultiAtlasFile = require('./MultiAtlasFile');
var MultiFile = require('../MultiFile');
var PVRParser = require('../../textures/parsers/PVRParser');
var verifyCompressedTexture = require('../../textures/parsers/VerifyCompressedTexture');
/**
* @classdesc
* A Compressed Texture File suitable for loading by the Loader.
*
* These are created when you use the Phaser.Loader.LoaderPlugin#texture method and are not typically created directly.
*
* For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#texture.
*
* @class CompressedTextureFile
* @extends Phaser.Loader.MultiFile
* @memberof Phaser.Loader.FileTypes
* @constructor
* @since 3.60.0
*
* @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader that is responsible for this file.
* @param {string} key - The key to use for this file.
* @param {Phaser.Types.Loader.FileTypes.CompressedTextureFileEntry} entry - The compressed texture file entry to load.
* @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
*/
var CompressedTextureFile = new Class({
Extends: MultiFile,
initialize:
function CompressedTextureFile (loader, key, entry, xhrSettings)
{
if (entry.multiAtlasURL)
{
var multi = new JSONFile(loader, {
key: key,
url: entry.multiAtlasURL,
xhrSettings: xhrSettings,
config: entry
});
MultiFile.call(this, loader, 'texture', key, [ multi ]);
}
else
{
var extension = entry.textureURL.substr(entry.textureURL.length - 3);
if (!entry.type)
{
entry.type = (extension.toLowerCase() === 'ktx') ? 'KTX' : 'PVR';
}
var image = new BinaryFile(loader, {
key: key,
url: entry.textureURL,
extension: extension,
xhrSettings: xhrSettings,
config: entry
});
if (entry.atlasURL)
{
var data = new JSONFile(loader, {
key: key,
url: entry.atlasURL,
xhrSettings: xhrSettings,
config: entry
});
MultiFile.call(this, loader, 'texture', key, [ image, data ]);
}
else
{
MultiFile.call(this, loader, 'texture', key, [ image ]);
}
}
this.config = entry;
},
/**
* Called by each File when it finishes loading.
*
* @method Phaser.Loader.FileTypes.CompressedTextureFile#onFileComplete
* @since 3.60.0
*
* @param {Phaser.Loader.File} file - The File that has completed processing.
*/
onFileComplete: function (file)
{
var index = this.files.indexOf(file);
if (index !== -1)
{
this.pending--;
if (!this.config.multiAtlasURL)
{
return;
}
if (file.type === 'json' && file.data.hasOwnProperty('textures'))
{
// Inspect the data for the files to now load
var textures = file.data.textures;
var config = this.config;
var loader = this.loader;
var currentBaseURL = loader.baseURL;
var currentPath = loader.path;
var currentPrefix = loader.prefix;
var baseURL = GetFastValue(config, 'multiBaseURL', this.baseURL);
var path = GetFastValue(config, 'multiPath', this.path);
var prefix = GetFastValue(config, 'prefix', this.prefix);
var textureXhrSettings = GetFastValue(config, 'textureXhrSettings');
if (baseURL)
{
loader.setBaseURL(baseURL);
}
if (path)
{
loader.setPath(path);
}
if (prefix)
{
loader.setPrefix(prefix);
}
for (var i = 0; i < textures.length; i++)
{
// "image": "texture-packer-multi-atlas-0.png",
var textureURL = textures[i].image;
var key = 'CMA' + this.multiKeyIndex + '_' + textureURL;
var image = new BinaryFile(loader, key, textureURL, textureXhrSettings);
this.addToMultiFile(image);
loader.addFile(image);
// "normalMap": "texture-packer-multi-atlas-0_n.png",
if (textures[i].normalMap)
{
var normalMap = new BinaryFile(loader, key, textures[i].normalMap, textureXhrSettings);
normalMap.type = 'normalMap';
image.setLink(normalMap);
this.addToMultiFile(normalMap);
loader.addFile(normalMap);
}
}
// Reset the loader settings
loader.setBaseURL(currentBaseURL);
loader.setPath(currentPath);
loader.setPrefix(currentPrefix);
}
}
},
/**
* Adds this file to its target cache upon successful loading and processing.
*
* @method Phaser.Loader.FileTypes.CompressedTextureFile#addToCache
* @since 3.60.0
*/
addToCache: function ()
{
function compressionWarning (message)
{
console.warn('Compressed Texture Invalid: "' + image.key + '". ' + message);
}
if (this.isReadyToProcess())
{
var entry = this.config;
if (entry.multiAtlasURL)
{
this.addMultiToCache();
}
else
{
var renderer = this.loader.systems.renderer;
var textureManager = this.loader.textureManager;
var textureData;
var image = this.files[0];
var json = this.files[1];
if (entry.type === 'PVR')
{
textureData = PVRParser(image.data);
}
else if (entry.type === 'KTX')
{
textureData = KTXParser(image.data);
if (!textureData)
{
compressionWarning('KTX file contains unsupported format.');
}
}
// Check block size.
if (textureData && !verifyCompressedTexture(textureData))
{
compressionWarning('Texture dimensions failed verification. Check the texture format specifications for ' + entry.format + ' 0x' + textureData.internalFormat.toString(16) + '.');
textureData = null;
}
// Check texture compression.
if (textureData && !renderer.supportsCompressedTexture(entry.format, textureData.internalFormat))
{
compressionWarning('Texture format ' + entry.format + ' with internal format ' + textureData.internalFormat + ' not supported by the GPU. Texture invalid. This is often due to the texture using sRGB instead of linear RGB.');
textureData = null;
}
if (textureData)
{
textureData.format = renderer.getCompressedTextureName(entry.format, textureData.internalFormat);
var atlasData = (json && json.data) ? json.data : null;
textureManager.addCompressedTexture(image.key, textureData, atlasData);
}
}
this.complete = true;
}
},
/**
* Adds all of the multi-file entties to their target caches upon successful loading and processing.
*
* @method Phaser.Loader.FileTypes.CompressedTextureFile#addMultiToCache
* @since 3.60.0
*/
addMultiToCache: function ()
{
var entry = this.config;
var json = this.files[0];
var data = [];
var images = [];
var normalMaps = [];
var renderer = this.loader.systems.renderer;
var textureManager = this.loader.textureManager;
var textureData;
for (var i = 1; i < this.files.length; i++)
{
var file = this.files[i];
if (file.type === 'normalMap')
{
continue;
}
var pos = file.key.indexOf('_');
var key = file.key.substr(pos + 1);
var image = file.data;
// Now we need to find out which json entry this mapped to
for (var t = 0; t < json.data.textures.length; t++)
{
var item = json.data.textures[t];
if (item.image === key)
{
if (entry.type === 'PVR')
{
textureData = PVRParser(image);
}
else if (entry.type === 'KTX')
{
textureData = KTXParser(image);
}
if (textureData && renderer.supportsCompressedTexture(entry.format, textureData.internalFormat))
{
textureData.format = renderer.getCompressedTextureName(entry.format, textureData.internalFormat);
images.push(textureData);
data.push(item);
if (file.linkFile)
{
normalMaps.push(file.linkFile.data);
}
}
break;
}
}
}
if (normalMaps.length === 0)
{
normalMaps = undefined;
}
textureManager.addAtlasJSONArray(this.key, images, data, normalMaps);
this.complete = true;
}
});
/**
* Adds a Compressed Texture file to the current load queue. This feature is WebGL only.
*
* This method takes a key and a configuration object, which lists the different formats
* and files associated with them.
*
* The texture format object should be ordered in GPU priority order, with IMG as the last entry.
*
* You can call this method from within your Scene's `preload`, along with any other files you wish to load:
*
* ```javascript
* preload ()
* {
* this.load.texture('yourPic', {
* ASTC: { type: 'PVR', textureURL: 'pic-astc-4x4.pvr' },
* PVRTC: { type: 'PVR', textureURL: 'pic-pvrtc-4bpp-rgba.pvr' },
* S3TC: { type: 'PVR', textureURL: 'pic-dxt5.pvr' },
* IMG: { textureURL: 'pic.png' }
* });
* ```
*
* If you wish to load a texture atlas, provide the `atlasURL` property:
*
* ```javascript
* preload ()
* {
* const path = 'assets/compressed';
*
* this.load.texture('yourAtlas', {
* 'ASTC': { type: 'PVR', textureURL: `${path}/textures-astc-4x4.pvr`, atlasURL: `${path}/textures.json` },
* 'PVRTC': { type: 'PVR', textureURL: `${path}/textures-pvrtc-4bpp-rgba.pvr`, atlasURL: `${path}/textures-pvrtc-4bpp-rgba.json` },
* 'S3TC': { type: 'PVR', textureURL: `${path}/textures-dxt5.pvr`, atlasURL: `${path}/textures-dxt5.json` },
* 'IMG': { textureURL: `${path}/textures.png`, atlasURL: `${path}/textures.json` }
* });
* }
* ```
*
* If you wish to load a Multi Atlas, as exported from Texture Packer Pro, use the `multiAtlasURL` property instead:
*
* ```javascript
* preload ()
* {
* const path = 'assets/compressed';
*
* this.load.texture('yourAtlas', {
* 'ASTC': { type: 'PVR', atlasURL: `${path}/textures.json` },
* 'PVRTC': { type: 'PVR', atlasURL: `${path}/textures-pvrtc-4bpp-rgba.json` },
* 'S3TC': { type: 'PVR', atlasURL: `${path}/textures-dxt5.json` },
* 'IMG': { atlasURL: `${path}/textures.json` }
* });
* }
* ```
*
* When loading a Multi Atlas you do not need to specify the `textureURL` property as it will be read from the JSON file.
*
* Instead of passing arguments you can pass a configuration object, such as:
*
* ```javascript
* this.load.texture({
* key: 'yourPic',
* url: {
* ASTC: { type: 'PVR', textureURL: 'pic-astc-4x4.pvr' },
* PVRTC: { type: 'PVR', textureURL: 'pic-pvrtc-4bpp-rgba.pvr' },
* S3TC: { type: 'PVR', textureURL: 'pic-dxt5.pvr' },
* IMG: { textureURL: 'pic.png' }
* }
* });
* ```
*
* See the documentation for `Phaser.Types.Loader.FileTypes.CompressedTextureFileConfig` for more details.
*
* The number of formats you provide to this function is up to you, but you should ensure you
* cover the primary platforms where appropriate.
*
* The 'IMG' entry is a fallback to a JPG or PNG, should the browser be unable to load any of the other
* formats presented to this function. You should really always include this, although it is optional.
*
* Phaser supports loading both the PVR and KTX container formats. Within those, it can parse
* the following texture compression formats:
*
* ETC
* ETC1
* ATC
* ASTC
* BPTC
* RGTC
* PVRTC
* S3TC
* S3TCSRGB
*
* For more information about the benefits of compressed textures please see the
* following articles:
*
* Texture Compression in 2020 (https://aras-p.info/blog/2020/12/08/Texture-Compression-in-2020/)
* Compressed GPU Texture Formats (https://themaister.net/blog/2020/08/12/compressed-gpu-texture-formats-a-review-and-compute-shader-decoders-part-1/)
*
* To create compressed texture files use a 3rd party application such as:
*
* Texture Packer (https://www.codeandweb.com/texturepacker/tutorials/how-to-create-sprite-sheets-for-phaser3?utm_source=ad&utm_medium=banner&utm_campaign=phaser-2018-10-16)
* PVRTexTool (https://developer.imaginationtech.com/pvrtextool/) - available for Windows, macOS and Linux.
* Mali Texture Compression Tool (https://developer.arm.com/tools-and-software/graphics-and-gaming/mali-texture-compression-tool)
* ASTC Encoder (https://github.com/ARM-software/astc-encoder)
*
* ASTCs must have a Channel Type of Unsigned Normalized Bytes (UNorm).
*
* The file is **not** loaded right away. It is added to a queue ready to be loaded either when the loader starts,
* or if it's already running, when the next free load slot becomes available. This happens automatically if you
* are calling this from within the Scene's `preload` method, or a related callback. Because the file is queued
* it means you cannot use the file immediately after calling this method, but must wait for the file to complete.
* The typical flow for a Phaser Scene is that you load assets in the Scene's `preload` method and then when the
* Scene's `create` method is called you are guaranteed that all of those assets are ready for use and have been
* loaded.
*
* The key must be a unique String. It is used to add the file to the global Texture Manager upon a successful load.
* The key should be unique both in terms of files being loaded and files already present in the Texture Manager.
* Loading a file using a key that is already taken will result in a warning. If you wish to replace an existing file
* then remove it from the Texture Manager first, before loading a new one.
*
* If you have specified a prefix in the loader, via `Loader.setPrefix` then this value will be prepended to this files
* key. For example, if the prefix was `LEVEL1.` and the key was `Data` the final key will be `LEVEL1.Data` and
* this is what you would use to retrieve the text from the Texture Manager.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* Unlike other file loaders in Phaser, the URLs must include the file extension.
*
* Note: The ability to load this type of file will only be available if the Compressed Texture File type has been built into Phaser.
* It is available in the default build but can be excluded from custom builds.
*
* @method Phaser.Loader.LoaderPlugin#texture
* @fires Phaser.Loader.Events#ADD
* @since 3.60.0
*
* @param {(string|Phaser.Types.Loader.FileTypes.CompressedTextureFileConfig|Phaser.Types.Loader.FileTypes.CompressedTextureFileConfig[])} key - The key to use for this file, or a file configuration object, or array of them.
* @param {Phaser.Types.Loader.FileTypes.CompressedTextureFileConfig} [url] - The compressed texture configuration object. Not required if passing a config object as the `key` parameter.
* @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - An XHR Settings configuration object. Used in replacement of the Loaders default XHR Settings.
*
* @return {this} The Loader instance.
*/
FileTypesManager.register('texture', function (key, url, xhrSettings)
{
var renderer = this.systems.renderer;
var AddEntry = function (loader, key, urls, xhrSettings)
{
var entry = {
format: null,
type: null,
textureURL: undefined,
atlasURL: undefined,
multiAtlasURL: undefined,
multiPath: undefined,
multiBaseURL: undefined
};
if (IsPlainObject(key))
{
var config = key;
key = GetFastValue(config, 'key');
urls = GetFastValue(config, 'url'),
xhrSettings = GetFastValue(config, 'xhrSettings');
}
var matched = false;
for (var textureBaseFormat in urls)
{
if (renderer.supportsCompressedTexture(textureBaseFormat))
{
var urlEntry = urls[textureBaseFormat];
if (typeof urlEntry === 'string')
{
entry.textureURL = urlEntry;
}
else
{
entry = Merge(urlEntry, entry);
}
entry.format = textureBaseFormat.toUpperCase();
matched = true;
break;
}
}
if (!matched)
{
console.warn('No supported compressed texture format or IMG fallback', key);
}
else if (entry.format === 'IMG')
{
var file;
var multifile;
if (entry.multiAtlasURL)
{
multifile = new MultiAtlasFile(loader, key, entry.multiAtlasURL, entry.multiPath, entry.multiBaseURL, xhrSettings);
file = multifile.files;
}
else if (entry.atlasURL)
{
multifile = new AtlasJSONFile(loader, key, entry.textureURL, entry.atlasURL, xhrSettings);
file = multifile.files;
}
else
{
file = new ImageFile(loader, key, entry.textureURL, xhrSettings);
}
loader.addFile(file);
}
else
{
var texture = new CompressedTextureFile(loader, key, entry, xhrSettings);
loader.addFile(texture.files);
}
};
if (Array.isArray(key))
{
for (var i = 0; i < key.length; i++)
{
AddEntry(this, key[i]);
}
}
else
{
AddEntry(this, key, url, xhrSettings);
}
return this;
});
module.exports = CompressedTextureFile;