cables_dev/cables/src/core/cgl/cgl_texture.js
import { Logger } from "cables-shared-client";
import { uuid } from "../utils.js";
import CgTexture from "../cg/cg_texture.js";
const DEFAULT_TEXTURE_SIZE = 8;
/**
* A Texture
* @namespace external:CGL
* @class
* @param {Context} __cgl cgl
* @param {Object} options
* @hideconstructor
* @example
* // generate a 256x256 pixel texture of random colors
* const size=256;
* const data = new Uint8Array(size*size*4);
*
* for(var x=0;x<size*size*4;x++) data[ x*4+3]=255;
*
* const tex=new CGL.Texture(cgl);
* tex.initFromData(data,size,size,CGL.Texture.FILTER_NEAREST,CGL.Texture.WRAP_REPEAT);
*/
class Texture extends CgTexture
{
constructor(__cgl, options = {})
{
super(options);
if (!__cgl) throw new Error("no cgl");
this._log = new Logger("cgl_texture");
this._cgl = __cgl;
this.tex = this._cgl.gl.createTexture();
this.loading = false;
this.flip = true;
this.flipped = false;
this.shadowMap = false;
this.deleted = false;
this.image = null;
this.anisotropic = 0;
this.filter = Texture.FILTER_NEAREST;
this.wrap = Texture.WRAP_CLAMP_TO_EDGE;
this.texTarget = this._cgl.gl.TEXTURE_2D;
if (options && options.type) this.texTarget = options.type;
this.textureType = Texture.TYPE_DEFAULT;
this.unpackAlpha = true;
this._fromData = true;
this._glDataType = -1;
this._glInternalFormat = -1;
this._glDataFormat = -1;
if (options)
{
if (options.isDepthTexture) this.textureType = Texture.TYPE_DEPTH;
if (options.isFloatingPointTexture === true) this.textureType = Texture.TYPE_FLOAT;
if ("textureType" in options) this.textureType = options.textureType;
if ("filter" in options) this.filter = options.filter;
if ("wrap" in options) this.wrap = options.wrap;
if ("unpackAlpha" in options) this.unpackAlpha = options.unpackAlpha;
if ("flip" in options) this.flip = options.flip;
if ("shadowMap" in options) this.shadowMap = options.shadowMap;
if ("anisotropic" in options) this.anisotropic = options.anisotropic;
}
else
{
options = {};
}
if (!options.pixelFormat && options.isFloatingPointTexture) this.pixelFormat = Texture.PFORMATSTR_RGBA32F;
if (this.textureType == Texture.TYPE_DEPTH) this.pixelFormat = Texture.PFORMATSTR_DEPTH;
this._cgl.profileData.profileTextureNew++;
this.setFormat(Texture.setUpGlPixelFormat(this._cgl, this.pixelFormat));
this._cgl.profileData.addHeavyEvent("texture created", this.name, options.width + "x" + options.height);
this.setSize(options.width, options.height);
this.getInfoOneLine();
}
isFloatingPoint()
{
return Texture.isPixelFormatFloat(this.pixelFormat);
}
/**
* returns true if otherTexture has same options (width/height/filter/wrap etc)
* @function compareSettings
* @memberof Texture
* @instance
* @param {Texture} tex otherTexture
* @returns {Boolean}
*/
compareSettings(tex)
{
// if (!tex) { this._log.warn("compare: no tex"); return false; }
// if (tex.width != this.width) this._log.warn("tex.width not equal", tex.width, this.width);
// if (tex.height != this.height) this._log.warn("tex.height not equal", tex.height, this.height);
// if (tex.filter != this.filter) this._log.warn("tex.filter not equal");
// if (tex.wrap != this.wrap) this._log.warn("tex.wrap not equal");
// if (tex.textureType != this.textureType) this._log.warn("tex.textureType not equal");
// if (tex.unpackAlpha != this.unpackAlpha) this._log.warn("tex.unpackAlpha not equal");
// if (tex.anisotropic != this.anisotropic) this._log.warn("tex.anisotropic not equal");
// if (tex.shadowMap != this.shadowMap) this._log.warn("tex.shadowMap not equal");
// if (tex.texTarget != this.texTarget) this._log.warn("tex.texTarget not equal");
// if (tex.flip != this.flip) this._log.warn("tex.flip not equal");
if (!tex) return false;
return (
tex.width == this.width &&
tex.height == this.height &&
tex.filter == this.filter &&
tex.wrap == this.wrap &&
tex.textureType == this.textureType &&
tex.unpackAlpha == this.unpackAlpha &&
tex.anisotropic == this.anisotropic &&
tex.shadowMap == this.shadowMap &&
tex.texTarget == this.texTarget &&
tex.flip == this.flip
);
}
/**
* returns a new texture with the same settings (does not copy texture itself)
* @function clone
* @memberof Texture
* @instance
* @returns {Texture}
*/
clone()
{
const newTex = new Texture(this._cgl, {
"name": this.name,
"filter": this.filter,
"anisotropic": this.anisotropic,
"wrap": this.wrap,
"textureType": this.textureType,
"pixelFormat": this.pixelFormat,
"unpackAlpha": this.unpackAlpha,
"flip": this.flip,
"width": this.width,
"height": this.height,
});
this._cgl.profileData.addHeavyEvent("texture created", this.name, this.width + "x" + this.height);
if (!this.compareSettings(newTex))
{
this._log.error("Cloned texture settings do not compare!");
this._log.error(this);
this._log.error(newTex);
}
return newTex;
}
setFormat(o)
{
this.pixelFormat = o.pixelFormat;
this._glDataFormat = o.glDataFormat;
this._glInternalFormat = o.glInternalFormat;
this._glDataType = o.glDataType;
}
/**
* set pixel size of texture
* @function setSize
* @memberof Texture
* @instance
* @param {Number} w width
* @param {Number} h height
*/
setSize(w, h)
{
if (this._cgl.aborted) return;
if (w != w || w <= 0 || !w) w = DEFAULT_TEXTURE_SIZE;
if (h != h || h <= 0 || !h) h = DEFAULT_TEXTURE_SIZE;
if (w > this._cgl.maxTexSize || h > this._cgl.maxTexSize) this._log.error("texture size too big! " + w + "x" + h + " / max: " + this._cgl.maxTexSize);
w = Math.min(w, this._cgl.maxTexSize);
h = Math.min(h, this._cgl.maxTexSize);
w = Math.floor(w);
h = Math.floor(h);
if (this.width == w && this.height == h) return;
w = this._cgl.checkTextureSize(w);
h = this._cgl.checkTextureSize(h);
this.width = w;
this.height = h;
this.deleted = false;
this.setFormat(Texture.setUpGlPixelFormat(this._cgl, this.pixelFormat));
this.shortInfoString = this.getInfoOneLine();// w + "x" + h + "";
this._cgl.gl.bindTexture(this.texTarget, this.tex);
this._cgl.profileData.profileTextureResize++;
const uarr = null;
this._cgl.gl.texImage2D(this.texTarget, 0, this._glInternalFormat, w, h, 0, this._glDataFormat, this._glDataType, uarr);
this._setFilter();
this.updateMipMap();
this._cgl.gl.bindTexture(this.texTarget, null);
}
/**
* @function initFromData
* @memberof Texture
* @instance
* @description create texturem from rgb data
* @param {Array<Number>} data rgb color array [r,g,b,a,r,g,b,a,...]
* @param {Number} w width
* @param {Number} h height
* @param {Number} filter
* @param {Number} wrap
*/
initFromData(data, w, h, filter, wrap)
{
this.filter = filter;
this.wrap = wrap;
if (filter == undefined) this.filter = Texture.FILTER_LINEAR;
if (wrap == undefined) this.wrap = Texture.WRAP_CLAMP_TO_EDGE;
this.width = w;
this.height = h;
this._fromData = true;
this.deleted = false;
if (this.height > this._cgl.maxTexSize || this.width > this._cgl.maxTexSize)
{
const t = CGL.Texture.getTempTexture(this._cgl);
this.width = t.width;
this.height = t.height;
this.tex = t.tex;
this._log.warn("[cgl_texture] texture size too big!", this.width, this.height, this._cgl.maxTexSize);
return;
}
if (this.flip) this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_FLIP_Y_WEBGL, this.flip);
this._cgl.gl.bindTexture(this.texTarget, this.tex);
this.setFormat(Texture.setUpGlPixelFormat(this._cgl, this.pixelFormat));
this._cgl.gl.texImage2D(this.texTarget, 0, this._glInternalFormat, w, h, 0, this._glDataFormat, this._glDataType, data);
this._setFilter();
this.updateMipMap();
if (this.flip) this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_FLIP_Y_WEBGL, false);
this._cgl.gl.bindTexture(this.texTarget, null);
}
updateMipMap()
{
if ((this._cgl.glVersion == 2 || this.isPowerOfTwo()) && this.filter == Texture.FILTER_MIPMAP)
{
this._cgl.gl.generateMipmap(this.texTarget);
this._cgl.profileData.profileGenMipMap++;
}
}
/**
* set texture data from an image/canvas object
* @function initTexture
* @memberof Texture
* @instance
* @param {Object} img image
* @param {Number} filter
*/
initTexture(img, filter)
{
this._cgl.printError("before initTexture");
this._cgl.checkFrameStarted("texture inittexture");
this._fromData = false;
this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.unpackAlpha);
if (img.width || img.videoWidth) this.width = img.videoWidth || img.width;
if (img.height || img.videoHeight) this.height = img.videoHeight || img.height;
if (filter !== undefined) this.filter = filter; // todo: can we remove this filter param?
if (img.height > this._cgl.maxTexSize || img.width > this._cgl.maxTexSize)
{
const t = CGL.Texture.getTempTexture(this._cgl);
this.width = t.width;
this.height = t.height;
this.tex = t.tex;
this._log.warn("[cgl_texture] texture size too big!", img.width, img.height, this._cgl.maxTexSize);
return;
}
this._cgl.gl.bindTexture(this.texTarget, this.tex);
this.deleted = false;
this.flipped = !this.flip;
if (this.flipped) this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_FLIP_Y_WEBGL, this.flipped);
this.setFormat(Texture.setUpGlPixelFormat(this._cgl, this.pixelFormat));
this._cgl.gl.texImage2D(this.texTarget, 0, this._glInternalFormat, this._glDataFormat, this._glDataType, img);
this._setFilter();
this.updateMipMap();
this._cgl.gl.bindTexture(this.texTarget, null);
this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
if (this.flipped) this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_FLIP_Y_WEBGL, false);
this.getInfoOneLine();
this._cgl.printError("initTexture");
}
/**
* delete texture. use this when texture is no longer needed
* @function delete
* @memberof Texture
* @instance
*/
dispose()
{
this.delete();
}
delete()
{
if (this.loading)
{
// cant delete texture when still loading
// setTimeout(this.delete.bind(this), 50);
return;
}
this.deleted = true;
this.width = 0;
this.height = 0;
this._cgl.profileData.profileTextureDelete++;
this._cgl.gl.deleteTexture(this.tex);
this.image = null;
this.tex = null;
}
/**
* @function isPowerOfTwo
* @memberof Texture
* @instance
* @description return true if texture width and height are both power of two
* @return {Boolean}
*/
isPowerOfTwo()
{
return Texture.isPowerOfTwo(this.width) && Texture.isPowerOfTwo(this.height);
}
printInfo()
{
console.log(this.getInfo());
}
getInfoReadable()
{
const info = this.getInfo();
let html = "";
info.name = info.name.substr(0, info.name.indexOf("?rnd="));
for (const i in info)
{
html += "* " + i + ": **" + info[i] + "**\n";
}
return html;
}
getInfoOneLine()
{
let txt = "" + this.width + "x" + this.height;
txt += " ";
// if (this.textureType === CGL.Texture.TYPE_FLOAT) txt += " 32bit"; else txt += " 8bit";
// if (this.textureType === CGL.Texture.TYPE_FLOAT) txt += " 32bit"; else txt += " 8bit";
txt += this.pixelFormat;
if (this.filter === CGL.Texture.FILTER_NEAREST) txt += " nearest";
if (this.filter === CGL.Texture.FILTER_LINEAR) txt += " linear";
if (this.filter === CGL.Texture.FILTER_MIPMAP) txt += " mipmap";
if (this.wrap === CGL.Texture.WRAP_CLAMP_TO_EDGE) txt += " clamp";
if (this.wrap === CGL.Texture.WRAP_REPEAT) txt += " repeat";
if (this.wrap === CGL.Texture.WRAP_MIRRORED_REPEAT) txt += " repeatmir";
this.shortInfoString = txt;
return txt;
}
getInfoOneLineShort()
{
let txt = "" + this.width + "x" + this.height;
// if (this.textureType === CGL.Texture.TYPE_FLOAT) txt += " 32bit"; else txt += " 8bit";
txt += " ";
txt += this.pixelFormat;
this.shortInfoString = txt;
return txt;
}
getInfo()
{
return Texture.getTexInfo(this);
}
_setFilter()
{
this._cgl.printError("before _setFilter");
if (!this._fromData)
{
this._cgl.gl.pixelStorei(this._cgl.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.unpackAlpha);
}
if (this.shadowMap)
{
this._cgl.gl.texParameteri(this._cgl.gl.TEXTURE_2D, this._cgl.gl.TEXTURE_COMPARE_MODE, this._cgl.gl.COMPARE_REF_TO_TEXTURE);
this._cgl.gl.texParameteri(this._cgl.gl.TEXTURE_2D, this._cgl.gl.TEXTURE_COMPARE_FUNC, this._cgl.gl.LEQUAL);
}
if (this.textureType == Texture.TYPE_FLOAT && this.filter == Texture.FILTER_MIPMAP)
{
this.filter = Texture.FILTER_LINEAR;
this._log.stack("texture: HDR and mipmap filtering at the same time is not possible");
}
if (this._cgl.glVersion == 1 && !this.isPowerOfTwo())
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MAG_FILTER, this._cgl.gl.NEAREST);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MIN_FILTER, this._cgl.gl.NEAREST);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_S, this._cgl.gl.CLAMP_TO_EDGE);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_T, this._cgl.gl.CLAMP_TO_EDGE);
this.filter = Texture.FILTER_NEAREST;
this.wrap = Texture.WRAP_CLAMP_TO_EDGE;
}
else
{
if (this.wrap == Texture.WRAP_CLAMP_TO_EDGE)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_S, this._cgl.gl.CLAMP_TO_EDGE);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_T, this._cgl.gl.CLAMP_TO_EDGE);
}
else if (this.wrap == Texture.WRAP_REPEAT)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_S, this._cgl.gl.REPEAT);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_T, this._cgl.gl.REPEAT);
}
else if (this.wrap == Texture.WRAP_MIRRORED_REPEAT)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_S, this._cgl.gl.MIRRORED_REPEAT);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_WRAP_T, this._cgl.gl.MIRRORED_REPEAT);
}
if (this.filter == Texture.FILTER_NEAREST)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MAG_FILTER, this._cgl.gl.NEAREST);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MIN_FILTER, this._cgl.gl.NEAREST);
}
else if (this.filter == Texture.FILTER_LINEAR)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MIN_FILTER, this._cgl.gl.LINEAR);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MAG_FILTER, this._cgl.gl.LINEAR);
}
else if (this.filter == Texture.FILTER_MIPMAP)
{
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MAG_FILTER, this._cgl.gl.LINEAR);
this._cgl.gl.texParameteri(this.texTarget, this._cgl.gl.TEXTURE_MIN_FILTER, this._cgl.gl.LINEAR_MIPMAP_LINEAR);
}
else
{
this._log.log("unknown texture filter!", this.filter);
throw new Error("unknown texture filter!" + this.filter);
}
if (this.anisotropic)
{
const ext = this._cgl.enableExtension("EXT_texture_filter_anisotropic");
if (this._cgl.maxAnisotropic)
{
const aniso = Math.min(this._cgl.maxAnisotropic, this.anisotropic);
this._cgl.gl.texParameterf(this._cgl.gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
}
this.getInfoOneLine();
this._cgl.printError("_setFilter");
}
}
/**
* @function load
* @static
* @memberof Texture
* @description load an image from an url
* @param {Context} cgl
* @param {String} url
* @param {Function} finishedCallback
* @param {Object} settings
* @return {Texture}
*/
Texture.load = function (cgl, url, finishedCallback, settings)
{
if (!url) return finishedCallback({ "error": true });
let loadingId = null;
if (!cgl.patch.loading.existByName(url)) loadingId = cgl.patch.loading.start("cgl.texture", url);
const texture = new Texture(cgl);
texture.name = url;
texture.image = new Image();
texture.image.crossOrigin = "anonymous";
texture.loading = true;
if (settings && settings.hasOwnProperty("filter")) texture.filter = settings.filter;
if (settings && settings.hasOwnProperty("flip")) texture.flip = settings.flip;
if (settings && settings.hasOwnProperty("wrap")) texture.wrap = settings.wrap;
if (settings && settings.hasOwnProperty("anisotropic")) texture.anisotropic = settings.anisotropic;
if (settings && settings.hasOwnProperty("unpackAlpha")) texture.unpackAlpha = settings.unpackAlpha;
if (settings && settings.hasOwnProperty("pixelFormat")) texture.pixelFormat = settings.pixelFormat;
texture.image.onabort = texture.image.onerror = (e) =>
{
console.warn("[cgl.texture.load] error loading texture", url, e);
texture.loading = false;
if (loadingId) cgl.patch.loading.finished(loadingId);
const error = { "error": true };
if (finishedCallback) finishedCallback(error, texture);
};
texture.image.onload = function (e)
{
cgl.addNextFrameOnceCallback(() =>
{
texture.initTexture(texture.image);
if (loadingId) cgl.patch.loading.finished(loadingId);
texture.loading = false;
if (finishedCallback) finishedCallback(null, texture);
});
};
texture.image.src = url;
return texture;
};
/**
* @static
* @function getTempTexture
* @memberof Texture
* @description returns the default temporary texture (grey diagonal stipes)
* @param {Context} cgl
* @return {Texture}
*/
Texture.getTempTexture = function (cgl)
{
if (!cgl) console.error("[getTempTexture] no cgl!");
if (!cgl.tempTexture) cgl.tempTexture = Texture.getTemporaryTexture(cgl, 256, Texture.FILTER_LINEAR, Texture.REPEAT);
return cgl.tempTexture;
};
/**
* @static
* @function getErrorTexture
* @memberof Texture
* @description returns the default temporary texture (grey diagonal stipes)
* @param {Context} cgl
* @return {Texture}
*/
Texture.getErrorTexture = function (cgl)
{
if (!cgl) console.error("[getTempTexture] no cgl!");
if (!cgl.errorTexture) cgl.errorTexture = Texture.getTemporaryTexture(cgl, 256, Texture.FILTER_LINEAR, Texture.REPEAT, 1, 0.2, 0.2);
return cgl.errorTexture;
};
/**
* @function getEmptyTexture
* @memberof Texture
* @instance
* @param cgl
* @param fp
* @description returns a reference to a small empty (transparent) texture
* @return {Texture}
*/
Texture.getEmptyTexture = function (cgl, fp)
{
if (fp) return Texture.getEmptyTextureFloat(cgl);
if (!cgl) console.error("[getEmptyTexture] no cgl!");
if (cgl.tempTextureEmpty) return cgl.tempTextureEmpty;
let size = 8;
cgl.tempTextureEmpty = new Texture(cgl, { "name": "emptyTexture" });
const data = Texture.getDefaultTextureData("empty", size);
cgl.tempTextureEmpty.initFromData(data, size, size, Texture.FILTER_NEAREST, Texture.WRAP_REPEAT);
return cgl.tempTextureEmpty;
};
/**
* @function getEmptyTextureFloat
* @memberof Texture
* @instance
* @param cgl
* @description returns a reference to a small empty (transparent) 32bit texture
* @return {Texture}
*/
Texture.getEmptyTextureFloat = function (cgl)
{
if (!cgl) console.error("[getEmptyTextureFloat] no cgl!");
if (cgl.tempTextureEmptyFloat) return cgl.tempTextureEmptyFloat;
cgl.tempTextureEmptyFloat = new Texture(cgl, { "name": "emptyTexture", "isFloatingPointTexture": true });
const data = new Float32Array(8 * 8 * 4).fill(1);
for (let i = 0; i < 8 * 8 * 4; i += 4) data[i + 3] = 0;
cgl.tempTextureEmptyFloat.initFromData(data, 8, 8, Texture.FILTER_NEAREST, Texture.WRAP_REPEAT);
return cgl.tempTextureEmptyFloat;
};
/**
* @function getRandomTexture
* @memberof Texture
* @static
* @param cgl
* @description returns a reference to a random texture
* @return {Texture}
*/
Texture.getRandomTexture = function (cgl)
{
if (!cgl) console.error("[getRandomTexture] no cgl!");
if (cgl.randomTexture) return cgl.randomTexture;
const size = 256;
const data = Texture.getDefaultTextureData("randomUInt", size);
cgl.randomTexture = new Texture(cgl);
cgl.randomTexture.initFromData(data, size, size, Texture.FILTER_NEAREST, Texture.WRAP_REPEAT);
return cgl.randomTexture;
};
/**
* @function getRandomFloatTexture
* @memberof Texture
* @static
* @param cgl
* @description returns a reference to a texture containing random numbers between -1 and 1
* @return {Texture}
*/
Texture.getRandomFloatTexture = function (cgl)
{
if (!cgl) console.error("[getRandomTexture] no cgl!");
if (cgl.getRandomFloatTexture) return cgl.getRandomFloatTexture;
const size = 256;
const data = Texture.getDefaultTextureData("randomFloat", size);
cgl.getRandomFloatTexture = new Texture(cgl, { "isFloatingPointTexture": true });
cgl.getRandomFloatTexture.initFromData(data, size, size, Texture.FILTER_NEAREST, Texture.WRAP_REPEAT);
return cgl.getRandomFloatTexture;
};
/**
* @function getBlackTexture
* @memberof Texture
* @static
* @param cgl
* @description returns a reference to a black texture
* @return {Texture}
*/
Texture.getBlackTexture = function (cgl)
{
if (!cgl) this._log.error("[getBlackTexture] no cgl!");
if (cgl.blackTexture) return cgl.blackTexture;
const size = 8;
const data = Texture.getDefaultTextureData("color", size, { "r": 0, "g": 0, "b": 0 });
cgl.blackTexture = new Texture(cgl);
cgl.blackTexture.initFromData(data, size, size, Texture.FILTER_NEAREST, Texture.WRAP_REPEAT);
return cgl.blackTexture;
};
/**
* @function getEmptyCubemapTexture
* @memberof Texture
* @static
* @param cgl
* @description returns an empty cubemap texture with rgba = [0, 0, 0, 0]
* @return {Texture}
*/
Texture.getEmptyCubemapTexture = function (cgl)
{
const faces = [
cgl.gl.TEXTURE_CUBE_MAP_POSITIVE_X,
cgl.gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
cgl.gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
cgl.gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
cgl.gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
cgl.gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
];
const tex = cgl.gl.createTexture();
const target = cgl.gl.TEXTURE_CUBE_MAP;
const filter = Texture.FILTER_NEAREST;
const wrap = Texture.WRAP_CLAMP_TO_EDGE;
const width = 8;
const height = 8;
cgl.profileData.profileTextureNew++;
cgl.gl.bindTexture(target, tex);
cgl.profileData.profileTextureResize++;
for (let i = 0; i < 6; i += 1)
{
const data = new Uint8Array(8 * 8 * 4);
cgl.gl.texImage2D(faces[i], 0, cgl.gl.RGBA, 8, 8, 0, cgl.gl.RGBA, cgl.gl.UNSIGNED_BYTE, data);
cgl.gl.texParameteri(target, cgl.gl.TEXTURE_MAG_FILTER, cgl.gl.NEAREST);
cgl.gl.texParameteri(target, cgl.gl.TEXTURE_MIN_FILTER, cgl.gl.NEAREST);
cgl.gl.texParameteri(target, cgl.gl.TEXTURE_WRAP_S, cgl.gl.CLAMP_TO_EDGE);
cgl.gl.texParameteri(target, cgl.gl.TEXTURE_WRAP_T, cgl.gl.CLAMP_TO_EDGE);
}
cgl.gl.bindTexture(target, null);
return {
"id": CABLES.uuid(),
"tex": tex,
"cubemap": tex,
"width": width,
"height": height,
"filter": filter,
"wrap": wrap,
"unpackAlpha": true,
"flip": true,
"_fromData": true,
"name": "emptyCubemapTexture",
"anisotropic": 0,
};
};
Texture.getTempGradientTexture = function (cgl) // deprecated...
{
if (!cgl) console.error("[getTempGradientTexture] no cgl!");
return Texture.getTempTexture(cgl);
};
Texture.getTemporaryTexture = function (cgl, size, filter, wrap, r, g, b)
{
const data = Texture.getDefaultTextureData("stripes", 256, { "r": r, "g": g, "b": b });
const temptex = new Texture(cgl);
temptex.initFromData(data, size, size, filter, wrap);
return temptex;
};
/**
* @static
* @function createFromImage
* @memberof Texture
* @description create texturem from image data (e.g. image or canvas)
* @param {Context} cgl
* @param {Object} img image
* @param {Object} options
*/
Texture.createFromImage = function (cgl, img, options)
{
options = options || {};
const texture = new Texture(cgl, options);
texture.flip = false;
texture.image = img;
texture.width = img.videoWidth || img.width || 8;
texture.height = img.videoHeight || img.height || 8;
if (options.hasOwnProperty("wrap"))texture.wrap = options.wrap;
texture.initTexture(img, options.filter);
return texture;
};
// deprecated!
Texture.fromImage = function (cgl, img, filter, wrap)
{
console.error("deprecated texture from image...");
const texture = new Texture(cgl);
texture.flip = false;
if (filter) texture.filter = filter;
if (wrap) texture.wrap = wrap;
texture.image = img;
texture.initTexture(img);
return texture;
};
/**
* @static
* @function isPowerOfTwo
* @memberof Texture
* @description returns true if x is power of two
* @param {Number} x
* @return {Boolean}
*/
Texture.isPowerOfTwo = function (x)
{
return x == 1 || x == 2 || x == 4 || x == 8 || x == 16 || x == 32 || x == 64 || x == 128 || x == 256 || x == 512 || x == 1024 || x == 2048 || x == 4096 || x == 8192 || x == 16384;
};
Texture.getTexInfo = function (tex)
{
const obj = {};
obj.name = tex.name;
obj["power of two"] = tex.isPowerOfTwo();
obj.size = tex.width + " x " + tex.height;
let targetString = tex.texTarget;
if (tex.texTarget == tex._cgl.gl.TEXTURE_2D) targetString = "TEXTURE_2D";
obj.target = targetString;
obj.unpackAlpha = tex.unpackAlpha;
if (tex.cubemap)obj.cubemap = true;
if (tex.textureType == Texture.TYPE_FLOAT) obj.textureType = "TYPE_FLOAT";
if (tex.textureType == Texture.TYPE_HALF_FLOAT) obj.textureType = "TYPE_HALF_FLOAT";
else if (tex.textureType == Texture.TYPE_DEPTH) obj.textureType = "TYPE_DEPTH";
else if (tex.textureType == Texture.TYPE_DEFAULT) obj.textureType = "TYPE_DEFAULT";
else obj.textureType = "UNKNOWN " + this.textureType;
if (tex.wrap == Texture.WRAP_CLAMP_TO_EDGE) obj.wrap = "CLAMP_TO_EDGE";
else if (tex.wrap == Texture.WRAP_REPEAT) obj.wrap = "WRAP_REPEAT";
else if (tex.wrap == Texture.WRAP_MIRRORED_REPEAT) obj.wrap = "WRAP_MIRRORED_REPEAT";
else obj.wrap = "UNKNOWN";
if (tex.filter == Texture.FILTER_NEAREST) obj.filter = "FILTER_NEAREST";
else if (tex.filter == Texture.FILTER_LINEAR) obj.filter = "FILTER_LINEAR";
else if (tex.filter == Texture.FILTER_MIPMAP) obj.filter = "FILTER_MIPMAP";
else obj.filter = "UNKNOWN";
obj.pixelFormat = tex.pixelFormat || "unknown";
return obj;
};
Texture.setUpGlPixelFormat = function (cgl, pixelFormatStr)
{
const o = {};
if (!pixelFormatStr)
{
cgl._log.error("no pixelformatstr!");
cgl._log.log(new Error());
pixelFormatStr = Texture.PFORMATSTR_RGBA8UB;
}
o.pixelFormatBase = pixelFormatStr;
o.pixelFormat = pixelFormatStr;
o.glDataType = cgl.gl.UNSIGNED_BYTE;
o.glInternalFormat = cgl.gl.RGBA8;
o.glDataFormat = cgl.gl.RGBA;
let floatDatatype = cgl.gl.FLOAT;
if (cgl.glUseHalfFloatTex)
{
if (pixelFormatStr == Texture.PFORMATSTR_RGBA32F) pixelFormatStr = Texture.PFORMATSTR_RGBA16F;
if (pixelFormatStr == Texture.PFORMATSTR_RG32F) pixelFormatStr = Texture.PFORMATSTR_RG16F;
if (pixelFormatStr == Texture.PFORMATSTR_R32F) pixelFormatStr = Texture.PFORMATSTR_R16F;
}
if (pixelFormatStr.contains("16bit"))
{
if (cgl.glVersion == 2)
{
// cgl.enableExtension("OES_texture_half_float");
const hasExt = cgl.enableExtension("EXT_color_buffer_half_float");
if (!hasExt)
{
console.warn("no 16bit extension, fallback to 32bit", pixelFormatStr);
// fallback to 32 bit?
if (pixelFormatStr == Texture.PFORMATSTR_RGBA16F) pixelFormatStr = Texture.PFORMATSTR_RGBA32F;
if (pixelFormatStr == Texture.PFORMATSTR_RGB16F) pixelFormatStr = Texture.PFORMATSTR_RGB32F;
if (pixelFormatStr == Texture.PFORMATSTR_RG16F) pixelFormatStr = Texture.PFORMATSTR_RG32F;
if (pixelFormatStr == Texture.PFORMATSTR_R16F) pixelFormatStr = Texture.PFORMATSTR_R32F;
}
else
{
floatDatatype = cgl.gl.HALF_FLOAT;
}
}
}
if (cgl.glVersion == 1)
{
o.glInternalFormat = cgl.gl.RGBA;
if (pixelFormatStr == Texture.PFORMATSTR_RGBA16F || pixelFormatStr == Texture.PFORMATSTR_RG16F || pixelFormatStr == Texture.PFORMATSTR_R16F)
{
const ext = cgl.enableExtension("OES_texture_half_float");
if (!ext) throw new Error("no half float texture extension");
floatDatatype = ext.HALF_FLOAT_OES;
}
}
if (pixelFormatStr == Texture.PFORMATSTR_RGBA8UB)
{
}
else if (pixelFormatStr == Texture.PFORMATSTR_RGB565)
{
o.glInternalFormat = cgl.gl.RGB565;
o.glDataFormat = cgl.gl.RGB;
}
else if (pixelFormatStr == Texture.PFORMATSTR_R8UB)
{
o.glInternalFormat = cgl.gl.R8;
o.glDataFormat = cgl.gl.RED;
}
else if (pixelFormatStr == Texture.PFORMATSTR_RG8UB)
{
o.glInternalFormat = cgl.gl.RG8;
o.glDataFormat = cgl.gl.RG;
}
else if (pixelFormatStr == Texture.PFORMATSTR_RGB8UB)
{
o.glInternalFormat = cgl.gl.RGB8;
o.glDataFormat = cgl.gl.RGB;
}
else if (pixelFormatStr == Texture.PFORMATSTR_SRGBA8)
{
o.glInternalFormat = cgl.gl.SRGB8_ALPHA8;
}
else if (pixelFormatStr == Texture.PFORMATSTR_R32F)
{
o.glInternalFormat = cgl.gl.R32F;
o.glDataFormat = cgl.gl.RED;
o.glDataType = floatDatatype;
}
else if (pixelFormatStr == Texture.PFORMATSTR_R16F)
{
o.glInternalFormat = cgl.gl.R16F;
o.glDataType = floatDatatype;
o.glDataFormat = cgl.gl.RED;
}
else if (pixelFormatStr == Texture.PFORMATSTR_RG16F)
{
o.glInternalFormat = cgl.gl.RG16F;
o.glDataType = floatDatatype;
o.glDataFormat = cgl.gl.RG;
}
else if (pixelFormatStr == Texture.PFORMATSTR_RGBA16F)
{
if (cgl.glVersion == 1) o.glInternalFormat = cgl.gl.RGBA;
else o.glInternalFormat = cgl.gl.RGBA16F;
o.glDataType = floatDatatype;
}
else if (pixelFormatStr == Texture.PFORMATSTR_R11FG11FB10F)
{
o.glInternalFormat = cgl.gl.R11F_G11F_B10F;
o.glDataType = floatDatatype;
o.glDataFormat = cgl.gl.RGB;
}
else if (pixelFormatStr == Texture.PFORMATSTR_RGBA32F)
{
if (cgl.glVersion == 1) o.glInternalFormat = cgl.gl.RGBA;
else o.glInternalFormat = cgl.gl.RGBA32F;
o.glDataType = floatDatatype;
}
else if (pixelFormatStr == Texture.PFORMATSTR_DEPTH)
{
if (cgl.glVersion == 1)
{
o.glInternalFormat = cgl.gl.DEPTH_COMPONENT;
o.glDataType = cgl.gl.UNSIGNED_SHORT;
o.glDataFormat = cgl.gl.DEPTH_COMPONENT;
}
else
{
o.glInternalFormat = cgl.gl.DEPTH_COMPONENT32F;
o.glDataType = cgl.gl.FLOAT;
o.glDataFormat = cgl.gl.DEPTH_COMPONENT;
}
}
else
{
console.log("unknown pixelformat ", pixelFormatStr);
}
/// //////
if (pixelFormatStr.contains("32bit") || pixelFormatStr == Texture.PFORMATSTR_R11FG11FB10F)
{
if (cgl.glVersion == 2) cgl.enableExtension("EXT_color_buffer_float");
if (cgl.glVersion == 2) cgl.enableExtension("EXT_float_blend");
cgl.enableExtension("OES_texture_float_linear"); // yes, i am sure, this is a webgl 1 and 2 ext
}
o.numColorChannels = Texture.getPixelFormatNumChannels(pixelFormatStr);
if (!o.glDataType || !o.glInternalFormat || !o.glDataFormat) console.log("pixelformat wrong ?!", pixelFormatStr, o.glDataType, o.glInternalFormat, o.glDataFormat, this);
return o;
};
Texture.getPixelFormatNumChannels =
(pxlFrmtStr) =>
{
if (pxlFrmtStr.startsWith("RGBA")) return 4;
if (pxlFrmtStr.startsWith("RGB")) return 3;
if (pxlFrmtStr.startsWith("RG")) return 2;
return 1;
};
Texture.isPixelFormatFloat =
(pxlFrmtStr) =>
{
return (pxlFrmtStr || "").contains("float");
};
Texture.isPixelFormatHalfFloat =
(pxlFrmtStr) =>
{
return (pxlFrmtStr || "").contains("float") && (pxlFrmtStr || "").contains("16bit");
};
export { Texture };