diff --git a/packages/vector-pools/src/api.ts b/packages/vector-pools/src/api.ts index f4b97f16a3..433565bb4b 100644 --- a/packages/vector-pools/src/api.ts +++ b/packages/vector-pools/src/api.ts @@ -1,22 +1,27 @@ import { IRelease, TypedArray } from "@thi.ng/api/api"; -import { Type } from "@thi.ng/malloc/api"; +import { Type, MemPoolOpts } from "@thi.ng/malloc/api"; import { StridedVec, Vec, ReadonlyVec } from "@thi.ng/vectors3/api"; export interface AttribSpec { - type: Type; + type: GLType | Type; size: number; - default: number | ReadonlyVec; + default?: number | ReadonlyVec; byteOffset: number; stride?: number; } +export interface AttribPoolOpts { + resizable: boolean; + mempool: MemPoolOpts; +} + export interface IVecPool extends IRelease { - malloc(size: number, type?: Type): TypedArray; + malloc(size: number, type?: GLType | Type): TypedArray; - mallocWrapped(size: number, stride?: number, type?: Type): StridedVec; + mallocWrapped(size: number, stride?: number, type?: GLType | Type): StridedVec; - mallocArray(num: number, size: number, cstride?: number, estride?: number, type?: Type): StridedVec[]; + mallocArray(num: number, size: number, cstride?: number, estride?: number, type?: GLType | Type): StridedVec[]; free(vec: StridedVec | TypedArray): boolean; @@ -26,4 +31,48 @@ export interface IVecPool extends IRelease { export type VecFactory = (buf: Vec, size: number, index: number, stride: number) => StridedVec; -export { Type }; +/** + * WebGL numeric type constants. These can be used by classes in this + * package as aliases for thi.ng/malloc's `Type` enum (see `GL2TYPE` LUT + * below), but also then used directly when initializing WebGL buffers + * from given attribute buffer specs. + * + * See `AttribPool` & readme examples for more details. + */ +export const enum GLType { + I8 = 0x1400, + U8 = 0x1401, + I16 = 0x1402, + U16 = 0x1403, + I32 = 0x1404, + U32 = 0x1405, + F32 = 0x1406, +}; + +/** + * Conversion from `GLType` to `Type`. + */ +export const GL2TYPE = { + [GLType.I8]: Type.I8, + [GLType.U8]: Type.U8, + [GLType.I16]: Type.I16, + [GLType.U16]: Type.U16, + [GLType.I32]: Type.I32, + [GLType.I32]: Type.I32, + [GLType.U32]: Type.U32, + [GLType.F32]: Type.F32, +}; + +/** + * Conversion from `Type` to `GLType`. + */ +export const TYPE2GL = { + [Type.I8]: GLType.I8, + [Type.U8]: GLType.U8, + [Type.I16]: GLType.I16, + [Type.U16]: GLType.U16, + [Type.I32]: GLType.I32, + [Type.I32]: GLType.I32, + [Type.U32]: GLType.U32, + [Type.F32]: GLType.F32, +}; diff --git a/packages/vector-pools/src/attrib-pool.ts b/packages/vector-pools/src/attrib-pool.ts index 7c79a2abf6..228d3cd090 100644 --- a/packages/vector-pools/src/attrib-pool.ts +++ b/packages/vector-pools/src/attrib-pool.ts @@ -2,12 +2,16 @@ import { IObjectOf, IRelease, TypedArray } from "@thi.ng/api/api"; import { assert } from "@thi.ng/api/assert"; import { align } from "@thi.ng/binary/align"; import { Pow2 } from "@thi.ng/binary/api"; -import { SIZEOF, MemPoolOpts } from "@thi.ng/malloc/api"; +import { SIZEOF } from "@thi.ng/malloc/api"; import { MemPool } from "@thi.ng/malloc/pool"; import { wrap } from "@thi.ng/malloc/wrap"; import { range } from "@thi.ng/transducers/iter/range"; import { ReadonlyVec, Vec } from "@thi.ng/vectors3/api"; -import { AttribSpec } from "./api"; +import { asNativeType } from "./convert"; +import { + AttribPoolOpts, + AttribSpec, +} from "./api"; /* * WASM mem : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... @@ -24,6 +28,7 @@ export class AttribPool implements attribs: IObjectOf; order: string[]; specs: IObjectOf; + opts: AttribPoolOpts; pool: MemPool; addr: number; capacity: number; @@ -35,11 +40,16 @@ export class AttribPool implements pool: number | ArrayBuffer | MemPool, capacity: number, specs: IObjectOf, - poolOpts?: MemPoolOpts + opts?: AttribPoolOpts ) { + this.opts = { + resizable: true, + ...opts + }; this.pool = !(pool instanceof MemPool) ? - new MemPool(pool, poolOpts) : + new MemPool(pool, this.opts.mempool) : pool; + this.opts = opts; this.capacity = capacity; this.specs = {}; this.attribs = {}; @@ -49,6 +59,10 @@ export class AttribPool implements this.initAttribs(specs, true); } + bytes() { + return new Uint8Array(this.pool.buf, this.addr, this.capacity * this.byteStride); + } + release(releasePool = true) { if (releasePool && this.pool) { this.pool.release(); @@ -75,11 +89,7 @@ export class AttribPool implements this.initDefaults(specs); } - getAttrib(id: string) { - return this.attribs[id]; - } - - getAttribVal(id: string, i: number): number | Vec { + attribValue(id: string, i: number): number | Vec { if (i >= this.capacity) return; const spec = this.specs[id]; assert(!!spec, `invalid attrib: ${id}`); @@ -106,7 +116,7 @@ export class AttribPool implements } } - setAttribVal(id: string, index: number, v: number | ReadonlyVec) { + setAttribValue(id: string, index: number, v: number | ReadonlyVec) { const spec = this.specs[id]; assert(!!spec, `invalid attrib: ${id}`); this.ensure(index); @@ -122,13 +132,41 @@ export class AttribPool implements (v).length <= spec.size, `wrong attrib val size, expected ${spec.size}, got ${(v).length}` ); - buf.set(v, index); + buf.set(v, index); } else { buf[index] = v; } return this; } + setAttribValues(id: string, vals: (number | ReadonlyVec)[]) { + const spec = this.specs[id]; + assert(!!spec, `invalid attrib: ${id}`); + const n = vals.length; + const v = vals[0]; + const stride = spec.stride; + this.ensure(n); + const buf = this.attribs[id]; + const isNum = typeof v === "number"; + assert( + () => (!isNum && spec.size > 1) || (isNum && spec.size === 1), + `incompatible value(s) for attrib: ${id}` + ); + if (!isNum) { + assert( + (v).length <= spec.size, + `wrong attrib val size, expected ${spec.size}, got ${(v).length}` + ); + for (let i = 0; i < n; i++) { + buf.set(vals[i], i * stride); + } + } else { + for (let i = 0; i < n; i++) { + buf[i * stride] = vals[i]; + } + } + } + removeAttrib(id: string) { if (!this.attribs[id]) return false; delete this.attribs[id]; @@ -140,14 +178,15 @@ export class AttribPool implements } ensure(newCapacity: number, fill = false) { - if (newCapacity < this.capacity) return; + if (newCapacity <= this.capacity) return; + assert(this.opts.resizable, `pool resizing disabled`); // TODO add realloc() const newAddr = this.pool.malloc(newCapacity * this.byteStride); assert(newAddr > 0, `out of memory`); for (let id in this.specs) { const a = this.specs[id]; const buf = wrap( - a.type, + asNativeType(a.type), this.pool.buf, newAddr + (a.byteOffset || 0), (newCapacity - 1) * a.stride + a.size @@ -156,7 +195,6 @@ export class AttribPool implements this.attribs[id] = buf; } if (fill) { - // TODO fill remainder with default values? this.setDefaults(this.specs, this.capacity, newCapacity); } this.pool.free(this.addr); @@ -169,7 +207,7 @@ export class AttribPool implements let maxSize = inclExisting ? this.maxAttribSize : 1; for (let id in specs) { const a = specs[id]; - const size = SIZEOF[a.type]; + const size = SIZEOF[asNativeType(a.type)]; maxSize = Math.max(maxSize, size); maxStride = Math.max(maxStride, a.byteOffset + a.size * size); } @@ -180,7 +218,7 @@ export class AttribPool implements for (let id in specs) { assert(!this.attribs[id], `attrib: ${id} already exists`); const a = specs[id]; - const size = SIZEOF[a.type]; + const size = SIZEOF[asNativeType(a.type)]; const isNum = typeof a.default === "number"; assert( () => (!isNum && a.size === (a.default).length) || (isNum && a.size === 1), @@ -197,14 +235,16 @@ export class AttribPool implements } protected updateOrder() { - this.order = Object.keys(this.specs).sort((a, b) => this.specs[a].byteOffset - this.specs[b].byteOffset); + this.order = Object.keys(this.specs).sort( + (a, b) => this.specs[a].byteOffset - this.specs[b].byteOffset + ); } protected initDefaults(specs: IObjectOf, start = 0, end = this.capacity) { for (let id in specs) { const a = specs[id]; this.attribs[id] = wrap( - a.type, + asNativeType(a.type), this.pool.buf, this.addr + (a.byteOffset || 0), (this.capacity - 1) * a.stride + a.size @@ -215,8 +255,9 @@ export class AttribPool implements protected setDefaults(specs: IObjectOf, start = 0, end = this.capacity) { for (let id in specs) { - const buf = this.attribs[id]; const a = specs[id]; + if (a.default == null) continue; + const buf = this.attribs[id]; const s = a.stride; const v = a.default; if (typeof v === "number") { @@ -225,7 +266,7 @@ export class AttribPool implements } } else { for (let i = start; i < end; i++) { - buf.set(v, i * s); + buf.set(v, i * s); } } } @@ -237,9 +278,12 @@ export class AttribPool implements const grow = newByteStride > this.byteStride; let newAddr = this.addr; if (grow) { + assert(this.opts.resizable, `pool resizing disabled`); // TODO realloc newAddr = this.pool.malloc(this.capacity * newByteStride); assert(newAddr > 0, `out of memory`); + } else if (!this.opts.resizable) { + return; } const sameBlock = newAddr === this.addr; const num = this.capacity - 1; @@ -250,10 +294,11 @@ export class AttribPool implements // create resized attrib views (in old or new address space) for (let id in specs) { const a = specs[id]; - const dStride = newByteStride / SIZEOF[a.type]; + const type = asNativeType(a.type); + const dStride = newByteStride / SIZEOF[type]; newAttribs[id] = [ wrap( - a.type, + type, this.pool.buf, newAddr + a.byteOffset, num * dStride + a.size diff --git a/packages/vector-pools/src/convert.ts b/packages/vector-pools/src/convert.ts new file mode 100644 index 0000000000..1017824142 --- /dev/null +++ b/packages/vector-pools/src/convert.ts @@ -0,0 +1,23 @@ +import { Type } from "@thi.ng/malloc/api"; +import { GL2TYPE, GLType, TYPE2GL } from "./api"; + +/** + * Returns canonical `Type` value of `type` by first attempting to + * resolve it as `GLType` enum. + * + * ``` + * nativeType(GLType.F32) => Type.F32 + * nativeType(Type.F32) => Type.F32 + * ``` + * + * @param type + */ +export const asNativeType = (type: GLType | Type): Type => { + const t = GL2TYPE[type]; + return t !== undefined ? t : type; +}; + +export const asGLType = (type: GLType | Type): GLType => { + const t = TYPE2GL[type]; + return t !== undefined ? t : type; +}; diff --git a/packages/vector-pools/src/index.ts b/packages/vector-pools/src/index.ts index 92c8f045e9..2a262e8c89 100644 --- a/packages/vector-pools/src/index.ts +++ b/packages/vector-pools/src/index.ts @@ -3,5 +3,5 @@ export * from "./alist"; export * from "./array-list"; export * from "./attrib-pool"; export * from "./linked-list"; -export * from "./pool"; +export * from "./vec-pool"; export * from "./wrap"; diff --git a/packages/vector-pools/src/pool.ts b/packages/vector-pools/src/vec-pool.ts similarity index 75% rename from packages/vector-pools/src/pool.ts rename to packages/vector-pools/src/vec-pool.ts index 7a4b623be8..8d5199f61f 100644 --- a/packages/vector-pools/src/pool.ts +++ b/packages/vector-pools/src/vec-pool.ts @@ -7,7 +7,8 @@ import { Type } from "@thi.ng/malloc"; import { IVector } from "@thi.ng/vectors3/api"; -import { IVecPool } from "./api"; +import { GLType, IVecPool } from "./api"; +import { asNativeType } from "./convert"; import { wrap } from "./wrap"; export class VecPool implements @@ -27,12 +28,16 @@ export class VecPool implements return this.pool.stats(); } - malloc(size: number, type: Type = Type.F32): TypedArray { - return this.pool.callocAs(type, size); + malloc(size: number, type: GLType | Type = Type.F32): TypedArray { + return this.pool.callocAs(asNativeType(type), size); } - mallocWrapped(size: number, stride = 1, type: Type = Type.F32): IVector { - const buf = this.pool.callocAs(type, size * stride); + mallocWrapped( + size: number, + stride = 1, + type: GLType | Type = Type.F32 + ): IVector { + const buf = this.pool.callocAs(asNativeType(type), size * stride); return wrap(buf, size, 0, stride); } @@ -61,8 +66,17 @@ export class VecPool implements * @param estride * @param type */ - mallocArray(num: number, size: number, cstride = 1, estride = size, type: Type = Type.F32): IVector[] { - const buf = this.malloc(Math.max(cstride, estride, size) * num, type); + mallocArray( + num: number, + size: number, + cstride = 1, + estride = size, + type: GLType | Type = Type.F32 + ): IVector[] { + const buf = this.malloc( + Math.max(cstride, estride, size) * num, + asNativeType(type) + ); if (!buf) return; const res: IVector[] = []; for (let i = 0; i < num; i += estride) {