mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-01-15 15:31:08 +00:00
330 lines
10 KiB
JavaScript
330 lines
10 KiB
JavaScript
// @ts-check
|
|
import Texture from './texture';
|
|
import { enums, glFilter, glTextureFmt } from './enums';
|
|
import { isPow2 } from './misc';
|
|
|
|
/**
|
|
* @typedef {HTMLImageElement | HTMLCanvasElement} HTMLImageSource
|
|
* @typedef {HTMLImageSource | ArrayBufferView} ImageSource
|
|
* @typedef {{width?: number, height?: number, minFilter?: number, magFilter?: number, mipFilter?: number, wrapS?: number, wrapT?: number, format?: number, genMipmaps?: boolean, images?: ImageSource[], image?: ImageSource, flipY?: boolean, premultiplyAlpha?: boolean, anisotropy?: number}} TextureUpdateOpts
|
|
* @typedef {import("../gfx/device").default} Device
|
|
*/
|
|
|
|
export default class Texture2D extends Texture {
|
|
/**
|
|
* @constructor
|
|
* @param {Device} device
|
|
* @param {TextureUpdateOpts} options
|
|
*/
|
|
constructor(device, options) {
|
|
super(device);
|
|
|
|
let gl = this._device._gl;
|
|
this._target = gl.TEXTURE_2D;
|
|
this._glID = gl.createTexture();
|
|
|
|
// always alloc texture in GPU when we create it.
|
|
options.images = options.images || [null];
|
|
this.update(options);
|
|
}
|
|
|
|
/**
|
|
* @method update
|
|
* @param {TextureUpdateOpts} options
|
|
*/
|
|
update(options) {
|
|
let gl = this._device._gl;
|
|
let genMipmaps = this._genMipmap;
|
|
|
|
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;
|
|
}
|
|
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)
|
|
;
|
|
}
|
|
|
|
// check if generate mipmap
|
|
if (options.genMipmaps !== undefined) {
|
|
this._genMipmap = options.genMipmaps;
|
|
genMipmaps = options.genMipmaps;
|
|
}
|
|
|
|
let maxSize = this._device.caps.maxTextureSize || Number.MAX_VALUE;
|
|
let textureMaxSize = Math.max(options.width || 0, options.height || 0);
|
|
if (maxSize < textureMaxSize)
|
|
console.warn(`The current texture size ${textureMaxSize} exceeds the maximum size [${maxSize}] supported on the device.`);
|
|
|
|
if (options.images !== undefined) {
|
|
if (options.images.length > 1) {
|
|
genMipmaps = false;
|
|
let maxLength = options.width > options.height ? options.width : options.height;
|
|
if (maxLength >> (options.images.length - 1) !== 1) {
|
|
console.error('texture-2d mipmap is invalid, should have a 1x1 mipmap.');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_2D, this._glID);
|
|
if (options.images !== undefined && options.images.length > 0) {
|
|
this._setMipmap(options.images, options.flipY, options.premultiplyAlpha);
|
|
if (options.images.length > 1) this._genMipmap = true;
|
|
}
|
|
if (genMipmaps) {
|
|
gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST);
|
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
this._genMipmap = 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 {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_2D, 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 {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_2D, 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 img = options.image;
|
|
|
|
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
|
|
if (flipY === undefined) {
|
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
} 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);
|
|
}
|
|
|
|
gl.texSubImage2D(gl.TEXTURE_2D, options.level, options.x, options.y, glFmt.format, glFmt.pixelType, img);
|
|
} else {
|
|
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 (this._compressed) {
|
|
gl.compressedTexSubImage2D(gl.TEXTURE_2D,
|
|
options.level,
|
|
options.x,
|
|
options.y,
|
|
options.width,
|
|
options.height,
|
|
glFmt.format,
|
|
img
|
|
);
|
|
} else {
|
|
gl.texSubImage2D(
|
|
gl.TEXTURE_2D,
|
|
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 img = options.image;
|
|
|
|
if (img && !ArrayBuffer.isView(img) && !(img instanceof ArrayBuffer)) {
|
|
if (flipY === undefined) {
|
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
} 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);
|
|
}
|
|
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
options.level,
|
|
glFmt.internalFormat,
|
|
glFmt.format,
|
|
glFmt.pixelType,
|
|
img
|
|
);
|
|
} else {
|
|
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 (this._compressed) {
|
|
gl.compressedTexImage2D(
|
|
gl.TEXTURE_2D,
|
|
options.level,
|
|
glFmt.internalFormat,
|
|
options.width,
|
|
options.height,
|
|
0,
|
|
img
|
|
);
|
|
} else {
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
options.level,
|
|
glFmt.internalFormat,
|
|
options.width,
|
|
options.height,
|
|
0,
|
|
glFmt.format,
|
|
glFmt.pixelType,
|
|
img
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
_setMipmap(images, flipY, premultiplyAlpha) {
|
|
let glFmt = glTextureFmt(this._format);
|
|
let options = {
|
|
width: this._width,
|
|
height: this._height,
|
|
flipY: flipY,
|
|
premultiplyAlpha: premultiplyAlpha,
|
|
level: 0,
|
|
image: null
|
|
};
|
|
|
|
for (let i = 0; i < images.length; ++i) {
|
|
options.level = i;
|
|
options.width = this._width >> i;
|
|
options.height = this._height >> i;
|
|
options.image = images[i];
|
|
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._genMipmap ? this._mipFilter : -1;
|
|
if (!pot && mipFilter !== -1) {
|
|
console.warn('NPOT textures do not support mipmap filter');
|
|
mipFilter = -1;
|
|
}
|
|
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, glFilter(gl, this._minFilter, mipFilter));
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, glFilter(gl, this._magFilter, -1));
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this._wrapS);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this._wrapT);
|
|
|
|
let ext = this._device.ext('EXT_texture_filter_anisotropic');
|
|
if (ext) {
|
|
gl.texParameteri(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, this._anisotropy);
|
|
}
|
|
}
|
|
} |