@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.
Files changed (4) hide show
  1. package/abi.mjs +1 -1
  2. package/gpu.mjs +3 -3
  3. package/package.json +2 -2
  4. 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 = "c947036666d5532b78179512c852d1fd58a143e067ca8c947055072ac7fc104a";
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 = document.body.appendChild(document.createElement("div"));
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:fixed;right:0.5rem;bottom:2rem;z-index:8;max-width:calc(100vw - 1rem);" +
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.4",
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.4"
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) === 0 && v.getUint32(28, true) === 0 &&
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
- const fixed = { 1: 32, 7: 64, 8: 40, 9: 48, 10: 32, 11: 16, 12: 16, 13: 8, 15: 88, 17: 32, 18: 48, 20: 48, 23: 64, 24: 8, 26: 48 }[opcode];
121
- if (fixed) ensure(size === fixed, "Wrong GPU command layout");
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 === 2) {
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 === 3) {
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 === 4 || opcode === 14) {
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 === 14) {
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 === 5) {
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 === 16) {
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 === 6) {
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([1,2,3,18,20,21,22,24,25,26].includes(op), "Command is invalid inside a render pass");
292
- if ([1,3,4,5,6,14,16,18,20,21,22].includes(op)) ensure(id > scope.high && id <= 0xffffffff && scope.objects.size + scope.retired.size < maxObjects, "GPU object quota", E.ENOSPC);
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>=1&&request.op<=7,"Unknown GPU operation",E.ENOTSUP);
690
- ensure((request.op!==1||size===40)&&(![3,5,6,7].includes(request.op)||size===32)&&(request.op!==4||size===56),"GPU operation layout");
691
- const parsed=request.op===2?records(request):null;
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===1) {
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;