Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/framework/handlers/font.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { path } from '../../core/path.js';
import { string } from '../../core/string.js';
import { FILTER_LINEAR } from '../../platform/graphics/constants.js';
import { http } from '../../platform/net/http.js';
import { FONT_MSDF } from '../font/constants.js';
import { Font } from '../font/font.js';
import { ResourceHandler } from './handler.js';

Expand Down Expand Up @@ -101,6 +103,13 @@ class FontHandler extends ResourceHandler {
const textures = new Array(numTextures);
const loader = this._loader;

// MSDF atlases must not be mipmapped: mip levels average the distance-field channels, and
// median(average) != average(median), so minified text samples a corrupted median. Request
// a non-mipmapped texture at creation time (mipmaps can't be disabled after creation on
// WebGPU) rather than mutating the loaded texture.
const isMsdf = !data.type || data.type === FONT_MSDF;
const textureOptions = isMsdf ? { mipmaps: false, minFilter: FILTER_LINEAR } : undefined;

const loadTexture = function (index) {
const onLoaded = function (err, texture) {
if (error) return;
Expand All @@ -120,9 +129,9 @@ class FontHandler extends ResourceHandler {
};

if (index === 0) {
loader.load(url, 'texture', onLoaded);
loader.load(url, 'texture', onLoaded, null, textureOptions);
} else {
loader.load(url.replace('.png', `${index}.png`), 'texture', onLoaded);
loader.load(url.replace('.png', `${index}.png`), 'texture', onLoaded, null, textureOptions);
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/framework/handlers/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ class ResourceHandler {
* @param {string} url - The URL of the resource to open.
* @param {*} data - The raw resource data passed by callback from {@link load}.
* @param {Asset} [asset] - Optional asset that is passed by ResourceLoader.
* @param {object} [options] - Optional resource-creation options passed by ResourceLoader.
* @returns {*} The parsed resource data.
*/
open(url, data, asset) {
open(url, data, asset, options) {
return data;
}

Expand Down
2 changes: 1 addition & 1 deletion src/framework/handlers/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class ResourceLoader {
}

try {
self._onSuccess(key, handler.open(urlObj.original, data, asset), extra);
self._onSuccess(key, handler.open(urlObj.original, data, asset, options), extra);
} catch (e) {
self._onFailure(key, e);
}
Expand Down
6 changes: 4 additions & 2 deletions src/framework/handlers/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,14 @@ class TextureHandler extends ResourceHandler {
this._getParser(url.original).load(url, callback, asset);
}

open(url, data, asset) {
open(url, data, asset, options) {
if (!url) {
return undefined;
}

const textureOptions = this._getTextureOptions(asset);
// asset-derived options, with any per-load creation options (e.g. mipmaps: false for
// MSDF font atlases) taking precedence
const textureOptions = { ...this._getTextureOptions(asset), ...options };
let texture = this._getParser(url).open(url, data, this._device, textureOptions);

if (texture === null) {
Expand Down
25 changes: 25 additions & 0 deletions test/framework/handlers/font-handler.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from 'chai';

import { Asset } from '../../../src/framework/asset/asset.js';
import { FILTER_LINEAR } from '../../../src/platform/graphics/constants.js';
import { createApp } from '../../app.mjs';
import { jsdomSetup, jsdomTeardown } from '../../jsdom.mjs';

Expand Down Expand Up @@ -72,4 +73,28 @@ describe('FontHandler', function () {
asset.on('error', err => done(new Error(err)));
});

// regression test: MSDF atlases must load without mipmaps and with a non-mip minFilter. Mip
// levels average the distance-field channels, corrupting the median under minification (which
// showed as a faint flickering line below thin strokes on small text).
it('loads MSDF atlas textures without mipmaps', function (done) {
const asset = new Asset('arial', 'font', {
url: 'http://localhost:3210/test/assets/fonts/arial.json'
});

app.assets.add(asset);
app.assets.load(asset);

asset.ready(function () {
const textures = asset.resource.textures;
expect(textures).to.have.lengthOf(1);
textures.forEach((texture) => {
expect(texture.mipmaps).to.equal(false);
expect(texture.minFilter).to.equal(FILTER_LINEAR);
});
done();
});

asset.on('error', err => done(new Error(err)));
});

});