fresnel.worker.pas2js.wasmapi.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. {
  2. This file is part of the Fresnel Library.
  3. Copyright (c) 2025 by the FPC & Lazarus teams.
  4. Pas2js Fresnel interface - Webassembly rendering API
  5. See the file COPYING.modifiedLGPL.txt, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. // hic sunt dracones
  12. {$mode objfpc}
  13. {$h+}
  14. {$modeswitch externalclass}
  15. {$modeswitch advancedrecords}
  16. {$DEFINE IMAGE_USEOSC}
  17. unit fresnel.worker.pas2js.wasmapi;
  18. interface
  19. uses
  20. SysUtils, Types,
  21. JS, WebOrWorker, WebWorker, wasienv,
  22. Rtl.WorkerCommands,
  23. fresnel.wasm.shared,
  24. fresnel.messages.pas2js.wasmapi,
  25. fresnel.menubuilder.pas2js.wasmapi,
  26. fresnel.shared.pas2js;
  27. type
  28. TMainThreadCallFunc = reference to function: TCanvasError;
  29. TTimerCallback = procedure (aCurrentTicks, aPreviousTicks : NativeInt);
  30. { TWasmFresnelWorkerApi }
  31. TWasmFresnelWorkerApi = class(TWasmFresnelSharedApi)
  32. private
  33. FMenuSupport : Boolean;
  34. FAtomicBuffer : TJSSharedArrayBuffer;
  35. FAtomicArray : TJSInt32Array;
  36. class var vLastPendingCallID : NativeInt;
  37. // temporary to ignore some non-essential API calls
  38. function CreateIgnoreCall(const aFuncName: String): TMainThreadCallFunc;
  39. function CreateMainThreadBlockingCall(const aFuncName: String) : TMainThreadCallFunc;
  40. function CreateMainThreadFireAndForgetCall(const aFuncName: String) : TMainThreadCallFunc;
  41. procedure DeclareMainThreadBlockingCall(aImportObject : TJSObject; const aFuncName: String);
  42. procedure DeclareMainThreadFireAndForgetCall(aImportObject : TJSObject; const aFuncName: String);
  43. procedure DeclareMainThreadCall(aImportObject : TJSObject; const aFuncName: String);
  44. function DrawOffscreenCanvasOnWindowCanvas(const aWindowID : TWindowCanvasID; const aCanvasID : TOffscreenCanvasID) : TCanvasError;
  45. procedure DoTimerTick;
  46. protected
  47. procedure SetMenuSupport(const val : Boolean);
  48. procedure SetupLocalStorageBridge;
  49. public
  50. constructor Create(aEnv : TPas2JSWASIEnvironment); override;
  51. procedure HandleFresnelCommand(Cmd: TFresnelMessage);
  52. procedure FillImportObject(aObject : TJSObject); override;
  53. property MenuSupport : Boolean read FMenuSupport write SetMenuSupport;
  54. // Menu
  55. function HandleMenuClick(aMenuID : TMenuID; aData : TWasmPointer) : Boolean; override;
  56. // RequestAnimationFrame
  57. procedure RequestAnimationFrame(userData: TWasmPointer);
  58. procedure DoHandleAnimationFrameMessage(aMessage : TFresnelMessage_RequestAnimationFrame);
  59. // UserMedia
  60. procedure EnumerateUserMedia(userData: TWasmPointer);
  61. procedure DoHandleEnumeratedUserMediaMessage(aMessage : TFresnelMessage_EnumerateUserMedia);
  62. procedure DoHandleUserMediaFrame(aMessage : TFresnelMessage_UserMediaFrame);
  63. end;
  64. // --------------------------------------------------------------------
  65. // --------------------------------------------------------------------
  66. // --------------------------------------------------------------------
  67. implementation
  68. // --------------------------------------------------------------------
  69. // --------------------------------------------------------------------
  70. // --------------------------------------------------------------------
  71. { TWasmFresnelWorkerApi }
  72. // Create
  73. //
  74. constructor TWasmFresnelWorkerApi.Create(aEnv: TPas2JSWASIEnvironment);
  75. begin
  76. inherited Create(aEnv);
  77. FAtomicBuffer := TJSSharedArrayBuffer.new(8);
  78. FAtomicArray := TJSInt32Array.new(FAtomicBuffer);
  79. TCommandDispatcher.Instance.specialize AddCommandHandler<TFresnelMessage>(cmdFresnel,@HandleFresnelCommand);
  80. SetupLocalStorageBridge;
  81. end;
  82. // CreateIgnoreCall
  83. //
  84. function TWasmFresnelWorkerApi.CreateIgnoreCall(const aFuncName: String): TMainThreadCallFunc;
  85. begin
  86. Result := function () : TCanvasError
  87. begin
  88. writeln('Call ignored: ', aFuncName);
  89. end;
  90. end;
  91. // CreateMainThreadBlockingCall
  92. //
  93. function Array_prototype_slice(val : JSValue) : TJSValueDynArray; external name 'Array.prototype.slice.call';
  94. function TWasmFresnelWorkerApi.CreateMainThreadBlockingCall(const aFuncName: String): TMainThreadCallFunc;
  95. begin
  96. Result := function () : TCanvasError
  97. var
  98. pendingCallID : Integer;
  99. lMessage : TFresnelMessage_FunctionCall;
  100. atomicWaitOutcome : String;
  101. begin
  102. Inc(vLastPendingCallID);
  103. pendingCallID := vLastPendingCallID;
  104. lMessage := TFresnelMessage_FunctionCall.new;
  105. lMessage.Typ := cFresnel_Message_Call;
  106. lMessage.ID := pendingCallID;
  107. lMessage.FuncName := aFuncName;
  108. lMessage.Args := Array_prototype_slice(JSArguments);
  109. lMessage.Memory := Env.Memory.buffer;
  110. lMessage.Atomic := FAtomicArray;
  111. TJSAtomics.store(FAtomicArray, 0, 0);
  112. TJSDedicatedWorkerGlobalScope(Self_).postMessage(lMessage);
  113. atomicWaitOutcome := TJSAtomics.wait(FAtomicArray, 0, 0);
  114. //Console.log('Atomics wait for pendingCallID ', pendingCallID, ' = ', atomicWaitOutcome);
  115. Result := TJSAtomics.load(FAtomicArray, 1);
  116. end;
  117. end;
  118. // CreateMainThreadFireAndForgetCall
  119. //
  120. function TWasmFresnelWorkerApi.CreateMainThreadFireAndForgetCall(const aFuncName: String): TMainThreadCallFunc;
  121. begin
  122. Result := function () : TCanvasError
  123. var
  124. pendingCallID : Integer;
  125. lMessage : TFresnelMessage_FunctionCall;
  126. begin
  127. Inc(vLastPendingCallID);
  128. pendingCallID := vLastPendingCallID;
  129. lMessage := TFresnelMessage_FunctionCall.new;
  130. lMessage.Typ := cFresnel_Message_Call;
  131. lMessage.ID := pendingCallID;
  132. lMessage.FuncName := aFuncName;
  133. lMessage.Args := Array_prototype_slice(JSArguments);
  134. TJSDedicatedWorkerGlobalScope(Self_).postMessage(lMessage);
  135. Result := ECANVAS_SUCCESS;
  136. end;
  137. end;
  138. // DeclareMainThreadBlockingCall
  139. //
  140. procedure TWasmFresnelWorkerApi.DeclareMainThreadBlockingCall(aImportObject: TJSObject; const aFuncName: String);
  141. begin
  142. aImportObject[aFuncName] := CreateMainThreadBlockingCall(aFuncName);
  143. end;
  144. // DeclareMainThreadFireAndForgetCall
  145. //
  146. procedure TWasmFresnelWorkerApi.DeclareMainThreadFireAndForgetCall(aImportObject: TJSObject; const aFuncName: String);
  147. begin
  148. aImportObject[aFuncName] := CreateMainThreadFireAndForgetCall(aFuncName);
  149. end;
  150. // DeclareMainThreadCall
  151. //
  152. procedure TWasmFresnelWorkerApi.DeclareMainThreadCall(aImportObject: TJSObject; const aFuncName: String);
  153. begin
  154. DeclareMainThreadBlockingCall(aImportObject, aFuncName);
  155. end;
  156. // HandleFresnelCommand
  157. //
  158. procedure TWasmFresnelWorkerApi.HandleFresnelCommand(Cmd : TFresnelMessage);
  159. var
  160. dataType: String;
  161. begin
  162. dataType := cmd.Typ;
  163. Case dataType of
  164. cFresnel_RequestAnimationFrame:
  165. DoHandleAnimationFrameMessage(TFresnelMessage_RequestAnimationFrame(cmd));
  166. cFresnel_EnqueueEvent:
  167. EnqueueEvent(TWindowEvent(TFresnelMessage_EnqueueEvent(cmd).Event));
  168. cFresnel_Tick:
  169. DoTimerTick;
  170. cFresnel_Message_Call:
  171. begin
  172. if (cmd['funcName'] = 'wake_main_thread') then
  173. MainThreadWake;
  174. end;
  175. cFresnel_UserMediaFrame:
  176. DoHandleUserMediaFrame(TFresnelMessage_UserMediaFrame(cmd));
  177. cFresnel_MenuClick:
  178. HandleMenuClick(
  179. TFresnelMessage_HandleMenuClick(cmd).MenuID,
  180. TFresnelMessage_HandleMenuClick(cmd).UserData
  181. );
  182. cFresnel_EnumerateUserMedia:
  183. DoHandleEnumeratedUserMediaMessage(TFresnelMessage_EnumerateUserMedia(cmd));
  184. else
  185. writeln('Unsupported Fresnel message type ', TJSJSON.stringify(cmd));
  186. end;
  187. end;
  188. // RequestAnimationFrame
  189. //
  190. procedure TWasmFresnelWorkerApi.RequestAnimationFrame(userData: TWasmPointer);
  191. var
  192. lMessage: TFresnelMessage_RequestAnimationFrame;
  193. begin
  194. lMessage := TFresnelMessage_RequestAnimationFrame(TFresnelMessage.newMessage(cFresnel_RequestAnimationFrame));
  195. lMessage.UserData := userData;
  196. TCommandDispatcher.Instance.SendCommand(lMessage);
  197. end;
  198. // DoHandleAnimationFrameMessage
  199. //
  200. procedure TWasmFresnelWorkerApi.DoHandleAnimationFrameMessage(aMessage : TFresnelMessage_RequestAnimationFrame);
  201. var
  202. lCallbackValue : JSValue;
  203. lCallback : TAnimationFrameCallback absolute lCallbackValue;
  204. begin
  205. lCallbackValue := InstanceExports['__fresnel_animation_frame'];
  206. if lCallbackValue then
  207. lCallback;
  208. end;
  209. // EnumerateUserMedia
  210. //
  211. procedure TWasmFresnelWorkerApi.EnumerateUserMedia(userData: TWasmPointer);
  212. var
  213. lMessage: TFresnelMessage_EnumerateUserMedia;
  214. begin
  215. lMessage := TFresnelMessage_EnumerateUserMedia(TFresnelMessage.NewMessage(cFresnel_EnumerateUserMedia));
  216. lMessage.UserData := userData;
  217. TCommandDispatcher.Instance.SendCommand(lMessage);
  218. end;
  219. // DoHandleEnumeratedUserMediaMessage
  220. //
  221. procedure TWasmFresnelWorkerApi.DoHandleEnumeratedUserMediaMessage(aMessage: TFresnelMessage_EnumerateUserMedia);
  222. var
  223. lCallback : JSValue;
  224. begin
  225. FEnumeratedUserMedia := aMessage.UserMediaData;
  226. lCallback := InstanceExports['__fresnel_usermedia_enumerated'];
  227. if lCallback then
  228. TUserMediaCallback(lCallback)(Length(FEnumeratedUserMedia), aMessage.UserData);
  229. end;
  230. // DoHandleUserMediaFrame
  231. //
  232. procedure TWasmFresnelWorkerApi.DoHandleUserMediaFrame(aMessage: TFresnelMessage_UserMediaFrame);
  233. var
  234. lBitmapID : Integer;
  235. lCallback : JSValue;
  236. begin
  237. lCallback := InstanceExports['__fresnel_usermedia_frame'];
  238. if lCallback then
  239. begin
  240. lBitmapID := StoreImageBitmap(aMessage.ImageBitmap);
  241. TUserMediaFrameCallback(lCallback)(aMessage.Timestamp, aMessage.VideoID, lBitmapID);
  242. end
  243. else aMessage.ImageBitmap.close;
  244. end;
  245. // DrawOffscreenCanvasOnWindowCanvas
  246. //
  247. function TWasmFresnelWorkerApi.DrawOffscreenCanvasOnWindowCanvas(
  248. const aWindowID: TWindowCanvasID; const aCanvasID: TOffscreenCanvasID) : TCanvasError;
  249. var
  250. lMessage : TFresnelMessage_DrawOffscreenCanvasOnWindow;
  251. pendingCallID : Integer;
  252. canvasRef : TOffscreenCanvasReference;
  253. canvas : TJSHTMLOffscreenCanvas;
  254. begin
  255. Inc(vLastPendingCallID);
  256. pendingCallID := vLastPendingCallID;
  257. canvasRef := GetOffscreenCanvasRef(aCanvasID);
  258. if canvasRef = nil then
  259. Exit(ECANVAS_NOCANVAS);
  260. canvas := canvasRef.Canvas;
  261. if (canvas.Width = 0) or (canvas.Height = 0) then
  262. Exit(ECANVAS_SUCCESS);
  263. lMessage := TFresnelMessage_DrawOffscreenCanvasOnWindow(TFresnelMessage.NewMessage(cFresnel_Message_DOCOW));
  264. lMessage.ID := pendingCallID;
  265. lMessage.Atomic := FAtomicArray;
  266. lMessage.WindowID := aWindowID;
  267. lMessage.ImageBitmap := canvas.transferToImageBitmap;
  268. TJSAtomics.store(FAtomicArray, 0, 0);
  269. TCommandDispatcher.Instance.SendCommand(lMessage, [ lMessage.ImageBitmap ]);
  270. TJSAtomics.wait(FAtomicArray, 0, 0);
  271. Result := TJSAtomics.load(FAtomicArray, 1);
  272. end;
  273. // DoTimerTick
  274. //
  275. var
  276. vLastTimeTimerTick : NativeInt;
  277. procedure TWasmFresnelWorkerApi.DoTimerTick;
  278. var
  279. Callback : JSValue;
  280. T : NativeInt;
  281. begin
  282. T := vLastTimeTimerTick;
  283. vLastTimeTimerTick := TJSDate.now;
  284. if not assigned(InstanceExports) then
  285. Console.log('DoTimerTick: no instance exports !')
  286. else
  287. begin
  288. Callback := InstanceExports['__fresnel_tick'];
  289. if Assigned(Callback) then
  290. TTimerCallback(CallBack)(vLastTimeTimerTick, T)
  291. else
  292. Console.warn('DoTimerTick: no tick callback !');
  293. end;
  294. end;
  295. // SetMenuSupport
  296. //
  297. procedure TWasmFresnelWorkerApi.SetMenuSupport(const val: Boolean);
  298. var
  299. lMessage : TFresnelMessage;
  300. begin
  301. if val = FMenuSupport then
  302. Exit;
  303. FMenuSupport := val;
  304. lMessage := TFresnelMessages.CreateMessage_MenuSupport(val);
  305. TCommandDispatcher.Instance.SendCommand(lMessage);
  306. end;
  307. // SetupLocalStorageBridge
  308. //
  309. procedure TWasmFresnelWorkerApi.SetupLocalStorageBridge;
  310. type
  311. TGetItemMainThread = reference to function (aName : String; aDataBuffer : TJSSharedArrayBuffer) : TCanvasError;
  312. var
  313. setItem, getItem, removeItem, clear : TMainThreadCallFunc;
  314. getItemMain : TGetItemMainThread;
  315. begin
  316. setItem := CreateMainThreadBlockingCall('localstorage_setitem');
  317. removeItem := CreateMainThreadBlockingCall('localstorage_removeitem');
  318. clear := CreateMainThreadBlockingCall('localstorage_clear');
  319. getItemMain := TGetItemMainThread(CreateMainThreadBlockingCall('localstorage_getitem'));
  320. getItem := TMainThreadCallFunc(
  321. function (aName : String) : JSValue
  322. var
  323. lDataBuffer : TJSSharedArrayBuffer;
  324. lStringArray : TJSUInt16Array;
  325. lErr : TCanvasError;
  326. lAttempts, lReturnedSize : Integer;
  327. begin
  328. lDataBuffer := TJSSharedArrayBuffer.new(16*1024); // initial guess of 16 kb
  329. lAttempts := 3;
  330. while lAttempts > 0 do
  331. begin
  332. lErr := getItemMain(aName, lDataBuffer);
  333. case lErr of
  334. EWASMEVENT_SUCCESS : begin
  335. lReturnedSize := TJSInt32Array.new(lDataBuffer, 0, 1)[0];
  336. if lReturnedSize = -1 then
  337. Exit(JS.Undefined);
  338. lStringArray := TJSUint16Array.new(lDataBuffer, 4, lReturnedSize);
  339. Result := TJSFunction(@TJSString.fromCharCode).apply(nil, TJSValueDynArray(lStringArray));
  340. Exit;
  341. end;
  342. EWASMEVENT_BUFFER_SIZE : begin
  343. lReturnedSize := TJSInt32Array.new(lDataBuffer, 0, 1)[0];
  344. lDataBuffer := TJSSharedArrayBuffer.new(lReturnedSize*2 + 1024); // adjust size with extra 1 kb margin
  345. end;
  346. else
  347. raise Exception.Create('LocalStorageBridge Error ' + IntToStr(lErr));
  348. end;
  349. Dec(lAttempts);
  350. end;
  351. raise Exception.Create('LocalStorageBridge failed after multiple attempts');
  352. end
  353. );
  354. asm
  355. self.localStorage = { getItem, setItem, removeItem, clear };
  356. end;
  357. end;
  358. // FillImportObject
  359. //
  360. procedure TWasmFresnelWorkerApi.FillImportObject(aObject: TJSObject);
  361. begin
  362. inherited FillImportObject(aObject);
  363. // Window
  364. DeclareMainThreadCall(aObject, 'canvas_allocate_window');
  365. DeclareMainThreadCall(aObject, 'canvas_deallocate_window');
  366. DeclareMainThreadCall(aObject, 'window_show_hide');
  367. aObject['canvas_draw_offscreen_on_window'] := @DrawOffscreenCanvasOnWindowCanvas;
  368. DeclareMainThreadCall(aObject, 'canvas_getrect');
  369. DeclareMainThreadCall(aObject, 'canvas_setrect');
  370. DeclareMainThreadCall(aObject, 'canvas_getsize');
  371. DeclareMainThreadCall(aObject, 'canvas_setsize');
  372. DeclareMainThreadCall(aObject, 'canvas_set_title');
  373. DeclareMainThreadCall(aObject, 'canvas_get_viewport_sizes');
  374. // Cursor
  375. DeclareMainThreadCall(aObject, 'cursor_set');
  376. // RequestAnimationFrame
  377. aObject['request_animation_frame'] := @RequestAnimationFrame;
  378. // Clipboard
  379. DeclareMainThreadCall(aObject, 'clipboard_read_text');
  380. DeclareMainThreadCall(aObject, 'clipboard_write_text');
  381. // Event
  382. aObject['event_set_special_keymap'] := CreateIgnoreCall('event_set_special_keymap');
  383. DeclareMainThreadFireAndForgetCall(aObject, 'wake_main_thread');
  384. // Menu
  385. DeclareMainThreadCall(aObject, 'menu_add_item');
  386. DeclareMainThreadCall(aObject, 'menu_remove_item');
  387. DeclareMainThreadCall(aObject, 'menu_update_item');
  388. // Debug
  389. aObject['console_log'] := @ConsoleLog;
  390. // UserMedia
  391. aObject['usermedia_enumerate'] := @EnumerateUserMedia;
  392. aObject['usermedia_getenumerated'] := @GetEnumeratedUserMedia;
  393. DeclareMainThreadCall(aObject, 'usermedia_startcapture');
  394. DeclareMainThreadCall(aObject, 'usermedia_stopcapture');
  395. DeclareMainThreadCall(aObject, 'usermedia_iscapturing');
  396. end;
  397. // HandleMenuClick
  398. //
  399. function TWasmFresnelWorkerApi.HandleMenuClick(aMenuID: TMenuID; aData: TWasmPointer): Boolean;
  400. var
  401. Callback : JSValue;
  402. begin
  403. Result:=False;
  404. if not assigned(InstanceExports) then
  405. Console.warn('No instance exports !')
  406. else
  407. begin
  408. Callback:=InstanceExports['__fresnel_menu_click'];
  409. if Assigned(Callback) then
  410. begin
  411. TMenuClickCallback(CallBack)(aMenuID,AData);
  412. Result:=True;
  413. end
  414. else
  415. Console.warn('No menu click callback !');
  416. end;
  417. end;
  418. end.