Set rgba channel in RenderWindow


As I’m waiting for RH-64925 to be resolved I’ve started testing the rhinocommon implementation of a RenderWindow viewer instead of the C++ SDK.

Now I’m trying to find the best way to set the frame buffer from an rgba float4 array in C++.

Looking at the API, the RenderWindow.Channel.SetValues and dotNET sample, this feels like a reasonable implementation:

Scene.RenderFrame();  // render image to temporary buffer in C++
var size = rw.Size();
IntPtr ptr = Marshal.AllocHGlobal(sizeof(float) * size.Width * size.Height * 4);
Scene.SetFrameBuffer(frameBuffer); // copy framebuffer to allocated ptr

using (var channel = rw.OpenChannel(RenderWindow.StandardChannels.RGBA))
   channel.SetValues(System.Drawing.Rectangle.FromLTRB(0, 0, size.Width, size.Height), rw.Size(), new PixelBuffer(ptr));

But it renders black.

I’ve verified that my values are in the IntPtr through:

float[] frameBuffer = new float[rw.Size().Width * rw.Size().Height * 4];
Marshal.Copy(ptr, frameBuffer, 0, frameBuffer.Length);

I’ve also looped through the buffer like so:

for (var x = 0; x < size.Width; x++)
     for (var y = 0; y < size.Height; y++)
          int loc = (y * size.Width + x) * 4;
          channel.SetValue(x, y, Color4f.FromArgb(frameBuffer[loc + 3], frameBuffer[loc], frameBuffer[loc + 1], frameBuffer[ loc + 2]));
     if (_shutdown) break;

which does show the image but it’s obviously very slow.

Any recommendation on the ideal workflow for this?

The best way is to pass around a pointer to your native framebuffer, don’t Marshal.Copy it.

For example lets look at how I do it for RhinoCycles - the renderer Cycles is native code, RhinoCycles is a RhinoCommon plug-in:

The function we look at is BlitPlixelsToRenderWindowChannel() in RhinoCycles.

Here we first:

  • get pointer to a pixel buffer (rgba, but also for normals and depth in separate passes) with GetPixelBuffer(). The pointer to the buffer is stored in pixel_buffer.
  • when a valid pixel_buffer is acquired create a Rhino.Render.PixelBuffer pb holder for it
  • on the RenderWindow open the Channel we need
  • use the channel to SetValuesRect(rect, size, pb)

You only need to make sure the memory that is pointed to by pixel_buffer is valid until right after SetValuesRect, after that it could be collected if your implementation likes to do that.

edit: to put it in other words

The pointer is expected to be an unmanaged pointer.

Thanks, got it working now!

Think I’ve tried this before but probably didn’t get my mashaling right for the pointer.

I assume then that the data in the unmanaged buffer is copied to an internal buffer as I call SetValues? and that I need to call SetValues every time there is new data in the unmanaged buffer?

That is correct. This is how RhinoCycles works: render, copy to an unmanaged buffer (owned by Cycles), give the pointer to said buffer to managed (RhinoCycles) so it can be passed to the RenderWindow, which in turn gives it back to unmanaged native Rhino code. SetValues indeed behind the scenes copies the data contained by your unmanaged buffer to its own for further processing.

The Rhino post-effects pipeline will do its thing (denoising, bloom, tone mapping, etc) on this internal buffer before showing it in the viewport or the render window.