Remix.run Logo
evmar 2 hours ago

[post author] I looked into this API but wasn't sure how to make good use of it. I may have misunderstood, maybe you could help!

The doc you linked has two forms of use, sync and async.

For sync: it seems the idea is for the worker to render into an OffscreenCanvas, then postMessage an ImageBitmap created with transferToImageBitmap from worker to main thread for drawing. It seems like it would need to allocate a new bitmap for each frame. Currently Theseus puts the pixel data in shared memory and the main thread copies it out (required to create an ImageData), which at least in principle could reuse the copy buffer (though it currently doesn't), which seems better? https://github.com/evmar/theseus/blob/a5a849dbcf8046a2d1837a...

For async: in this the idea is have the worker render into an OffscreenCanvas linked to the on-screen one. But it seems to get an OffscreenCanvas in a worker, the main thread canvas must .transferControlToOffscreen() it to the worker. Under the current synchronization model[1] the only time the worker can receive a message is during startup, because the rest of the time it's deep in its own wasm call stacks. This means that if the worker needs to resize its canvas and then paint to it, it's stuck.

[1] I wrote "current" because after writing this post I learned about JSPI which might help with this.

modeless 2 hours ago | parent [-]

I think after you call transferControlToOffscreen() and transfer the OffscreenCanvas to the worker, then the worker can control the size of the canvas with the width and height properties of the OffscreenCanvas. I'm not sure how this interacts with layout though.

IIRC the transferToImageBitmap path is efficient and doesn't necessarily copy anything. The APIs are designed to allow the ImageBitmap you get to be a reference to the GPU texture that is the backbuffer of the canvas, not a copy. When you transfer it to the main thread you are supposed to draw it using an ImageBitmapRenderingContext, which doesn't need to do an extra blit. It's just directly composited with the rest of the page, all staying on the GPU. In theory if it's a full screen canvas with no DOM on top the browser could even skip compositing entirely though I don't know if that's implemented anywhere.

Of course there are probably a lot of ways to fall off the fast path. And I guess you are doing software rendering for GDI so you're not starting out with pixels on the GPU in the first place. I'm not sure what the best path is for you but I think you can probably benefit from OffscreenCanvas in some way.

evmar an hour ago | parent [-]

Thanks a lot for this, I will put it on my list to investigate.

I've gone in circles a few times with how to think about image buffer management because I also support DirectDraw, which is designed to be backed by accelerated graphics, with operations like scaling bitblit. (Currently the Theseus implementation uses a shared "Surface" type as the backing store for both GDI Windows and DirectX Surfaces.)

It's a bit complicated by a few things. (1) DirectX surfaces can be "locked" to access as pixel buffers, so any accelerated surface indirection I guess would need to be able to copy pixels back down into emulator memory. Which I guess I could just implement. (2) There's a bunch of different modes for operations like bitblit like setting a color key for transparency that I can't implement with the canvas API, so I think I'd need to use GL shaders if I want acceleration, not just canvas.