Re: [go-nuts] Writing bitmap data to a canvas with WebAssembly

2023-11-07 Thread Kai O'Reilly
Apologies for any confusion I created. I was referring to the time spent 
transferring the image from Go to JS and placing it on the canvas, not the 
actual time it takes the browser to render the canvas, as the transfer and 
placing time is the only thing that can be easily controlled.

On Tuesday, November 7, 2023 at 8:36:23 PM UTC-8 robert engels wrote:

> Your numbers do not make sense.
>
> At 300 us a frame, it would be more than 3k frames a second - which would 
> only be possible for an extremely tiny area.
>
> You need to include the time to produce the data when considering “render 
> time” - otherwise you are talking apples and oranges.
>
> On Nov 7, 2023, at 9:43 PM, Kai O'Reilly  wrote:
>
> For future reference for anyone who comes across this thread, you can 
> directly pass RGBA image data to a canvas, which is around 200 times faster 
> than encoding it to a PNG and rendering it to an offscreen image. Also, 
> passing the unsafe pointer to the slice to JavaScript instead of copying 
> the bytes improves performance by another factor of 5. These two changes 
> combined reduced the render time for me from roughly 300 milliseconds to 
> 300 microseconds (a 1000x improvement). This performance is enough to run 
> an interactive pure Go GUI through WASM without any noticeable lag, just by 
> writing images to a canvas.
>
> This is the relevant Go code:
>
> ```go
>  sz := dw.image.Bounds().Size()
> ptr := uintptr(unsafe.Pointer([0]))
> js.Global().Call("displayImage", ptr, len(dw.image.Pix), sz.X, sz.Y)
> ```
>
> And this is the relevant JS code:
>
> ```js
> const appCanvas = document.getElementById('app');
> const appCanvasCtx = appCanvas.getContext('2d');
>
> let wasm;
> let memoryBytes;
>
> // displayImage takes the pointer to the target image in the wasm linear 
> memory
> // and its length. Then, it gets the resulting byte slice and creates an 
> image data
> // with the given width and height.
> function displayImage(pointer, length, w, h) {
>   // if it doesn't exist or is detached, we have to make it
>   if (!memoryBytes || memoryBytes.byteLength === 0) {
> memoryBytes = new Uint8ClampedArray(wasm.instance.exports.mem.buffer);
>   }
>
>   // using subarray instead of slice gives a 5x performance improvement 
> due to no copying
>   let bytes = memoryBytes.subarray(pointer, pointer + length);
>   let data = new ImageData(bytes, w, h);
>   appCanvasCtx.putImageData(data, 0, 0);
> }
> ```
> In the JS code, `wasm` is initialized as the result of 
> `WebAssembly.instantiateStreaming`.
>
> On Friday, July 24, 2020 at 10:53:14 AM UTC-7 Mark Farnan wrote:
>
>> Little old, but this might also help. 
>>
>>  A while back I built a helper package to deal with these issues for 
>> canvas rendering from Go.  
>>
>> https://github.com/markfarnan/go-canvas  
>>
>> I'm currently working on it to add  WebGL support & get it working in 
>> TinyGo (some issues still).
>>
>> Regards
>>
>> Mark.
>>
>>
>>
>> On Sunday, 22 March 2020 at 09:08:40 UTC+1 Scott Pakin wrote:
>>
>>> I figure I ought to follow up with some results.  First, I got the 
>>> suggested approach of local render + js.CopyBytesToJS 
>>>  + update canvas from 
>>> image to work, so thanks, Agniva and Howard!  Second, for the benefit of 
>>> future readers of this thread, one thing that wasn't obvious to me is that 
>>> one needs to render the image data in a browser-recognizable image format—I 
>>> used PNG —*not* 
>>> raw {red, green, blue, alpha} bytes as is needed when writing directly to a 
>>> canvas 's image data.  
>>> Third, I used JavaScript code like the following to update an invisible 
>>> img  then copy the image 
>>> data from there to a visible canvas:
>>>
>>> function copyBytesToCanvas(data) {
>>> let blob = new Blob([data], {"type": "image/png"});
>>> let img = document.getElementById("myImage");
>>> img.onload = function() {
>>> let canvas = document.getElementById("myCanvas");
>>> let ctx = canvas.getContext("2d");
>>> ctx.drawImage(this, 0, 0);
>>> };
>>> img.src = URL.createObjectURL(blob);
>>> }
>>>
>>> Fourth, the performance is indeed substantially faster than my previous 
>>> approach based on using SetIndex 
>>>  to write directly 
>>> to the canvas, even though the new approach requires the extra steps of 
>>> encoding the image in PNG format and copying the image data from an img 
>>> to a canvas.  The following performance data, measured with Go 1.14 and 
>>> Chromium 80.0.3987.132 on an Ubuntu Linux system, is averaged over 10 
>>> runs:
>>>
>>> *Old*: 686.9 ± 7.6 ms
>>> *New*: 290.4 ± 4.1 ms (284.3 ± 4.2 on the WebAssembly side plus 6.0 ± 
>>> 2.3 on the JavaScript side)
>>>
>>> This is the time to render a simple 800×800 

Re: [go-nuts] Writing bitmap data to a canvas with WebAssembly

2023-11-07 Thread robert engels
Your numbers do not make sense.

At 300 us a frame, it would be more than 3k frames a second - which would only 
be possible for an extremely tiny area.

You need to include the time to produce the data when considering “render time” 
- otherwise you are talking apples and oranges.

> On Nov 7, 2023, at 9:43 PM, Kai O'Reilly  wrote:
> 
> For future reference for anyone who comes across this thread, you can 
> directly pass RGBA image data to a canvas, which is around 200 times faster 
> than encoding it to a PNG and rendering it to an offscreen image. Also, 
> passing the unsafe pointer to the slice to JavaScript instead of copying the 
> bytes improves performance by another factor of 5. These two changes combined 
> reduced the render time for me from roughly 300 milliseconds to 300 
> microseconds (a 1000x improvement). This performance is enough to run an 
> interactive pure Go GUI through WASM without any noticeable lag, just by 
> writing images to a canvas.
> 
> This is the relevant Go code:
> 
> ```go
>  sz := dw.image.Bounds().Size()
> ptr := uintptr(unsafe.Pointer([0]))
> js.Global().Call("displayImage", ptr, len(dw.image.Pix), sz.X, sz.Y)
> ```
> 
> And this is the relevant JS code:
> 
> ```js
> const appCanvas = document.getElementById('app');
> const appCanvasCtx = appCanvas.getContext('2d');
> 
> let wasm;
> let memoryBytes;
> 
> // displayImage takes the pointer to the target image in the wasm linear 
> memory
> // and its length. Then, it gets the resulting byte slice and creates an 
> image data
> // with the given width and height.
> function displayImage(pointer, length, w, h) {
>   // if it doesn't exist or is detached, we have to make it
>   if (!memoryBytes || memoryBytes.byteLength === 0) {
> memoryBytes = new Uint8ClampedArray(wasm.instance.exports.mem.buffer);
>   }
> 
>   // using subarray instead of slice gives a 5x performance improvement due 
> to no copying
>   let bytes = memoryBytes.subarray(pointer, pointer + length);
>   let data = new ImageData(bytes, w, h);
>   appCanvasCtx.putImageData(data, 0, 0);
> }
> ```
> In the JS code, `wasm` is initialized as the result of 
> `WebAssembly.instantiateStreaming`.
> 
> On Friday, July 24, 2020 at 10:53:14 AM UTC-7 Mark Farnan wrote:
> Little old, but this might also help. 
> 
>  A while back I built a helper package to deal with these issues for canvas 
> rendering from Go.  
> 
> https://github.com/markfarnan/go-canvas 
>   
> 
> I'm currently working on it to add  WebGL support & get it working in TinyGo 
> (some issues still).
> 
> Regards
> 
> Mark.
> 
> 
> 
> On Sunday, 22 March 2020 at 09:08:40 UTC+1 Scott Pakin wrote:
> I figure I ought to follow up with some results.  First, I got the suggested 
> approach of local render + js.CopyBytesToJS 
>  + update canvas from image 
> to work, so thanks, Agniva and Howard!  Second, for the benefit of future 
> readers of this thread, one thing that wasn't obvious to me is that one needs 
> to render the image data in a browser-recognizable image format—I used PNG 
> —not raw {red, 
> green, blue, alpha} bytes as is needed when writing directly to a canvas 
> 's image data.  Third, I used 
> JavaScript code like the following to update an invisible img 
>  then copy the image data from 
> there to a visible canvas:
> 
> function copyBytesToCanvas(data) {
> let blob = new Blob([data], {"type": "image/png"});
> let img = document.getElementById("myImage");
> img.onload = function() {
> let canvas = document.getElementById("myCanvas");
> let ctx = canvas.getContext("2d");
> ctx.drawImage(this, 0, 0);
> };
> img.src = URL.createObjectURL(blob);
> }
> 
> Fourth, the performance is indeed substantially faster than my previous 
> approach based on using SetIndex 
>  to write directly to the 
> canvas, even though the new approach requires the extra steps of encoding the 
> image in PNG format and copying the image data from an img to a canvas.  The 
> following performance data, measured with Go 1.14 and Chromium 80.0.3987.132 
> on an Ubuntu Linux system, is averaged over 10 runs:
> 
> Old: 686.9 ± 7.6 ms
> New: 290.4 ± 4.1 ms (284.3 ± 4.2 on the WebAssembly side plus 6.0 ± 2.3 on 
> the JavaScript side)
> 
> This is the time to render a simple 800×800 gradient pattern.
> 
> I hope others find this useful.
> 
> — Scott
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to golang-nuts+unsubscr...@googlegroups.com 
> .
> To view this discussion on the web visit 
> 

[go-nuts] Writing bitmap data to a canvas with WebAssembly

2020-03-11 Thread Scott Pakin
I'm new to WebAssembly and am trying to get Go to generate a bitmapped 
image to display in the Web browser.  Here's what I've come up with so far:

func DrawPicture(this js.Value, args []js.Value) interface{} {
// Create a canvas on the Web page.
const width = 256
const height = 256
canvas := js.Global().Get("document").Call("getElementById", 
"my-canvas")
canvas.Set("width", width)
canvas.Set("height", height)
canvas.Set("style", "border: thin solid black")
ctx := canvas.Call("getContext", "2d")
ctx.Call("clearRect", 0, 0, width, height)


// Render a picture.
imgData := ctx.Call("createImageData", width, height)
data := imgData.Get("data")
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
ofs := 4 * (y*width + x)
data.SetIndex(ofs+2, x+y)
data.SetIndex(ofs+3, 255)
}
}
ctx.Call("putImageData", imgData, 0, 0)
return nil
}

My question is: Is SetIndex 
 the most efficient way 
to write the image's pixels?  I see that syscall/js 
 provides a CopyBytesToJS 
 function, but I was 
unable to get that to work because the image data is a Uint8ClampedArray
 and CopyBytesToJS expects a Uint8Array.  I kept trying to populate a Go 
[]byte and transfer that to the image but never succeeded in making that 
work.

Thanks,
— Scott

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/78570dbf-9e6e-429b-a776-76ce50f5c7e2%40googlegroups.com.