blaze-2d
Version:
A fast and simple WebGL 2 2D game engine written in TypeScript
163 lines • 5.64 kB
JavaScript
import Logger from "../logger";
import { loadTexture } from "../utils/gl";
import TextureAtlas from "./atlas";
/**
* Handles loading and unloading of textures on the GPU as needed.
*/
export default class TextureLoader {
/**
* Sets up the texture loader for use, should only be called once.
*
* @param gl The WebGL context to use for texture loading
*/
static init(gl) {
this.ready = true;
this.gl = gl;
this.textureUnits = [];
this.loaded.clear();
for (let i = 0; i < gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) - 1; i++) {
this.textureUnits.push({
unit: gl[`TEXTURE${i}`],
texture: undefined,
lastUsed: -1,
});
}
}
/**
* Checks if the loader is ready to be used for texture loading/unloading.
*
* @throws When TextureLoader.init has not yet been called
*/
static checkReady() {
if (!this.ready)
throw Logger.error("TextureLoader", "TextureLoader.init must be called before the loader can be used.");
}
/**
* Loads a texture into any available texture unit on the GPU, if no unit is available then the unit with the longest time since it was last used is overwritten.
*
* @param texture The texture to load
* @returns Wether or not the texture was loaded
*/
static loadTexture(texture) {
this.checkReady();
if (this.isLoaded(texture) && !texture.isOld)
return true;
// mark texture as not old anymore
texture.isOld = false;
const unit = this.getUnitOfTexture(texture) || this.findReplaceableTextureUnit();
if (unit.texture)
this.unloadTexture(unit.texture, unit);
unit.texture = texture;
unit.lastUsed = performance.now();
this.loaded.set(texture, unit.unit);
texture.setTextureUnit(unit.unit);
loadTexture(this.gl, unit);
}
/**
* Unloads the texture from the GPU.
*
* @param texture The texture to unload
* @param unit The texture unit the `texture` belongs to, improves performance as the unit does not need to be searched for.
* @returns Wether or not the texture was unloaded
*/
static unloadTexture(texture, unit) {
this.checkReady();
if (!this.isLoaded(texture))
return true;
this.loaded.delete(texture);
if (!unit) {
unit = this.getUnitOfTexture(texture);
if (!unit)
return false;
}
unit.texture = undefined;
return true;
}
/**
* Unloads and then reloads the provided texture.
*
* @param texture The texture to reload
* @returns Wether or not the texture was reloaded
*/
static reloadTexture(texture) {
this.unloadTexture(texture);
return this.loadTexture(texture);
}
/**
* Checks if a texture is loaded, ie the texture's image is loaded in a texture unit on the GPU.
*
* @param texture The texture to check
* @returns Wether or not the texture is loaded
*/
static isLoaded(texture) {
this.checkReady();
return this.loaded.has(texture);
}
/**
* Finds the texture unit the given texture is loaded in.
*
* @param texture The texture to find
* @returns The unit the texture is loaded in or undefined
*/
static getUnitOfTexture(texture) {
this.checkReady();
if (!this.isLoaded(texture))
return undefined;
const unitIndex = this.textureUnits.findIndex((u) => u.texture === texture);
if (unitIndex === -1)
return undefined;
return this.textureUnits[unitIndex];
}
/**
* Finds a free texture unit.
*
* @returns A free texture unit or undefined when no units are free
*/
static getFreeTextureUnit() {
this.checkReady();
const unitIndex = this.textureUnits.findIndex((u) => u.texture === undefined);
if (unitIndex === -1)
return undefined;
return this.textureUnits[unitIndex];
}
/**
* Finds the best texture unit to replace.
*
* First looks for free texture units and if none are available then it will return the texture unit with the oldest last used time.
*
* Texture units that have a {@link TextureAtlas} in them will be ignored.
*
* @returns The most replaceable texture unit
*/
static findReplaceableTextureUnit() {
this.checkReady();
let unit = this.getFreeTextureUnit();
if (unit)
return unit;
let minLastUsed = Number.MAX_SAFE_INTEGER;
for (const u of this.textureUnits) {
if (u.lastUsed < minLastUsed && !(u.texture instanceof TextureAtlas)) {
minLastUsed = u.lastUsed;
unit = u;
}
}
return unit;
}
/**
* Finds a {@link TextureUnit} by its WebGL texture unit number and updates its lastUsed time to the current time from `performance.now()`
*
* @param unit The WebGL texture unit number
* @returns Wether or not the TextureUnit was updated
*/
static updateLastUsed(unit) {
const unitIndex = this.textureUnits.findIndex((u) => u.unit === unit);
if (unitIndex === -1)
return false;
this.textureUnits[unitIndex].lastUsed = performance.now();
return true;
}
}
TextureLoader.ready = false;
TextureLoader.loaded = new Map();
TextureLoader.textureUnits = [];
//# sourceMappingURL=loader.js.map