Advance Blender 5.2 web parity through M12-03D
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-17 17:30:27 -04:00
parent 0fe8d2bb56
commit 5a11045ca5
148 changed files with 11683 additions and 66 deletions

View File

@@ -0,0 +1,26 @@
import { loadAssetCatalogIndexedDBSnapshot } from "../storage/asset-catalog-indexeddb";
const scope = self as unknown as {
onmessage: ((event: MessageEvent<{ databaseName: string }>) => void) | null;
postMessage: (value: unknown) => void;
};
function openDatabase(name: string): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error ?? new Error("Catalog restart database open failed"));
});
}
scope.onmessage = (event): void => {
void openDatabase(event.data.databaseName).then(async (database) => {
try {
return await loadAssetCatalogIndexedDBSnapshot(database);
}
finally {
database.close();
}
}).then((snapshot) => scope.postMessage({ ok: true, snapshot }))
.catch((error) => scope.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) }));
};

View File

@@ -0,0 +1,13 @@
import { displayAssetPreview } from "../assets/AssetPreviewDisplay";
const scope = self as unknown as {
onmessage: ((event: MessageEvent<{ identity: unknown; bytes: ArrayBuffer }>) => void) | null;
postMessage: (value: unknown, transfer?: Transferable[]) => void;
};
scope.onmessage = (event): void => {
const canvas = new OffscreenCanvas(1, 1);
void displayAssetPreview(canvas, "OFFSCREEN_CANVAS_2D", event.data.identity, event.data.bytes)
.then((result) => scope.postMessage({ ok: true, receipt: result.receipt, pixels: result.pixels }, [result.pixels.buffer]))
.catch((error) => scope.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) }));
};

View File

@@ -29,6 +29,12 @@ import {
gatePaintPBVHCapability,
PAINT_PBVH_WASM_ENTRYPOINT,
} from "../../../protocol/paint-pbvh-capability";
import {
createLibraryMainAppendReceipt,
parseLibraryMainAppendRequest,
sha256LibraryBytes,
type LibraryAppendClosureIR,
} from "../../../protocol/library-main-append";
type WasmModule = {
_malloc: (size: number) => number;
@@ -39,6 +45,7 @@ type WasmModule = {
_web_engine_get_allocated_bytes: () => number;
_web_engine_open_blend: (handle: number, data: number, length: number) => number;
_web_engine_apply_command: (handle: number, data: number, length: number) => number;
_web_engine_append_library_object: (handle: number, source: number, sourceLength: number, metadata: number, metadataLength: number) => number;
_web_engine_undo: (handle: number) => number;
_web_engine_redo: (handle: number) => number;
_web_engine_get_scene_snapshot: (handle: number, data: number, length: number) => number;
@@ -603,6 +610,30 @@ function nativeErrorFor(target: WasmModule, fallbackCode: ErrorReport["code"]):
return report(mappedCode, message);
}
function assertAppendedClosure(
snapshot: ReturnType<typeof parseSceneSnapshotIR>,
closure: LibraryAppendClosureIR,
): void {
const name = (value: string) => value.slice(value.indexOf("/") + 1);
const objectId = `object:${name(closure.object)}`;
const meshId = `mesh:${name(closure.mesh)}`;
const materialId = `material:${name(closure.material)}`;
const imageId = `image:${name(closure.image)}`;
const object = snapshot.nodes.find((item) => item.id === objectId);
const mesh = snapshot.meshes.find((item) => item.id === meshId);
const material = snapshot.materials.find((item) => item.id === materialId);
const image = snapshot.images.find((item) => item.id === imageId);
const materialImageIds = new Set([
...(material?.imageIds ?? []),
...(material?.nodes?.flatMap((node) => node.imageId ? [node.imageId] : []) ?? []),
]);
if (!object || object.type !== "MESH" || object.dataId !== meshId || !mesh ||
mesh.materialSlotIds?.length !== 1 || mesh.materialSlotIds[0] !== materialId ||
!material || !materialImageIds.has(imageId) || !image || image.libraryLinked !== false) {
throw report("ASSET_SOURCE_HASH_MISMATCH", "WASM Main append did not publish the expected fully-local dependency closure");
}
}
async function initialize(): Promise<WebEngineStatus> {
if (module) return status();
if (!initializationPromise) {
@@ -1316,6 +1347,55 @@ async function handleRequest(event: MessageEvent<WebEngineRequest>): Promise<voi
};
break;
}
case "appendLibraryObject": {
await initialize();
const beforeSnapshot = currentSnapshot ?? (await readSnapshotResult()).snapshot;
let appendRequest;
try {
appendRequest = await parseLibraryMainAppendRequest(request.command.request, beforeSnapshot.revision);
const sourceSha256 = await sha256LibraryBytes(request.command.source);
if (sourceSha256 !== appendRequest.binding.source.sourceSha256) {
throw report("ASSET_SOURCE_HASH_MISMATCH", "library append bytes do not match the bound source SHA-256");
}
}
catch (error) {
reportProtocolError(error, "ASSET_MANIFEST_INVALID");
}
const metadata = {
schemaVersion: appendRequest.schemaVersion,
baseRevision: appendRequest.baseRevision,
sourceLocator: appendRequest.binding.source.sourceLocator,
sourceDataBlockId: appendRequest.binding.sourceDataBlockId,
expectedClosure: appendRequest.expectedClosure,
};
const sourceInput = copyIntoWasm(request.command.source);
const metadataInput = copyCommand(metadata);
try {
if (!module || module._web_engine_append_library_object(
handle,
sourceInput.pointer,
sourceInput.length,
metadataInput.pointer,
metadataInput.length,
) !== 0) {
throw nativeError("ASSET_MANIFEST_INVALID");
}
}
finally {
module?._free(metadataInput.pointer);
module?._free(sourceInput.pointer);
}
const scene = await readSnapshotResult();
assertAppendedClosure(scene.snapshot, appendRequest.expectedClosure);
currentSnapshot = scene.snapshot;
result = {
status: status(),
...publishIncrementalScene(scene),
delta: readDelta(),
libraryAppend: createLibraryMainAppendReceipt(appendRequest, scene.snapshot.revision),
};
break;
}
case "generateLOD": {
await initialize();
const payload = request.command.payload;