@ringozz/godot 4.7.2-639 → 4.7.2-641
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/README.md +1 -1
- package/package.json +4 -4
- package/src/load.ts +6 -5
- package/src/web-image.ts +53 -13
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ const b = basis.fromAxisAngle([0, 1, 0], 1.57);
|
|
|
77
77
|
|
|
78
78
|
### Async resource loading
|
|
79
79
|
|
|
80
|
-
The engine loads pre-baked native resources (`.scn`, `.ctex`, …) through `ResourceLoader`. On desktop these are real files (`res://` = cwd); on web there is no filesystem, so the asset's bundled files — `.import` sidecars as text and imported products (`.scn`/`.ctex`) as emitted asset paths — are staged into Emscripten's in-memory MEMFS (transient, no IndexedDB, no persistent copy) by the generated asset modules, handing the fetched bytes straight to MEMFS via Godot's own `copyToFS` (`FS.createDataFile` with `canOwn` — zero copy, JS-heap only). On web, compressed-texture products are decoded first: for `.ctex` (and the layered `.ctexarray` / `.ccube` / `.ccubearray`) the embedded PNG/WebP blobs go through the browser's `createImageBitmap` and the container is rebuilt as raw RGBA (`DATA_FORMAT_IMAGE`) — per layer for arrays — so no image codec runs in wasm. A built-in **transcoder** instead rebuilds the container as a device-native block-compressed texture
|
|
80
|
+
The engine loads pre-baked native resources (`.scn`, `.ctex`, …) through `ResourceLoader`. On desktop these are real files (`res://` = cwd); on web there is no filesystem, so the asset's bundled files — `.import` sidecars as text and imported products (`.scn`/`.ctex`) as emitted asset paths — are staged into Emscripten's in-memory MEMFS (transient, no IndexedDB, no persistent copy) by the generated asset modules, handing the fetched bytes straight to MEMFS via Godot's own `copyToFS` (`FS.createDataFile` with `canOwn` — zero copy, JS-heap only). On web, compressed-texture products are decoded first: for `.ctex` (and the layered `.ctexarray` / `.ccube` / `.ccubearray`) the embedded PNG/WebP blobs go through the browser's `createImageBitmap` and the container is rebuilt as raw RGBA (`DATA_FORMAT_IMAGE`) — per layer for arrays — so no image codec runs in wasm. A built-in **transcoder** instead rebuilds the container as a device-native block-compressed texture via `@ludicon/spark.js`, choosing the format from the source file's name — normal maps → BC5/EAC-RG, alpha albedo and HDR/sky → BC7/ASTC, terrain weight planes → BC4/EAC-R, everything else (colour/ORM) → BC1/ETC2 first, each with a BC7/ASTC and finally a raw-RGBA fallback — and encoding every layer of an array/cubemap in parallel; its chunk is fetched along with the rest of the app (so it isn't on the critical path to the first texture), the WebGPU device is warmed as soon as it lands, and any decode/encode failure falls back to the raw RGBA path. `.ctex3d` has its own layout and is left to the engine. The source file itself is never fetched.
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
83
|
import { loadResourceAsync } from '@ringozz/godot/load';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringozz/godot",
|
|
3
3
|
"author": "Vladimir Davidovich",
|
|
4
|
-
"version": "4.7.2-
|
|
4
|
+
"version": "4.7.2-641",
|
|
5
5
|
"description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"precision": "single"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"@ringozz/godot-macos-arm64": "^4.7.2-
|
|
69
|
-
"@ringozz/godot-windows-x86_64": "^4.7.2-
|
|
70
|
-
"@ringozz/godot-web-wasm32": "^4.7.2-
|
|
68
|
+
"@ringozz/godot-macos-arm64": "^4.7.2-641",
|
|
69
|
+
"@ringozz/godot-windows-x86_64": "^4.7.2-641",
|
|
70
|
+
"@ringozz/godot-web-wasm32": "^4.7.2-641"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"@types/bun": "*"
|
package/src/load.ts
CHANGED
|
@@ -15,14 +15,15 @@ import { decodeCtex } from './web-image.ts';
|
|
|
15
15
|
/**
|
|
16
16
|
* Produces the bytes staged for a `.ctex`: the platform transcoder (resolved
|
|
17
17
|
* once by `@ringozz/godot/transcoder`) when it yields a result, otherwise the raw
|
|
18
|
-
* RGBA8 browser decode.
|
|
19
|
-
*
|
|
18
|
+
* RGBA8 browser decode. `path` (the resource path being staged) is forwarded so
|
|
19
|
+
* the transcoder can pick a block format from the file name. Never throws — a
|
|
20
|
+
* failed transcode degrades to the uncompressed path.
|
|
20
21
|
*/
|
|
21
|
-
async function transcodeCtex(bytes: Uint8Array): Promise<Uint8Array> {
|
|
22
|
+
async function transcodeCtex(bytes: Uint8Array, path: string): Promise<Uint8Array> {
|
|
22
23
|
const encode = await transcoder;
|
|
23
24
|
if (encode) {
|
|
24
25
|
try {
|
|
25
|
-
const out = await encode(bytes);
|
|
26
|
+
const out = await encode(bytes, path);
|
|
26
27
|
if (out) {
|
|
27
28
|
return out;
|
|
28
29
|
}
|
|
@@ -137,7 +138,7 @@ async function materialize([resPath, entry]: [string, AssetFile]): Promise<void>
|
|
|
137
138
|
// transcoder (e.g. spark.js) may instead rebuild the container as a
|
|
138
139
|
// device-native block-compressed texture by the built-in transcoder —
|
|
139
140
|
// except for `EXACT_TEXTURE_RE`'s routes, which must stay raw (see there).
|
|
140
|
-
bytes = EXACT_TEXTURE_RE.test(resPath) ? await decodeCtex(bytes) : await transcodeCtex(bytes);
|
|
141
|
+
bytes = EXACT_TEXTURE_RE.test(resPath) ? await decodeCtex(bytes) : await transcodeCtex(bytes, resPath);
|
|
141
142
|
}
|
|
142
143
|
} else {
|
|
143
144
|
return;
|
package/src/web-image.ts
CHANGED
|
@@ -26,9 +26,16 @@ const SUB_HEADER_SIZE = 16;
|
|
|
26
26
|
const DATA_FORMAT_PNG = 1;
|
|
27
27
|
const DATA_FORMAT_WEBP = 2;
|
|
28
28
|
|
|
29
|
-
// `Image::Format` targets we emit (`core/io/image.h`).
|
|
30
|
-
//
|
|
31
|
-
|
|
29
|
+
// `Image::Format` targets we emit (`core/io/image.h`). Every compressed target
|
|
30
|
+
// is a 4x4 block format; they differ only in bytes per block, so the size math
|
|
31
|
+
// is a small table rather than a per-format switch.
|
|
32
|
+
export const FORMAT_DXT1 = 17; // BC1
|
|
33
|
+
export const FORMAT_RGTC_R = 20; // BC4
|
|
34
|
+
export const FORMAT_RGTC_RG = 21; // BC5
|
|
35
|
+
export const FORMAT_BPTC_RGBA = 22; // BC7
|
|
36
|
+
export const FORMAT_ETC2_R11 = 26; // EAC R
|
|
37
|
+
export const FORMAT_ETC2_RG11 = 28; // EAC RG
|
|
38
|
+
export const FORMAT_ETC2_RGB8 = 30;
|
|
32
39
|
export const FORMAT_ASTC_4x4 = 35;
|
|
33
40
|
|
|
34
41
|
// Image-body data starts here: outer header + sub-header.
|
|
@@ -37,9 +44,30 @@ export const BLOB_TABLE_OFFSET = OUTER_HEADER_SIZE + SUB_HEADER_SIZE;
|
|
|
37
44
|
const DATA_FORMAT_IMAGE = 0;
|
|
38
45
|
const FORMAT_RGBA8 = 5;
|
|
39
46
|
const RGBA8_PIXEL_SIZE = 4;
|
|
47
|
+
|
|
40
48
|
const FORMAT_BIT_STREAM = 1 << 22;
|
|
41
49
|
const MAGIC_LAYERED = 0x4c; // 'GSTL'; 'GST2' (0x32) is the single-image layout
|
|
42
50
|
|
|
51
|
+
// Bytes per 4x4 block for each compressed target; RGBA8 is 4 bytes/pixel.
|
|
52
|
+
const BLOCK_BYTES: Record<number, number> = {
|
|
53
|
+
[FORMAT_DXT1]: 8,
|
|
54
|
+
[FORMAT_ETC2_RGB8]: 8,
|
|
55
|
+
[FORMAT_ETC2_R11]: 8,
|
|
56
|
+
[FORMAT_RGTC_R]: 8,
|
|
57
|
+
[FORMAT_RGTC_RG]: 16,
|
|
58
|
+
[FORMAT_ETC2_RG11]: 16,
|
|
59
|
+
[FORMAT_BPTC_RGBA]: 16,
|
|
60
|
+
[FORMAT_ASTC_4x4]: 16,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Bytes per 4x4 block of a compressed `Image::Format` target (RGBA8 reports 4,
|
|
65
|
+
* its bytes per pixel). Unknown formats fall back to a 16-byte block.
|
|
66
|
+
*/
|
|
67
|
+
export function blockBytes(format: number): number {
|
|
68
|
+
return format === FORMAT_RGBA8 ? RGBA8_PIXEL_SIZE : (BLOCK_BYTES[format] ?? 16);
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
export interface CtexLayer {
|
|
44
72
|
/** `DATA_FORMAT_PNG` (1) / `DATA_FORMAT_WEBP` (2). */
|
|
45
73
|
dataFormat: number;
|
|
@@ -132,12 +160,12 @@ export function mipChainLength(width: number, height: number): number {
|
|
|
132
160
|
}
|
|
133
161
|
|
|
134
162
|
// Bytes of one `DATA_FORMAT_IMAGE` image body: sub-header + tightly packed
|
|
135
|
-
// levels. RGBA8 is 4 bytes/pixel;
|
|
136
|
-
// dimensions round up, matching `Image::_get_dst_image_size`.
|
|
163
|
+
// levels. RGBA8 is 4 bytes/pixel; compressed targets are 4x4 blocks (8 or 16
|
|
164
|
+
// bytes) whose dimensions round up, matching `Image::_get_dst_image_size`.
|
|
137
165
|
function bodySize(width: number, height: number, mipmaps: number, format: number): number {
|
|
138
166
|
const rgba8 = format === FORMAT_RGBA8;
|
|
139
167
|
const block = rgba8 ? 1 : 4;
|
|
140
|
-
const bytesPerBlock =
|
|
168
|
+
const bytesPerBlock = blockBytes(format);
|
|
141
169
|
let dataSize = 0;
|
|
142
170
|
for (let level = 0; level <= mipmaps; level++) {
|
|
143
171
|
const [w, h] = mipLevelDimensions(width, height, level);
|
|
@@ -192,10 +220,10 @@ export function allocateCtex(
|
|
|
192
220
|
// ---------------------------------------------------------------------------
|
|
193
221
|
|
|
194
222
|
/**
|
|
195
|
-
* Compressed `Image::Format` targets
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
223
|
+
* Compressed `Image::Format` targets the running device can upload. A list
|
|
224
|
+
* because the encoder may also reject a format the browser exposes (e.g. spark
|
|
225
|
+
* skips BC on iOS); the caller intersects it with spark's supported set, and
|
|
226
|
+
* order here is not significant (the encoder has its own preference chain).
|
|
199
227
|
*/
|
|
200
228
|
export function pickGodotFormats(): number[] {
|
|
201
229
|
let gl: WebGL2RenderingContext | null = null;
|
|
@@ -226,9 +254,20 @@ export function pickGodotFormats(): number[] {
|
|
|
226
254
|
if (exts.has('EXT_texture_compression_bptc')) {
|
|
227
255
|
formats.push(FORMAT_BPTC_RGBA);
|
|
228
256
|
}
|
|
257
|
+
if (exts.has('EXT_texture_compression_rgtc')) {
|
|
258
|
+
formats.push(FORMAT_RGTC_RG);
|
|
259
|
+
formats.push(FORMAT_RGTC_R);
|
|
260
|
+
}
|
|
229
261
|
if (exts.has('WEBGL_compressed_texture_astc')) {
|
|
230
262
|
formats.push(FORMAT_ASTC_4x4);
|
|
231
263
|
}
|
|
264
|
+
if (exts.has('WEBGL_compressed_texture_s3tc')) {
|
|
265
|
+
formats.push(FORMAT_DXT1);
|
|
266
|
+
}
|
|
267
|
+
// ETC2/EAC is core WebGL2 (ES 3.0), which is how Godot's web config
|
|
268
|
+
// reports it (`etc2_supported = true` for non-GLES-over-GL) — so these
|
|
269
|
+
// are always uploadable, independent of any extension.
|
|
270
|
+
formats.push(FORMAT_ETC2_RGB8, FORMAT_ETC2_RG11, FORMAT_ETC2_R11);
|
|
232
271
|
} finally {
|
|
233
272
|
// Don't retain the probe context for the page's lifetime.
|
|
234
273
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
|
@@ -238,10 +277,11 @@ export function pickGodotFormats(): number[] {
|
|
|
238
277
|
|
|
239
278
|
/**
|
|
240
279
|
* Transforms fetched `.ctex` bytes into replacement bytes, or `null` to decline
|
|
241
|
-
* (staging then keeps the raw RGBA8 decode).
|
|
242
|
-
*
|
|
280
|
+
* (staging then keeps the raw RGBA8 decode). `path` is the resource path being
|
|
281
|
+
* staged, so a platform transcoder can pick a block format from the file name.
|
|
282
|
+
* Produced by `transcoder*.ts` and installed by `load.ts`'s staging.
|
|
243
283
|
*/
|
|
244
|
-
export type CtexTranscoder = (bytes: Uint8Array) => Promise<Uint8Array | null>;
|
|
284
|
+
export type CtexTranscoder = (bytes: Uint8Array, path: string) => Promise<Uint8Array | null>;
|
|
245
285
|
|
|
246
286
|
/**
|
|
247
287
|
* Decodes the embedded PNG/WebP payloads of a `.ctex` with the browser's
|