@bigmistqke/view.gl 0.1.0

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/src/utils.ts ADDED
@@ -0,0 +1,266 @@
1
+ import type {
2
+ AttributeKind,
3
+ FramebufferOptions,
4
+ GL,
5
+ KIND_TO_SIZE_MAP,
6
+ KIND_TO_UNIFORM_FN_NAME_MAP,
7
+ TextureOptions,
8
+ UniformKind,
9
+ } from './types'
10
+
11
+ export function mapObject<T extends Record<string, any>, TReturn>(
12
+ value: T,
13
+ callback: (value: T[keyof T], key: Extract<keyof T, string>, index: number) => TReturn,
14
+ ): { [TKey in keyof T]: TReturn } {
15
+ return Object.fromEntries(
16
+ Object.entries(value).map(([key, value], index) => [key, callback(value, key, index)]),
17
+ )
18
+ }
19
+
20
+ export function assertedNotNullish<T>(value: T, message?: string): NonNullable<T> {
21
+ if (value === undefined || value === null) throw new Error(message)
22
+ return value
23
+ }
24
+
25
+ function createGLShader(gl: GL, type: number, source: string): WebGLShader {
26
+ const shader = gl.createShader(type)
27
+ if (!shader) throw new Error('Failed to create shader')
28
+
29
+ gl.shaderSource(shader, source)
30
+ gl.compileShader(shader)
31
+
32
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
33
+ const info = gl.getShaderInfoLog(shader)
34
+ gl.deleteShader(shader)
35
+ throw new Error(
36
+ `Failed to compile ${type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader: ${info}`,
37
+ )
38
+ }
39
+
40
+ return shader
41
+ }
42
+
43
+ export function createProgram(gl: GL, vertexSource: string, fragmentSource: string): WebGLProgram {
44
+ const program = gl.createProgram()
45
+ if (!program) throw new Error('Failed to create WebGL program')
46
+
47
+ const vertexShader = createGLShader(gl, gl.VERTEX_SHADER, vertexSource)
48
+ const fragmentShader = createGLShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
49
+
50
+ gl.attachShader(program, vertexShader)
51
+ gl.attachShader(program, fragmentShader)
52
+ gl.linkProgram(program)
53
+
54
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
55
+ const info = gl.getProgramInfoLog(program)
56
+ gl.deleteProgram(program)
57
+ gl.deleteShader(vertexShader)
58
+ gl.deleteShader(fragmentShader)
59
+ throw new Error(`Failed to link program: ${info}`)
60
+ }
61
+
62
+ // Clean up shaders after linking
63
+ gl.deleteShader(vertexShader)
64
+ gl.deleteShader(fragmentShader)
65
+
66
+ return program
67
+ }
68
+
69
+ /**********************************************************************************/
70
+ /* */
71
+ /* Kind To Uniform Name */
72
+ /* */
73
+ /**********************************************************************************/
74
+
75
+ export const kindToUniformFnName = <T extends UniformKind>(
76
+ kind: T,
77
+ ): KIND_TO_UNIFORM_FN_NAME_MAP[T] => {
78
+ switch (kind[0]) {
79
+ // mat
80
+ case 'm':
81
+ return 'Matrix' + kind.slice(3) + 'fv'
82
+ // sampler/booleans/integer
83
+ case 's':
84
+ case 'b':
85
+ case 'i':
86
+ return (kind.match(/\d/)?.[0] ?? '1') + 'i'
87
+ // unsigned integers
88
+ case 'u':
89
+ return (kind[4] || '1') + 'ui'
90
+ // vec
91
+ case 'v':
92
+ return kind[3] + 'f'
93
+ default:
94
+ switch (kind) {
95
+ case 'float':
96
+ return '1f'
97
+ case 'uint':
98
+ return '1ui'
99
+ default:
100
+ return '1i'
101
+ }
102
+ }
103
+ }
104
+
105
+ /**********************************************************************************/
106
+ /* */
107
+ /* Kind To Size */
108
+ /* */
109
+ /**********************************************************************************/
110
+
111
+ export const kindToSize = <T extends UniformKind>(kind: T): KIND_TO_SIZE_MAP[T] => {
112
+ switch (kind[0]) {
113
+ case 'm':
114
+ const [a, b] = kind.match(/\d+/g)!.map(Number)
115
+ return a * (b || a)
116
+ case 'v':
117
+ return +kind.match(/\d/)![0]
118
+ default:
119
+ return 1
120
+ }
121
+ }
122
+
123
+ /**********************************************************************************/
124
+ /* */
125
+ /* Guards */
126
+ /* */
127
+ /**********************************************************************************/
128
+
129
+ export const isMatKind = <
130
+ T extends UniformKind | AttributeKind,
131
+ TPrefix extends string,
132
+ TPostfix extends string,
133
+ >(
134
+ kind: T,
135
+ ): kind is T & `${TPrefix}mat${TPostfix}` => kind.includes('mat')
136
+
137
+ export const isSamplerKind = <TPrefix extends string, TPostfix extends string>(
138
+ kind: UniformKind,
139
+ ): kind is UniformKind & `${TPrefix}sampler${TPostfix}` => kind.includes('sampler')
140
+
141
+ export const isVecKind = <TPrefix extends string, TPostfix extends string>(
142
+ kind: UniformKind,
143
+ ): kind is UniformKind & `${TPrefix}vec${TPostfix}` => kind.includes('vec')
144
+
145
+ /**********************************************************************************/
146
+ /* */
147
+ /* Create Texture */
148
+ /* */
149
+ /**********************************************************************************/
150
+
151
+ export function createTexture(
152
+ gl: GL,
153
+ {
154
+ target = 'TEXTURE_2D',
155
+ internalFormat = 'RGBA',
156
+ format = 'RGBA',
157
+ type = 'UNSIGNED_BYTE',
158
+ minFilter = 'NEAREST',
159
+ magFilter = 'NEAREST',
160
+ wrapS = 'CLAMP_TO_EDGE',
161
+ wrapT = 'CLAMP_TO_EDGE',
162
+ width,
163
+ height,
164
+ }: TextureOptions,
165
+ data?: ArrayBufferView | null,
166
+ ): WebGLTexture {
167
+ const texture = gl.createTexture()
168
+
169
+ function getTextureConstant(name: string) {
170
+ if (!(name in gl)) {
171
+ throw new Error(`Attempted to create webgl2-only texture (${name}) in webgl1`)
172
+ }
173
+ return gl[name]
174
+ }
175
+
176
+ gl.bindTexture(gl[target], texture)
177
+ gl.texImage2D(
178
+ gl[target],
179
+ 0,
180
+ getTextureConstant(internalFormat),
181
+ width,
182
+ height,
183
+ 0,
184
+ getTextureConstant(format),
185
+ getTextureConstant(type),
186
+ data ?? null,
187
+ )
188
+
189
+ console.log(minFilter)
190
+ // Set texture parameters
191
+ gl.texParameteri(gl[target], gl.TEXTURE_MIN_FILTER, gl[minFilter])
192
+ gl.texParameteri(gl[target], gl.TEXTURE_MAG_FILTER, gl[magFilter])
193
+ gl.texParameteri(gl[target], gl.TEXTURE_WRAP_S, gl[wrapS])
194
+ gl.texParameteri(gl[target], gl.TEXTURE_WRAP_T, gl[wrapT])
195
+
196
+ return texture
197
+ }
198
+
199
+ /**********************************************************************************/
200
+ /* */
201
+ /* Create Framebuffer */
202
+ /* */
203
+ /**********************************************************************************/
204
+
205
+ const FRAMEBUFFER_ATTACHMENT_MAP = {
206
+ color: 'COLOR_ATTACHMENT0',
207
+ depth: 'DEPTH_ATTACHMENT',
208
+ stencil: 'STENCIL_ATTACHMENT',
209
+ depthStencil: 'DEPTH_STENCIL_ATTACHMENT',
210
+ } as const
211
+
212
+ class FramebufferError extends Error {
213
+ constructor(gl: GL, status: number) {
214
+ let errorMessage = `Framebuffer '${name}' not complete. Status: `
215
+ switch (status) {
216
+ case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
217
+ errorMessage += 'FRAMEBUFFER_INCOMPLETE_ATTACHMENT'
218
+ break
219
+ case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
220
+ errorMessage += 'FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT'
221
+ break
222
+ case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
223
+ errorMessage += 'FRAMEBUFFER_INCOMPLETE_DIMENSIONS'
224
+ break
225
+ case gl.FRAMEBUFFER_UNSUPPORTED:
226
+ errorMessage += 'FRAMEBUFFER_UNSUPPORTED'
227
+ break
228
+ default:
229
+ errorMessage += `Unknown (${status})`
230
+ }
231
+ super(errorMessage)
232
+ }
233
+ }
234
+
235
+ export function createFramebuffer(gl: GL, { attachment, texture, ...options }: FramebufferOptions) {
236
+ // Create texture for the framebuffer
237
+ texture ??= createTexture(gl, options)
238
+
239
+ // Create framebuffer
240
+ const framebuffer = assertedNotNullish(
241
+ gl.createFramebuffer(),
242
+ `Failed to create framebuffer: ${name}`,
243
+ )
244
+
245
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
246
+
247
+ gl.framebufferTexture2D(
248
+ gl.FRAMEBUFFER,
249
+ // Determine attachment point based on attachment kind
250
+ gl[FRAMEBUFFER_ATTACHMENT_MAP[attachment]],
251
+ gl.TEXTURE_2D,
252
+ texture,
253
+ 0,
254
+ )
255
+
256
+ // Check framebuffer completeness
257
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
258
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
259
+ throw new FramebufferError(gl, status)
260
+ }
261
+
262
+ return {
263
+ texture,
264
+ framebuffer,
265
+ }
266
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": ".",
4
+ "allowSyntheticDefaultImports": true,
5
+ "allowJs": true,
6
+ "baseUrl": ".",
7
+ "esModuleInterop": true,
8
+ "declaration": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "isolatedModules": true,
11
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
12
+ "module": "ESNext",
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "noUncheckedIndexedAccess": true,
16
+ "outDir": "dist",
17
+ "noEmit": true,
18
+ "resolveJsonModule": true,
19
+ "skipLibCheck": true,
20
+ "sourceMap": true,
21
+ "strict": true,
22
+ "target": "ESNext",
23
+ "types": ["vite/client"],
24
+ "paths": {
25
+ "view.gl": ["src/index.ts"],
26
+ "view.gl/tag": ["src/tag.ts"]
27
+ }
28
+ },
29
+ "include": ["src", "dev/extensions"]
30
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/tag.ts'],
5
+ format: ['esm'],
6
+ splitting: false,
7
+ sourcemap: true,
8
+ minify: false,
9
+ clean: true,
10
+ dts: true,
11
+ })