@daugasauron/dolly-host-gpu 0.1.4 → 0.1.5-rc.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/abi.mjs +1 -1
- package/gpu.mjs +3 -3
- package/package.json +2 -2
- package/worker.mjs +38 -21
package/abi.mjs
CHANGED
|
@@ -47,4 +47,4 @@ export const DOLLY_GPU_END_RENDER_PASS = 24;
|
|
|
47
47
|
export const DOLLY_GPU_DRAW_MESH = 25;
|
|
48
48
|
export const DOLLY_GPU_VIEWPORT = 26;
|
|
49
49
|
export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
|
|
50
|
-
export const DOLLY_GPU_ABI_DIGEST = "
|
|
50
|
+
export const DOLLY_GPU_ABI_DIGEST = "f2798b5850f46ad4287cdfd82fe36970af78c5184acd6cbefdded38f49444259";
|
package/gpu.mjs
CHANGED
|
@@ -37,11 +37,12 @@ function announce({ indicators }, element, state, borderColor) {
|
|
|
37
37
|
function indicate(page, info, unavailable) {
|
|
38
38
|
let element = document.querySelector("#gpu-status");
|
|
39
39
|
if (!element) {
|
|
40
|
-
element
|
|
40
|
+
// In Dolly's element; a display-less embedding has none, and the indicator shows nowhere.
|
|
41
|
+
element = (page.mount ?? document.createElement("div")).appendChild(document.createElement("div"));
|
|
41
42
|
element.id = "gpu-status";
|
|
42
43
|
element.className = "page-indicator";
|
|
43
44
|
element.setAttribute("role", "status");
|
|
44
|
-
element.style.cssText = "position:
|
|
45
|
+
element.style.cssText = "position:absolute;right:0.5rem;bottom:2rem;z-index:8;max-width:calc(100% - 1rem);" +
|
|
45
46
|
"padding:0.15rem 0.45rem;background:#262626;border:1px solid;font-size:13px;pointer-events:none";
|
|
46
47
|
}
|
|
47
48
|
if (unavailable) {
|
|
@@ -78,7 +79,6 @@ export function browser(page) {
|
|
|
78
79
|
let status = {};
|
|
79
80
|
return {
|
|
80
81
|
configuration: { canvas: surface }, transfers: surface ? [surface] : [],
|
|
81
|
-
get status() { return status; },
|
|
82
82
|
page: { get gpu() { return status; } },
|
|
83
83
|
get surfaceSize() { return status.active ? { width: status.width, height: status.height } : undefined; },
|
|
84
84
|
// The provider reports the adapter of each device it creates, or why it has none.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.5-rc.0",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"name": "@daugasauron/dolly-host-gpu",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@daugasauron/dolly": "0.1.
|
|
7
|
+
"@daugasauron/dolly": "0.1.5-rc.0"
|
|
8
8
|
},
|
|
9
9
|
"description": "Dolly's gpu@0 host module: WebGPU on the browser's adapter, over the display.",
|
|
10
10
|
"repository": {
|
package/worker.mjs
CHANGED
|
@@ -21,6 +21,28 @@ const maxBytes = 4 * 1024 ** 3, bufferCeiling = 1024 ** 3, maxObjects = 4096;
|
|
|
21
21
|
const queueFull = scope => scope.surface && scope.frames.length >= 3 ? scope.frames[0]
|
|
22
22
|
: scope.submissions.length >= 64 ? scope.submissions[0] : null;
|
|
23
23
|
const timestampQueries = A.DOLLY_GPU_MAX_COMMANDS * 2;
|
|
24
|
+
// A batch record's byte length: exact for one of fixed layout, the least for
|
|
25
|
+
// one with a variable tail, which records() checks.
|
|
26
|
+
const fixedRecordBytes = new Map([[A.DOLLY_GPU_CREATE_BUFFER, 32], [A.DOLLY_GPU_RENDER, 64],
|
|
27
|
+
[A.DOLLY_GPU_COMPUTE, 40], [A.DOLLY_GPU_COPY_BUFFER, 48], [A.DOLLY_GPU_MAP_READ, 32], [A.DOLLY_GPU_UNMAP, 16],
|
|
28
|
+
[A.DOLLY_GPU_RELEASE, 16], [A.DOLLY_GPU_SUBMIT, 8], [A.DOLLY_GPU_RENDER_VERTEX, 88],
|
|
29
|
+
[A.DOLLY_GPU_CAPTURE_FRAME, 32], [A.DOLLY_GPU_CREATE_TEXTURE, 48], [A.DOLLY_GPU_CREATE_SAMPLER, 48],
|
|
30
|
+
[A.DOLLY_GPU_BEGIN_RENDER_PASS, 64], [A.DOLLY_GPU_END_RENDER_PASS, 8], [A.DOLLY_GPU_VIEWPORT, 48]]);
|
|
31
|
+
const leastRecordBytes = new Map([[A.DOLLY_GPU_WRITE_BUFFER, 32], [A.DOLLY_GPU_CREATE_SHADER, 24],
|
|
32
|
+
[A.DOLLY_GPU_RENDER_PIPELINE, 40], [A.DOLLY_GPU_COMPUTE_PIPELINE, 32], [A.DOLLY_GPU_BIND_GROUP, 32],
|
|
33
|
+
[A.DOLLY_GPU_VERTEX_PIPELINE, 48], [A.DOLLY_GPU_COMPUTE_CONSTANTS, 32], [A.DOLLY_GPU_WRITE_TEXTURE, 48],
|
|
34
|
+
[A.DOLLY_GPU_GRAPHICS_PIPELINE, 112], [A.DOLLY_GPU_RESOURCE_GROUP, 32], [A.DOLLY_GPU_DRAW_MESH, 80]]);
|
|
35
|
+
// The records a render pass may hold, between BEGIN_RENDER_PASS and END_RENDER_PASS.
|
|
36
|
+
const renderPassRecords = new Set([A.DOLLY_GPU_CREATE_BUFFER, A.DOLLY_GPU_WRITE_BUFFER, A.DOLLY_GPU_CREATE_SHADER,
|
|
37
|
+
A.DOLLY_GPU_CREATE_TEXTURE, A.DOLLY_GPU_CREATE_SAMPLER, A.DOLLY_GPU_GRAPHICS_PIPELINE, A.DOLLY_GPU_RESOURCE_GROUP,
|
|
38
|
+
A.DOLLY_GPU_END_RENDER_PASS, A.DOLLY_GPU_DRAW_MESH, A.DOLLY_GPU_VIEWPORT]);
|
|
39
|
+
// The records that create an object, charged to the scope's object quota before they run.
|
|
40
|
+
const creatingRecords = new Set([A.DOLLY_GPU_CREATE_BUFFER, A.DOLLY_GPU_CREATE_SHADER, A.DOLLY_GPU_RENDER_PIPELINE,
|
|
41
|
+
A.DOLLY_GPU_COMPUTE_PIPELINE, A.DOLLY_GPU_BIND_GROUP, A.DOLLY_GPU_VERTEX_PIPELINE, A.DOLLY_GPU_COMPUTE_CONSTANTS,
|
|
42
|
+
A.DOLLY_GPU_CREATE_TEXTURE, A.DOLLY_GPU_CREATE_SAMPLER, A.DOLLY_GPU_GRAPHICS_PIPELINE, A.DOLLY_GPU_RESOURCE_GROUP]);
|
|
43
|
+
// A request's byte length, header included; BATCH's is its records'.
|
|
44
|
+
const requestBytes = new Map([[A.DOLLY_GPU_OPEN, 40], [A.DOLLY_GPU_WAIT, 32], [A.DOLLY_GPU_READ, 56],
|
|
45
|
+
[A.DOLLY_GPU_CLOSE, 32], [A.DOLLY_GPU_INFO, 32], [A.DOLLY_GPU_CAPABILITIES, 32]]);
|
|
24
46
|
let maxBuffer = bufferCeiling, capabilities;
|
|
25
47
|
const slots = Array(A.DOLLY_GPU_SLOTS).fill(null), generations = slots.map(() => 0);
|
|
26
48
|
let heap, memory, mailbox, control, canvas, context, device, format;
|
|
@@ -95,7 +117,7 @@ function header(bytes) {
|
|
|
95
117
|
ensure(bytes.length >= 32, "Short GPU packet");
|
|
96
118
|
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
97
119
|
const scope = integer(v, 8), sequence = integer(v, 16);
|
|
98
|
-
ensure(v.getUint32(0, true) ===
|
|
120
|
+
ensure(v.getUint32(0, true) === A.DOLLY_GPU_VERSION && v.getUint32(28, true) === 0 &&
|
|
99
121
|
v.getUint32(24, true) === bytes.length - 32 && scope > 0 && scope <= 0xffffffff &&
|
|
100
122
|
sequence > 0 && sequence <= 0xffffffff, "Invalid GPU header");
|
|
101
123
|
return { op: v.getUint32(4, true), scope, sequence, index: (scope - 1) % slots.length, bytes, v };
|
|
@@ -117,24 +139,20 @@ function records(request) {
|
|
|
117
139
|
const opcode = v.getUint32(offset, true), size = v.getUint32(offset + 4, true);
|
|
118
140
|
ensure(size >= 8 && size % 8 === 0 && size <= bytes.length - offset, "Invalid GPU command length");
|
|
119
141
|
const b = bytes.subarray(offset, offset + size), w = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else {
|
|
123
|
-
const minimum = { 2: 32, 3: 24, 4: 40, 5: 32, 6: 32, 14: 48, 16: 32, 19: 48, 21: 112, 22: 32, 25: 80 }[opcode];
|
|
124
|
-
ensure(minimum && size >= minimum, "Unsupported GPU command", E.ENOTSUP);
|
|
125
|
-
}
|
|
142
|
+
if (fixedRecordBytes.has(opcode)) ensure(size === fixedRecordBytes.get(opcode), "Wrong GPU command layout");
|
|
143
|
+
else ensure(leastRecordBytes.has(opcode) && size >= leastRecordBytes.get(opcode), "Unsupported GPU command", E.ENOTSUP);
|
|
126
144
|
// Validate every variable-length span before any command has side effects.
|
|
127
|
-
if (opcode ===
|
|
145
|
+
if (opcode === A.DOLLY_GPU_WRITE_BUFFER) {
|
|
128
146
|
const at = w.getUint32(24, true), n = w.getUint32(28, true);
|
|
129
147
|
ensure(at >= 32 && at <= size - n && n % 4 === 0, "Invalid GPU upload span");
|
|
130
|
-
} else if (opcode ===
|
|
148
|
+
} else if (opcode === A.DOLLY_GPU_CREATE_SHADER) {
|
|
131
149
|
ensure(w.getUint32(20, true) === 0, "Shader reserved field");
|
|
132
150
|
text(b, 24, w.getUint32(16, true));
|
|
133
|
-
} else if (opcode ===
|
|
151
|
+
} else if (opcode === A.DOLLY_GPU_RENDER_PIPELINE || opcode === A.DOLLY_GPU_VERTEX_PIPELINE) {
|
|
134
152
|
const vs = w.getUint32(32, true), fs = w.getUint32(36, true);
|
|
135
153
|
ensure(vs <= 64 && fs <= 64, "GPU entry name too long");
|
|
136
154
|
let at = 40;
|
|
137
|
-
if (opcode ===
|
|
155
|
+
if (opcode === A.DOLLY_GPU_VERTEX_PIPELINE) {
|
|
138
156
|
const count = w.getUint32(44, true), stride = w.getUint32(40, true);
|
|
139
157
|
ensure(count > 0 && count <= 8 && stride > 0 && stride <= 2048 && stride % 4 === 0 && size >= 48 + count * 16, "Vertex layout");
|
|
140
158
|
const locations = new Set();
|
|
@@ -146,10 +164,10 @@ function records(request) {
|
|
|
146
164
|
at = 48 + count * 16;
|
|
147
165
|
}
|
|
148
166
|
text(b, at, vs); text(b, at + vs, fs);
|
|
149
|
-
} else if (opcode ===
|
|
167
|
+
} else if (opcode === A.DOLLY_GPU_COMPUTE_PIPELINE) {
|
|
150
168
|
ensure(w.getUint32(28, true) === 0 && w.getUint32(24, true) <= 64, "Compute entry name");
|
|
151
169
|
text(b, 32, w.getUint32(24, true));
|
|
152
|
-
} else if (opcode ===
|
|
170
|
+
} else if (opcode === A.DOLLY_GPU_COMPUTE_CONSTANTS) {
|
|
153
171
|
const entryBytes = w.getUint32(24,true), count = w.getUint32(28,true);
|
|
154
172
|
ensure(entryBytes <= 64 && count <= 16, "Compute constants layout");
|
|
155
173
|
text(b,32,entryBytes);
|
|
@@ -164,7 +182,7 @@ function records(request) {
|
|
|
164
182
|
at=(at+16+n+7)&~7;
|
|
165
183
|
}
|
|
166
184
|
ensure(at===size,"Trailing compute constant bytes");
|
|
167
|
-
} else if (opcode ===
|
|
185
|
+
} else if (opcode === A.DOLLY_GPU_BIND_GROUP) {
|
|
168
186
|
const n = w.getUint32(24, true);
|
|
169
187
|
ensure(n <= A.DOLLY_GPU_MAX_BINDINGS && size === 32 + n * 24 && w.getUint32(28, true) === 0, "Bind group layout");
|
|
170
188
|
}
|
|
@@ -288,13 +306,12 @@ async function batch(scope, commands) {
|
|
|
288
306
|
for (const { opcode: op, b, w } of commands) {
|
|
289
307
|
const id = b.length >= 16 && op !== A.DOLLY_GPU_VIEWPORT ? integer(w, 8) : 0;
|
|
290
308
|
if (op !== A.DOLLY_GPU_COMPUTE) endCompute();
|
|
291
|
-
if (renderPass) ensure(
|
|
292
|
-
if (
|
|
309
|
+
if (renderPass) ensure(renderPassRecords.has(op), "Command is invalid inside a render pass");
|
|
310
|
+
if (creatingRecords.has(op)) ensure(id > scope.high && id <= 0xffffffff && scope.objects.size + scope.retired.size < maxObjects, "GPU object quota", E.ENOSPC);
|
|
293
311
|
if (op === A.DOLLY_GPU_CREATE_BUFFER) {
|
|
294
312
|
const size = integer(w, 16), usage = w.getUint32(24, true);
|
|
295
313
|
ensure(size > 0 && size <= maxBuffer && size <= maxBytes - usedBytes, "GPU allocation quota", E.ENOMEM);
|
|
296
314
|
ensure(w.getUint32(28, true) === 0 && usage > 0 && (usage & ~1023) === 0, "Buffer usage");
|
|
297
|
-
ensure(id > scope.high && scope.objects.size + scope.retired.size < maxObjects, "GPU resource quota", E.ENOSPC);
|
|
298
315
|
const buffer = device.createBuffer({ size, usage });
|
|
299
316
|
insert(scope, id, "buffer", buffer, size); usedBytes += size;
|
|
300
317
|
} else if (op === A.DOLLY_GPU_WRITE_BUFFER) {
|
|
@@ -686,11 +703,11 @@ self.onmessage = event => {
|
|
|
686
703
|
} else {
|
|
687
704
|
ensure(Number.isSafeInteger(address)&&Number.isSafeInteger(size)&&address>0&&size>=32&&size<=A.DOLLY_GPU_PACKET_BYTES&&address<=memory.byteLength-size,"Invalid GPU request span",E.EFAULT);
|
|
688
705
|
const request=header(new Uint8Array(memory,address,size).slice());
|
|
689
|
-
ensure(request.op>=
|
|
690
|
-
ensure((request.op
|
|
691
|
-
const parsed=request.op===
|
|
706
|
+
ensure(request.op>=A.DOLLY_GPU_OPEN&&request.op<=A.DOLLY_GPU_CAPABILITIES,"Unknown GPU operation",E.ENOTSUP);
|
|
707
|
+
ensure(!requestBytes.has(request.op)||requestBytes.get(request.op)===size,"GPU operation layout");
|
|
708
|
+
const parsed=request.op===A.DOLLY_GPU_BATCH?records(request):null;
|
|
692
709
|
let scope=slots[request.index];
|
|
693
|
-
if(request.op===
|
|
710
|
+
if(request.op===A.DOLLY_GPU_OPEN) {
|
|
694
711
|
ensure(!scope&&request.scope>generations[request.index],"GPU scope slot is busy",E.EBUSY);
|
|
695
712
|
scope={id:request.scope,index:request.index,sequence:0,objects:new Map(),retired:new Map(),high:0,submissions:[],frames:[],lost:false,surface:false};
|
|
696
713
|
generations[request.index]=scope.id;slots[request.index]=scope;
|