@thi.ng/webgl
Version:
WebGL & GLSL abstraction layer
293 lines (292 loc) • 7.17 kB
JavaScript
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
};