Skip to content

local pyodide preview#1052

Draft
Vipitis wants to merge 3 commits into
fastplotlib:mainfrom
Vipitis:browser
Draft

local pyodide preview#1052
Vipitis wants to merge 3 commits into
fastplotlib:mainfrom
Vipitis:browser

Conversation

@Vipitis
Copy link
Copy Markdown

@Vipitis Vipitis commented May 9, 2026

Hey,

as I getting wgpu-py and pygfx to run in the browser I also tried some of the fpl examples to find some more problems.

thought I will just share the script which is really janky and adapted from rendercanvas, wgpu and pygfx... but mostly works. It includes a bunch of hacks to make examples work. It also pulls in the dependencies from the PR branch doc previews... so this might break when those are updated or merged etc.

I haven't looked into any fpl specific problems with pyodide, but there are a few I saw already (like the tool bar is missing?, windows overlapping?) - just clicking around to see any issues that aren't covered in the wgpu and pygfx examples and already found something that is now fixed.

to try this yourself, simply clone this branch (or copy the script really...)

> cd examples
> python serve_browser_examples.py

I only tested this on Win10 with Chrome but you should get this list of all the example files and most will run. You only run the script to patch in the local fpl wheel and serve the http. All execution is happening in the browser using pyodide. (so you could theoretically copy the html source and host it statically anywhere)

fpl_snippet.mp4

load times and performance is really bad at the moment.
I haven't looked into how the docs work for fpl, there is a really janky iframe injection that puts embeds interactive examples in the pygfx docs for example which I might be able to port that to here to make it even more accessible.

let me know if you have any questions or suggestions and I will see how much time I can find - just really excited for what the future will enable with pyodide support.

@kushalkolar
Copy link
Copy Markdown
Member

This is really cool! The toolbar requires imgui, was easy to get it working by just adding "imgui_bundle" to the deps. Was able to run stuff in chrome after enabling the wgpu experimental features. Firefox gave RuntimeError: WebAssembly stack switching not supported in this JavaScript runtime. Some examples that use uint8 don't work but this is a great start 🥳

@Vipitis
Copy link
Copy Markdown
Author

Vipitis commented May 9, 2026

The toolbar requires imgui, was easy to get it working by just adding "imgui_bundle" to the deps.

The way micropip resolves dependencies is a little odd to me, especially when you use .whl instead of a wheel on the index. Like the order matters for version conflicts etc. I saw imgui examples working, but that's due to a pyodide helper. I will add imgui_bundle to the list of deps there. Which might not be needed for every single example anyway.
I think it's also an option to require imgui when sys.platform == "emscripten" in the pyproject.toml

Firefox gave RuntimeError: WebAssembly stack switching not supported in this JavaScript runtime.

JSPI is used by pyodide to await an async function, it's used for the wgpu-py mappings. Looks like it's still worked on for Firefox. I don't think moving pygfx to all async would drop that requirement... not sure.

Some examples that use uint8 don't work

Can you share which examples caused errors for you? I can look into if they are are an issue in my wgpu changes that needs fixes.

@kushalkolar
Copy link
Copy Markdown
Member

Firefox gave RuntimeError: WebAssembly stack switching not supported in this JavaScript runtime.

JSPI is used by pyodide to await an async function, it's used for the wgpu-py mappings. Looks like it's still worked on for Firefox. I don't think moving pygfx to all async would drop that requirement... not sure.

This is the full traceback I get on firefox, maybe I can try updating.

Failed to load: PythonError: Traceback (most recent call last): File "/lib/python313.zip/_pyodide/_base.py", line 597, in eval_code_async await CodeRunner( ...<9 lines>... .run_async(globals, locals) File "/lib/python313.zip/_pyodide/_base.py", line 411, in run_async coroutine = eval(self.code, globals, locals) File "", line 11, in File "/lib/python3.13/site-packages/fastplotlib/__init__.py", line 6, in from .utils import loop # noqa ^^^^^^^^^^^^^^^^^^^^^^^ File "/lib/python3.13/site-packages/fastplotlib/utils/__init__.py", line 5, in from .functions import * File "/lib/python3.13/site-packages/fastplotlib/utils/functions.py", line 7, in from pygfx import Texture, Color File "/lib/python3.13/site-packages/pygfx/__init__.py", line 9, in from .objects import * File "/lib/python3.13/site-packages/pygfx/objects/__init__.py", line 79, in from ._ruler import Ruler File "/lib/python3.13/site-packages/pygfx/objects/_ruler.py", line 8, in from ._text import MultiText File "/lib/python3.13/site-packages/pygfx/objects/_text.py", line 43, in from ..utils import text as textmodule File "/lib/python3.13/site-packages/pygfx/utils/text/__init__.py", line 17, in from ._fontmanager import FontProps, FontManager, font_manager # noqa: F401 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontmanager.py", line 369, in font_manager = FontManager() File "/lib/python3.13/site-packages/pygfx/utils/text/_fontmanager.py", line 130, in __init__ self._load_fonts() ~~~~~~~~~~~~~~~~^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontmanager.py", line 165, in _load_fonts for ff in get_all_fonts(): ~~~~~~~~~~~~~^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontfinder.py", line 214, in get_all_fonts return get_builtin_fonts() | get_system_fonts() ~~~~~~~~~~~~~~~~~^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontfinder.py", line 220, in get_builtin_fonts return {FontFile(p) for p in file_paths} ~~~~~~~~^^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontfinder.py", line 71, in __init__ self._get_face() # load it early so the direct access to freetype attributes is skipped... feels janky. ~~~~~~~~~~~~~~^^ File "/lib/python3.13/site-packages/pygfx/utils/text/_fontfinder.py", line 97, in _get_face run_sync(face.load().then(lambda _: document.fonts.add(face), lambda err: print(err))) ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RuntimeError: WebAssembly stack switching not supported in this JavaScript runtime 

Some examples that use uint8 don't work

Can you share which examples caused errors for you? I can look into if they are are an issue in my wgpu changes that needs fixes.

I got it in the ndwidget branch, but that might be because we recently allowed direct uint8 textures there. But in main the colormap picker doesn't show up when you right-click the colorbar in the image_widget/image_widget.py example. I wonder if it's an issue with the wgpu texture upload. It's basically this wgpu-imgui example: https://github.com/pygfx/wgpu-py/blob/main/examples/imgui_cmap_picker.py

@Vipitis
Copy link
Copy Markdown
Author

Vipitis commented May 10, 2026

the colormap picker doesn't show up when you right-click the colorbar in the image_widget/image_widget.py example. I wonder if it's an issue with the wgpu texture upload. It's basically this wgpu-imgui example: https://github.com/pygfx/wgpu-py/blob/main/examples/imgui_cmap_picker.py

ah, that was one example I never tried in wgpu-py and it also had this issue (it spews your browser console with a sorta helpful message). I pushed a commit that fixes the behaviour. But I think there might be an actual problem with how we transform data from python to js (some dimension of the shape might be lost), as the default (no args) for .create_view() seems to behave as expected natively but not in the browser.

I will look into that later, thanks!

@kushalkolar
Copy link
Copy Markdown
Member

I wonder what performance we can get with compute shaders here, like what's the overhead. When I get a chance I want to try running this stuff on it: https://github.com/kushalkolar/wgpu-sparse-matvec

@Vipitis
Copy link
Copy Markdown
Author

Vipitis commented May 10, 2026

I am currently seeing about 20% performance compared to native when you look at framerates. But there is a lot to that. Moving data is probably the worst right now since it's doing 3 or more copies. And scheduling as issues because of the waiting and being driven by the browser framerate too.
There should be very little overhead once you run compute on the GPU. Because it's the same GPU you would use native. But might be fun to measure it.

I think one limitation with wasm is 2GB of memory. But not sure if that applies to resources on the GPU

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants