ogl-typescript
Version:
TypeScript port of OGL WebGL Library
161 lines • 7.76 kB
JavaScript
// TODO: facilitate Compressed Textures
// TODO: delete texture
// TODO: check is ArrayBuffer.isView is best way to check for Typed Arrays?
// TODO: use texSubImage2D for updates
// TODO: need? encoding = linearEncoding
// TODO: support non-compressed mipmaps uploads
const emptyPixel = new Uint8Array(4);
function isPowerOf2(value) {
return (value & (value - 1)) === 0;
}
let ID = 1;
const isCompressedImage = (image) => image.isCompressedTexture === true;
export class Texture {
constructor(gl, { image, target = gl.TEXTURE_2D, type = gl.UNSIGNED_BYTE, format = gl.RGBA, internalFormat = format, wrapS = gl.CLAMP_TO_EDGE, wrapT = gl.CLAMP_TO_EDGE, generateMipmaps = true, minFilter = generateMipmaps ? gl.NEAREST_MIPMAP_LINEAR : gl.LINEAR, magFilter = gl.LINEAR, premultiplyAlpha = false, unpackAlignment = 4, flipY = target == gl.TEXTURE_2D ? true : false, anisotropy = 0, level = 0, width, // used for RenderTargets or Data Textures
height = width, } = {}) {
this.gl = gl;
this.id = ID++;
this.image = image;
this.target = target;
this.type = type;
this.format = format;
this.internalFormat = internalFormat;
this.minFilter = minFilter;
this.magFilter = magFilter;
this.wrapS = wrapS;
this.wrapT = wrapT;
this.generateMipmaps = generateMipmaps;
this.premultiplyAlpha = premultiplyAlpha;
this.unpackAlignment = unpackAlignment;
this.flipY = flipY;
this.anisotropy = Math.min(anisotropy, this.gl.renderer.parameters.maxAnisotropy);
this.level = level;
this.width = width;
this.height = height;
this.texture = this.gl.createTexture();
this.store = {
image: null,
};
// Alias for state store to avoid redundant calls for global state
this.glState = this.gl.renderer.state;
// State store to avoid redundant calls for per-texture state
this.state = {
minFilter: this.gl.NEAREST_MIPMAP_LINEAR,
magFilter: this.gl.LINEAR,
wrapS: this.gl.REPEAT,
wrapT: this.gl.REPEAT,
anisotropy: 0,
};
}
bind() {
// Already bound to active texture unit
if (this.glState.textureUnits[this.glState.activeTextureUnit] === this.id)
return;
this.gl.bindTexture(this.target, this.texture);
this.glState.textureUnits[this.glState.activeTextureUnit] = this.id;
}
update(textureUnit = 0) {
const needsUpdate = !(this.image === this.store.image && !this.needsUpdate);
// Make sure that texture is bound to its texture unit
if (needsUpdate || this.glState.textureUnits[textureUnit] !== this.id) {
// set active texture unit to perform texture functions
this.gl.renderer.activeTexture(textureUnit);
this.bind();
}
if (!needsUpdate)
return;
this.needsUpdate = false;
if (this.flipY !== this.glState.flipY) {
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, this.flipY);
this.glState.flipY = this.flipY;
}
if (this.premultiplyAlpha !== this.glState.premultiplyAlpha) {
this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);
this.glState.premultiplyAlpha = this.premultiplyAlpha;
}
if (this.unpackAlignment !== this.glState.unpackAlignment) {
this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, this.unpackAlignment);
this.glState.unpackAlignment = this.unpackAlignment;
}
if (this.minFilter !== this.state.minFilter) {
this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.minFilter);
this.state.minFilter = this.minFilter;
}
if (this.magFilter !== this.state.magFilter) {
this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.magFilter);
this.state.magFilter = this.magFilter;
}
if (this.wrapS !== this.state.wrapS) {
this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.wrapS);
this.state.wrapS = this.wrapS;
}
if (this.wrapT !== this.state.wrapT) {
this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.wrapT);
this.state.wrapT = this.wrapT;
}
if (this.anisotropy && this.anisotropy !== this.state.anisotropy) {
this.gl.texParameterf(this.target, this.gl.renderer.getExtension('EXT_texture_filter_anisotropic').TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropy);
this.state.anisotropy = this.anisotropy;
}
if (this.image) {
if (this.image.width) {
this.width = this.image.width;
this.height = this.image.height;
}
if (this.target === this.gl.TEXTURE_CUBE_MAP) {
// For cube maps
for (let i = 0; i < 6; i++) {
this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, this.level, this.internalFormat, this.format, this.type, this.image[i]);
}
}
else if (ArrayBuffer.isView(this.image)) {
// Data texture
this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0, this.format, this.type, this.image);
}
else if (isCompressedImage(this.image)) {
// Compressed texture
let m;
for (let level = 0; level < this.image.length; level++) {
m = this.image[level];
this.gl.compressedTexImage2D(this.target, level, this.internalFormat, m.width, m.height, 0, m.data);
}
}
else {
// Regular texture
this.gl.texImage2D(this.target, this.level, this.internalFormat, this.format, this.type, this.image);
}
if (this.generateMipmaps) {
// For WebGL1, if not a power of 2, turn off mips, set wrapping to clamp to edge and minFilter to linear
if (!this.gl.renderer.isWebgl2 &&
(!isPowerOf2(this.image.width) || !isPowerOf2(this.image.height))) {
this.generateMipmaps = false;
this.wrapS = this.wrapT = this.gl.CLAMP_TO_EDGE;
this.minFilter = this.gl.LINEAR;
}
else {
this.gl.generateMipmap(this.target);
}
}
// Callback for when data is pushed to GPU
this.onUpdate && this.onUpdate();
}
else {
if (this.target === this.gl.TEXTURE_CUBE_MAP) {
// Upload empty pixel for each side while no image to avoid errors while image or video loading
for (let i = 0; i < 6; i++) {
this.gl.texImage2D(this.gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, emptyPixel);
}
}
else if (this.width) {
// image intentionally left null for RenderTarget
this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0, this.format, this.type, null);
}
else {
// Upload empty pixel if no image to avoid errors while image or video loading
this.gl.texImage2D(this.target, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, emptyPixel);
}
}
this.store.image = this.image;
}
}
//# sourceMappingURL=Texture.js.map