UNPKG

ogl

Version:
210 lines (184 loc) 8.96 kB
// TODO: delete texture // TODO: use texSubImage2D for updates (video or when loaded) // 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; 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, wrapR = gl.CLAMP_TO_EDGE, generateMipmaps = target === (gl.TEXTURE_2D || gl.TEXTURE_CUBE_MAP), minFilter = generateMipmaps ? gl.NEAREST_MIPMAP_LINEAR : gl.LINEAR, magFilter = gl.LINEAR, premultiplyAlpha = false, unpackAlignment = 4, flipY = target == (gl.TEXTURE_2D || gl.TEXTURE_3D) ? true : false, anisotropy = 0, level = 0, width, // used for RenderTargets or Data Textures height = width, length = 1, } = {} ) { 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.wrapR = wrapR; 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.length = length; 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 = {}; this.state.minFilter = this.gl.NEAREST_MIPMAP_LINEAR; this.state.magFilter = this.gl.LINEAR; this.state.wrapS = this.gl.REPEAT; this.state.wrapT = this.gl.REPEAT; this.state.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.wrapR !== this.state.wrapR) { this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_R, this.wrapR); this.state.wrapR = this.wrapR; } 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 if (this.target === this.gl.TEXTURE_2D) { this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0, this.format, this.type, this.image); } else if (this.target === this.gl.TEXTURE_2D_ARRAY || this.target === this.gl.TEXTURE_3D) { this.gl.texImage3D(this.target, this.level, this.internalFormat, this.width, this.height, this.length, 0, this.format, this.type, this.image); } } else if (this.image.isCompressedTexture) { // Compressed texture for (let level = 0; level < this.image.length; level++) { this.gl.compressedTexImage2D(this.target, level, this.internalFormat, this.image[level].width, this.image[level].height, 0, this.image[level].data); } } else { // Regular texture if (this.target === this.gl.TEXTURE_2D) { this.gl.texImage2D(this.target, this.level, this.internalFormat, this.format, this.type, this.image); } else { this.gl.texImage3D(this.target, this.level, this.internalFormat, this.width, this.height, this.length, 0, 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 if (this.target === this.gl.TEXTURE_2D) { this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0, this.format, this.type, null); } else { this.gl.texImage3D(this.target, this.level, this.internalFormat, this.width, this.height, this.length, 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; } }