Skip to content

Commit

Permalink
fix uniform block setting
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Sep 24, 2021
1 parent 0d2bcb0 commit cfabc65
Show file tree
Hide file tree
Showing 9 changed files with 18,985 additions and 35 deletions.
84 changes: 49 additions & 35 deletions src/programs.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,30 +329,30 @@ function samplerArraySetter(gl, type, unit, location, size) {
}

typeMap[FLOAT] = { Type: Float32Array, size: 4, setter: floatSetter, arraySetter: floatArraySetter, };
typeMap[FLOAT_VEC2] = { Type: Float32Array, size: 8, setter: floatVec2Setter, };
typeMap[FLOAT_VEC3] = { Type: Float32Array, size: 12, setter: floatVec3Setter, };
typeMap[FLOAT_VEC4] = { Type: Float32Array, size: 16, setter: floatVec4Setter, };
typeMap[FLOAT_VEC2] = { Type: Float32Array, size: 8, setter: floatVec2Setter, cols: 2, };
typeMap[FLOAT_VEC3] = { Type: Float32Array, size: 12, setter: floatVec3Setter, cols: 3, };
typeMap[FLOAT_VEC4] = { Type: Float32Array, size: 16, setter: floatVec4Setter, cols: 4, };
typeMap[INT] = { Type: Int32Array, size: 4, setter: intSetter, arraySetter: intArraySetter, };
typeMap[INT_VEC2] = { Type: Int32Array, size: 8, setter: intVec2Setter, };
typeMap[INT_VEC3] = { Type: Int32Array, size: 12, setter: intVec3Setter, };
typeMap[INT_VEC4] = { Type: Int32Array, size: 16, setter: intVec4Setter, };
typeMap[INT_VEC2] = { Type: Int32Array, size: 8, setter: intVec2Setter, cols: 2, };
typeMap[INT_VEC3] = { Type: Int32Array, size: 12, setter: intVec3Setter, cols: 3, };
typeMap[INT_VEC4] = { Type: Int32Array, size: 16, setter: intVec4Setter, cols: 4, };
typeMap[UNSIGNED_INT] = { Type: Uint32Array, size: 4, setter: uintSetter, arraySetter: uintArraySetter, };
typeMap[UNSIGNED_INT_VEC2] = { Type: Uint32Array, size: 8, setter: uintVec2Setter, };
typeMap[UNSIGNED_INT_VEC3] = { Type: Uint32Array, size: 12, setter: uintVec3Setter, };
typeMap[UNSIGNED_INT_VEC4] = { Type: Uint32Array, size: 16, setter: uintVec4Setter, };
typeMap[UNSIGNED_INT_VEC2] = { Type: Uint32Array, size: 8, setter: uintVec2Setter, cols: 2, };
typeMap[UNSIGNED_INT_VEC3] = { Type: Uint32Array, size: 12, setter: uintVec3Setter, cols: 3, };
typeMap[UNSIGNED_INT_VEC4] = { Type: Uint32Array, size: 16, setter: uintVec4Setter, cols: 4, };
typeMap[BOOL] = { Type: Uint32Array, size: 4, setter: intSetter, arraySetter: intArraySetter, };
typeMap[BOOL_VEC2] = { Type: Uint32Array, size: 8, setter: intVec2Setter, };
typeMap[BOOL_VEC3] = { Type: Uint32Array, size: 12, setter: intVec3Setter, };
typeMap[BOOL_VEC4] = { Type: Uint32Array, size: 16, setter: intVec4Setter, };
typeMap[FLOAT_MAT2] = { Type: Float32Array, size: 16, setter: floatMat2Setter, };
typeMap[FLOAT_MAT3] = { Type: Float32Array, size: 36, setter: floatMat3Setter, };
typeMap[FLOAT_MAT4] = { Type: Float32Array, size: 64, setter: floatMat4Setter, };
typeMap[FLOAT_MAT2x3] = { Type: Float32Array, size: 24, setter: floatMat23Setter, };
typeMap[FLOAT_MAT2x4] = { Type: Float32Array, size: 32, setter: floatMat24Setter, };
typeMap[FLOAT_MAT3x2] = { Type: Float32Array, size: 24, setter: floatMat32Setter, };
typeMap[FLOAT_MAT3x4] = { Type: Float32Array, size: 48, setter: floatMat34Setter, };
typeMap[FLOAT_MAT4x2] = { Type: Float32Array, size: 32, setter: floatMat42Setter, };
typeMap[FLOAT_MAT4x3] = { Type: Float32Array, size: 48, setter: floatMat43Setter, };
typeMap[BOOL_VEC2] = { Type: Uint32Array, size: 8, setter: intVec2Setter, cols: 2, };
typeMap[BOOL_VEC3] = { Type: Uint32Array, size: 12, setter: intVec3Setter, cols: 3, };
typeMap[BOOL_VEC4] = { Type: Uint32Array, size: 16, setter: intVec4Setter, cols: 4, };
typeMap[FLOAT_MAT2] = { Type: Float32Array, size: 32, setter: floatMat2Setter, rows: 2, cols: 2, };
typeMap[FLOAT_MAT3] = { Type: Float32Array, size: 48, setter: floatMat3Setter, rows: 3, cols: 3, };
typeMap[FLOAT_MAT4] = { Type: Float32Array, size: 64, setter: floatMat4Setter, rows: 4, cols: 4, };
typeMap[FLOAT_MAT2x3] = { Type: Float32Array, size: 32, setter: floatMat23Setter, rows: 2, cols: 3, };
typeMap[FLOAT_MAT2x4] = { Type: Float32Array, size: 32, setter: floatMat24Setter, rows: 2, cols: 4, };
typeMap[FLOAT_MAT3x2] = { Type: Float32Array, size: 48, setter: floatMat32Setter, rows: 3, cols: 2, };
typeMap[FLOAT_MAT3x4] = { Type: Float32Array, size: 48, setter: floatMat34Setter, rows: 3, cols: 4, };
typeMap[FLOAT_MAT4x2] = { Type: Float32Array, size: 64, setter: floatMat42Setter, rows: 4, cols: 2, };
typeMap[FLOAT_MAT4x3] = { Type: Float32Array, size: 64, setter: floatMat43Setter, rows: 4, cols: 3, };
typeMap[SAMPLER_2D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D, };
typeMap[SAMPLER_CUBE] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_CUBE_MAP, };
typeMap[SAMPLER_3D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_3D, };
Expand Down Expand Up @@ -1110,17 +1110,19 @@ const arraySuffixRE = /\[\d+\]\.$/; // better way to check?

const pad = (v, padding) => ((v + (padding - 1)) / padding | 0) * padding;

function createUniformBlockUniformSetter(view, Type, typeSize, paddedSize, isArray) {
if (isArray) {
const numElements = typeSize / Type.BYTES_PER_ELEMENT;
const numPaddedElements = paddedSize / Type.BYTES_PER_ELEMENT;
function createUniformBlockUniformSetter(view, isArray, rows, cols) {
if (isArray || rows) {
cols = cols || 1;
const numElements = view.length;
const totalRows = numElements / 4;
return function(value) {
let dst = 0;
for (let src = 0; src < value.length; src += numElements) {
for (let i = 0; i < numElements; ++i) {
view[dst + i] = value[src + i];
let src = 0;
for (let row = 0; row < totalRows; ++row) {
for (let col = 0; col < cols; ++col) {
view[dst++] = value[src++];
}
dst += numPaddedElements;
dst += 4 - cols;
}
};
} else {
Expand Down Expand Up @@ -1203,10 +1205,6 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN
const setters = {};
blockSpec.uniformIndices.forEach(function(uniformNdx) {
const data = uniformData[uniformNdx];
const typeInfo = typeMap[data.type];
const Type = typeInfo.Type;
const paddedSize = pad(typeInfo.size, 16);
const length = typeInfo.size + (data.size - 1) * paddedSize;
let name = data.name;
if (name.startsWith(prefix)) {
name = name.substr(prefix.length);
Expand All @@ -1215,9 +1213,14 @@ function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockN
if (isArray) {
name = name.substr(0, name.length - 3);
}
const uniformView = new Type(array, data.offset, length / Type.BYTES_PER_ELEMENT);
const typeInfo = typeMap[data.type];
const Type = typeInfo.Type;
const byteLength = isArray
? pad(typeInfo.size, 16) * data.size
: typeInfo.size * data.size;
const uniformView = new Type(array, data.offset, byteLength / Type.BYTES_PER_ELEMENT);
uniforms[name] = uniformView;
setters[name] = createUniformBlockUniformSetter(uniformView, Type, typeInfo.size, paddedSize, isArray);
setters[name] = createUniformBlockUniformSetter(uniformView, isArray, typeInfo.rows, typeInfo.cols);
});
return {
name: blockName,
Expand Down Expand Up @@ -1324,6 +1327,17 @@ function setUniformBlock(gl, programInfo, uniformBlockInfo) {
*
* Arrays can be JavaScript arrays or typed arrays
*
* **IMPORTANT!**, packing in a UniformBlock is unintuitive.
* For example the actual layout of `someVec3Array` above in memory
* is `1, 2, 3, unused, 4, 5, 6, unused`. twgl takes in 6 values
* as shown about and copies them, skipping the padding. This might
* be confusing if you're already familiar with Uniform blocks.
*
* If you want to deal with the padding yourself you can access the array
* buffer views directly. eg:
*
* someBlockInfo.someVec3Array.set([1, 2, 3, 0, 4, 5, 6, 0]);
*
* Any name that doesn't match will be ignored
* @memberOf module:twgl/programs
*/
Expand Down
173 changes: 173 additions & 0 deletions test/assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
export const config = {};

export function setConfig(options) {
Object.assign(config, options);
}

function formatMsg(msg) {
return `${msg}${msg ? ': ' : ''}`;
}

export function assertTruthy(actual, msg = '') {
if (!config.noLint && !actual) {
throw new Error(`${formatMsg(msg)}expected: truthy, actual: ${actual}`);
}
}

export function assertFalsy(actual, msg = '') {
if (!config.noLint && actual) {
throw new Error(`${formatMsg(msg)}expected: falsy, actual: ${actual}`);
}
}

export function assertStringMatchesRegEx(actual, regex, msg = '') {
if (!config.noLint && !regex.test(actual)) {
throw new Error(`${formatMsg(msg)}expected: ${regex}, actual: ${actual}`);
}
}

export function assertEqual(actual, expected, msg = '') {
if (!config.noLint && actual !== expected) {
throw new Error(`${formatMsg(msg)}expected: ${expected} to equal actual: ${actual}`);
}
}

export function assertNotEqual(actual, expected, msg = '') {
if (!config.noLint && actual === expected) {
throw new Error(`${formatMsg(msg)}expected: ${expected} to not equal actual: ${actual}`);
}
}

export function assertArrayEqual(actual, expected, msg = '') {
if (actual.length !== expected.length) {
throw new Error(`${formatMsg(msg)}expected: array.length ${expected.length} to equal actual.length: ${actual.length}`);
}
const errors = [];
for (let i = 0; i < actual.length; ++i) {
if (actual[i] !== expected[i]) {
errors.push(`${formatMsg(msg)}expected: expected[${i}] ${expected[i]} to equal actual[${i}]: ${actual[i]}`);
if (errors.length === 10) {
break;
}
}
}
if (errors.length > 0) {
throw new Error(errors.join('\n'));
}
}

export function assertThrowsWith(func, expectations, msg = '') {
let error = '';
if (config.throwOnError === false) {
const origFn = console.error;
const errors = [];
console.error = function(...args) {
errors.push(args.join(' '));
};
func();
console.error = origFn;
if (errors.length) {
error = errors.join('\n');
console.error(error);
}
} else {
try {
func();
} catch (e) {
console.error(e); // eslint-disable-line
error = e;
}

}

if (config.noLint) {
return;
}

assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg);
}

// check if it throws it throws with x
export function assertIfThrowsItThrowsWith(func, expectations, msg = '') {
let error = '';
let threw = false;
if (config.throwOnError === false) {
const origFn = console.error;
const errors = [];
console.error = function(...args) {
errors.push(args.join(' '));
};
func();
console.error = origFn;
if (errors.length) {
error = errors.join('\n');
console.error(error);
}
} else {
try {
func();
} catch (e) {
console.error(e); // eslint-disable-line
error = e;
threw = true;
}

}

if (config.noLint) {
return;
}

if (!threw) {
return;
}

assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg);
}

function assertStringMatchesREs(actual, expectations, msg) {
for (const expectation of expectations) {
if (expectation instanceof RegExp) {
if (!expectation.test(actual)) {
throw new Error(`${formatMsg(msg)}expected: ${expectation}, actual: ${actual}`);
}
}
}

}
export function assertWarnsWith(func, expectations, msg = '') {
const warnings = [];
const origWarnFn = console.warn;
console.warn = function(...args) {
origWarnFn.call(this, ...args);
warnings.push(args.join(' '));
};

let error;
try {
func();
} catch (e) {
error = e;
}

console.warn = origWarnFn;

if (error) {
throw error;
}

if (config.noLint) {
return;
}

assertStringMatchesREs(warnings.join(' '), expectations, msg);
}

export default {
false: assertFalsy,
equal: assertEqual,
matchesRegEx: assertStringMatchesRegEx,
notEqual: assertNotEqual,
throwsWith: assertThrowsWith,
true: assertTruthy,
};
74 changes: 74 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TWGL Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="mocha.css">
<style>
#mocha #other {
text-decoration: underline;
}
</style>
</head>
<body>
<div id="mocha">
</div>
<script>
// this is here for puppeteer. It's resolved in index.js
// so we can await on window.testPromiseInfo
function makePromise() {
const info = {};
const promise = new Promise((resolve, reject) => {
Object.assign(info, {resolve, reject});
});
info.promise = promise;
return info;
}

window.testsPromiseInfo = makePromise();
</script>
<script src="mocha.js"></script>
<script type="module">
/* global document */
/* global mocha */
/* global URLSearchParams */
/* global window */
import {setConfig} from './assert.js';

async function main() {
mocha.setup('bdd');
mocha.fullTrace();
mocha.timeout(0);
const query = Object.fromEntries(new URLSearchParams(window.location.search).entries());
if (query.timeout !== undefined) {
mocha.timeout(query.timeout);
}
const lint = query.lint !== 'false';
const throwOnError = !query.warn;

const src = query.src ? '../src/twgl-full.js' : '../dist/4.x/twgl-full.module.js';

try {
window.twgl = await import(src);
loadScript('index.js', 'module');
} catch (e) {
console.err(e);
}
}

function loadScript(url, type = 'text/javascript') {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.type = type;
script.src = url;
document.head.appendChild(script);
});
}

main();
</script>
</body>
</html>
11 changes: 11 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* global mocha */

import './tests/program-tests.js';

const settings = Object.fromEntries(new URLSearchParams(window.location.search).entries());
if (settings.reporter) {
mocha.reporter(settings.reporter);
}
mocha.run((failures) => {
window.testsPromiseInfo.resolve(failures);
});
7 changes: 7 additions & 0 deletions test/mocha-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const describe = window.describe;
export const it = window.it;
export const before = window.before;
export const after = window.after;
export const beforeEach = window.beforeEach;
export const afterEach = window.afterEach;

Loading

0 comments on commit cfabc65

Please sign in to comment.