@next2d/builder 0.1.7 → 0.1.10
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/dist/cli.d.ts +59 -0
- package/dist/cli.js +141 -0
- package/dist/constants.d.ts +53 -0
- package/dist/constants.js +64 -0
- package/dist/context.d.ts +29 -0
- package/dist/context.js +19 -0
- package/dist/electron.d.ts +10 -0
- package/dist/electron.js +142 -0
- package/dist/index.js +18 -754
- package/dist/native.d.ts +27 -0
- package/dist/native.js +123 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +40 -0
- package/dist/web.d.ts +18 -0
- package/dist/web.js +70 -0
- package/dist/xbox.d.ts +62 -0
- package/dist/xbox.js +426 -0
- package/package.json +12 -9
- package/templates/xbox/CMakeLists.txt +116 -11
- package/templates/xbox/README.md +34 -8
- package/templates/xbox/cmake/FetchDawn.cmake +47 -6
- package/templates/xbox/cmake/FindV8.cmake +15 -0
- package/templates/xbox/js/bootstrap.js +151 -28
- package/templates/xbox/js/selftest.js +740 -90
- package/templates/xbox/src/AssetLoader.cpp +115 -12
- package/templates/xbox/src/AssetLoader.h +4 -0
- package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
- package/templates/xbox/src/EmbeddedAssets.h +60 -0
- package/templates/xbox/src/EventLoop.cpp +13 -1
- package/templates/xbox/src/EventLoop.h +12 -0
- package/templates/xbox/src/bindings/Audio.cpp +211 -31
- package/templates/xbox/src/bindings/Bindings.h +10 -0
- package/templates/xbox/src/bindings/Canvas.cpp +77 -2
- package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
- package/templates/xbox/src/bindings/Console.cpp +6 -1
- package/templates/xbox/src/bindings/DomShims.cpp +143 -0
- package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
- package/templates/xbox/src/bindings/Fetch.cpp +2 -8
- package/templates/xbox/src/bindings/Image.cpp +243 -15
- package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
- package/templates/xbox/src/bindings/RasterCore.h +105 -1
- package/templates/xbox/src/bindings/Video.cpp +153 -10
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
- package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
- package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
- package/templates/xbox/src/gpu/DawnContext.h +18 -0
- package/templates/xbox/src/main.cpp +459 -8
- package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
- package/templates/xbox/src/platform/AudioDecoder.h +18 -0
- package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
- package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
- package/templates/xbox/src/platform/DecodeQueue.h +27 -0
- package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
- package/templates/xbox/src/platform/ImageDecoder.h +18 -0
- package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
- package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
- package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
- package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
- package/templates/xbox/src/v8/CodeCache.h +223 -0
- package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
- package/templates/xbox/src/v8/V8Util.h +79 -0
- package/templates/xbox/src/v8/WeakHandle.h +45 -0
- package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
- package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
- package/templates/xbox/tests/check_local.sh +7 -2
- package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
- package/templates/xbox/tests/raster_test.cpp +50 -0
- package/templates/xbox/tests/windows_platform_test.cpp +75 -3
- package/templates/xbox/third_party/LICENSES.md +14 -0
- package/templates/xbox/third_party/dr_mp3.h +5412 -0
- package/templates/xbox/third_party/dr_wav.h +9131 -0
- package/templates/xbox/third_party/stb_image.h +7988 -0
- package/templates/xbox/third_party/stb_truetype.h +5079 -0
- package/templates/xbox/third_party/stb_vorbis.c +5584 -0
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
#include "v8/V8Util.h"
|
|
6
6
|
|
|
7
7
|
#include <Windows.h>
|
|
8
|
+
#include <bcrypt.h>
|
|
9
|
+
#include <cstdio>
|
|
10
|
+
|
|
11
|
+
#pragma comment(lib, "bcrypt.lib")
|
|
8
12
|
|
|
9
13
|
namespace next2d {
|
|
10
14
|
|
|
@@ -107,6 +111,116 @@ void ClipboardWriteText(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
107
111
|
resolver->Resolve(ctx, v8::Undefined(isolate)).Check();
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
// crypto.getRandomValues(typedArray): OS 乱数で埋めて同じ view を返す
|
|
115
|
+
void CryptoGetRandomValues(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
116
|
+
{
|
|
117
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
118
|
+
if (args.Length() < 1 || !args[0]->IsArrayBufferView()) {
|
|
119
|
+
v8util::ThrowTypeError(isolate, "getRandomValues requires a TypedArray");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
auto view = args[0].As<v8::ArrayBufferView>();
|
|
123
|
+
std::vector<uint8_t> bytes(view->ByteLength());
|
|
124
|
+
if (!bytes.empty()) {
|
|
125
|
+
BCryptGenRandom(nullptr, bytes.data(), static_cast<ULONG>(bytes.size()),
|
|
126
|
+
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
127
|
+
// view のバッキングへ書き戻す
|
|
128
|
+
auto ab = view->Buffer();
|
|
129
|
+
auto* dst = static_cast<uint8_t*>(ab->GetBackingStore()->Data()) + view->ByteOffset();
|
|
130
|
+
memcpy(dst, bytes.data(), bytes.size());
|
|
131
|
+
}
|
|
132
|
+
args.GetReturnValue().Set(args[0]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// crypto.randomUUID(): RFC 4122 v4
|
|
136
|
+
void CryptoRandomUUID(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
137
|
+
{
|
|
138
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
139
|
+
uint8_t b[16] = {};
|
|
140
|
+
BCryptGenRandom(nullptr, b, sizeof(b), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
141
|
+
b[6] = static_cast<uint8_t>((b[6] & 0x0f) | 0x40); // version 4
|
|
142
|
+
b[8] = static_cast<uint8_t>((b[8] & 0x3f) | 0x80); // variant
|
|
143
|
+
char buf[37];
|
|
144
|
+
std::snprintf(buf, sizeof(buf),
|
|
145
|
+
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
146
|
+
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
|
147
|
+
b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
|
|
148
|
+
args.GetReturnValue().Set(Str(isolate, buf));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- 名前付きストレージの永続化バックエンド ---------------------------------
|
|
152
|
+
// localStorage / indexedDB のセマンティクスは Polyfills.cpp の JS 側が実装し、
|
|
153
|
+
// ここは名前ごとのファイル I/O のみを担う。
|
|
154
|
+
// 保存先: %LOCALAPPDATA%\Next2D\<name>.json (PC / Gaming.Desktop / CI)。
|
|
155
|
+
//
|
|
156
|
+
// コンソール実機 (Gaming.Xbox.*) のストレージ対応 (要 devkit 検証):
|
|
157
|
+
// - localStorage / indexedDB のようなタイトルローカル永続化は XPersistentLocalStorage
|
|
158
|
+
// (XPersistentLocalStorageGetPath で得たパス配下へ同じファイル I/O) が対応先。
|
|
159
|
+
// 同期 API のため現行の同期ストレージ契約 (StorageLoad/StorageSave) にそのまま載る。
|
|
160
|
+
// - ユーザー毎のクラウド同期セーブが要る場合のみ XGameSave (非同期・XUser 必須) を使う。
|
|
161
|
+
// いずれも console GDK ヘッダと signed-in user / SCID 構成が必要で、devkit 実機でしか
|
|
162
|
+
// コンパイル・実行検証できないため未実装 (現状はファイルパスのフォールバックで動作)。
|
|
163
|
+
std::wstring StorageFilePath(const std::string& name)
|
|
164
|
+
{
|
|
165
|
+
// ファイル名として安全な文字だけを残す
|
|
166
|
+
std::wstring safe;
|
|
167
|
+
for (char c : name) {
|
|
168
|
+
const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
169
|
+
(c >= '0' && c <= '9') || c == '_' || c == '-';
|
|
170
|
+
safe.push_back(ok ? static_cast<wchar_t>(c) : L'_');
|
|
171
|
+
}
|
|
172
|
+
if (safe.empty()) {
|
|
173
|
+
safe = L"storage";
|
|
174
|
+
}
|
|
175
|
+
wchar_t base[MAX_PATH] = {};
|
|
176
|
+
if (!GetEnvironmentVariableW(L"LOCALAPPDATA", base, MAX_PATH)) {
|
|
177
|
+
return safe + L".json"; // フォールバック: カレント
|
|
178
|
+
}
|
|
179
|
+
std::wstring dir = std::wstring(base) + L"\\Next2D";
|
|
180
|
+
CreateDirectoryW(dir.c_str(), nullptr);
|
|
181
|
+
return dir + L"\\" + safe + L".json";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// __next2d_storage_load(name) -> string
|
|
185
|
+
void StorageLoad(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
186
|
+
{
|
|
187
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
188
|
+
const std::string name = args.Length() > 0
|
|
189
|
+
? v8util::ToStdString(isolate, args[0]) : "localStorage";
|
|
190
|
+
HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_READ, FILE_SHARE_READ,
|
|
191
|
+
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
192
|
+
if (h == INVALID_HANDLE_VALUE) {
|
|
193
|
+
args.GetReturnValue().Set(Str(isolate, ""));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
DWORD size = GetFileSize(h, nullptr);
|
|
197
|
+
std::string data(size, '\0');
|
|
198
|
+
DWORD read = 0;
|
|
199
|
+
ReadFile(h, data.data(), size, &read, nullptr);
|
|
200
|
+
CloseHandle(h);
|
|
201
|
+
data.resize(read);
|
|
202
|
+
args.GetReturnValue().Set(Str(isolate, data));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// __next2d_storage_save(name, data)
|
|
206
|
+
void StorageSave(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
207
|
+
{
|
|
208
|
+
if (args.Length() < 2) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
212
|
+
const std::string name = v8util::ToStdString(isolate, args[0]);
|
|
213
|
+
const std::string data = v8util::ToStdString(isolate, args[1]);
|
|
214
|
+
HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_WRITE, 0,
|
|
215
|
+
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
216
|
+
if (h == INVALID_HANDLE_VALUE) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
DWORD written = 0;
|
|
220
|
+
WriteFile(h, data.data(), static_cast<DWORD>(data.size()), &written, nullptr);
|
|
221
|
+
CloseHandle(h);
|
|
222
|
+
}
|
|
223
|
+
|
|
110
224
|
// new OffscreenCanvas(width, height)
|
|
111
225
|
void OffscreenCanvasConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
112
226
|
{
|
|
@@ -144,6 +258,19 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
|
|
|
144
258
|
SetValue(isolate, global, "self", global);
|
|
145
259
|
NoopEventTarget(isolate, global);
|
|
146
260
|
|
|
261
|
+
// crypto (getRandomValues / randomUUID — OS 乱数)。
|
|
262
|
+
// player の WebGLUtil が ID 生成に crypto.randomUUID を使う。
|
|
263
|
+
{
|
|
264
|
+
v8::Local<v8::Object> crypto = v8::Object::New(isolate);
|
|
265
|
+
SetMethod(isolate, crypto, "getRandomValues", CryptoGetRandomValues);
|
|
266
|
+
SetMethod(isolate, crypto, "randomUUID", CryptoRandomUUID);
|
|
267
|
+
SetValue(isolate, global, "crypto", crypto);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// localStorage のファイル I/O バックエンド (Polyfills.cpp の JS が利用)
|
|
271
|
+
SetMethod(isolate, global, "__next2d_storage_load", StorageLoad);
|
|
272
|
+
SetMethod(isolate, global, "__next2d_storage_save", StorageSave);
|
|
273
|
+
|
|
147
274
|
// devicePixelRatio
|
|
148
275
|
SetValue(isolate, global, "devicePixelRatio",
|
|
149
276
|
v8::Number::New(isolate, host->device_pixel_ratio));
|
|
@@ -158,6 +285,15 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
|
|
|
158
285
|
SetValue(isolate, global, "innerWidth", v8::Integer::New(isolate, host->viewport_width));
|
|
159
286
|
SetValue(isolate, global, "innerHeight", v8::Integer::New(isolate, host->viewport_height));
|
|
160
287
|
|
|
288
|
+
// scrollX / scrollY (非スクロールの全画面アプリでは常に 0)。
|
|
289
|
+
// 未定義だと player の PlayerSetCurrentMousePointService 冒頭
|
|
290
|
+
// `let x = window.scrollX;` が undefined になり、以降 x が NaN 化して
|
|
291
|
+
// stage.pointer 全体が NaN → ヒットテストが全 miss → ボタンが一切反応しない。
|
|
292
|
+
SetValue(isolate, global, "scrollX", v8::Integer::New(isolate, 0));
|
|
293
|
+
SetValue(isolate, global, "scrollY", v8::Integer::New(isolate, 0));
|
|
294
|
+
SetValue(isolate, global, "pageXOffset", v8::Integer::New(isolate, 0));
|
|
295
|
+
SetValue(isolate, global, "pageYOffset", v8::Integer::New(isolate, 0));
|
|
296
|
+
|
|
161
297
|
// navigator (gpu/getGamepads は後段のバインディングが付与)
|
|
162
298
|
v8::Local<v8::Object> navigator = v8::Object::New(isolate);
|
|
163
299
|
SetValue(isolate, navigator, "userAgent",
|
|
@@ -176,6 +312,8 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
|
|
|
176
312
|
// document
|
|
177
313
|
v8::Local<v8::Object> document = v8::Object::New(isolate);
|
|
178
314
|
SetMethod(isolate, document, "createElement", CreateElement);
|
|
315
|
+
// 起動時点で読み込み完了扱い (ゲームの boot 分岐が readyState === "loading" で待機しないように)
|
|
316
|
+
SetValue(isolate, document, "readyState", Str(isolate, "complete"));
|
|
179
317
|
SetMethod(isolate, document, "getElementById",
|
|
180
318
|
[](const v8::FunctionCallbackInfo<v8::Value>& a) { a.GetReturnValue().SetNull(); });
|
|
181
319
|
SetMethod(isolate, document, "querySelector",
|
|
@@ -216,6 +354,11 @@ void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, H
|
|
|
216
354
|
InstallAudio(isolate, global, host);
|
|
217
355
|
InstallWebGPU(isolate, global, host); // navigator.gpu
|
|
218
356
|
InstallGamepad(isolate, global, host); // navigator.getGamepads
|
|
357
|
+
|
|
358
|
+
// TextEncoder/TextDecoder/queueMicrotask。bootstrap.js はメインコンテキスト
|
|
359
|
+
// でのみ実行されるため、worker を含む全コンテキストにはここで注入する
|
|
360
|
+
// (Vite バンドルの worker は評価時に TextDecoder を要求する)。
|
|
361
|
+
InstallPolyfills(isolate, isolate->GetCurrentContext());
|
|
219
362
|
}
|
|
220
363
|
|
|
221
364
|
} // namespace next2d
|
|
@@ -96,6 +96,15 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
const std::string type = ToStdString(isolate, type_val);
|
|
99
|
+
|
|
100
|
+
// event.target / currentTarget を配送先に設定する。
|
|
101
|
+
// player のポインタハンドラは `let t = e.target; t && (…, t.setPointerCapture(…), …)`
|
|
102
|
+
// のように先頭で event.target をガードするため、未設定だと pointerdown/move が
|
|
103
|
+
// 丸ごと no-op になり「ボタンが全く反応しない」。DOM 準拠でここで設定する。
|
|
104
|
+
(void) event->Set(ctx, Str(isolate, "target"), target);
|
|
105
|
+
(void) event->Set(ctx, Str(isolate, "currentTarget"), target);
|
|
106
|
+
(void) event->Set(ctx, Str(isolate, "srcElement"), target);
|
|
107
|
+
|
|
99
108
|
v8::Local<v8::Value> args1[1] = { event };
|
|
100
109
|
|
|
101
110
|
// on<type> プロパティ
|
|
@@ -104,6 +113,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
|
|
|
104
113
|
if (target->Get(ctx, Str(isolate, onname.c_str())).ToLocal(&on) && on->IsFunction()) {
|
|
105
114
|
v8::TryCatch tc(isolate);
|
|
106
115
|
(void) on.As<v8::Function>()->Call(ctx, target, 1, args1);
|
|
116
|
+
v8util::ReportCaught(isolate, &tc, onname.c_str());
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
// __listeners[type]
|
|
@@ -118,6 +128,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
|
|
|
118
128
|
if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
|
|
119
129
|
v8::TryCatch tc(isolate);
|
|
120
130
|
(void) fn.As<v8::Function>()->Call(ctx, target, 1, args1);
|
|
131
|
+
v8util::ReportCaught(isolate, &tc, ("listener:" + type).c_str());
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
134
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "HostContext.h"
|
|
4
4
|
#include "AssetLoader.h"
|
|
5
5
|
#include "v8/V8Util.h"
|
|
6
|
+
#include "v8/WeakHandle.h"
|
|
6
7
|
|
|
7
8
|
#include <cstring>
|
|
8
9
|
#include <vector>
|
|
@@ -84,12 +85,6 @@ void ResponseJson(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
84
85
|
args.GetReturnValue().Set(resolver->GetPromise());
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
// External に持たせた vector を解放するための弱参照コールバック
|
|
88
|
-
void ReleaseResponseData(const v8::WeakCallbackInfo<std::vector<uint8_t>>& info)
|
|
89
|
-
{
|
|
90
|
-
delete info.GetParameter();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
88
|
v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
|
94
89
|
std::vector<uint8_t>&& bytes, bool ok, int status)
|
|
95
90
|
{
|
|
@@ -101,8 +96,7 @@ v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context>
|
|
|
101
96
|
response->SetInternalField(0, v8::External::New(isolate, data));
|
|
102
97
|
|
|
103
98
|
// GC 時に解放
|
|
104
|
-
|
|
105
|
-
handle->SetWeak(data, ReleaseResponseData, v8::WeakCallbackType::kParameter);
|
|
99
|
+
v8util::AttachWeak(isolate, response, data);
|
|
106
100
|
|
|
107
101
|
v8util::SetValue(isolate, response, "ok", v8::Boolean::New(isolate, ok));
|
|
108
102
|
v8util::SetValue(isolate, response, "status", v8::Integer::New(isolate, status));
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
#include "HostContext.h"
|
|
4
4
|
#include "AssetLoader.h"
|
|
5
|
+
#include "EventTarget.h"
|
|
5
6
|
#include "ImageSource.h"
|
|
6
|
-
#include "platform/
|
|
7
|
+
#include "platform/DecodeQueue.h"
|
|
8
|
+
#include "platform/ImageDecoder.h"
|
|
7
9
|
#include "v8/V8Util.h"
|
|
10
|
+
#include "v8/WeakHandle.h"
|
|
8
11
|
|
|
9
12
|
#include <objbase.h>
|
|
10
13
|
|
|
11
14
|
#include <cstring>
|
|
15
|
+
#include <iostream>
|
|
16
|
+
#include <memory>
|
|
17
|
+
#include <utility>
|
|
12
18
|
#include <vector>
|
|
13
19
|
|
|
14
20
|
namespace next2d {
|
|
@@ -18,12 +24,112 @@ using v8util::ToStdString;
|
|
|
18
24
|
|
|
19
25
|
namespace {
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
// base64 デコード (data: URL 用。Vite は小さい画像をインライン化する)
|
|
28
|
+
bool ImageDecodeBase64(const std::string& in, std::vector<uint8_t>* out)
|
|
22
29
|
{
|
|
23
|
-
|
|
30
|
+
auto val = [](char c) -> int {
|
|
31
|
+
if (c >= 'A' && c <= 'Z') return c - 'A';
|
|
32
|
+
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
|
33
|
+
if (c >= '0' && c <= '9') return c - '0' + 52;
|
|
34
|
+
if (c == '+') return 62;
|
|
35
|
+
if (c == '/') return 63;
|
|
36
|
+
return -1;
|
|
37
|
+
};
|
|
38
|
+
out->clear();
|
|
39
|
+
out->reserve(in.size() / 4 * 3);
|
|
40
|
+
int buf = 0, bits = 0;
|
|
41
|
+
for (char c : in) {
|
|
42
|
+
if (c == '=' || c == '\r' || c == '\n') continue;
|
|
43
|
+
const int v = val(c);
|
|
44
|
+
if (v < 0) return false;
|
|
45
|
+
buf = buf << 6 | v;
|
|
46
|
+
bits += 6;
|
|
47
|
+
if (bits >= 8) {
|
|
48
|
+
bits -= 8;
|
|
49
|
+
out->push_back(static_cast<uint8_t>(buf >> bits & 0xFF));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src に指定された URL (assets 相対 / data:) を読み込み、WIC デコードを
|
|
56
|
+
// バックグラウンドスレッド (DecodeQueue) へ逃がす。完了はメインループの
|
|
57
|
+
// decodequeue::Pump() から反映される (ブラウザ同様、src 設定後に非同期で
|
|
58
|
+
// load イベントが届く。img.src = url; img.onload = fn の順でも拾える)。
|
|
59
|
+
// 同期デコードは画面遷移時に 1 秒超のフレームヒッチになる (--perf で計測済み)。
|
|
60
|
+
void LoadImageFromSrc(v8::Isolate* isolate, v8::Local<v8::Object> self, const std::string& url)
|
|
61
|
+
{
|
|
62
|
+
std::vector<uint8_t> input;
|
|
63
|
+
if (url.rfind("data:", 0) == 0) {
|
|
64
|
+
const auto comma = url.find(',');
|
|
65
|
+
if (comma != std::string::npos) {
|
|
66
|
+
const std::string meta = url.substr(5, comma - 5);
|
|
67
|
+
const std::string payload = url.substr(comma + 1);
|
|
68
|
+
if (meta.find(";base64") != std::string::npos) {
|
|
69
|
+
ImageDecodeBase64(payload, &input);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
// pak (メモリ) 読みなので同期で問題ない。重いのはデコードのみ。
|
|
74
|
+
AssetLoader* assets = HostContext::From(isolate)->assets;
|
|
75
|
+
auto bytes = assets->ReadBinary(url);
|
|
76
|
+
if (bytes) {
|
|
77
|
+
input = std::move(*bytes);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
auto image_ref = std::make_shared<v8::Global<v8::Object>>(isolate, self);
|
|
82
|
+
auto decoded = std::make_shared<DecodedImage>();
|
|
83
|
+
auto ok = std::make_shared<bool>(false);
|
|
84
|
+
|
|
85
|
+
decodequeue::Submit(
|
|
86
|
+
// プールスレッド: WIC デコードのみ (V8 に触れない)
|
|
87
|
+
[input = std::move(input), decoded, ok]() {
|
|
88
|
+
*ok = DecodeImage(input, *decoded);
|
|
89
|
+
},
|
|
90
|
+
// メインスレッド: 結果を image オブジェクトへ反映し load を発火
|
|
91
|
+
[isolate, image_ref, decoded, ok]() {
|
|
92
|
+
v8::HandleScope hs(isolate);
|
|
93
|
+
v8::Local<v8::Object> img_obj = image_ref->Get(isolate);
|
|
94
|
+
v8::Local<v8::Context> ctx = img_obj->GetCreationContextChecked();
|
|
95
|
+
v8::Context::Scope cs(ctx);
|
|
96
|
+
|
|
97
|
+
// «堅牢化» デコード失敗時の扱い。
|
|
98
|
+
// player の ShapeLoadSrcUseCase は image に "load" ハンドラのみ登録し
|
|
99
|
+
// "error" を監視しない。"error" だと Event.COMPLETE が来ず ImageShapeAtom
|
|
100
|
+
// .load() の Promise が永久未解決になり、ページ build (=画面遷移) が
|
|
101
|
+
// ローディングのままフリーズする。失敗時は 1x1 透明のプレースホルダを
|
|
102
|
+
// 割り当てて "load" として扱う (該当画像だけが空になる)。
|
|
103
|
+
auto* img = new DecodedImage(std::move(*decoded));
|
|
104
|
+
if (!*ok) {
|
|
105
|
+
img->width = 1;
|
|
106
|
+
img->height = 1;
|
|
107
|
+
img->rgba.assign(4, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
img_obj->SetInternalField(0, v8::External::New(isolate, img));
|
|
111
|
+
v8util::AttachWeak(isolate, img_obj, img);
|
|
112
|
+
v8util::SetValue(isolate, img_obj, "width",
|
|
113
|
+
v8::Integer::NewFromUnsigned(isolate, img->width));
|
|
114
|
+
v8util::SetValue(isolate, img_obj, "height",
|
|
115
|
+
v8::Integer::NewFromUnsigned(isolate, img->height));
|
|
116
|
+
v8util::SetValue(isolate, img_obj, "naturalWidth",
|
|
117
|
+
v8::Integer::NewFromUnsigned(isolate, img->width));
|
|
118
|
+
v8util::SetValue(isolate, img_obj, "naturalHeight",
|
|
119
|
+
v8::Integer::NewFromUnsigned(isolate, img->height));
|
|
120
|
+
v8util::SetValue(isolate, img_obj, "complete", v8::Boolean::New(isolate, true));
|
|
121
|
+
|
|
122
|
+
// 常に "load" を発火し、遷移を進行させる
|
|
123
|
+
v8::Local<v8::Object> ev = v8::Object::New(isolate);
|
|
124
|
+
v8util::SetValue(isolate, ev, "type", Str(isolate, "load"));
|
|
125
|
+
v8util::SetValue(isolate, ev, "target", img_obj);
|
|
126
|
+
DispatchEvent(isolate, img_obj, ev);
|
|
127
|
+
|
|
128
|
+
image_ref->Reset();
|
|
129
|
+
});
|
|
24
130
|
}
|
|
25
131
|
|
|
26
|
-
// Image コンストラクタ: src セッターで読み込み+デコードし
|
|
132
|
+
// Image コンストラクタ: src セッターで読み込み+デコードし load イベントを発火する。
|
|
27
133
|
void ImageConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
28
134
|
{
|
|
29
135
|
v8::Isolate* isolate = args.GetIsolate();
|
|
@@ -31,11 +137,46 @@ void ImageConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
31
137
|
v8util::ThrowTypeError(isolate, "Image must be called with new");
|
|
32
138
|
return;
|
|
33
139
|
}
|
|
34
|
-
|
|
35
|
-
args.This()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
140
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
141
|
+
v8::Local<v8::Object> self = args.This();
|
|
142
|
+
|
|
143
|
+
v8util::SetValue(isolate, self, "__isImageElement", v8::Boolean::New(isolate, true));
|
|
144
|
+
v8util::SetValue(isolate, self, "tagName", Str(isolate, "IMG"));
|
|
145
|
+
v8util::SetValue(isolate, self, "localName", Str(isolate, "img"));
|
|
146
|
+
v8util::SetValue(isolate, self, "complete", v8::Boolean::New(isolate, false));
|
|
147
|
+
v8util::SetValue(isolate, self, "width", v8::Integer::New(isolate, 0));
|
|
148
|
+
v8util::SetValue(isolate, self, "height", v8::Integer::New(isolate, 0));
|
|
149
|
+
v8util::SetValue(isolate, self, "naturalWidth", v8::Integer::New(isolate, 0));
|
|
150
|
+
v8util::SetValue(isolate, self, "naturalHeight", v8::Integer::New(isolate, 0));
|
|
151
|
+
InstallEventTarget(isolate, self); // addEventListener("load"/"error")
|
|
152
|
+
|
|
153
|
+
self->SetNativeDataProperty(ctx, Str(isolate, "src"),
|
|
154
|
+
[](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
155
|
+
v8::Local<v8::Value> v;
|
|
156
|
+
if (info.This()->Get(info.GetIsolate()->GetCurrentContext(),
|
|
157
|
+
Str(info.GetIsolate(), "__src")).ToLocal(&v) && !v->IsUndefined()) {
|
|
158
|
+
info.GetReturnValue().Set(v);
|
|
159
|
+
} else {
|
|
160
|
+
info.GetReturnValue().Set(Str(info.GetIsolate(), ""));
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
[](v8::Local<v8::Name>, v8::Local<v8::Value> value,
|
|
164
|
+
const v8::PropertyCallbackInfo<void>& info) {
|
|
165
|
+
v8::Isolate* iso = info.GetIsolate();
|
|
166
|
+
v8util::SetValue(iso, info.This(), "__src", value);
|
|
167
|
+
LoadImageFromSrc(iso, info.This(), ToStdString(iso, value));
|
|
168
|
+
}).Check();
|
|
169
|
+
|
|
170
|
+
// decode(): 読み込み済みなら resolve、未読み込みでも resolve (簡易)
|
|
171
|
+
v8util::SetMethod(isolate, self, "decode",
|
|
172
|
+
[](const v8::FunctionCallbackInfo<v8::Value>& a) {
|
|
173
|
+
v8::Isolate* iso = a.GetIsolate();
|
|
174
|
+
auto r = v8::Promise::Resolver::New(iso->GetCurrentContext()).ToLocalChecked();
|
|
175
|
+
r->Resolve(iso->GetCurrentContext(), v8::Undefined(iso)).Check();
|
|
176
|
+
a.GetReturnValue().Set(r->GetPromise());
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
args.GetReturnValue().Set(self);
|
|
39
180
|
}
|
|
40
181
|
|
|
41
182
|
// createImageBitmap(source) -> Promise<ImageBitmap>
|
|
@@ -71,16 +212,83 @@ void CreateImageBitmap(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
71
212
|
if (bytes) {
|
|
72
213
|
input = std::move(*bytes);
|
|
73
214
|
}
|
|
215
|
+
} else if (args[0]->IsObject()) {
|
|
216
|
+
v8::Local<v8::Object> obj = args[0].As<v8::Object>();
|
|
217
|
+
|
|
218
|
+
// 1) ImageData ({ data: Uint8ClampedArray, width, height }):
|
|
219
|
+
// デコード不要でそのまま RGBA として取り込む
|
|
220
|
+
// (webgpu レンダラのキャプチャ/フィルタ→ImageBitmap 化の経路)。
|
|
221
|
+
v8::Local<v8::Value> data_v, w_v, h_v;
|
|
222
|
+
if (obj->Get(ctx, Str(isolate, "data")).ToLocal(&data_v) && data_v->IsArrayBufferView() &&
|
|
223
|
+
obj->Get(ctx, Str(isolate, "width")).ToLocal(&w_v) && w_v->IsNumber() &&
|
|
224
|
+
obj->Get(ctx, Str(isolate, "height")).ToLocal(&h_v) && h_v->IsNumber()) {
|
|
225
|
+
const auto w = static_cast<uint32_t>(w_v.As<v8::Number>()->Value());
|
|
226
|
+
const auto h = static_cast<uint32_t>(h_v.As<v8::Number>()->Value());
|
|
227
|
+
auto view = data_v.As<v8::ArrayBufferView>();
|
|
228
|
+
if (view->ByteLength() < static_cast<size_t>(w) * h * 4) {
|
|
229
|
+
resolver->Reject(ctx, v8::Exception::TypeError(
|
|
230
|
+
Str(isolate, "ImageData size mismatch"))).Check();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
auto* raw = new DecodedImage();
|
|
234
|
+
raw->width = w;
|
|
235
|
+
raw->height = h;
|
|
236
|
+
raw->rgba.resize(static_cast<size_t>(w) * h * 4);
|
|
237
|
+
view->CopyContents(raw->rgba.data(), raw->rgba.size());
|
|
238
|
+
resolver->Resolve(ctx, WrapImageBitmap(isolate, raw)).Check();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2) ImageBitmap / 2D 済み canvas / video: ピクセルを複製して新規 ImageBitmap
|
|
243
|
+
const uint8_t* px = nullptr;
|
|
244
|
+
uint32_t sw = 0, sh = 0;
|
|
245
|
+
if (GetImageSourcePixels(isolate, obj, &px, &sw, &sh) && px) {
|
|
246
|
+
auto* raw = new DecodedImage();
|
|
247
|
+
raw->width = sw;
|
|
248
|
+
raw->height = sh;
|
|
249
|
+
raw->rgba.assign(px, px + static_cast<size_t>(sw) * sh * 4);
|
|
250
|
+
resolver->Resolve(ctx, WrapImageBitmap(isolate, raw)).Check();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 3) Blob: 内包バイト列 (エンコード済み画像) を WIC デコードへ回す
|
|
255
|
+
v8::Local<v8::Value> blob_v;
|
|
256
|
+
if (obj->Get(ctx, Str(isolate, "__blobData")).ToLocal(&blob_v) && blob_v->IsArrayBuffer()) {
|
|
257
|
+
auto store = blob_v.As<v8::ArrayBuffer>()->GetBackingStore();
|
|
258
|
+
input.assign(static_cast<uint8_t*>(store->Data()),
|
|
259
|
+
static_cast<uint8_t*>(store->Data()) + store->ByteLength());
|
|
260
|
+
}
|
|
74
261
|
}
|
|
75
262
|
|
|
76
|
-
|
|
77
|
-
if (input.empty() || !DecodeImageWithWIC(input, *img)) {
|
|
78
|
-
delete img;
|
|
263
|
+
if (input.empty()) {
|
|
79
264
|
resolver->Reject(ctx, v8::Exception::Error(Str(isolate, "Image decode failed"))).Check();
|
|
80
265
|
return;
|
|
81
266
|
}
|
|
82
267
|
|
|
83
|
-
|
|
268
|
+
// WIC デコードはバックグラウンドスレッドへ (Promise なので元々非同期契約)
|
|
269
|
+
auto resolver_ref =
|
|
270
|
+
std::make_shared<v8::Global<v8::Promise::Resolver>>(isolate, resolver);
|
|
271
|
+
auto decoded = std::make_shared<DecodedImage>();
|
|
272
|
+
auto ok = std::make_shared<bool>(false);
|
|
273
|
+
|
|
274
|
+
decodequeue::Submit(
|
|
275
|
+
[input = std::move(input), decoded, ok]() {
|
|
276
|
+
*ok = DecodeImage(input, *decoded);
|
|
277
|
+
},
|
|
278
|
+
[isolate, resolver_ref, decoded, ok]() {
|
|
279
|
+
v8::HandleScope hs(isolate);
|
|
280
|
+
v8::Local<v8::Promise::Resolver> r = resolver_ref->Get(isolate);
|
|
281
|
+
v8::Local<v8::Context> c = r->GetCreationContextChecked();
|
|
282
|
+
v8::Context::Scope cs(c);
|
|
283
|
+
if (*ok) {
|
|
284
|
+
auto* img = new DecodedImage(std::move(*decoded));
|
|
285
|
+
r->Resolve(c, WrapImageBitmap(isolate, img)).Check();
|
|
286
|
+
} else {
|
|
287
|
+
r->Reject(c, v8::Exception::Error(
|
|
288
|
+
Str(isolate, "Image decode failed"))).Check();
|
|
289
|
+
}
|
|
290
|
+
resolver_ref->Reset();
|
|
291
|
+
});
|
|
84
292
|
}
|
|
85
293
|
|
|
86
294
|
} // namespace
|
|
@@ -99,8 +307,7 @@ v8::Local<v8::Object> WrapImageBitmap(v8::Isolate* isolate, DecodedImage* image)
|
|
|
99
307
|
v8util::SetValue(isolate, obj, "height", v8::Integer::NewFromUnsigned(isolate, image->height));
|
|
100
308
|
v8util::SetMethod(isolate, obj, "close", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
|
|
101
309
|
|
|
102
|
-
|
|
103
|
-
handle->SetWeak(image, ReleaseDecoded, v8::WeakCallbackType::kParameter);
|
|
310
|
+
v8util::AttachWeak(isolate, obj, image);
|
|
104
311
|
return obj;
|
|
105
312
|
}
|
|
106
313
|
|
|
@@ -126,6 +333,25 @@ bool GetImageSourcePixels(v8::Isolate* isolate, v8::Local<v8::Value> source,
|
|
|
126
333
|
return true;
|
|
127
334
|
}
|
|
128
335
|
|
|
336
|
+
// Image 要素 (new Image() + src): 読み込み完了後は内部フィールドに DecodedImage
|
|
337
|
+
v8::Local<v8::Value> is_image;
|
|
338
|
+
if (obj->Get(ctx, Str(isolate, "__isImageElement")).ToLocal(&is_image) &&
|
|
339
|
+
is_image->IsBoolean() && is_image.As<v8::Boolean>()->Value()) {
|
|
340
|
+
if (obj->InternalFieldCount() < 1) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
v8::Local<v8::Data> field = obj->GetInternalField(0);
|
|
344
|
+
if (!field->IsValue() || !field.As<v8::Value>()->IsExternal()) {
|
|
345
|
+
return false; // 未読み込み
|
|
346
|
+
}
|
|
347
|
+
auto* img = static_cast<DecodedImage*>(
|
|
348
|
+
field.As<v8::Value>().As<v8::External>()->Value());
|
|
349
|
+
*out_rgba = img->rgba.data();
|
|
350
|
+
*out_width = img->width;
|
|
351
|
+
*out_height = img->height;
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
129
355
|
// video 要素: 現在フレームを返す
|
|
130
356
|
v8::Local<v8::Value> is_video;
|
|
131
357
|
if (obj->Get(ctx, Str(isolate, "__isVideoElement")).ToLocal(&is_video) &&
|
|
@@ -151,6 +377,8 @@ void InstallImage(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
|
|
|
151
377
|
v8::Local<v8::FunctionTemplate> image_tmpl =
|
|
152
378
|
v8::FunctionTemplate::New(isolate, ImageConstructor);
|
|
153
379
|
image_tmpl->SetClassName(Str(isolate, "Image"));
|
|
380
|
+
// 内部フィールド0: デコード済み DecodedImage* (src セッターが設定)
|
|
381
|
+
image_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
154
382
|
global->Set(ctx, Str(isolate, "Image"),
|
|
155
383
|
image_tmpl->GetFunction(ctx).ToLocalChecked()).Check();
|
|
156
384
|
|