@bigmistqke/view.gl 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tag.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tag.ts"],"sourcesContent":["import { toID, view } from '.'\nimport { createProgram } from './gl'\nimport type { Prettify } from './type-utils'\nimport type {\n AttributeDefinition,\n AttributeKind,\n AttributeTokenFn as AttributeTagMethod,\n AttributeToken,\n CompileResult,\n GL,\n GLSL,\n GLSLSlot,\n GLSLToSchema,\n InterleaveToken,\n UniformDefinition,\n UniformKind,\n UniformTokenFn as UniformTagMethod,\n ViewSchema,\n ViewSchemaPartial,\n} from './types'\nimport { createUpsertMap, once } from './utils'\n\nexport function glsl<TSlot extends GLSLSlot, const TSlots extends TSlot[]>(\n template: TemplateStringsArray,\n ...slots: [...TSlots]\n): GLSL<TSlots> {\n return { template, slots, type: 'glsl' }\n}\n\nexport const uniform = new Proxy(\n {} as {\n [TKey in UniformKind]: UniformTagMethod<TKey>\n },\n {\n get(target, property) {\n // @ts-expect-error\n if (typeof property === 'symbol') return target[property]\n return (key: string, options?: Omit<UniformDefinition, 'kind'>) => ({\n type: 'uniform',\n key,\n kind: property,\n ...options,\n })\n },\n },\n)\n\nexport const attribute = new Proxy(\n {} as {\n [TKey in AttributeKind]: AttributeTagMethod<TKey>\n },\n {\n get(target, property) {\n // @ts-expect-error\n if (typeof property === 'symbol') return target[property]\n return (key: string, options?: Omit<AttributeDefinition, 'kind'>) => ({\n type: 'attribute',\n key,\n kind: property,\n ...options,\n })\n },\n },\n)\n\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n const TOptions extends Omit<AttributeDefinition, 'kind'>,\n>(key: TKey, layout: TLayout, instanced: TOptions): InterleaveToken<TKey, TLayout, TOptions>\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n>(key: TKey, layout: TLayout): InterleaveToken<TKey, TLayout, { instanced: false }>\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n const TOptions extends Omit<AttributeDefinition, 'kind'>,\n>(key: TKey, layout: TLayout, { instanced, buffer }: Partial<TOptions> = {}) {\n return {\n type: 'interleavedAttribute',\n key,\n // remove instanced- and type-property\n layout: layout.map(({ key, kind }) => ({\n key,\n kind,\n })),\n instanced: !!instanced,\n buffer,\n } as unknown as InterleaveToken<TKey, TLayout, TOptions>\n}\n\n/**********************************************************************************/\n/* */\n/* Compile */\n/* */\n/**********************************************************************************/\n\nexport interface CompileOptions<TSchema extends ViewSchemaPartial> {\n schema?: TSchema\n webgl2?: boolean\n /**\n * Disposes what `compile` made: the program, and the buffers the view created\n * for itself. Buffers named by the schema belong to whoever made them.\n */\n signal?: AbortSignal\n}\n\nexport function compile<\n TVertex extends GLSL,\n TFragment extends GLSL,\n TSchema extends ViewSchemaPartial,\n>(\n gl: WebGLRenderingContext,\n vertex: TVertex,\n fragment: TFragment,\n options?: CompileOptions<TSchema> | undefined,\n) {\n const _vertex = resolveGLSLTag(vertex, options) as { template: string; schema: ViewSchema }\n const _fragment = resolveGLSLTag(fragment, options) as { template: string; schema: ViewSchema }\n\n const schema = {\n uniforms: {\n ..._vertex.schema.uniforms,\n ..._fragment.schema.uniforms,\n },\n attributes: {\n ..._vertex.schema.attributes,\n ..._fragment.schema.attributes,\n },\n interleavedAttributes: {\n ..._vertex.schema.interleavedAttributes,\n ..._fragment.schema.interleavedAttributes,\n },\n // Never produced by a tag — a buffer has no name in the source to be\n // parsed out of one — but `options.schema` may name buffers for the view to\n // own, and the deep merge below indexes this object by every key it finds\n // there. Missing, that threw.\n buffers: {} as NonNullable<ViewSchema['buffers']>,\n }\n\n // Deep merge options.schema with generated schema\n for (const kind in options?.schema) {\n const schemaKind = schema[kind as keyof typeof schema]\n const configSchemaKind = options?.schema[kind]\n\n for (const key in configSchemaKind) {\n schemaKind[key] = {\n ...schemaKind[key],\n ...configSchemaKind[key],\n }\n }\n }\n\n try {\n const program = createProgram(gl, _vertex.template, _fragment.template)\n\n // The same rule the views follow: `compile` made this program, so `compile`\n // is what deletes it. A program handed in from outside would not be ours to\n // delete, and there is no way to hand one in.\n const resolvedView = view(gl, program, schema)\n const dispose = once(() => {\n resolvedView.dispose()\n gl.deleteProgram(program)\n })\n options?.signal?.addEventListener('abort', dispose)\n\n return {\n program,\n schema,\n view: resolvedView,\n dispose,\n vertex: _vertex.template,\n fragment: _fragment.template,\n } as unknown as Prettify<CompileResult<TVertex, TFragment, TSchema>>\n } catch (error) {\n console.error('Error while creating WebGLProgram - vertex\\n\\n', _vertex.template)\n console.error('Error while creating WebGLProgram - fragment\\n\\n', _fragment.template)\n\n throw error\n }\n}\n\nfunction resolveGLSLTag<TTag extends GLSL>(tag: TTag, options?: { webgl2?: boolean }) {\n return {\n template: compile.toString(tag, options),\n schema: compile.toSchema(tag),\n }\n}\n\ncompile.toSchema = function <TTag extends GLSL>(tag: TTag) {\n const result = {\n uniforms: {},\n attributes: {},\n interleavedAttributes: {},\n }\n\n tag.slots.forEach(function handleSlot(slot: GLSLSlot) {\n if (typeof slot !== 'object') {\n return\n }\n\n if (Array.isArray(slot)) {\n slot.forEach(handleSlot)\n return\n }\n\n if (slot.type === 'glsl') {\n const { uniforms, attributes, interleavedAttributes } = compile.toSchema(slot)\n\n result.uniforms = {\n ...result.uniforms,\n ...uniforms,\n }\n result.attributes = {\n ...result.attributes,\n ...attributes,\n }\n result.interleavedAttributes = {\n ...result.interleavedAttributes,\n ...interleavedAttributes,\n }\n\n return\n }\n\n const { key: name, type, ...rest } = slot\n\n // @ts-expect-error\n result[`${type}s`][name] = rest\n })\n\n return result as unknown as Prettify<GLSLToSchema<TTag>>\n}\n\ncompile.toString = function <TTag extends GLSL>(\n { template: [initial, ...rest], slots }: TTag,\n config?: { webgl2?: boolean },\n) {\n const v300 = config?.webgl2 ?? !!initial?.startsWith('#version 300 es')\n\n let template = initial ?? ''\n\n for (let i = 0; i < rest.length; i++) {\n template += `${resolveGlslSlotToString(slots[i]!, v300)}${rest[i]}`\n }\n\n return template\n}\n\nfunction resolveGlslSlotToString(slot: GLSLSlot, v300: boolean): string {\n if (typeof slot !== 'object') {\n return toID(slot)\n }\n\n if (Array.isArray(slot)) {\n return slot.map(slot => resolveGlslSlotToString(slot, v300)).join('')\n }\n\n switch (slot.type) {\n case 'glsl':\n return compile.toString(slot)\n case 'interleavedAttribute':\n return slot.layout.reduce(\n (a, v) =>\n v300 ? `${a}in ${v.kind} ${toID(v.key)};\\n` : `${a}attribute ${v.kind} ${toID(v.key)};\\n`,\n '',\n )\n case 'uniform':\n if ('size' in slot) {\n return `${slot.type} ${slot.kind} ${toID(slot.key)}[${slot.size}];`\n }\n return `${slot.type} ${slot.kind} ${toID(slot.key)};`\n }\n\n if (slot.type === 'attribute') {\n return `${v300 ? 'in' : slot.type} ${slot.kind} ${toID(slot.key)};`\n }\n\n throw new Error(`Unexpected slot: ${JSON.stringify(slot)}`)\n}\n\nconst QUAD_FLOAT_ARRAY = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])\nconst QUAD_BUFFER_MAP = createUpsertMap(WeakMap<GL, WebGLBuffer>)\n\ncompile.toQuad = function <TFragment extends GLSL, TSchema extends ViewSchemaPartial>(\n gl: GL,\n fragment: TFragment,\n options?: CompileOptions<TSchema>,\n) {\n const buffer = QUAD_BUFFER_MAP.getOrInsert(gl, gl.createBuffer.bind(gl))\n\n const webgl2 = options?.webgl2 ?? fragment.template[0]?.startsWith('#version 300 es')\n\n const vertex = webgl2\n ? glsl`#version 300 es\nprecision mediump float;\n\n${attribute.vec2('a_quad', { buffer })}\n\nout vec2 v_uv;\n\nvoid main() {\n v_uv = a_quad;\n gl_Position = vec4(v_uv, 0.0, 1.0);\n}`\n : glsl`precision mediump float;\n\n${attribute.vec2('a_quad', { buffer })}\n\nvarying vec2 v_uv;\n\nvoid main() {\n v_uv = a_quad;\n gl_Position = vec4(v_uv, 0.0, 1.0);\n}`\n\n const result = compile(gl, vertex, fragment, options)\n\n // @ts-expect-error\n result.view.attributes.a_quad.set(QUAD_FLOAT_ARRAY)\n\n return result\n}\n"],"mappings":"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","names":["key","slot"]}
1
+ {"version":3,"sources":["../src/tag.ts"],"sourcesContent":["import { toID, view } from '.'\nimport { createProgram } from './gl'\nimport type { Prettify } from './type-utils'\nimport type {\n AttributeDefinition,\n AttributeKind,\n AttributeTokenFn as AttributeTagMethod,\n AttributeToken,\n CompileResult,\n GL,\n GLSL,\n GLSLSlot,\n GLSLToSchema,\n InterleaveToken,\n UniformDefinition,\n UniformKind,\n UniformTokenFn as UniformTagMethod,\n ViewSchema,\n ViewSchemaPartial,\n} from './types'\nimport { once } from './utils'\n\nexport function glsl<TSlot extends GLSLSlot, const TSlots extends TSlot[]>(\n template: TemplateStringsArray,\n ...slots: [...TSlots]\n): GLSL<TSlots> {\n return { template, slots, type: 'glsl' }\n}\n\nexport const uniform = new Proxy(\n {} as {\n [TKey in UniformKind]: UniformTagMethod<TKey>\n },\n {\n get(target, property) {\n // @ts-expect-error\n if (typeof property === 'symbol') return target[property]\n return (key: string, options?: Omit<UniformDefinition, 'kind'>) => ({\n type: 'uniform',\n key,\n kind: property,\n ...options,\n })\n },\n },\n)\n\nexport const attribute = new Proxy(\n {} as {\n [TKey in AttributeKind]: AttributeTagMethod<TKey>\n },\n {\n get(target, property) {\n // @ts-expect-error\n if (typeof property === 'symbol') return target[property]\n return (key: string, options?: Omit<AttributeDefinition, 'kind'>) => ({\n type: 'attribute',\n key,\n kind: property,\n ...options,\n })\n },\n },\n)\n\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n const TOptions extends Omit<AttributeDefinition, 'kind'>,\n>(key: TKey, layout: TLayout, instanced: TOptions): InterleaveToken<TKey, TLayout, TOptions>\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n>(key: TKey, layout: TLayout): InterleaveToken<TKey, TLayout, { instanced: false }>\nexport function interleave<\n const TKey extends string | symbol,\n const TLayout extends Array<AttributeToken<string | symbol, AttributeKind, undefined>>,\n const TOptions extends Omit<AttributeDefinition, 'kind'>,\n>(key: TKey, layout: TLayout, { instanced, buffer }: Partial<TOptions> = {}) {\n return {\n type: 'interleavedAttribute',\n key,\n // remove instanced- and type-property\n layout: layout.map(({ key, kind }) => ({\n key,\n kind,\n })),\n instanced: !!instanced,\n buffer,\n } as unknown as InterleaveToken<TKey, TLayout, TOptions>\n}\n\n/**********************************************************************************/\n/* */\n/* Compile */\n/* */\n/**********************************************************************************/\n\nexport interface CompileOptions<TSchema extends ViewSchemaPartial> {\n schema?: TSchema\n webgl2?: boolean\n /**\n * Disposes what `compile` made: the program, and the buffers the view created\n * for itself. Buffers named by the schema belong to whoever made them.\n */\n signal?: AbortSignal\n}\n\nexport function compile<\n TVertex extends GLSL,\n TFragment extends GLSL,\n TSchema extends ViewSchemaPartial,\n>(\n gl: WebGLRenderingContext,\n vertex: TVertex,\n fragment: TFragment,\n options?: CompileOptions<TSchema> | undefined,\n) {\n const _vertex = resolveGLSLTag(vertex, options) as { template: string; schema: ViewSchema }\n const _fragment = resolveGLSLTag(fragment, options) as { template: string; schema: ViewSchema }\n\n const schema = {\n uniforms: {\n ..._vertex.schema.uniforms,\n ..._fragment.schema.uniforms,\n },\n attributes: {\n ..._vertex.schema.attributes,\n ..._fragment.schema.attributes,\n },\n interleavedAttributes: {\n ..._vertex.schema.interleavedAttributes,\n ..._fragment.schema.interleavedAttributes,\n },\n // Never produced by a tag — a buffer has no name in the source to be\n // parsed out of one — but `options.schema` may name buffers for the view to\n // own, and the deep merge below indexes this object by every key it finds\n // there. Missing, that threw.\n buffers: {} as NonNullable<ViewSchema['buffers']>,\n }\n\n // Deep merge options.schema with generated schema\n for (const kind in options?.schema) {\n const schemaKind = schema[kind as keyof typeof schema]\n const configSchemaKind = options?.schema[kind]\n\n for (const key in configSchemaKind) {\n schemaKind[key] = {\n ...schemaKind[key],\n ...configSchemaKind[key],\n }\n }\n }\n\n try {\n const program = createProgram(gl, _vertex.template, _fragment.template)\n\n // The same rule the views follow: `compile` made this program, so `compile`\n // is what deletes it. A program handed in from outside would not be ours to\n // delete, and there is no way to hand one in.\n const resolvedView = view(gl, program, schema)\n const dispose = once(() => {\n resolvedView.dispose()\n gl.deleteProgram(program)\n })\n options?.signal?.addEventListener('abort', dispose)\n\n return {\n program,\n schema,\n view: resolvedView,\n dispose,\n vertex: _vertex.template,\n fragment: _fragment.template,\n } as unknown as Prettify<CompileResult<TVertex, TFragment, TSchema>>\n } catch (error) {\n console.error('Error while creating WebGLProgram - vertex\\n\\n', _vertex.template)\n console.error('Error while creating WebGLProgram - fragment\\n\\n', _fragment.template)\n\n throw error\n }\n}\n\nfunction resolveGLSLTag<TTag extends GLSL>(tag: TTag, options?: { webgl2?: boolean }) {\n return {\n template: compile.toString(tag, options),\n schema: compile.toSchema(tag),\n }\n}\n\ncompile.toSchema = function <TTag extends GLSL>(tag: TTag) {\n const result = {\n uniforms: {},\n attributes: {},\n interleavedAttributes: {},\n }\n\n tag.slots.forEach(function handleSlot(slot: GLSLSlot) {\n if (typeof slot !== 'object') {\n return\n }\n\n if (Array.isArray(slot)) {\n slot.forEach(handleSlot)\n return\n }\n\n if (slot.type === 'glsl') {\n const { uniforms, attributes, interleavedAttributes } = compile.toSchema(slot)\n\n result.uniforms = {\n ...result.uniforms,\n ...uniforms,\n }\n result.attributes = {\n ...result.attributes,\n ...attributes,\n }\n result.interleavedAttributes = {\n ...result.interleavedAttributes,\n ...interleavedAttributes,\n }\n\n return\n }\n\n const { key: name, type, ...rest } = slot\n\n // @ts-expect-error\n result[`${type}s`][name] = rest\n })\n\n return result as unknown as Prettify<GLSLToSchema<TTag>>\n}\n\ncompile.toString = function <TTag extends GLSL>(\n { template: [initial, ...rest], slots }: TTag,\n config?: { webgl2?: boolean },\n) {\n const v300 = config?.webgl2 ?? !!initial?.startsWith('#version 300 es')\n\n let template = initial ?? ''\n\n for (let i = 0; i < rest.length; i++) {\n template += `${resolveGlslSlotToString(slots[i]!, v300)}${rest[i]}`\n }\n\n return template\n}\n\nfunction resolveGlslSlotToString(slot: GLSLSlot, v300: boolean): string {\n if (typeof slot !== 'object') {\n return toID(slot)\n }\n\n if (Array.isArray(slot)) {\n return slot.map(slot => resolveGlslSlotToString(slot, v300)).join('')\n }\n\n switch (slot.type) {\n case 'glsl':\n return compile.toString(slot)\n case 'interleavedAttribute':\n return slot.layout.reduce(\n (a, v) =>\n v300 ? `${a}in ${v.kind} ${toID(v.key)};\\n` : `${a}attribute ${v.kind} ${toID(v.key)};\\n`,\n '',\n )\n case 'uniform':\n if ('size' in slot) {\n return `${slot.type} ${slot.kind} ${toID(slot.key)}[${slot.size}];`\n }\n return `${slot.type} ${slot.kind} ${toID(slot.key)};`\n }\n\n if (slot.type === 'attribute') {\n return `${v300 ? 'in' : slot.type} ${slot.kind} ${toID(slot.key)};`\n }\n\n throw new Error(`Unexpected slot: ${JSON.stringify(slot)}`)\n}\n\nconst QUAD_FLOAT_ARRAY = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])\n\ncompile.toQuad = function <TFragment extends GLSL, TSchema extends ViewSchemaPartial>(\n gl: GL,\n fragment: TFragment,\n options?: CompileOptions<TSchema>,\n) {\n const webgl2 = options?.webgl2 ?? fragment.template[0]?.startsWith('#version 300 es')\n\n const vertex = webgl2\n ? glsl`#version 300 es\nprecision mediump float;\n\n${attribute.vec2('a_quad')}\n\nout vec2 v_uv;\n\nvoid main() {\n v_uv = a_quad;\n gl_Position = vec4(v_uv, 0.0, 1.0);\n}`\n : glsl`precision mediump float;\n\n${attribute.vec2('a_quad')}\n\nvarying vec2 v_uv;\n\nvoid main() {\n v_uv = a_quad;\n gl_Position = vec4(v_uv, 0.0, 1.0);\n}`\n\n const result = compile(gl, vertex, fragment, options)\n\n // @ts-expect-error\n result.view.attributes.a_quad.set(QUAD_FLOAT_ARRAY)\n\n return result\n}\n"],"mappings":";;;;;;;;;;AAsBO,SAAS,KACd,aACG,OACW;AACd,SAAO,EAAE,UAAU,OAAO,MAAM,OAAO;AACzC;AAEO,IAAM,UAAU,IAAI;AAAA,EACzB,CAAC;AAAA,EAGD;AAAA,IACE,IAAI,QAAQ,UAAU;AAEpB,UAAI,OAAO,aAAa,SAAU,QAAO,OAAO,QAAQ;AACxD,aAAO,CAAC,KAAa,aAA+C;AAAA,QAClE,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,YAAY,IAAI;AAAA,EAC3B,CAAC;AAAA,EAGD;AAAA,IACE,IAAI,QAAQ,UAAU;AAEpB,UAAI,OAAO,aAAa,SAAU,QAAO,OAAO,QAAQ;AACxD,aAAO,CAAC,KAAa,aAAiD;AAAA,QACpE,MAAM;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AAWO,SAAS,WAId,KAAW,QAAiB,EAAE,WAAW,OAAO,IAAuB,CAAC,GAAG;AAC3E,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA;AAAA,IAEA,QAAQ,OAAO,IAAI,CAAC,EAAE,KAAAA,MAAK,KAAK,OAAO;AAAA,MACrC,KAAAA;AAAA,MACA;AAAA,IACF,EAAE;AAAA,IACF,WAAW,CAAC,CAAC;AAAA,IACb;AAAA,EACF;AACF;AAkBO,SAAS,QAKd,IACA,QACA,UACA,SACA;AACA,QAAM,UAAU,eAAe,QAAQ,OAAO;AAC9C,QAAM,YAAY,eAAe,UAAU,OAAO;AAElD,QAAM,SAAS;AAAA,IACb,UAAU;AAAA,MACR,GAAG,QAAQ,OAAO;AAAA,MAClB,GAAG,UAAU,OAAO;AAAA,IACtB;AAAA,IACA,YAAY;AAAA,MACV,GAAG,QAAQ,OAAO;AAAA,MAClB,GAAG,UAAU,OAAO;AAAA,IACtB;AAAA,IACA,uBAAuB;AAAA,MACrB,GAAG,QAAQ,OAAO;AAAA,MAClB,GAAG,UAAU,OAAO;AAAA,IACtB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,SAAS,CAAC;AAAA,EACZ;AAGA,aAAW,QAAQ,SAAS,QAAQ;AAClC,UAAM,aAAa,OAAO,IAA2B;AACrD,UAAM,mBAAmB,SAAS,OAAO,IAAI;AAE7C,eAAW,OAAO,kBAAkB;AAClC,iBAAW,GAAG,IAAI;AAAA,QAChB,GAAG,WAAW,GAAG;AAAA,QACjB,GAAG,iBAAiB,GAAG;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,cAAc,IAAI,QAAQ,UAAU,UAAU,QAAQ;AAKtE,UAAM,eAAe,KAAK,IAAI,SAAS,MAAM;AAC7C,UAAM,UAAU,KAAK,MAAM;AACzB,mBAAa,QAAQ;AACrB,SAAG,cAAc,OAAO;AAAA,IAC1B,CAAC;AACD,aAAS,QAAQ,iBAAiB,SAAS,OAAO;AAElD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,UAAU,UAAU;AAAA,IACtB;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,kDAAkD,QAAQ,QAAQ;AAChF,YAAQ,MAAM,oDAAoD,UAAU,QAAQ;AAEpF,UAAM;AAAA,EACR;AACF;AAEA,SAAS,eAAkC,KAAW,SAAgC;AACpF,SAAO;AAAA,IACL,UAAU,QAAQ,SAAS,KAAK,OAAO;AAAA,IACvC,QAAQ,QAAQ,SAAS,GAAG;AAAA,EAC9B;AACF;AAEA,QAAQ,WAAW,SAA6B,KAAW;AACzD,QAAM,SAAS;AAAA,IACb,UAAU,CAAC;AAAA,IACX,YAAY,CAAC;AAAA,IACb,uBAAuB,CAAC;AAAA,EAC1B;AAEA,MAAI,MAAM,QAAQ,SAAS,WAAW,MAAgB;AACpD,QAAI,OAAO,SAAS,UAAU;AAC5B;AAAA,IACF;AAEA,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAK,QAAQ,UAAU;AACvB;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,EAAE,UAAU,YAAY,sBAAsB,IAAI,QAAQ,SAAS,IAAI;AAE7E,aAAO,WAAW;AAAA,QAChB,GAAG,OAAO;AAAA,QACV,GAAG;AAAA,MACL;AACA,aAAO,aAAa;AAAA,QAClB,GAAG,OAAO;AAAA,QACV,GAAG;AAAA,MACL;AACA,aAAO,wBAAwB;AAAA,QAC7B,GAAG,OAAO;AAAA,QACV,GAAG;AAAA,MACL;AAEA;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,MAAM,MAAM,GAAG,KAAK,IAAI;AAGrC,WAAO,GAAG,IAAI,GAAG,EAAE,IAAI,IAAI;AAAA,EAC7B,CAAC;AAED,SAAO;AACT;AAEA,QAAQ,WAAW,SACjB,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,GAAG,MAAM,GACtC,QACA;AACA,QAAM,OAAO,QAAQ,UAAU,CAAC,CAAC,SAAS,WAAW,iBAAiB;AAEtE,MAAI,WAAW,WAAW;AAE1B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,gBAAY,GAAG,wBAAwB,MAAM,CAAC,GAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;AAAA,EACnE;AAEA,SAAO;AACT;AAEA,SAAS,wBAAwB,MAAgB,MAAuB;AACtE,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,KAAK,IAAI;AAAA,EAClB;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,CAAAC,UAAQ,wBAAwBA,OAAM,IAAI,CAAC,EAAE,KAAK,EAAE;AAAA,EACtE;AAEA,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,QAAQ,SAAS,IAAI;AAAA,IAC9B,KAAK;AACH,aAAO,KAAK,OAAO;AAAA,QACjB,CAAC,GAAG,MACF,OAAO,GAAG,CAAC,MAAM,EAAE,IAAI,IAAI,KAAK,EAAE,GAAG,CAAC;AAAA,IAAQ,GAAG,CAAC,aAAa,EAAE,IAAI,IAAI,KAAK,EAAE,GAAG,CAAC;AAAA;AAAA,QACtF;AAAA,MACF;AAAA,IACF,KAAK;AACH,UAAI,UAAU,MAAM;AAClB,eAAO,GAAG,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI;AAAA,MACjE;AACA,aAAO,GAAG,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,GAAG,CAAC;AAAA,EACtD;AAEA,MAAI,KAAK,SAAS,aAAa;AAC7B,WAAO,GAAG,OAAO,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,GAAG,CAAC;AAAA,EAClE;AAEA,QAAM,IAAI,MAAM,oBAAoB,KAAK,UAAU,IAAI,CAAC,EAAE;AAC5D;AAEA,IAAM,mBAAmB,IAAI,aAAa,CAAC,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC;AAEpF,QAAQ,SAAS,SACf,IACA,UACA,SACA;AACA,QAAM,SAAS,SAAS,UAAU,SAAS,SAAS,CAAC,GAAG,WAAW,iBAAiB;AAEpF,QAAM,SAAS,SACX;AAAA;AAAA;AAAA,EAGJ,UAAU,KAAK,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQpB;AAAA;AAAA,EAEJ,UAAU,KAAK,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASxB,QAAM,SAAS,QAAQ,IAAI,QAAQ,UAAU,OAAO;AAGpD,SAAO,KAAK,WAAW,OAAO,IAAI,gBAAgB;AAElD,SAAO;AACT;","names":["key","slot"]}
@@ -1,9 +1,6 @@
1
1
  type Prettify<T> = {
2
2
  [K in keyof T]: T[K];
3
3
  } & {};
4
- type RemoveSuffix<T, S extends string> = {
5
- [K in keyof T as K extends `${infer Prefix}${S}` ? Prefix : K]: T[K];
6
- };
7
4
  type DeepMerge<A extends readonly [...any]> = A extends [infer L, ...infer R] ? DeepMergeTwo<L, DeepMerge<R>> : unknown;
8
5
  type DeepMergeTwo<TBase, TOverride> = {
9
6
  [K in keyof TBase | keyof TOverride]: K extends keyof TOverride ? K extends keyof TBase ? ShallowMerge<[TBase[K], TOverride[K]]> : TOverride[K] : K extends keyof TBase ? TBase[K] : never;
@@ -450,10 +447,9 @@ interface ViewOptions {
450
447
  *
451
448
  * The other two lifetimes have their own spellings, and neither is this one. A
452
449
  * buffer that outlives the view, or is shared between views, is a standalone
453
- * `bufferView` — `compile.toQuad`'s per-context quad, a buffer handed from one
454
- * program's view to another's. A buffer that belongs to somebody else is NAMED
455
- * by an attribute schema, and is borrowed: the view uses it and never deletes
456
- * it.
450
+ * `bufferView` — a buffer handed from one program's view to another's. A
451
+ * buffer that belongs to somebody else is NAMED by an attribute schema, and is
452
+ * borrowed: the view uses it and never deletes it.
457
453
  */
458
454
  interface ViewSchema {
459
455
  uniforms?: UniformSchema;
@@ -596,4 +592,4 @@ type CompileResult<TVertex extends GLSL, TFragment extends GLSL, TOverrideSchema
596
592
  dispose(): void;
597
593
  } : never;
598
594
 
599
- export type { TextureParameterName as $, AttributeSchema as A, BufferSchema as B, AttributeFormat as C, AttributeDefinition as D, AttributeMethods as E, FramebufferDefinition as F, GL as G, InterleavedAttributeLayout as H, InterleavedAttributeSchema as I, InterleavedAttributeDefinition as J, KIND_TO_UNIFORM_FN_NAME_MAP as K, BufferSource as L, ConstantAttributeMethods as M, ConstantSource as N, InterleavedAttributeMethods as O, BufferDefinition as P, BufferMethods as Q, RemoveSuffix as R, Source as S, TextureDefinition as T, UniformSchema as U, ViewOptions as V, Framebuffer as W, TextureTarget as X, TextureSchema as Y, TextureMethods as Z, TextureView as _, ViewSchema as a, TextureParameterValue as a0, TextureParameters as a1, InternalFormat as a2, Format as a3, PixelType as a4, TexImage2DOptions as a5, ViewSchemaPartial as a6, GLSLResult as a7, GLSLSlot as a8, GLSL as a9, UniformToken as aa, UniformTokenFn as ab, AttributeToken as ac, AttributeTokenFn as ad, InterleaveToken as ae, GLSLToSchema as af, GLSLToView as ag, GLSLSlotsToSchema as ah, FlattenSlots as ai, MergeGLSLSchema as aj, CompileResult as ak, Prettify as al, View as b, VertexArrayParticipant as c, VertexArrayMethods as d, UniformView as e, AttributeView as f, InterleavedAttributeView as g, BufferView as h, GLTarget as i, GLUsage as j, GLTextureTarget as k, GLTextureFormat as l, GLTextureType as m, GLTextureFilter as n, GLTextureWrap as o, KIND_TO_SIZE_MAP as p, UniformFnName as q, UniformKind as r, UniformKindMap as s, UniformDefinition as t, UniformElementMethods as u, UniformArrayMethods as v, UniformMethods as w, UniformValue as x, AttributeShortHand as y, AttributeKind as z };
595
+ export type { TextureParameterValue as $, AttributeSchema as A, BufferSchema as B, AttributeFormat as C, AttributeDefinition as D, AttributeMethods as E, FramebufferDefinition as F, GL as G, InterleavedAttributeLayout as H, InterleavedAttributeSchema as I, InterleavedAttributeDefinition as J, KIND_TO_UNIFORM_FN_NAME_MAP as K, BufferSource as L, ConstantAttributeMethods as M, ConstantSource as N, InterleavedAttributeMethods as O, BufferDefinition as P, BufferMethods as Q, Framebuffer as R, Source as S, TextureDefinition as T, UniformSchema as U, ViewOptions as V, TextureTarget as W, TextureSchema as X, TextureMethods as Y, TextureView as Z, TextureParameterName as _, ViewSchema as a, TextureParameters as a0, InternalFormat as a1, Format as a2, PixelType as a3, TexImage2DOptions as a4, ViewSchemaPartial as a5, GLSLResult as a6, GLSLSlot as a7, GLSL as a8, UniformToken as a9, UniformTokenFn as aa, AttributeToken as ab, AttributeTokenFn as ac, InterleaveToken as ad, GLSLToSchema as ae, GLSLToView as af, GLSLSlotsToSchema as ag, FlattenSlots as ah, MergeGLSLSchema as ai, CompileResult as aj, Prettify as ak, View as b, VertexArrayParticipant as c, VertexArrayMethods as d, UniformView as e, AttributeView as f, InterleavedAttributeView as g, BufferView as h, GLTarget as i, GLUsage as j, GLTextureTarget as k, GLTextureFormat as l, GLTextureType as m, GLTextureFilter as n, GLTextureWrap as o, KIND_TO_SIZE_MAP as p, UniformFnName as q, UniformKind as r, UniformKindMap as s, UniformDefinition as t, UniformElementMethods as u, UniformArrayMethods as v, UniformMethods as w, UniformValue as x, AttributeShortHand as y, AttributeKind as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigmistqke/view.gl",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "👀 Utilities for managing WebGL resources.",
5
5
  "exports": {
6
6
  ".": {
@@ -16,26 +16,11 @@
16
16
  "types": "./dist/gl.d.mts"
17
17
  }
18
18
  },
19
- "scripts": {
20
- "build": "tsup",
21
- "bump": "bumpp",
22
- "dev:build": "vite build dev",
23
- "dev:preview": "vite preview dev",
24
- "dev:watch": "concurrently \"pnpm dev:preview\" \"vite dev:build --watch\"",
25
- "dev": "vite serve dev",
26
- "format": "prettier -w \"src/**/*.{js,ts,json,css,tsx,jsx}\" \"dev/**/*.{js,ts,json,css,tsx,jsx}\"",
27
- "lint:types": "tsc --noEmit",
28
- "prepublishOnly": "pnpm test && pnpm build",
29
- "test:coverage": "vitest run --coverage",
30
- "test:ui": "vitest --ui",
31
- "test": "vitest --run"
32
- },
33
19
  "keywords": [
34
20
  "WebGL"
35
21
  ],
36
22
  "author": "",
37
23
  "license": "ISC",
38
- "packageManager": "pnpm@10.5.1",
39
24
  "devDependencies": {
40
25
  "@vitest/ui": "^3.2.4",
41
26
  "bumpp": "^10.4.0",
@@ -47,5 +32,18 @@
47
32
  "vite": "^7.0.2",
48
33
  "vite-tsconfig-paths": "^5.1.4",
49
34
  "vitest": "^3.2.4"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "bump": "bumpp",
39
+ "dev:build": "vite build dev",
40
+ "dev:preview": "vite preview dev",
41
+ "dev:watch": "concurrently \"pnpm dev:preview\" \"vite dev:build --watch\"",
42
+ "dev": "vite serve dev",
43
+ "format": "prettier -w \"src/**/*.{js,ts,json,css,tsx,jsx}\" \"dev/**/*.{js,ts,json,css,tsx,jsx}\"",
44
+ "lint:types": "tsc --noEmit",
45
+ "test:coverage": "vitest run --coverage",
46
+ "test:ui": "vitest --ui",
47
+ "test": "vitest --run"
50
48
  }
51
- }
49
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * A shader that did not compile. `infoLog` is what the driver answered, `null`
3
+ * and `''` kept apart; `glError` is `getError()` read at the failure.
4
+ */
5
+ export class ShaderCompileError extends Error {
6
+ override name = 'ShaderCompileError'
7
+
8
+ constructor(
9
+ readonly stage: 'vertex' | 'fragment',
10
+ readonly infoLog: string | null,
11
+ readonly glError: number,
12
+ ) {
13
+ super(`Failed to compile ${stage} shader: ${infoLog}`)
14
+ }
15
+ }
16
+
17
+ /**
18
+ * A program that did not link. `infoLog` is what the driver answered, `null`
19
+ * and `''` kept apart; `glError` is `getError()` read at the failure.
20
+ */
21
+ export class ProgramLinkError extends Error {
22
+ override name = 'ProgramLinkError'
23
+
24
+ constructor(
25
+ readonly infoLog: string | null,
26
+ readonly glError: number,
27
+ ) {
28
+ super(`Failed to link program: ${infoLog}`)
29
+ }
30
+ }
31
+
32
+ export type GLResource = 'shader' | 'program' | 'buffer' | 'vertexArray' | 'texture' | 'framebuffer'
33
+
34
+ /** A `create*` call that answered `null`. */
35
+ export class GLCreateError extends Error {
36
+ override name = 'GLCreateError'
37
+
38
+ constructor(readonly resource: GLResource) {
39
+ super(`Failed to create ${resource}`)
40
+ }
41
+ }
42
+
43
+ /** A program with no active attribute of that name: `getAttribLocation` answered -1. */
44
+ export class AttributeNotFoundError extends Error {
45
+ override name = 'AttributeNotFoundError'
46
+
47
+ constructor(readonly attribute: string) {
48
+ super(`Attribute '${attribute}' not found`)
49
+ }
50
+ }
51
+
52
+ /** Throws a {@link GLCreateError} for a `create*` that answered `null`. */
53
+ export function created<T>(value: T | null, resource: GLResource): T {
54
+ if (value === null) {
55
+ throw new GLCreateError(resource)
56
+ }
57
+ return value
58
+ }
package/src/gl.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { RemoveSuffix } from './type-utils'
2
1
  import type { GL, ViewOptions } from './types'
2
+ import { created, ProgramLinkError, ShaderCompileError } from './errors'
3
3
  import { once } from './utils'
4
4
 
5
5
  function isWebGL2RenderingContext(
@@ -15,18 +15,19 @@ function isWebGL2RenderingContext(
15
15
  /**********************************************************************************/
16
16
 
17
17
  export function createShader(gl: GL, type: number, source: string): WebGLShader {
18
- const shader = gl.createShader(type)
19
- if (!shader) throw new Error('Failed to create shader')
18
+ const shader = created(gl.createShader(type), 'shader')
20
19
 
21
20
  gl.shaderSource(shader, source)
22
21
  gl.compileShader(shader)
23
22
 
24
23
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
25
- const info = gl.getShaderInfoLog(shader)
26
- gl.deleteShader(shader)
27
- throw new Error(
28
- `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
24
+ const error = new ShaderCompileError(
25
+ type === gl.VERTEX_SHADER ? 'vertex' : 'fragment',
26
+ gl.getShaderInfoLog(shader),
27
+ gl.getError(),
29
28
  )
29
+ gl.deleteShader(shader)
30
+ throw error
30
31
  }
31
32
 
32
33
  return shader
@@ -44,8 +45,7 @@ export function createProgram(
44
45
  fragmentSource: string,
45
46
  { signal }: ViewOptions = {},
46
47
  ): WebGLProgram {
47
- const program = gl.createProgram()
48
- if (!program) throw new Error('Failed to create WebGL program')
48
+ const program = created(gl.createProgram(), 'program')
49
49
 
50
50
  const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource)
51
51
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
@@ -55,11 +55,11 @@ export function createProgram(
55
55
  gl.linkProgram(program)
56
56
 
57
57
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
58
- const info = gl.getProgramInfoLog(program)
58
+ const error = new ProgramLinkError(gl.getProgramInfoLog(program), gl.getError())
59
59
  gl.deleteProgram(program)
60
60
  gl.deleteShader(vertexShader)
61
61
  gl.deleteShader(fragmentShader)
62
- throw new Error(`Failed to link program: ${info}`)
62
+ throw error
63
63
  }
64
64
 
65
65
  // Clean up shaders after linking
@@ -80,33 +80,18 @@ export function createProgram(
80
80
  /* */
81
81
  /**********************************************************************************/
82
82
 
83
- const INSTANCED_ARRAYS_WRAPPER_MAP = new WeakMap<
84
- GL,
85
- RemoveSuffix<
86
- Pick<
87
- ANGLE_instanced_arrays,
88
- 'drawArraysInstancedANGLE' | 'drawElementsInstancedANGLE' | 'vertexAttribDivisorANGLE'
89
- >,
90
- 'ANGLE'
91
- > | null
92
- >()
83
+ /** Instancing on `gl`: the context on WebGL2, `ANGLE_instanced_arrays` on WebGL1, else `undefined`. */
93
84
  export function getInstancedArrays(gl: GL) {
94
85
  if (isWebGL2RenderingContext(gl)) return gl
95
86
 
96
- const cached = INSTANCED_ARRAYS_WRAPPER_MAP.get(gl)
97
- if (cached) return cached
98
-
99
87
  const ext = gl.getExtension('ANGLE_instanced_arrays')
100
88
  if (!ext) return undefined
101
89
 
102
- const wrapper = {
90
+ return {
103
91
  drawArraysInstanced: ext.drawArraysInstancedANGLE.bind(ext),
104
92
  drawElementsInstanced: ext.drawElementsInstancedANGLE.bind(ext),
105
93
  vertexAttribDivisor: ext.vertexAttribDivisorANGLE.bind(ext),
106
94
  }
107
- INSTANCED_ARRAYS_WRAPPER_MAP.set(gl, wrapper)
108
-
109
- return wrapper
110
95
  }
111
96
 
112
97
  /**********************************************************************************/
@@ -115,31 +100,16 @@ export function getInstancedArrays(gl: GL) {
115
100
  /* */
116
101
  /**********************************************************************************/
117
102
 
118
- const VERTEX_ARRAY_OBJECT_WRAPPER_MAP = new WeakMap<
119
- GL,
120
- RemoveSuffix<
121
- Pick<
122
- OES_vertex_array_object,
123
- 'bindVertexArrayOES' | 'createVertexArrayOES' | 'deleteVertexArrayOES'
124
- >,
125
- 'OES'
126
- > | null
127
- >()
103
+ /** Vertex arrays on `gl`: the context on WebGL2, `OES_vertex_array_object` on WebGL1, else `null`. */
128
104
  export function getVertexArrayObject(gl: GL) {
129
105
  if (isWebGL2RenderingContext(gl)) return gl
130
106
 
131
- const cached = VERTEX_ARRAY_OBJECT_WRAPPER_MAP.get(gl)
132
- if (cached) return cached
133
-
134
107
  const ext = gl.getExtension('OES_vertex_array_object')
135
108
  if (!ext) return null
136
109
 
137
- const wrapper = {
110
+ return {
138
111
  bindVertexArray: ext.bindVertexArrayOES.bind(ext),
139
112
  createVertexArray: ext.createVertexArrayOES.bind(ext),
140
113
  deleteVertexArray: ext.deleteVertexArrayOES.bind(ext),
141
114
  }
142
- VERTEX_ARRAY_OBJECT_WRAPPER_MAP.set(gl, wrapper)
143
-
144
- return wrapper
145
115
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AttributeNotFoundError, created } from './errors'
1
2
  import { getInstancedArrays, getVertexArrayObject } from './gl'
2
3
  import type {
3
4
  AttributeFormat,
@@ -31,6 +32,7 @@ import {
31
32
  once,
32
33
  } from './utils'
33
34
  import { createUpsertMap, mapObject } from './utils'
35
+ export * from './errors'
34
36
  export * from './types'
35
37
  // Documented under Utils but reachable from nowhere until now: utils is not a
36
38
  // build entry, and named rather than star so the internal helpers stay internal.
@@ -148,7 +150,7 @@ function bindDefaultVertexArray(gl: GL): () => void {
148
150
  if (!feature) {
149
151
  return () => {}
150
152
  }
151
- const previous = gl.getParameter(VERTEX_ARRAY_BINDING) as WebGLVertexArrayObject | null
153
+ const previous = readVertexArrayBinding(gl)
152
154
  feature.bindVertexArray(null)
153
155
  return () => feature.bindVertexArray(previous)
154
156
  }
@@ -180,8 +182,8 @@ export function vaoView(
180
182
  dispose() {},
181
183
  }
182
184
  : (() => {
183
- const vertexArray = assertedNotNullish(feature.createVertexArray())
184
- const previous = gl.getParameter(VERTEX_ARRAY_BINDING) as WebGLVertexArrayObject | null
185
+ const vertexArray = created(feature.createVertexArray(), 'vertexArray')
186
+ const previous = readVertexArrayBinding(gl)
185
187
  feature.bindVertexArray(vertexArray)
186
188
  // Discarding the restorers is the point: the state lives in this array
187
189
  // now, and the array is thrown away whole.
@@ -192,9 +194,7 @@ export function vaoView(
192
194
 
193
195
  return {
194
196
  bind() {
195
- const previousVertexArray = gl.getParameter(
196
- VERTEX_ARRAY_BINDING,
197
- ) as WebGLVertexArrayObject | null
197
+ const previousVertexArray = readVertexArrayBinding(gl)
198
198
  feature.bindVertexArray(vertexArray)
199
199
  // Context state the array cannot hold — see applyToContext.
200
200
  participants.forEach(participant => participant.applyToContext?.())
@@ -366,6 +366,12 @@ function handleAttribute(
366
366
  const VERTEX_ATTRIB_ARRAY_DIVISOR = 0x88fe
367
367
  const VERTEX_ARRAY_BINDING = 0x85b5
368
368
 
369
+ // Anything but an array or null would make the restore throw a TypeError.
370
+ function readVertexArrayBinding(gl: GL): WebGLVertexArrayObject | null {
371
+ const bound: unknown = gl.getParameter(VERTEX_ARRAY_BINDING)
372
+ return typeof bound === 'object' ? (bound as WebGLVertexArrayObject | null) : null
373
+ }
374
+
369
375
  // Reads the divisor currently attached to a location, 0 where instancing is
370
376
  // unavailable and nothing can have set one
371
377
  function readDivisor(gl: GL, location: number): number {
@@ -389,14 +395,13 @@ export function attributeView<T extends AttributeSchema>(
389
395
  const name = toID(key)
390
396
 
391
397
  // Whoever made the buffer deletes it. A schema can name a buffer that
392
- // belongs to another view — the quad `compile.toQuad` shares across every
393
- // program on a context, an attribute two programs step over — and
398
+ // belongs to another view — an attribute two programs step over — and
394
399
  // deleting one of those would take it out from under its owner.
395
- const buffer = providedBuffer ?? assertedNotNullish(gl.createBuffer())
400
+ const buffer = providedBuffer ?? created(gl.createBuffer(), 'buffer')
396
401
 
397
402
  const location = gl.getAttribLocation(program, name)
398
403
  if (location < 0) {
399
- throw new Error(`Attribute '${name}' not found`)
404
+ throw new AttributeNotFoundError(name)
400
405
  }
401
406
 
402
407
  const size = kindToSize(kind)
@@ -551,7 +556,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
551
556
  const location = gl.getAttribLocation(program, name)
552
557
 
553
558
  if (location < 0) {
554
- throw new Error(`Attribute '${name}' not found`)
559
+ throw new AttributeNotFoundError(name)
555
560
  }
556
561
 
557
562
  locations.push(location)
@@ -582,7 +587,7 @@ export function interleavedAttributeView<T extends InterleavedAttributeSchema>(
582
587
  const stride = index
583
588
 
584
589
  // Create a buffer
585
- const buffer = assertedNotNullish(gl.createBuffer())
590
+ const buffer = created(gl.createBuffer(), 'buffer')
586
591
 
587
592
  // This layout used to create a vertex array of its own, here, which is why
588
593
  // the other participants in a draw had to be bound after it to be included
@@ -670,7 +675,7 @@ export function bufferView<T extends BufferSchema>(
670
675
  ): BufferView<T> {
671
676
  // Initialize buffers
672
677
  const buffers = mapObject(schema, ({ target = 'ARRAY_BUFFER', usage = 'STATIC_DRAW' }) => {
673
- const buffer = assertedNotNullish(gl.createBuffer())
678
+ const buffer = created(gl.createBuffer(), 'buffer')
674
679
 
675
680
  function applyToVertexArray() {
676
681
  // Snapshot before the change, restore in the disposer. Restoring the
package/src/tag.ts CHANGED
@@ -18,7 +18,7 @@ import type {
18
18
  ViewSchema,
19
19
  ViewSchemaPartial,
20
20
  } from './types'
21
- import { createUpsertMap, once } from './utils'
21
+ import { once } from './utils'
22
22
 
23
23
  export function glsl<TSlot extends GLSLSlot, const TSlots extends TSlot[]>(
24
24
  template: TemplateStringsArray,
@@ -281,22 +281,19 @@ function resolveGlslSlotToString(slot: GLSLSlot, v300: boolean): string {
281
281
  }
282
282
 
283
283
  const QUAD_FLOAT_ARRAY = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1])
284
- const QUAD_BUFFER_MAP = createUpsertMap(WeakMap<GL, WebGLBuffer>)
285
284
 
286
285
  compile.toQuad = function <TFragment extends GLSL, TSchema extends ViewSchemaPartial>(
287
286
  gl: GL,
288
287
  fragment: TFragment,
289
288
  options?: CompileOptions<TSchema>,
290
289
  ) {
291
- const buffer = QUAD_BUFFER_MAP.getOrInsert(gl, gl.createBuffer.bind(gl))
292
-
293
290
  const webgl2 = options?.webgl2 ?? fragment.template[0]?.startsWith('#version 300 es')
294
291
 
295
292
  const vertex = webgl2
296
293
  ? glsl`#version 300 es
297
294
  precision mediump float;
298
295
 
299
- ${attribute.vec2('a_quad', { buffer })}
296
+ ${attribute.vec2('a_quad')}
300
297
 
301
298
  out vec2 v_uv;
302
299
 
@@ -306,7 +303,7 @@ void main() {
306
303
  }`
307
304
  : glsl`precision mediump float;
308
305
 
309
- ${attribute.vec2('a_quad', { buffer })}
306
+ ${attribute.vec2('a_quad')}
310
307
 
311
308
  varying vec2 v_uv;
312
309
 
package/src/types.ts CHANGED
@@ -670,10 +670,9 @@ export interface ViewOptions {
670
670
  *
671
671
  * The other two lifetimes have their own spellings, and neither is this one. A
672
672
  * buffer that outlives the view, or is shared between views, is a standalone
673
- * `bufferView` — `compile.toQuad`'s per-context quad, a buffer handed from one
674
- * program's view to another's. A buffer that belongs to somebody else is NAMED
675
- * by an attribute schema, and is borrowed: the view uses it and never deletes
676
- * it.
673
+ * `bufferView` — a buffer handed from one program's view to another's. A
674
+ * buffer that belongs to somebody else is NAMED by an attribute schema, and is
675
+ * borrowed: the view uses it and never deletes it.
677
676
  */
678
677
  export interface ViewSchema {
679
678
  uniforms?: UniformSchema
package/src/utils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { created } from './errors'
1
2
  import type {
2
3
  AttributeKind,
3
4
  FramebufferDefinition,
@@ -135,7 +136,7 @@ export function createTexture(
135
136
  data?: ArrayBufferView | null,
136
137
  { signal }: ViewOptions = {},
137
138
  ): WebGLTexture {
138
- const texture = assertedNotNullish(gl.createTexture(), 'Failed to create texture')
139
+ const texture = created(gl.createTexture(), 'texture')
139
140
 
140
141
  function getTextureConstant(name: string) {
141
142
  if (!(name in gl)) {
@@ -217,7 +218,7 @@ export function createFramebuffer(
217
218
  const texture = providedTexture ?? createTexture(gl, definition)
218
219
 
219
220
  // Create framebuffer
220
- const framebuffer = assertedNotNullish(gl.createFramebuffer(), 'Failed to create framebuffer')
221
+ const framebuffer = created(gl.createFramebuffer(), 'framebuffer')
221
222
 
222
223
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
223
224