UNPKG

@thi.ng/webgl

Version:

WebGL & GLSL abstraction layer

293 lines (292 loc) 7.17 kB
import { isArray } from "@thi.ng/checks/is-array"; import { withoutKeysObj } from "@thi.ng/object-utils/without-keys"; import { TEX_FORMATS, TextureFilter, TextureFormat, TextureRepeat, TextureTarget, TextureType } from "./api/texture.js"; import { isGL2Context } from "./checks.js"; import { error } from "./error.js"; const $bind = (op) => (textures) => { if (!textures) return; for (let i = textures.length, tex; i-- > 0; ) { (tex = textures[i]) && tex[op](i); } }; const bindTextures = $bind("bind"); const unbindTextures = $bind("unbind"); class Texture { gl; tex; target; format; filter; wrap; type; size; constructor(gl, opts = {}) { this.gl = gl; this.tex = gl.createTexture() || error("error creating WebGL texture"); this.configure({ filter: TextureFilter.NEAREST, wrap: TextureRepeat.CLAMP, ...opts }); } configure(opts = {}, unbind = true) { const gl = this.gl; const target = opts.target || this.target || TextureTarget.TEXTURE_2D; const format = opts.format || this.format || TextureFormat.RGBA; const decl = TEX_FORMATS[format]; const type = opts.type || this.type || decl.types[0]; !this.target && (this.target = target); this.format = format; this.type = type; gl.bindTexture(this.target, this.tex); opts.flip !== void 0 && gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, opts.flip ? 1 : 0); opts.premultiply !== void 0 && gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, opts.premultiply ? 1 : 0 ); this.configureImage(target, opts); opts.mipmap && gl.generateMipmap(target); this.configureFilter(target, opts); this.configureWrap(target, opts); this.configureLOD(target, opts); this.configureLevels(target, opts); unbind && gl.bindTexture(this.target, null); return true; } configureImage(target, opts) { if (opts.image === void 0) return; target === TextureTarget.TEXTURE_3D ? this.configureImage3d(target, opts) : this.configureImage2d(target, opts); } configureImage2d(target, opts) { const level = opts.level || 0; const pos = opts.pos || [0, 0, 0]; const { image, width, height } = opts; const decl = TEX_FORMATS[this.format]; const baseFormat = decl.format; const { gl, type, format } = this; if (width && height) { if (opts.sub) { gl.texSubImage2D( target, level, pos[0], pos[1], width, height, baseFormat, type, image ); } else { if (level === 0) { this.size = [width, height]; } gl.texImage2D( target, level, format, width, height, 0, baseFormat, type, image ); } } else { if (opts.sub) { gl.texSubImage2D( target, level, pos[0], pos[1], baseFormat, type, image ); } else { if (image != null && level === 0) { this.size = [image.width, image.height]; } gl.texImage2D( target, level, format, baseFormat, type, image ); } } } configureImage3d(target, opts) { const { image, width, height, depth } = opts; if (!(width && height && depth)) return; const level = opts.level || 0; const pos = opts.pos || [0, 0, 0]; const decl = TEX_FORMATS[this.format]; const baseFormat = decl.format; const { gl, type, format } = this; if (opts.sub) { gl.texSubImage3D( target, level, pos[0], pos[1], pos[2], width, height, depth, baseFormat, type, image ); } else { if (level === 0) { this.size = [width, height, depth]; } gl.texImage3D( target, level, format, width, height, depth, 0, baseFormat, type, image ); } } configureFilter(target, opts) { const gl = this.gl; const flt = opts.filter || this.filter || TextureFilter.NEAREST; let t1, t2; if (isArray(flt)) { t1 = flt[0]; t2 = flt[1] || t1; this.filter = [t1, t2]; } else { this.filter = [flt, flt, flt]; t1 = t2 = flt; } gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, t1); gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, t2); } configureWrap(target, opts) { const gl = this.gl; const wrap = opts.wrap || this.wrap || TextureRepeat.CLAMP; let t1, t2, t3; if (isArray(wrap)) { t1 = wrap[0]; t2 = wrap[1] || t1; t3 = wrap[2] || t1; this.wrap = [t1, t2, t3]; } else { t1 = t2 = t3 = wrap; this.wrap = [wrap, wrap, wrap]; } gl.texParameteri(target, gl.TEXTURE_WRAP_S, t1); gl.texParameteri(target, gl.TEXTURE_WRAP_T, t2); isGL2Context(gl) && target === gl.TEXTURE_3D && gl.texParameteri( target, gl.TEXTURE_WRAP_R, t3 ); } configureLOD(target, opts) { const gl = this.gl; if (opts.lod) { const [t1, t2] = opts.lod; t1 && gl.texParameterf( target, gl.TEXTURE_MIN_LOD, t1 ); t2 && gl.texParameterf( target, gl.TEXTURE_MAX_LOD, t2 ); } } configureLevels(target, opts) { const gl = this.gl; if (opts.minMaxLevel) { const [t1, t2] = opts.minMaxLevel; gl.texParameteri( target, gl.TEXTURE_BASE_LEVEL, t1 ); gl.texParameteri( target, gl.TEXTURE_MAX_LEVEL, t2 ); } } bind(id = 0) { const gl = this.gl; gl.activeTexture(gl.TEXTURE0 + id); gl.bindTexture(this.target, this.tex); return true; } unbind(id = 0) { const gl = this.gl; gl.activeTexture(gl.TEXTURE0 + id); gl.bindTexture(this.target, null); return true; } release() { if (this.tex) { this.gl.deleteTexture(this.tex); delete this.tex; delete this.gl; return true; } return false; } } const defTexture = (gl, opts) => new Texture(gl, opts); const defTextureCubeMap = (gl, faces, opts = {}) => { const tex = new Texture(gl, { target: gl.TEXTURE_CUBE_MAP }); const faceOpts = withoutKeysObj(opts, [ "target", "image", "filter", "mipmap" ]); for (let i = 0; i < 6; i++) { faceOpts.target = gl.TEXTURE_CUBE_MAP_POSITIVE_X + i; faceOpts.image = faces[i]; tex.configure(faceOpts); } tex.configure({ filter: opts.filter, mipmap: opts.mipmap }); return tex; }; const defTextureFloat = (gl, data, width, height, format, type) => new Texture(gl, { filter: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, format: format || (isGL2Context(gl) ? TextureFormat.RGBA32F : TextureFormat.RGBA), type: type || gl.FLOAT, image: data, width, height }); export { Texture, bindTextures, defTexture, defTextureCubeMap, defTextureFloat, unbindTextures };