@ringozz/godot 4.7.2-639 → 4.7.2-643
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 +89 -31
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-643",
|
|
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-643",
|
|
69
|
+
"@ringozz/godot-windows-x86_64": "^4.7.2-643",
|
|
70
|
+
"@ringozz/godot-web-wasm32": "^4.7.2-643"
|
|
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
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// a single parse/allocate pair covers everything — a container is just a list of
|
|
13
13
|
// layers.
|
|
14
14
|
//
|
|
15
|
-
// Exposes the container/format helpers, `pickGodotFormats` (
|
|
16
|
-
//
|
|
15
|
+
// Exposes the container/format helpers, `pickGodotFormats` (the driver's
|
|
16
|
+
// compressed-format list) and `decodeCtex` — the default staging path, which
|
|
17
17
|
// decodes the PNG/WebP payloads with the browser and rebuilds a raw RGBA8
|
|
18
18
|
// container so no image codec runs in wasm. The optional compressed-transcode
|
|
19
19
|
// hook lives in load.ts.
|
|
@@ -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);
|
|
@@ -181,21 +209,55 @@ export function allocateCtex(
|
|
|
181
209
|
}
|
|
182
210
|
|
|
183
211
|
// ---------------------------------------------------------------------------
|
|
184
|
-
// Capability probing.
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// `
|
|
190
|
-
//
|
|
191
|
-
//
|
|
212
|
+
// Capability probing. GLES3 uploads a compressed format only when its config
|
|
213
|
+
// reports it supported, and this build compiles in NO decompressors, so a format
|
|
214
|
+
// the device can't upload breaks the texture rather than degrading. Ask the
|
|
215
|
+
// driver (`COMPRESSED_TEXTURE_FORMATS`) rather than reading extension names:
|
|
216
|
+
// Chrome on Windows lists no ETC2/EAC and rejects those uploads, while
|
|
217
|
+
// `config.cpp` would still route one to it. RGTC arrays are the one place 2D and
|
|
218
|
+
// layered differ — `_texture_set_data` force-decompresses them for
|
|
219
|
+
// `TEXTURE_2D_ARRAY`, gated here on `Image::can_decompress("s3tc")`.
|
|
192
220
|
// ---------------------------------------------------------------------------
|
|
193
221
|
|
|
222
|
+
// `CompressedTexture` GL enums -> Godot `Image::Format`, for the targets the
|
|
223
|
+
// encoder can emit; only the unsigned-normal variants (their siblings share it).
|
|
224
|
+
const GL_TO_GODOT_FORMAT: Record<number, number> = {
|
|
225
|
+
0x83f0: FORMAT_DXT1, // COMPRESSED_RGB_S3TC_DXT1_EXT
|
|
226
|
+
0x8dbb: FORMAT_RGTC_R, // COMPRESSED_RED_RGTC1_EXT
|
|
227
|
+
0x8dbd: FORMAT_RGTC_RG, // COMPRESSED_RED_GREEN_RGTC2_EXT
|
|
228
|
+
0x8e8c: FORMAT_BPTC_RGBA, // COMPRESSED_RGBA_BPTC_UNORM_EXT
|
|
229
|
+
0x9270: FORMAT_ETC2_R11, // COMPRESSED_R11_EAC
|
|
230
|
+
0x9272: FORMAT_ETC2_RG11, // COMPRESSED_RG11_EAC
|
|
231
|
+
0x9274: FORMAT_ETC2_RGB8, // COMPRESSED_RGB8_ETC2
|
|
232
|
+
0x93b0: FORMAT_ASTC_4x4, // COMPRESSED_RGBA_ASTC_4x4_KHR
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** Enabled before reading the list — a browser may only list a format once its extension is enabled. */
|
|
236
|
+
const PROBED_EXTENSIONS = [
|
|
237
|
+
'EXT_texture_compression_bptc',
|
|
238
|
+
'EXT_texture_compression_rgtc',
|
|
239
|
+
'WEBGL_compressed_texture_s3tc',
|
|
240
|
+
'WEBGL_compressed_texture_astc',
|
|
241
|
+
'WEBGL_compressed_texture_etc',
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
/** The Godot targets among a driver's `COMPRESSED_TEXTURE_FORMATS` list, in list order and deduplicated (pure). */
|
|
245
|
+
export function godotFormatsFor(glFormats: ArrayLike<number>): number[] {
|
|
246
|
+
const formats = new Set<number>();
|
|
247
|
+
for (let i = 0; i < glFormats.length; i++) {
|
|
248
|
+
const godot = GL_TO_GODOT_FORMAT[glFormats[i]!];
|
|
249
|
+
if (godot !== undefined) {
|
|
250
|
+
formats.add(godot);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return [...formats];
|
|
254
|
+
}
|
|
255
|
+
|
|
194
256
|
/**
|
|
195
|
-
* Compressed `Image::Format` targets
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
257
|
+
* Compressed `Image::Format` targets the running device can upload. A list
|
|
258
|
+
* because the encoder may also reject a format the browser exposes (e.g. spark
|
|
259
|
+
* skips BC on iOS); the caller intersects it with spark's supported set, and
|
|
260
|
+
* order here is not significant (the encoder has its own preference chain).
|
|
199
261
|
*/
|
|
200
262
|
export function pickGodotFormats(): number[] {
|
|
201
263
|
let gl: WebGL2RenderingContext | null = null;
|
|
@@ -220,28 +282,24 @@ export function pickGodotFormats(): number[] {
|
|
|
220
282
|
if (!gl) {
|
|
221
283
|
return [];
|
|
222
284
|
}
|
|
223
|
-
const formats: number[] = [];
|
|
224
285
|
try {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
formats.push(FORMAT_BPTC_RGBA);
|
|
228
|
-
}
|
|
229
|
-
if (exts.has('WEBGL_compressed_texture_astc')) {
|
|
230
|
-
formats.push(FORMAT_ASTC_4x4);
|
|
286
|
+
for (const name of PROBED_EXTENSIONS) {
|
|
287
|
+
gl.getExtension(name);
|
|
231
288
|
}
|
|
289
|
+
return godotFormatsFor(gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS) as ArrayLike<number>);
|
|
232
290
|
} finally {
|
|
233
291
|
// Don't retain the probe context for the page's lifetime.
|
|
234
292
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
|
235
293
|
}
|
|
236
|
-
return formats;
|
|
237
294
|
}
|
|
238
295
|
|
|
239
296
|
/**
|
|
240
297
|
* Transforms fetched `.ctex` bytes into replacement bytes, or `null` to decline
|
|
241
|
-
* (staging then keeps the raw RGBA8 decode).
|
|
242
|
-
*
|
|
298
|
+
* (staging then keeps the raw RGBA8 decode). `path` is the resource path being
|
|
299
|
+
* staged, so a platform transcoder can pick a block format from the file name.
|
|
300
|
+
* Produced by `transcoder*.ts` and installed by `load.ts`'s staging.
|
|
243
301
|
*/
|
|
244
|
-
export type CtexTranscoder = (bytes: Uint8Array) => Promise<Uint8Array | null>;
|
|
302
|
+
export type CtexTranscoder = (bytes: Uint8Array, path: string) => Promise<Uint8Array | null>;
|
|
245
303
|
|
|
246
304
|
/**
|
|
247
305
|
* Decodes the embedded PNG/WebP payloads of a `.ctex` with the browser's
|