2017-08-26 16:47:18 -04:00
|
|
|
// pathfinder/client/src/buffer-texture.ts
|
|
|
|
//
|
|
|
|
// Copyright © 2017 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
import * as glmatrix from 'gl-matrix';
|
|
|
|
|
|
|
|
import {setTextureParameters, UniformMap} from './gl-utils';
|
2017-10-18 22:47:44 -04:00
|
|
|
import {assert, expectNotNull} from './utils';
|
2017-08-26 16:47:18 -04:00
|
|
|
|
|
|
|
export default class PathfinderBufferTexture {
|
2017-09-28 17:34:48 -04:00
|
|
|
readonly texture: WebGLTexture;
|
|
|
|
readonly uniformName: string;
|
|
|
|
|
|
|
|
private size: glmatrix.vec2;
|
|
|
|
private glType: number;
|
2017-10-18 22:47:44 -04:00
|
|
|
private destroyed: boolean;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
constructor(gl: WebGLRenderingContext, uniformName: string) {
|
|
|
|
this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!");
|
|
|
|
this.size = glmatrix.vec2.create();
|
|
|
|
this.uniformName = uniformName;
|
|
|
|
this.glType = 0;
|
2017-10-18 22:47:44 -04:00
|
|
|
this.destroyed = false;
|
2017-08-26 16:47:18 -04:00
|
|
|
}
|
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
destroy(gl: WebGLRenderingContext): void {
|
|
|
|
assert(!this.destroyed, "Buffer texture destroyed!");
|
|
|
|
gl.deleteTexture(this.texture);
|
|
|
|
this.destroyed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array): void {
|
|
|
|
assert(!this.destroyed, "Buffer texture destroyed!");
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
|
|
|
|
|
|
const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
|
2017-10-18 22:47:44 -04:00
|
|
|
const areaNeeded = Math.ceil(data.length / 4);
|
|
|
|
if (glType !== this.glType || areaNeeded > this.area) {
|
|
|
|
const sideLength = nextPowerOfTwo(Math.ceil(Math.sqrt(areaNeeded)));
|
|
|
|
this.size = glmatrix.vec2.clone([sideLength, sideLength]);
|
2017-08-26 16:47:18 -04:00
|
|
|
this.glType = glType;
|
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
gl.texImage2D(gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
gl.RGBA,
|
|
|
|
sideLength,
|
|
|
|
sideLength,
|
|
|
|
0,
|
|
|
|
gl.RGBA,
|
|
|
|
glType,
|
|
|
|
null);
|
2017-08-26 16:47:18 -04:00
|
|
|
setTextureParameters(gl, gl.NEAREST);
|
|
|
|
}
|
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
const mainDimensions = glmatrix.vec4.clone([
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
this.size[0],
|
|
|
|
(areaNeeded / this.size[0]) | 0,
|
|
|
|
]);
|
|
|
|
const remainderDimensions = glmatrix.vec4.clone([
|
|
|
|
0,
|
|
|
|
mainDimensions[3],
|
|
|
|
areaNeeded % this.size[0],
|
|
|
|
1,
|
|
|
|
]);
|
2017-08-26 16:47:18 -04:00
|
|
|
const splitIndex = mainDimensions[2] * mainDimensions[3] * 4;
|
|
|
|
|
|
|
|
if (mainDimensions[2] > 0 && mainDimensions[3] > 0) {
|
|
|
|
gl.texSubImage2D(gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
mainDimensions[0],
|
|
|
|
mainDimensions[1],
|
|
|
|
mainDimensions[2],
|
|
|
|
mainDimensions[3],
|
|
|
|
gl.RGBA,
|
|
|
|
this.glType,
|
|
|
|
data.slice(0, splitIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remainderDimensions[2] > 0) {
|
|
|
|
// Round data up to a multiple of 4 elements if necessary.
|
|
|
|
let remainderLength = data.length - splitIndex;
|
|
|
|
let remainder: Float32Array | Uint8Array;
|
2017-09-28 17:34:48 -04:00
|
|
|
if (remainderLength % 4 === 0) {
|
2017-08-26 16:47:18 -04:00
|
|
|
remainder = data.slice(splitIndex);
|
|
|
|
} else {
|
|
|
|
remainderLength += 4 - remainderLength % 4;
|
|
|
|
remainder = new (data.constructor as any)(remainderLength);
|
|
|
|
remainder.set(data.slice(splitIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
gl.texSubImage2D(gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
remainderDimensions[0],
|
|
|
|
remainderDimensions[1],
|
|
|
|
remainderDimensions[2],
|
|
|
|
remainderDimensions[3],
|
|
|
|
gl.RGBA,
|
|
|
|
this.glType,
|
|
|
|
remainder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): void {
|
|
|
|
assert(!this.destroyed, "Buffer texture destroyed!");
|
|
|
|
|
2017-08-26 16:47:18 -04:00
|
|
|
gl.activeTexture(gl.TEXTURE0 + textureUnit);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
2017-10-18 22:47:44 -04:00
|
|
|
gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], this.size[0], this.size[1]);
|
2017-08-26 16:47:18 -04:00
|
|
|
gl.uniform1i(uniforms[this.uniformName], textureUnit);
|
|
|
|
}
|
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
private get area(): number {
|
|
|
|
assert(!this.destroyed, "Buffer texture destroyed!");
|
2017-08-26 16:47:18 -04:00
|
|
|
return this.size[0] * this.size[1];
|
|
|
|
}
|
2017-10-18 22:47:44 -04:00
|
|
|
}
|
2017-08-26 16:47:18 -04:00
|
|
|
|
2017-10-18 22:47:44 -04:00
|
|
|
/// https://stackoverflow.com/a/466242
|
|
|
|
function nextPowerOfTwo(n: number): number {
|
|
|
|
n--;
|
|
|
|
n |= n >> 1;
|
|
|
|
n |= n >> 2;
|
|
|
|
n |= n >> 4;
|
|
|
|
n |= n >> 8;
|
|
|
|
n |= n >> 16;
|
|
|
|
return n + 1;
|
2017-08-26 16:47:18 -04:00
|
|
|
}
|