oria-webgpu 0.1.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/LICENSE +21 -0
- package/README.ko.md +175 -0
- package/README.md +179 -0
- package/dist/binding/bind-group-layout.d.ts +6 -0
- package/dist/binding/bind-group-layout.d.ts.map +1 -0
- package/dist/binding/bind-group.d.ts +26 -0
- package/dist/binding/bind-group.d.ts.map +1 -0
- package/dist/commands/command-encoder.d.ts +3 -0
- package/dist/commands/command-encoder.d.ts.map +1 -0
- package/dist/core/context.d.ts +8 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/device.d.ts +7 -0
- package/dist/core/device.d.ts.map +1 -0
- package/dist/core/gpu.d.ts +3 -0
- package/dist/core/gpu.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +623 -0
- package/dist/index.js.map +1 -0
- package/dist/pass/compute-pass.d.ts +3 -0
- package/dist/pass/compute-pass.d.ts.map +1 -0
- package/dist/pass/render-pass.d.ts +3 -0
- package/dist/pass/render-pass.d.ts.map +1 -0
- package/dist/pipeline/compute-pipeline.d.ts +3 -0
- package/dist/pipeline/compute-pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline-cache.d.ts +18 -0
- package/dist/pipeline/pipeline-cache.d.ts.map +1 -0
- package/dist/pipeline/render-pipeline.d.ts +3 -0
- package/dist/pipeline/render-pipeline.d.ts.map +1 -0
- package/dist/resources/buffer.d.ts +5 -0
- package/dist/resources/buffer.d.ts.map +1 -0
- package/dist/resources/readback.d.ts +10 -0
- package/dist/resources/readback.d.ts.map +1 -0
- package/dist/resources/sampler.d.ts +3 -0
- package/dist/resources/sampler.d.ts.map +1 -0
- package/dist/resources/texture.d.ts +4 -0
- package/dist/resources/texture.d.ts.map +1 -0
- package/dist/shader/reflection.d.ts +26 -0
- package/dist/shader/reflection.d.ts.map +1 -0
- package/dist/shader/shader.d.ts +10 -0
- package/dist/shader/shader.d.ts.map +1 -0
- package/dist/types/public.d.ts +211 -0
- package/dist/types/public.d.ts.map +1 -0
- package/dist/utils/alignment.d.ts +6 -0
- package/dist/utils/alignment.d.ts.map +1 -0
- package/dist/utils/hash.d.ts +11 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cheonghakim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# oria-webgpu
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **한국어**
|
|
4
|
+
|
|
5
|
+
그래픽과 컴퓨트를 위한 최소한의, 타입이 명확한 WebGPU 래퍼입니다. "WebGPU용 TWGL"이라고 생각하시면 됩니다.
|
|
6
|
+
|
|
7
|
+
**[라이브 데모](https://cheonghakim.github.io/oria-webgpu/)**
|
|
8
|
+
|
|
9
|
+
**이 라이브러리는 렌더링 엔진이 아닙니다.** 씬(scene), 라이팅, 머티리얼, 로더, 애니메이션 시스템이 필요하다면 Three.js나 Babylon.js를 사용하세요.
|
|
10
|
+
|
|
11
|
+
## 이 라이브러리가 존재하는 이유
|
|
12
|
+
|
|
13
|
+
WebGPU는 장황하지만 복잡하지는 않습니다. oria-webgpu는 반복되는 부분들 —
|
|
14
|
+
어댑터/디바이스 설정, 버퍼 크기 계산, 파이프라인 디스크립터 보일러플레이트, 렌더 패스
|
|
15
|
+
생명주기 — 을 제거하면서도 모든 네이티브 WebGPU 개념(`GPUDevice`, `GPUBuffer`,
|
|
16
|
+
`GPURenderPipeline` 등)을 보이고 접근 가능하게 유지합니다. 여전히 WebGPU 방식으로
|
|
17
|
+
생각해야 하지만, 코드는 훨씬 적게 씁니다.
|
|
18
|
+
|
|
19
|
+
## 상태
|
|
20
|
+
|
|
21
|
+
v0.1 (진행 중). 지금까지 구현된 것: `createGPU`, 버퍼(`storageBuffer` 축약 헬퍼 포함),
|
|
22
|
+
텍스처(ImageBitmap/canvas/VideoFrame로부터의 업로드 및 캔버스 크기에 맞춘 `depthTexture`
|
|
23
|
+
헬퍼 포함), 샘플러, 공유 파이프라인 캐시가 적용된 렌더/컴퓨트 파이프라인, 렌더패스/컴퓨트패스
|
|
24
|
+
빌더, named bind group을 지원하는 WGSL 바인딩 리플렉션, GPU 버퍼 리드백(`gpu.readBuffer`).
|
|
25
|
+
|
|
26
|
+
실제 WebGPU 브라우저(Chromium, headed 모드 — WebGPU는 실제 GPU 접근이 필요한데, 이
|
|
27
|
+
플랫폼의 headless 모드는 GPU 접근을 허용하지 않습니다)에서 정상 렌더링됨을 확인했습니다:
|
|
28
|
+
정확한 삼각형 geometry, 정확한 정점별 색상 보간, 유니폼으로 구동되는 실시간 회전,
|
|
29
|
+
그리고 동작하는 WebGPU-vs-WebGL2 instanced-draw 비교. 그 전까지는 mock을 사용한
|
|
30
|
+
유닛 테스트 로직만 검증되어 있었고, 아직 이를 실행하는 CI/Playwright 스위트는 없습니다
|
|
31
|
+
(Limitations 참고).
|
|
32
|
+
|
|
33
|
+
## 데모
|
|
34
|
+
|
|
35
|
+
**[cheonghakim.github.io/oria-webgpu](https://cheonghakim.github.io/oria-webgpu/)** — 또는 로컬에서 실행:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
npm run demo
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
두 개의 탭으로 구성됩니다: **Usage** — 실제로 실행 중인 oria-webgpu 코드가 옆에 그대로
|
|
43
|
+
표시되는(패널 자체가 그 함수의 소스 코드이므로, 실제 실행되는 코드와 어긋날 수 없습니다)
|
|
44
|
+
작은 회전 삼각형 — 그리고 **WebGPU vs WebGL** — 동일한 per-instance 움직임을 두 API로
|
|
45
|
+
나란히 렌더링하며 실시간 fps와 CPU submit 시간을 보여줍니다. [examples/demo](examples/demo)
|
|
46
|
+
참고.
|
|
47
|
+
|
|
48
|
+
instance 개수가 적을 때는 양쪽 다 디스플레이의 vsync fps에 그냥 걸려 있을 뿐입니다 —
|
|
49
|
+
이건 "차이가 없다"는 뜻이 아니라, 유의미한 결과를 낼 만큼 부하가 크지 않다는 뜻입니다.
|
|
50
|
+
약 150,000개 이상에서는 프래그먼트당 셰이더 작업이 실제로 vsync를 넘어설 만큼 무거워지고,
|
|
51
|
+
이 지점에서는 두 API가 사실상 동일한 fps를 보입니다: 단일 steady-state instanced draw call은
|
|
52
|
+
성숙한 WebGL2 드라이버에서 이미 거의 최적화되어 있어서, 이 워크로드는 WebGPU의 진짜
|
|
53
|
+
장점(많은 draw call/상태 변경, 컴퓨트, 낮은 드라이버 오버헤드)이 드러나는 지점이 아닙니다.
|
|
54
|
+
|
|
55
|
+
## 빠른 시작
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install oria-webgpu
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createGPU } from "oria-webgpu"
|
|
63
|
+
|
|
64
|
+
const gpu = await createGPU(canvas)
|
|
65
|
+
|
|
66
|
+
const pipeline = gpu.renderPipeline({
|
|
67
|
+
vertex: { code: shaderWGSL, entryPoint: "vsMain" },
|
|
68
|
+
fragment: { code: shaderWGSL, entryPoint: "fsMain", targets: [gpu.format] },
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
gpu.render((pass) => {
|
|
72
|
+
pass.pipeline(pipeline).draw(3)
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
또는 이 저장소를 클론해서 예제를 바로 실행할 수도 있습니다. 별도 프로젝트가 필요 없습니다:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npm run example:triangle
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 바인딩
|
|
84
|
+
|
|
85
|
+
이름이 붙은 리소스는 셰이더의 `@group`/`@binding` 선언을 훑는 작은 WGSL 리플렉션
|
|
86
|
+
과정을 통해 bind group으로 해석됩니다 — 일반적인 경우라면 수동으로
|
|
87
|
+
`GPUBindGroupLayout`/`GPUBindGroup`을 만들 필요가 없습니다:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const pipeline = gpu.renderPipeline({
|
|
91
|
+
vertex: { code: shaderWGSL, entryPoint: "vsMain" },
|
|
92
|
+
fragment: { code: shaderWGSL, entryPoint: "fsMain", targets: [gpu.format] },
|
|
93
|
+
bindings: {
|
|
94
|
+
camera: cameraBuffer, // `@group(0) @binding(0) var<uniform> camera: Camera;` 와 매칭
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 바인딩은 나중에 다시 (재)해석하거나, 패스 단위로도 지정할 수 있습니다:
|
|
99
|
+
pipeline.bindings({ camera: cameraBuffer })
|
|
100
|
+
gpu.render((pass) => {
|
|
101
|
+
pass.pipeline(pipeline).bindings({ camera: cameraBuffer }).draw(3)
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`@group`은 선언된 모든 바인딩이 채워졌을 때만 빌드됩니다 — 한 번에 모두 넘기거나,
|
|
106
|
+
여러 번에 걸쳐 점진적으로 넘겨도 됩니다. 이것은 작고 예측 가능한 파서이며(`reflectWGSLBindings`
|
|
107
|
+
참고) 완전한 WGSL 문법 분석기가 아닙니다. 확신 있게 분류할 수 없는 선언은 그냥 건너뜁니다.
|
|
108
|
+
언제든 네이티브 `device.createBindGroup(...)`과 `pass.raw.setBindGroup(...)`으로
|
|
109
|
+
직접 내려갈 수 있습니다.
|
|
110
|
+
|
|
111
|
+
## 파이프라인은 캐싱됩니다
|
|
112
|
+
|
|
113
|
+
`gpu.renderPipeline(...)`/`gpu.computePipeline(...)`은 서로 다른 디스크립터별로
|
|
114
|
+
캐싱됩니다 — 동일한 셰이더 소스, entry point, vertex layout, target, format으로
|
|
115
|
+
다시 호출하면 WebGPU에게 다시 컴파일을 요청하는 대신 *동일한* 파이프라인 객체를
|
|
116
|
+
반환합니다. `bindings`는 이 identity에 포함되지 않습니다(캐시 hit이든 miss든 매
|
|
117
|
+
호출마다 다시 해석·적용되므로), 그래서 매 프레임 새로운 바인딩 리소스로
|
|
118
|
+
`gpu.renderPipeline({...})`을 호출하는 것이 정상적인 패턴입니다. 강제로 다시
|
|
119
|
+
빌드해야 한다면 `gpu.clearPipelineCache()`를 호출하세요.
|
|
120
|
+
|
|
121
|
+
## 컴퓨트 리드백
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const particles = gpu.storageBuffer({ data }) // 기본값: STORAGE | COPY_DST | COPY_SRC
|
|
125
|
+
|
|
126
|
+
gpu.compute((pass) => {
|
|
127
|
+
pass.pipeline(pipeline).bindings({ particles }).dispatch(particles.size / 4 / 64)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const result = await gpu.readBuffer(particles, Float32Array)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`readBuffer`는 버퍼를 staging 버퍼로 복사하고, 매핑한 뒤, 데이터를 복사해내고,
|
|
134
|
+
staging 버퍼를 정리합니다. 소스 버퍼는 `COPY_SRC` usage가 필요합니다
|
|
135
|
+
(`storageBuffer()`는 기본으로 포함합니다).
|
|
136
|
+
|
|
137
|
+
## 탈출구 (Escape hatches)
|
|
138
|
+
|
|
139
|
+
래핑된 모든 객체는 자신의 네이티브 WebGPU 대응물을 그대로 노출합니다:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
gpu.device // GPUDevice
|
|
143
|
+
gpu.context // GPUCanvasContext
|
|
144
|
+
buffer.raw // GPUBuffer
|
|
145
|
+
pipeline.raw // GPURenderPipeline
|
|
146
|
+
pass.raw // GPURenderPassEncoder (gpu.render 콜백 내부)
|
|
147
|
+
|
|
148
|
+
gpu.command((encoder) => {
|
|
149
|
+
// 완전한 네이티브 WebGPU 커맨드 인코더
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 한계 (v0.1)
|
|
154
|
+
|
|
155
|
+
- `gpu.render()`는 아직 멀티샘플 resolve target을 지원하지 않습니다 — 파이프라인의
|
|
156
|
+
`multisample` 옵션은 노출되어 있지만, 패스 헬퍼에 멀티샘플 attachment + resolve
|
|
157
|
+
target을 넘길 방법이 없어서 이 헬퍼를 통해서는 MSAA를 실제로 사용할 수 없습니다
|
|
158
|
+
(완전히 수동으로 구성하는 `gpu.command()` 탈출구를 통해서만 가능). 데모를 만들면서
|
|
159
|
+
발견했습니다: WebGL2 캔버스는 기본값이 `antialias: true`라서, 그대로 두면 WebGPU는
|
|
160
|
+
받지 못하는 공짜 MSAA 스무딩을 WebGL만 조용히 받게 되어 불공정한 비교가 됩니다 —
|
|
161
|
+
`antialias: false`를 명시해서 맞춰줬습니다.
|
|
162
|
+
- WGSL 리플렉션은 완전한 문법 분석기가 아니라 작은 패턴 기반 파서입니다 — 정확히 무엇을
|
|
163
|
+
인식하는지는 [src/shader/reflection.ts](src/shader/reflection.ts)를 참고하세요.
|
|
164
|
+
- 선언된 바인딩을 전부 채우지 못한 `@group`은 bind group을 만들어내지 않습니다;
|
|
165
|
+
이는 Oria가 아니라 WebGPU 자체의 검증(validation)이 draw/dispatch 시점에 보고합니다.
|
|
166
|
+
- `depthTexture()`는 호출 시점의 캔버스 크기를 스냅샷으로 찍습니다 — 캔버스 크기를
|
|
167
|
+
따라가게 하려면 `gpu.resize()` 이후 다시 호출하세요(암묵적인 auto-resize는 없습니다).
|
|
168
|
+
- 아직 indexed-cube/texture/particles 예제나 Playwright 통합 테스트는 없습니다.
|
|
169
|
+
- `storageBuffer()`/`buffer()`는 상향된 limit을 요청하지 않으므로, *기본값*
|
|
170
|
+
`maxStorageBufferBindingSize`(128 MiB)나 `maxBufferSize`(256 MiB)를 넘는 버퍼는
|
|
171
|
+
검증에 실패합니다 — 이는 에러를 던지는 게 아니라 콘솔 warning으로만 나타나므로
|
|
172
|
+
놓치기 쉽습니다. 더 큰 버퍼가 필요하다면 `createGPU(...)`에 `requiredLimits`를
|
|
173
|
+
넘기세요(먼저 `adapter.limits`로 확인해야 합니다).
|
|
174
|
+
|
|
175
|
+
이 항목들은 다음 단계들에서 채워질 예정입니다 — 전체 로드맵은 개발 계획을 참고하세요.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# oria-webgpu
|
|
2
|
+
|
|
3
|
+
**English** | [한국어](README.ko.md)
|
|
4
|
+
|
|
5
|
+
A minimal, typed WebGPU wrapper for graphics and compute. Think "TWGL for WebGPU."
|
|
6
|
+
|
|
7
|
+
**[Live demo](https://cheonghakim.github.io/oria-webgpu/)**
|
|
8
|
+
|
|
9
|
+
**This library is not a rendering engine.** If you need scenes, lighting, materials,
|
|
10
|
+
loaders, or animation systems, use Three.js or Babylon.js instead.
|
|
11
|
+
|
|
12
|
+
## Why this library exists
|
|
13
|
+
|
|
14
|
+
WebGPU is verbose but not complicated. oria-webgpu removes the repetitive parts —
|
|
15
|
+
adapter/device setup, buffer sizing, pipeline descriptor boilerplate, render-pass
|
|
16
|
+
lifecycle — while keeping every native WebGPU concept (`GPUDevice`, `GPUBuffer`,
|
|
17
|
+
`GPURenderPipeline`, ...) visible and reachable. You should still think in WebGPU
|
|
18
|
+
terms; you just write less of it.
|
|
19
|
+
|
|
20
|
+
## Status
|
|
21
|
+
|
|
22
|
+
v0.1 (in progress). Implemented so far: `createGPU`, buffers (including a
|
|
23
|
+
`storageBuffer` shorthand), textures (including upload from
|
|
24
|
+
ImageBitmap/canvas/VideoFrame and a canvas-sized `depthTexture` helper),
|
|
25
|
+
samplers, render pipelines, compute pipelines with a shared pipeline cache,
|
|
26
|
+
the render-pass/compute-pass builders, WGSL binding reflection with named bind
|
|
27
|
+
groups, and GPU buffer readback (`gpu.readBuffer`).
|
|
28
|
+
|
|
29
|
+
Verified rendering correctly in a real WebGPU browser (Chromium, headed —
|
|
30
|
+
WebGPU needs actual GPU access, which headless mode on this platform doesn't
|
|
31
|
+
grant): correct triangle geometry, correct per-vertex color interpolation, a
|
|
32
|
+
live uniform-driven rotation, and a working WebGPU-vs-WebGL2 instanced-draw
|
|
33
|
+
comparison. Only unit-tested logic had been checked before that; there is no
|
|
34
|
+
CI/Playwright suite running this yet (see Limitations).
|
|
35
|
+
|
|
36
|
+
## Demo
|
|
37
|
+
|
|
38
|
+
**[cheonghakim.github.io/oria-webgpu](https://cheonghakim.github.io/oria-webgpu/)** — or run it locally:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install
|
|
42
|
+
npm run demo
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Two tabs: **Usage** — a small rotating triangle with the exact running
|
|
46
|
+
oria-webgpu code shown alongside it (the panel is that function's own source, so
|
|
47
|
+
it can't drift from what's actually executing) — and **WebGPU vs WebGL** — the
|
|
48
|
+
same per-instance motion rendered by both APIs side by side, with live fps and
|
|
49
|
+
CPU submit-time readouts. See [examples/demo](examples/demo).
|
|
50
|
+
|
|
51
|
+
At low instance counts both sides just idle at the display's vsync fps — that's
|
|
52
|
+
not "no difference," it's not enough load to mean anything. Above ~150,000
|
|
53
|
+
instances the per-fragment shader work is heavy enough to genuinely exceed
|
|
54
|
+
vsync, and at that point both APIs land at essentially the same fps: a single
|
|
55
|
+
steady-state instanced draw call is already near-optimal on a mature WebGL2
|
|
56
|
+
driver, so this particular workload isn't where WebGPU's real advantages (many
|
|
57
|
+
draw calls / state changes, compute, lower driver overhead) would show up.
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install oria-webgpu
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createGPU } from "oria-webgpu"
|
|
67
|
+
|
|
68
|
+
const gpu = await createGPU(canvas)
|
|
69
|
+
|
|
70
|
+
const pipeline = gpu.renderPipeline({
|
|
71
|
+
vertex: { code: shaderWGSL, entryPoint: "vsMain" },
|
|
72
|
+
fragment: { code: shaderWGSL, entryPoint: "fsMain", targets: [gpu.format] },
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
gpu.render((pass) => {
|
|
76
|
+
pass.pipeline(pipeline).draw(3)
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or clone this repo and run the examples directly, no separate project needed:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install
|
|
84
|
+
npm run example:triangle
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Bindings
|
|
88
|
+
|
|
89
|
+
Named resources are resolved to bind groups using a small WGSL reflection pass
|
|
90
|
+
over your shader's `@group`/`@binding` declarations — no manual
|
|
91
|
+
`GPUBindGroupLayout`/`GPUBindGroup` wiring for the common case:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const pipeline = gpu.renderPipeline({
|
|
95
|
+
vertex: { code: shaderWGSL, entryPoint: "vsMain" },
|
|
96
|
+
fragment: { code: shaderWGSL, entryPoint: "fsMain", targets: [gpu.format] },
|
|
97
|
+
bindings: {
|
|
98
|
+
camera: cameraBuffer, // matches `@group(0) @binding(0) var<uniform> camera: Camera;`
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Bindings can also be (re)resolved later, or per-pass:
|
|
103
|
+
pipeline.bindings({ camera: cameraBuffer })
|
|
104
|
+
gpu.render((pass) => {
|
|
105
|
+
pass.pipeline(pipeline).bindings({ camera: cameraBuffer }).draw(3)
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A `@group` is only built once every binding it declares has been supplied — set
|
|
110
|
+
them together, or incrementally across calls. This is a small, predictable
|
|
111
|
+
parser (see `reflectWGSLBindings`), not a full WGSL grammar; anything it can't
|
|
112
|
+
confidently classify is skipped. You can always fall back to a native
|
|
113
|
+
`device.createBindGroup(...)` and `pass.raw.setBindGroup(...)`.
|
|
114
|
+
|
|
115
|
+
## Pipelines are cached
|
|
116
|
+
|
|
117
|
+
`gpu.renderPipeline(...)`/`gpu.computePipeline(...)` are cached per distinct
|
|
118
|
+
descriptor — calling them again with the same shader source, entry points,
|
|
119
|
+
vertex layout, targets, and format returns the *same* pipeline object rather
|
|
120
|
+
than asking WebGPU to recompile it. `bindings` isn't part of that identity (it's
|
|
121
|
+
resolved and reapplied on every call, cache hit or miss), so it's normal to call
|
|
122
|
+
`gpu.renderPipeline({...})` every frame with fresh binding resources. Call
|
|
123
|
+
`gpu.clearPipelineCache()` if you need to force a rebuild.
|
|
124
|
+
|
|
125
|
+
## Compute readback
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const particles = gpu.storageBuffer({ data }) // STORAGE | COPY_DST | COPY_SRC by default
|
|
129
|
+
|
|
130
|
+
gpu.compute((pass) => {
|
|
131
|
+
pass.pipeline(pipeline).bindings({ particles }).dispatch(particles.size / 4 / 64)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const result = await gpu.readBuffer(particles, Float32Array)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`readBuffer` copies the buffer into a staging buffer, maps it, copies the data
|
|
138
|
+
out, and cleans the staging buffer up. The source buffer needs `COPY_SRC` usage
|
|
139
|
+
(`storageBuffer()` includes it by default).
|
|
140
|
+
|
|
141
|
+
## Escape hatches
|
|
142
|
+
|
|
143
|
+
Every wrapped object exposes its native WebGPU counterpart:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
gpu.device // GPUDevice
|
|
147
|
+
gpu.context // GPUCanvasContext
|
|
148
|
+
buffer.raw // GPUBuffer
|
|
149
|
+
pipeline.raw // GPURenderPipeline
|
|
150
|
+
pass.raw // GPURenderPassEncoder (inside gpu.render callbacks)
|
|
151
|
+
|
|
152
|
+
gpu.command((encoder) => {
|
|
153
|
+
// full native WebGPU command encoder
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Limitations (v0.1)
|
|
158
|
+
|
|
159
|
+
- `gpu.render()` doesn't support a multisample resolve target yet — a pipeline's
|
|
160
|
+
`multisample` option is exposed, but there's no way to give the pass helper a
|
|
161
|
+
multisampled attachment + resolve target, so MSAA isn't actually reachable
|
|
162
|
+
through it (only via the `gpu.command()` escape hatch, built fully by hand).
|
|
163
|
+
Found via the demo: WebGL2's canvas defaults to `antialias: true`, so an
|
|
164
|
+
unfair comparison would silently give WebGL free MSAA smoothing that WebGPU
|
|
165
|
+
wasn't also getting — fixed there by passing `antialias: false` to match.
|
|
166
|
+
- WGSL reflection is a small pattern-based parser, not a full grammar — see
|
|
167
|
+
[src/shader/reflection.ts](src/shader/reflection.ts) for exactly what it recognizes.
|
|
168
|
+
- A `@group` that never gets all of its declared bindings produces no bind group;
|
|
169
|
+
WebGPU's own validation (not Oria's) reports that at draw/dispatch time.
|
|
170
|
+
- `depthTexture()` snapshots the canvas's current size — call it again after
|
|
171
|
+
`gpu.resize()` if you need it to track the canvas (no implicit auto-resize).
|
|
172
|
+
- No indexed-cube/texture/particles examples or Playwright integration tests yet.
|
|
173
|
+
- `storageBuffer()`/`buffer()` don't request elevated limits, so a buffer past
|
|
174
|
+
the *default* `maxStorageBufferBindingSize` (128 MiB) or `maxBufferSize`
|
|
175
|
+
(256 MiB) fails validation — as a warning in the console, not a thrown error,
|
|
176
|
+
so it's easy to miss. Pass `requiredLimits` to `createGPU(...)` (checked
|
|
177
|
+
against `adapter.limits` first) if you need larger buffers.
|
|
178
|
+
|
|
179
|
+
These land in subsequent phases — see the development plan for the full roadmap.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BindingReflection } from "../shader/reflection";
|
|
2
|
+
/** Groups flat reflection results by their @group index. */
|
|
3
|
+
export declare function groupByGroupIndex(reflection: BindingReflection[]): Map<number, BindingReflection[]>;
|
|
4
|
+
/** Merges reflection from multiple shader stages (e.g. vertex + fragment) by name. */
|
|
5
|
+
export declare function mergeReflection(...stages: BindingReflection[][]): BindingReflection[];
|
|
6
|
+
//# sourceMappingURL=bind-group-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-group-layout.d.ts","sourceRoot":"","sources":["../../src/binding/bind-group-layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAE7D,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAQnG;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,GAAG,MAAM,EAAE,iBAAiB,EAAE,EAAE,GAAG,iBAAiB,EAAE,CAQrF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BindingReflection } from "../shader/reflection";
|
|
2
|
+
import type { BindingResource } from "../types/public";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves named resources to GPUBindGroups for one pipeline, using its WGSL
|
|
5
|
+
* reflection and the pipeline's own auto-generated bind group layouts.
|
|
6
|
+
*
|
|
7
|
+
* State accumulates across calls: setting `camera` now and `texSampler`/`tex`
|
|
8
|
+
* later keeps `camera` in that @group's bind group. A group is only (re)built
|
|
9
|
+
* once every binding it declares has been supplied at least once; until then
|
|
10
|
+
* `apply()` just caches what it was given and waits. A group that never gets
|
|
11
|
+
* completed produces no entry in `boundGroups` — WebGPU's own validation will
|
|
12
|
+
* report a missing bind group if you draw/dispatch with the pipeline anyway.
|
|
13
|
+
*/
|
|
14
|
+
export declare class BindGroupResolver {
|
|
15
|
+
private readonly device;
|
|
16
|
+
private readonly getBindGroupLayout;
|
|
17
|
+
private readonly byName;
|
|
18
|
+
private readonly byGroup;
|
|
19
|
+
private readonly resourceCache;
|
|
20
|
+
private readonly groupCache;
|
|
21
|
+
constructor(device: GPUDevice, getBindGroupLayout: (group: number) => GPUBindGroupLayout, reflection: BindingReflection[]);
|
|
22
|
+
get boundGroups(): ReadonlyMap<number, GPUBindGroup>;
|
|
23
|
+
apply(namedResources: Record<string, BindingResource>): void;
|
|
24
|
+
private buildGroup;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=bind-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind-group.d.ts","sourceRoot":"","sources":["../../src/binding/bind-group.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,sBAAsB,CAAA;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAsD,MAAM,iBAAiB,CAAA;AAyG1G;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB;IAO1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAPrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;gBAG1C,MAAM,EAAE,SAAS,EACjB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,kBAAkB,EAC1E,UAAU,EAAE,iBAAiB,EAAE;IAMjC,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAEnD;IAED,KAAK,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI;IAgB5D,OAAO,CAAC,UAAU;CAcnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-encoder.d.ts","sourceRoot":"","sources":["../../src/commands/command-encoder.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,GACvC,IAAI,CAIN"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CreateGPUOptions } from "../types/public";
|
|
2
|
+
export interface ConfiguredContext {
|
|
3
|
+
context: GPUCanvasContext;
|
|
4
|
+
format: GPUTextureFormat;
|
|
5
|
+
}
|
|
6
|
+
export declare function configureCanvasContext(canvas: HTMLCanvasElement, device: GPUDevice, options: CreateGPUOptions): ConfiguredContext;
|
|
7
|
+
export declare function resizeCanvas(canvas: HTMLCanvasElement, width?: number, height?: number, pixelRatio?: number): void;
|
|
8
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,gBAAgB,GACxB,iBAAiB,CAkBnB;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,iBAAiB,EACzB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,SAA8D,GACvE,IAAI,CAKN"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CreateGPUOptions } from "../types/public";
|
|
2
|
+
export interface RequestedDevice {
|
|
3
|
+
adapter: GPUAdapter;
|
|
4
|
+
device: GPUDevice;
|
|
5
|
+
}
|
|
6
|
+
export declare function requestDevice(options: CreateGPUOptions): Promise<RequestedDevice>;
|
|
7
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../src/core/device.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE,SAAS,CAAA;CAClB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAoCvF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpu.d.ts","sourceRoot":"","sources":["../../src/core/gpu.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAMV,gBAAgB,EAEhB,UAAU,EAWX,MAAM,iBAAiB,CAAA;AA2KxB,wBAAsB,SAAS,CAC7B,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,UAAU,CAAC,CAOrB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createGPU } from "./core/gpu";
|
|
2
|
+
export { OriaError } from "./utils/validation";
|
|
3
|
+
export { alignTo, alignBufferSize } from "./utils/alignment";
|
|
4
|
+
export { reflectWGSLBindings } from "./shader/reflection";
|
|
5
|
+
export type { BindingResource, Buffer, BufferDescriptor, ComputePassBuilder, ComputePipeline, ComputePipelineDescriptor, CreateGPUOptions, DepthTextureDescriptor, FragmentState, GPUContext, RenderPassBuilder, RenderPipeline, RenderPipelineDescriptor, RenderTargetOptions, ResizeOptions, StorageBufferDescriptor, Texture, TextureDescriptor, TextureFromSourceDescriptor, TextureSource, TypedArrayConstructor, VertexState, } from "./types/public";
|
|
6
|
+
export type { BindingReflection, ResourceCategory } from "./shader/reflection";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,YAAY,EACV,eAAe,EACf,MAAM,EACN,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,uBAAuB,EACvB,OAAO,EACP,iBAAiB,EACjB,2BAA2B,EAC3B,aAAa,EACb,qBAAqB,EACrB,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA"}
|