@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/.claude/settings.local.json +8 -0
- package/.prettierrc +10 -0
- package/README.md +338 -0
- package/dev/bezier.ts +428 -0
- package/dev/game_of_life.ts +250 -0
- package/dev/grid.ts +223 -0
- package/dev/index.html +20 -0
- package/dev/index.ts +94 -0
- package/dev/pix_sim.ts +549 -0
- package/dev/tetris.ts +439 -0
- package/dev/tsconfig.json +4 -0
- package/dev/utils.ts +61 -0
- package/dev/vite.config.ts +42 -0
- package/package.json +29 -0
- package/src/features.ts +59 -0
- package/src/index.ts +313 -0
- package/src/tag.ts +193 -0
- package/src/types.ts +718 -0
- package/src/utils.ts +266 -0
- package/tsconfig.json +30 -0
- package/tsup.config.ts +11 -0
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
|
+
}
|