import Texture from './texture'; import { enums, glFilter, glTextureFmt } from './enums'; import { isPow2 } from './misc'; export default class TextureCube extends Texture { /** * @constructor * @param {Device} device * @param {Object} options * @param {Array} options.images * @param {Boolean} options.genMipmaps * @param {Number} options.width * @param {Number} options.height * @param {TEXTURE_FMT_*} options.format * @param {Number} options.anisotropy * @param {FILTER_*} options.minFilter * @param {FILTER_*} options.magFilter * @param {FILTER_*} options.mipFilter * @param {WRAP_*} options.wrapS * @param {WRAP_*} options.wrapT * @param {WRAP_*} options.wrapR * @param {Boolean} options.flipY * @param {Boolean} options.premultiplyAlpha */ constructor(device, options) { super(device); let gl = this._device._gl; this._target = gl.TEXTURE_CUBE_MAP; this._glID = gl.createTexture(); this.update(options); } /** * @method update * @param {Object} options * @param {Array} options.images * @param {Boolean} options.genMipmaps * @param {Number} options.width * @param {Number} options.height * @param {TEXTURE_FMT_*} options.format * @param {Number} options.anisotropy * @param {FILTER_*} options.minFilter * @param {FILTER_*} options.magFilter * @param {FILTER_*} options.mipFilter * @param {WRAP_*} options.wrapS * @param {WRAP_*} options.wrapT * @param {WRAP_*} options.wrapR * @param {Boolean} options.flipY * @param {Boolean} options.premultiplyAlpha */ update(options) { let gl = this._device._gl; let genMipmaps = this._genMipmaps; if (options) { if (options.width !== undefined) { this._width = options.width; } if (options.height !== undefined) { this._height = options.height; } if (options.anisotropy !== undefined) { this._anisotropy = options.anisotropy; } if (options.minFilter !== undefined) { this._minFilter = options.minFilter; } if (options.magFilter !== undefined) { this._magFilter = options.magFilter; } if (options.mipFilter !== undefined) { this._mipFilter = options.mipFilter; } if (options.wrapS !== undefined) { this._wrapS = options.wrapS; } if (options.wrapT !== undefined) { this._wrapT = options.wrapT; } // wrapR available in webgl2 // if (options.wrapR !== undefined) { // this._wrapR = options.wrapR; // } if (options.format !== undefined) { this._format = options.format; this._compressed = (this._format >= enums.TEXTURE_FMT_RGB_DXT1 && this._format <= enums.TEXTURE_FMT_RGBA_PVRTC_4BPPV1) || (this._format >= enums.TEXTURE_FMT_RGB_ETC2 && this._format <= enums.TEXTURE_FMT_RGBA_ETC2) || (this._format >= enums.TEXTURE_FMT_RGBA_ASTC_4X4 && this._format <= enums.TEXTURE_FMT_SRGBA_ASTC_12X12) ; } // check if generate mipmap if (options.genMipmaps !== undefined) { this._genMipmaps = options.genMipmaps; genMipmaps = options.genMipmaps; } if (options.images !== undefined) { if (options.images.length > 1) { genMipmaps = false; if (options.width !== options.height) { console.warn('texture-cube width and height should be identical.'); } if (options.width >> (options.images.length - 1) !== 1) { console.error('texture-cube mipmap is invalid. please set mipmap as 1x1, 2x2, 4x4 ... nxn'); } } } } // NOTE: get pot after this._width, this._height has been assigned. let pot = isPow2(this._width) && isPow2(this._height); if (!pot) { genMipmaps = false; } gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID); if (options.images !== undefined && options.images.length > 0) { this._setMipmap(options.images, options.flipY, options.premultiplyAlpha); if (options.images.length > 1) this._genMipmaps = true; } if (genMipmaps) { gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST); gl.generateMipmap(gl.TEXTURE_CUBE_MAP); this._genMipmaps = true; } this._setTexInfo(); this._device._restoreTexture(0); } /** * @method updateSubImage * @param {Object} options * @param {Number} options.x * @param {Number} options.y * @param {Number} options.width * @param {Number} options.height * @param {Number} options.level * @param {Number} options.faceIndex * @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image * @param {Boolean} options.flipY * @param {Boolean} options.premultiplyAlpha */ updateSubImage(options) { let gl = this._device._gl; let glFmt = glTextureFmt(this._format); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID); this._setSubImage(glFmt, options); this._device._restoreTexture(0); } /** * @method updateImage * @param {Object} options * @param {Number} options.width * @param {Number} options.height * @param {Number} options.level * @param {Number} options.faceIndex * @param {HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ArrayBufferView} options.image * @param {Boolean} options.flipY * @param {Boolean} options.premultiplyAlpha */ updateImage(options) { let gl = this._device._gl; let glFmt = glTextureFmt(this._format); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, this._glID); this._setImage(glFmt, options); this._device._restoreTexture(0); } _setSubImage(glFmt, options) { let gl = this._device._gl; let flipY = options.flipY; let premultiplyAlpha = options.premultiplyAlpha; let faceIndex = options.faceIndex; let img = options.image; if (flipY === undefined) { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); } else { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); } if (premultiplyAlpha === undefined) { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); } else { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha); } if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) { gl.texSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, options.x, options.y, glFmt.format, glFmt.pixelType, img); } else { if (this._compressed) { gl.compressedTexSubImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, options.x, options.y, options.width, options.height, glFmt.format, img ); } else { gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, options.x, options.y, options.width, options.height, glFmt.format, glFmt.pixelType, img ); } } } _setImage(glFmt, options) { let gl = this._device._gl; let flipY = options.flipY; let premultiplyAlpha = options.premultiplyAlpha; let faceIndex = options.faceIndex; let img = options.image; if (flipY === undefined) { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); } else { gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY); } if (premultiplyAlpha === undefined) { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); } else { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiplyAlpha); } if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) { gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, glFmt.internalFormat, glFmt.format, glFmt.pixelType, img ); } else { if (this._compressed) { gl.compressedTexImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, glFmt.internalFormat, options.width, options.height, 0, img ); } else { gl.texImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, options.level, glFmt.internalFormat, options.width, options.height, 0, glFmt.format, glFmt.pixelType, img ); } } } // levelImages = [imagePosX, imageNegX, imagePosY, imageNegY, imagePosZ, imageNegz] // images = [levelImages0, levelImages1, ...] _setMipmap(images, flipY, premultiplyAlpha) { let glFmt = glTextureFmt(this._format); let options = { width: this._width, height: this._height, faceIndex: 0, flipY: flipY, premultiplyAlpha: premultiplyAlpha, level: 0, image: null }; for (let i = 0; i < images.length; ++i) { let levelImages = images[i]; options.level = i; options.width = this._width >> i; options.height = this._height >> i; for (let face = 0; face < 6; ++face) { options.faceIndex = face; options.image = levelImages[face]; this._setImage(glFmt, options); } } } _setTexInfo() { let gl = this._device._gl; let pot = isPow2(this._width) && isPow2(this._height); // WebGL1 doesn't support all wrap modes with NPOT textures if (!pot && (this._wrapS !== enums.WRAP_CLAMP || this._wrapT !== enums.WRAP_CLAMP)) { console.warn('WebGL1 doesn\'t support all wrap modes with NPOT textures'); this._wrapS = enums.WRAP_CLAMP; this._wrapT = enums.WRAP_CLAMP; } let mipFilter = this._genMipmaps ? this._mipFilter : -1; if (!pot && mipFilter !== -1) { console.warn('NPOT textures do not support mipmap filter'); mipFilter = -1; } gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, glFilter(gl, this._minFilter, mipFilter)); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, glFilter(gl, this._magFilter, -1)); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, this._wrapS); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, this._wrapT); // wrapR available in webgl2 // gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, this._wrapR); let ext = this._device.ext('EXT_texture_filter_anisotropic'); if (ext) { gl.texParameteri(gl.TEXTURE_CUBE_MAP, ext.TEXTURE_MAX_ANISOTROPY_EXT, this._anisotropy); } } }