@volter/blender-engine 0.1.49 → 0.1.50
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.
|
@@ -198,6 +198,21 @@ export async function startEmscriptenBlenderEngine(
|
|
|
198
198
|
if (text.startsWith('@@VOLTER-READY ')) readyLine = text.slice('@@VOLTER-READY '.length);
|
|
199
199
|
options.log(sessionLevel(text), text);
|
|
200
200
|
};
|
|
201
|
+
// THE HEAP ITSELF, not a view of it. Blender runs on a pthread, so when its heap grows there
|
|
202
|
+
// this worker's `module.HEAPU8` keeps the length it had until the glue next refreshes it -- and
|
|
203
|
+
// no export the door calls does. MEASURED 2026-09-29 on the Stoneguard scene from
|
|
204
|
+
// game-benchmarks main: a mesh's columns at 1.14 GB were read through a 433 MB view, and the
|
|
205
|
+
// pull failed "outside the export arena". The glue builds its memory with `new
|
|
206
|
+
// WebAssembly.Memory` and exports no handle to it, so that constructor is observed while the
|
|
207
|
+
// factory runs; every read below takes a fresh view of `memory.buffer`.
|
|
208
|
+
const Memory = WebAssembly.Memory;
|
|
209
|
+
let memory: WebAssembly.Memory | null = null;
|
|
210
|
+
(WebAssembly as { Memory: unknown }).Memory = function (descriptor: WebAssembly.MemoryDescriptor) {
|
|
211
|
+
const made = new Memory(descriptor);
|
|
212
|
+
memory ??= made;
|
|
213
|
+
return made;
|
|
214
|
+
} as unknown as typeof WebAssembly.Memory;
|
|
215
|
+
const heap = (): Uint8Array => (memory ? new Uint8Array(memory.buffer) : module.HEAPU8);
|
|
201
216
|
const module = await factory({
|
|
202
217
|
arguments: ['--background', '--factory-startup', '--python', SESSION_SCRIPT],
|
|
203
218
|
locateFile: (file: string) => artifactUrl(file),
|
|
@@ -253,6 +268,8 @@ export async function startEmscriptenBlenderEngine(
|
|
|
253
268
|
);
|
|
254
269
|
},
|
|
255
270
|
],
|
|
271
|
+
}).finally(() => {
|
|
272
|
+
(WebAssembly as { Memory: unknown }).Memory = Memory;
|
|
256
273
|
});
|
|
257
274
|
// `main()` runs on a pthread (`-sPROXY_TO_PTHREAD`), so the factory resolves
|
|
258
275
|
// long before the session exists. The ready line is what says it does.
|
|
@@ -329,13 +346,15 @@ export async function startEmscriptenBlenderEngine(
|
|
|
329
346
|
// A VIEW, NOT A COPY. The arena is wasm linear memory and under
|
|
330
347
|
// `-sPROXY_TO_PTHREAD` that memory is SHARED, so `session-frame.mts`
|
|
331
348
|
// slices every column it reads out of here; this call itself is free.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
349
|
+
// A DIRECT ARENA (address 0) IS THE WHOLE HEAP, at its length now: the size the door reports
|
|
350
|
+
// is `emscripten_get_heap_size()`, which is Blender's pthread's own view of the heap and is as
|
|
351
|
+
// stale as this worker's was (measured: 431 MB while a column lay at 2.0 GB).
|
|
352
|
+
readArena: async () => {
|
|
353
|
+
const base = module._blender_web_export_buffer();
|
|
354
|
+
return base === 0 ? heap() : heap().subarray(base, base + module._blender_web_export_buffer_size());
|
|
355
|
+
},
|
|
337
356
|
bootMs,
|
|
338
|
-
memoryBytes: () =>
|
|
357
|
+
memoryBytes: () => heap().length,
|
|
339
358
|
releasedPayloadBytes,
|
|
340
359
|
};
|
|
341
360
|
}
|
package/browser/session.py
CHANGED
|
@@ -162,6 +162,8 @@ def _mark(label):
|
|
|
162
162
|
MEMORY_MARKS.append([label] + [round(reading.get(key, 0) / 1048576)
|
|
163
163
|
for key in ("heap", "mallocInUse", "mallocFree", "guarded", "guardedPeak")])
|
|
164
164
|
del MEMORY_MARKS[:-64]
|
|
165
|
+
# Also said, so an engine that dies of memory names in its last output the stage it reached.
|
|
166
|
+
_say("@@VOLTER-MEMORY %s heap=%d inUse=%d free=%d" % tuple(MEMORY_MARKS[-1][:4]))
|
|
165
167
|
_real_stderr = sys.stderr
|
|
166
168
|
|
|
167
169
|
|
|
@@ -1611,7 +1613,7 @@ class Session:
|
|
|
1611
1613
|
images = [name for name, image in frame["images"].items() if image.get("deferred")]
|
|
1612
1614
|
if not meshes and not images:
|
|
1613
1615
|
return False
|
|
1614
|
-
|
|
1616
|
+
_asked({"hold": frame})
|
|
1615
1617
|
options = {"buffer_path": EXPORT_BUFFER_PATH} if EXPORT_BUFFER_PATH else {}
|
|
1616
1618
|
for kind, keys, door in (("mesh", meshes, _blender_web.export_mesh),
|
|
1617
1619
|
("image", images, _blender_web.export_image)):
|
|
@@ -1626,7 +1628,7 @@ class Session:
|
|
|
1626
1628
|
if piece[kind] is None:
|
|
1627
1629
|
del frame["images"][key]
|
|
1628
1630
|
continue
|
|
1629
|
-
|
|
1631
|
+
_asked({kind: key, "piece": piece[kind]})
|
|
1630
1632
|
return True
|
|
1631
1633
|
|
|
1632
1634
|
def _send_encoded_image(self, key, deferred):
|
|
@@ -1666,7 +1668,7 @@ class Session:
|
|
|
1666
1668
|
with open(path, "wb") as fh:
|
|
1667
1669
|
fh.write(data)
|
|
1668
1670
|
try:
|
|
1669
|
-
|
|
1671
|
+
_asked({"image": key, "piece": {
|
|
1670
1672
|
"revision": deferred["revision"],
|
|
1671
1673
|
"mime": mime,
|
|
1672
1674
|
"colorspace": "sRGB" if image.colorspace_settings.name == "sRGB" else "data",
|
|
@@ -2014,6 +2016,17 @@ def _pause(since):
|
|
|
2014
2016
|
time.sleep(0.0002 if time.monotonic() - since < _BRISK_SECONDS else 0.002)
|
|
2015
2017
|
|
|
2016
2018
|
|
|
2019
|
+
def _asked(payload):
|
|
2020
|
+
"""`ask`, failing as the tab failed: a piece the worker could not take comes back as
|
|
2021
|
+
`{"error": ...}`, and a pull that read past it presented a frame missing that piece, refused
|
|
2022
|
+
later under another name ("deferred and never pulled")."""
|
|
2023
|
+
answer = ask(payload)
|
|
2024
|
+
if isinstance(answer, dict) and answer.get("error"):
|
|
2025
|
+
raise RuntimeError("The tab refused %s: %s" % (
|
|
2026
|
+
next((key for key in ("mesh", "image", "hold") if key in payload), "an ask"), answer["error"]))
|
|
2027
|
+
return answer
|
|
2028
|
+
|
|
2029
|
+
|
|
2017
2030
|
def ask(payload):
|
|
2018
2031
|
"""Block until the tab answers. The worker's own thread is free while this
|
|
2019
2032
|
waits; only the Blender pthread is held, which is where the operator is.
|