Skip to content

Commit

Permalink
Add detection parsers to Assets (pixijs#8482)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zyie committed Jul 14, 2022
1 parent 9c6c295 commit 4776c29
Show file tree
Hide file tree
Showing 20 changed files with 327 additions and 46 deletions.
58 changes: 40 additions & 18 deletions packages/assets/src/Assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { extensions, ExtensionType } from '@pixi/core';
import { BackgroundLoader } from './BackgroundLoader';
import { Cache } from './cache/Cache';
import { cacheSpritesheet, cacheTextureArray } from './cache/parsers';
import type { FormatDetectionParser } from './detections';
import { detectAvif, detectWebp } from './detections';
import type {
LoadAsset,
LoaderParser } from './loader';
LoaderParser
} from './loader';
import {
loadJson,
loadSpritesheet,
Expand All @@ -18,8 +21,6 @@ import type { PreferOrder, ResolveAsset, ResolverBundle, ResolverManifest, Resol
import { resolveSpriteSheetUrl, resolveTextureUrl } from './resolver';
import { Resolver } from './resolver/Resolver';
import { convertToList } from './utils/convertToList';
import { detectAvif } from './utils/detections/detectAvif';
import { detectWebp } from './utils/detections/detectWebp';
import { isSingleItem } from './utils/isSingleItem';

export type ProgressCallback = (progress: number) => void;
Expand Down Expand Up @@ -230,6 +231,8 @@ export class AssetsClass
/** takes care of loading assets in the background */
private readonly _backgroundLoader: BackgroundLoader;

private _detections: FormatDetectionParser[] = [];

private _initialized = false;

constructor()
Expand Down Expand Up @@ -284,32 +287,40 @@ export class AssetsClass
const resolutionPref = options.texturePreference?.resolution ?? 1;
const resolution = (typeof resolutionPref === 'number') ? [resolutionPref] : resolutionPref;

let format: string[];
let formats: string[];

if (options.texturePreference?.format)
{
const formatPref = options.texturePreference?.format;

format = (typeof formatPref === 'string') ? [formatPref] : formatPref;
formats = (typeof formatPref === 'string') ? [formatPref] : formatPref;

// we should remove any formats that are not supported by the browser
for (const detection of this._detections)
{
if (!await detection.test())
{
formats = await detection.remove(formats);
}
}
}
else
{
format = ['avif', 'webp', 'png', 'jpg', 'jpeg'];
}

if (!(await detectWebp()))
{
format = format.filter((format) => format !== 'webp');
}
formats = ['png', 'jpg', 'jpeg'];

if (!(await detectAvif()))
{
format = format.filter((format) => format !== 'avif');
// we should add any formats that are supported by the browser
for (const detection of this._detections)
{
if (await detection.test())
{
formats = await detection.add(formats);
}
}
}

this.resolver.prefer({
params: {
format,
format: formats,
resolution,
},
});
Expand Down Expand Up @@ -769,6 +780,12 @@ export class AssetsClass

await this.loader.unload(resolveArray);
}

/** All the detection parsers currently added to the Assets class. */
public get detections(): FormatDetectionParser[]
{
return this._detections;
}
}

export const Assets = new AssetsClass();
Expand All @@ -777,7 +794,8 @@ export const Assets = new AssetsClass();
extensions
.handleByList(ExtensionType.LoadParser, Assets.loader.parsers)
.handleByList(ExtensionType.ResolveParser, Assets.resolver.parsers)
.handleByList(ExtensionType.CacheParser, Assets.cache.parsers);
.handleByList(ExtensionType.CacheParser, Assets.cache.parsers)
.handleByList(ExtensionType.DetectionParser, Assets.detections);

extensions.add(
loadTextures,
Expand All @@ -793,5 +811,9 @@ extensions.add(

// resolve extensions
resolveTextureUrl,
resolveSpriteSheetUrl
resolveSpriteSheetUrl,

// detection extensions
detectWebp,
detectAvif
);
12 changes: 12 additions & 0 deletions packages/assets/src/detections/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ExtensionMetadata } from '@pixi/core';

export interface FormatDetectionParser
{
extension?: ExtensionMetadata;
test: () => Promise<boolean>,
add: (formats: string[]) => Promise<string[]>,
remove: (formats: string[]) => Promise<string[]>,
}

export * from './parsers';
export * from './utils';
20 changes: 20 additions & 0 deletions packages/assets/src/detections/parsers/detectAvif.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ExtensionType } from '@pixi/core';
import { settings } from '@pixi/settings';
import type { FormatDetectionParser } from '..';
import { addFormats, removeFormats } from '../utils/detectUtils';

export const detectAvif: FormatDetectionParser = {
extension: ExtensionType.DetectionParser,
test: async (): Promise<boolean> =>
{
if (!globalThis.createImageBitmap) return false;

// eslint-disable-next-line max-len
const avifData = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
const blob = await settings.ADAPTER.fetch(avifData).then((r) => r.blob());

return createImageBitmap(blob).then(() => true, () => false);
},
add: addFormats('avif'),
remove: removeFormats('avif')
};
11 changes: 11 additions & 0 deletions packages/assets/src/detections/parsers/detectBasis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BasisParser } from '@pixi/basis';
import { ExtensionType } from '@pixi/core';
import type { FormatDetectionParser } from '..';
import { addFormats, removeFormats } from '../utils/detectUtils';

export const detectBasis = {
extension: ExtensionType.DetectionParser,
test: async (): Promise<boolean> => !!(BasisParser.basisBinding && BasisParser.TranscoderWorker.wasmSource),
add: addFormats('basis'),
remove: removeFormats('basis')
} as FormatDetectionParser;
73 changes: 73 additions & 0 deletions packages/assets/src/detections/parsers/detectCompressedTextures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { CompressedTextureExtensionRef, CompressedTextureExtensions } from '@pixi/compressed-textures';
import { ExtensionType } from '@pixi/core';
import { settings } from '@pixi/settings';
import type { FormatDetectionParser } from '..';

let storedGl: WebGLRenderingContext;
let extensions: Partial<CompressedTextureExtensions>;

function getCompressedTextureExtensions()
{
extensions = {
s3tc: storedGl.getExtension('WEBGL_compressed_texture_s3tc'),
s3tc_sRGB: storedGl.getExtension('WEBGL_compressed_texture_s3tc_srgb'), /* eslint-disable-line camelcase */
etc: storedGl.getExtension('WEBGL_compressed_texture_etc'),
etc1: storedGl.getExtension('WEBGL_compressed_texture_etc1'),
pvrtc: storedGl.getExtension('WEBGL_compressed_texture_pvrtc')
|| storedGl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc'),
atc: storedGl.getExtension('WEBGL_compressed_texture_atc'),
astc: storedGl.getExtension('WEBGL_compressed_texture_astc')
} as Partial<CompressedTextureExtensions>;
}

export const detectCompressedTextures = {
extension: ExtensionType.DetectionParser,
test: async (): Promise<boolean> =>
{
// Auto-detect WebGL compressed-texture extensions
const canvas = settings.ADAPTER.createCanvas();
const gl = canvas.getContext('webgl');

if (!gl)
{
// #if _DEBUG
console.warn('WebGL not available for compressed textures.');
// #endif

return false;
}

storedGl = gl;

return true;
},
add: async (formats: string[]): Promise<string[]> =>
{
if (!extensions) getCompressedTextureExtensions();

const textureFormats = [];

// Assign all available compressed-texture formats
for (const extensionName in extensions)
{
const extension = extensions[extensionName as CompressedTextureExtensionRef];

if (!extension)
{
continue;
}

textureFormats.push(extensionName);
}

formats.unshift(...textureFormats);

return formats;
},
remove: async (formats: string[]): Promise<string[]> =>
{
if (!extensions) getCompressedTextureExtensions();

return formats.filter((f) => !(f in extensions));
},
} as FormatDetectionParser;
19 changes: 19 additions & 0 deletions packages/assets/src/detections/parsers/detectWebp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ExtensionType } from '@pixi/core';
import { settings } from '@pixi/settings';
import type { FormatDetectionParser } from '..';
import { addFormats, removeFormats } from '../utils/detectUtils';

export const detectWebp = {
extension: ExtensionType.DetectionParser,
test: async (): Promise<boolean> =>
{
if (!globalThis.createImageBitmap) return false;

const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
const blob = await settings.ADAPTER.fetch(webpData).then((r) => r.blob());

return createImageBitmap(blob).then(() => true, () => false);
},
add: addFormats('webp'),
remove: removeFormats('webp')
} as FormatDetectionParser;
4 changes: 4 additions & 0 deletions packages/assets/src/detections/parsers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './detectAvif';
export * from './detectCompressedTextures';
export * from './detectBasis';
export * from './detectWebp';
26 changes: 26 additions & 0 deletions packages/assets/src/detections/utils/detectUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function addFormats(...format: string[]): (formats: string[]) => Promise<string[]>
{
return async (formats: string[]) =>
{
formats.unshift(...format);

return formats;
};
}
export function removeFormats(...format: string[]): (formats: string[]) => Promise<string[]>
{
return async (formats: string[]) =>
{
for (const f of format)
{
const index = formats.indexOf(f);

if (index !== -1)
{
formats.splice(index, 1);
}
}

return formats;
};
}
1 change: 1 addition & 0 deletions packages/assets/src/detections/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './detectUtils';
1 change: 1 addition & 0 deletions packages/assets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './utils';
export * from './cache';
export * from './loader';
export * from './resolver';
export * from './detections';

1 change: 1 addition & 0 deletions packages/assets/src/resolver/parsers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './resolveSpriteSheetUrl';
export * from './resolveTextureUrl';
export * from './resolveCompressedTextureUrl';
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ExtensionType } from '@pixi/core';
import { settings } from '@pixi/settings';

import type { ResolveAsset, ResolveURLParser } from '../types';

export const resolveCompressedTextureUrl = {
extension: ExtensionType.ResolveParser,
test: (value: string) =>
{
const temp = value.split('?')[0];
const extension = temp.split('.').pop();

return ['basis', 'ktx', 'dds'].includes(extension);
},
parse: (value: string): ResolveAsset =>
{
const temp = value.split('?')[0];
const extension = temp.split('.').pop();

if (extension === 'ktx')
{
const extensions = [
'.s3tc.ktx',
'.s3tc_sRGB.ktx',
'.etc.ktx',
'.etc1.ktx',
'.pvrt.ktx',
'.atc.ktx',
'.astc.ktx'
];

// check if value ends with one of the extensions
if (extensions.some((ext) => value.endsWith(ext)))
{
return {
resolution: parseFloat(settings.RETINA_PREFIX.exec(value)?.[1] ?? '1'),
format: extensions.find((ext) => value.endsWith(ext)),
src: value,
};
}
}

return {
resolution: parseFloat(settings.RETINA_PREFIX.exec(value)?.[1] ?? '1'),
format: value.split('.').pop(),
src: value,
};
},
} as ResolveURLParser;
12 changes: 0 additions & 12 deletions packages/assets/src/utils/detections/detectAvif.ts

This file was deleted.

11 changes: 0 additions & 11 deletions packages/assets/src/utils/detections/detectWebp.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/assets/src/utils/detections/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/assets/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './path';
export * from './detections';
export * from './url';
export * from './convertToList';
export * from './createStringVariations';
Expand Down
2 changes: 1 addition & 1 deletion packages/assets/test/assets.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ describe('Assets', () =>

it('should map all names', async () =>
{
Assets.init({
await Assets.init({
basePath,
});

Expand Down

0 comments on commit 4776c29

Please sign in to comment.