New WIP render plugin for OSPRay

Hi everyone, I’m happy to announce a new plugin for Rhino that adds support for the Intel OSPRay renderer. OSPRay is an “open source, scalable, and portable ray tracing engine for high-performance, high-fidelity visualization on Intel Architecture CPUs.”:
https://www.ospray.org/

The plugin is still in very early development and missing a lot of functionality, but can already make some interesting images; I’ve been playing around rendering data sets of NYC with 100s of thousands of components with render times of just a few minutes. The renderer incorporates the Intel Open Image Denoiser so the pictures look pretty decent with even just a few render passes.

The plugin is open source and hosted on GitHub:

There’s a Rhino 6 Windows plugin available for download, though I had problems installing it on my local machine so I would be curious if it works for other people. It may be easier at this stage to compile the plugin from source to get it working (there are instructions on the GitHub page).

Any feedback or help with development is greatly appreciated!

PS. I have no affiliation with Intel, just a happy user of the renderer.

NYC scene with 132255 model components rendered in 1m 16s on a 4 core Xeon:

NYC scene with 366474 model components rendered in 1m 53s on a 4 core Xeon:

The same scene rendered with the Rhino 6 renderer takes 13m 29s:

4 Likes

This looks like a nice renderer for users to add to their rendering arsenal.

I briefly looked at the Github repository for your plug-in. I think it would be useful if you looked into the ChangeQueue mechanism which is part of the realtime display integration in the RDK for render engines. This is a technology to centralize all sorts of geometry and render content handling that could make it easier to build out your plug-in features. Especially for interactive viewport rendering, block instances(?), automatic render mesh creation and so on.

Some very simple sample code: ChangeQueue, RealtimeRenderer

Hi, thanks for taking a look at the GitHub issues! I’ll definitely check out the ChangeQueue mechanism, I’m new to the Rhino SDK and wasn’t sure if I was taking the right direction with the implementation.

I’ve only used OSPray for a short time but it seems nice; the source code builds easily and the documentations is well written. I thought it would be an interesting addition to Rhino since it specializes in large data sets and might be good for large architectural scenes. I was pleasantly surprised by the speed of the renders and the nice job the denoiser does. It’s currently CPU only though apparently they are working on a GPU version (I’m guessing as part of their discrete graphics launch later this year).

Hi Nathan, I took a look at the examples and was able to write a basic ChangeQueue for my plugin, so far everything seems to be working fairly well and it’s nice to have things like render mesh creation and block instances happen automatically.

Now I would like to try and get interactive viewport rendering working and was wondering if you had any advice on the lifetime of the ChangeQueue object and how it interacts with the DisplayMode? My current use of it for the modal rendering is quite simple, I create and destroy it each frame along with a call to CreateWorld(). But I’m guessing for viewport rendering it should not be re-created each frame?

For the DisplayMode, what’s the recommended way to connect it to the ChangeQueue? There is a pure virtual function on DisplayMode that mentions the ChangeQueue, but in the RealtimeRenderer example it doesn’t have any implementation:

/** Create the world for this display mode instance. This sets up the changequeue. */
virtual void CreateWorld(const CRhinoDoc& doc, const ON_3dmView& view, const CDisplayPipelineAttributes& attributes) = 0;

Thanks, Darby

I’ll answer mostly from the RhinoCommon perspective, as that is what I use myself for the Cycles integration (and it is the only way to get the RenderPlugin also on Mac…)

When a user changes to your implementation of the realtime display mode an instance of your implementation is created. This instance will live for as long as the viewport is running your mode. You’ll typically create an instance of your render engine and changequeue on StartRenderer. They both live for as long as your display mode instance lives.

For RhinoCycles when the StartRenderer function is called on my display mode implementation I instantiate ViewportRenderEngine. This in turn instantiates its own ChangeQueue version (ChangeDatabase as I called my own implementation). The viewport display mode implementation calls the CreateWorld function on the ViewportRenderEngine, which in turn calls the CreateWorld function of the ChangeQueue.

You don’t really need to CreateWorld on the display mode implementation, it is only really useful for getting the CDisplayPipelineAttributes for when a ViewCaptureToFile or ViewCaptureToClipboard is run, but not strictly necessary. The method name is a bit of a misnomer, but we won’t be breaking the SDK now.

Hopefully this gives enough insight in how to proceed.

Personally I’d suggest to write PInvoke wrapper around the Osprey engine and implement your plug-in using RhinoCommon (C# or other .NET language), so that you can have your plug-in also running on Mac OS.

Thanks a lot for the advice, I’ll try it out and see how far I can get.

I would definitely like to have a macOS version at some point though I didn’t know RhinoCommon was a requirement. Isn’t there a fair bit of overhead converting the data between the C++ and .NET layers?

There is obviously some, but it isn’t that bad. In Rhino WIP (Rhino v7 in some forseeable future) we have made some great improvements to drawing from a unmanaged renderer that is wrapped in .NET and provided as plug-in to Rhino. For instance the difference between new ‘direct-to-renderwindow-channel-write’ and old OpenGL-driven drawing of rendering results into the Viewport (and render window for modal rendering) is pretty much neglible.

Most of the overhead is in handling data prior to rendering, but otherwise this is not a problem. Cycles integrates fine, and we have seen the RhinoCommon system work well also for ProRender.

The native OSPRay API is actually C so I assume that would make wrapping it in .NET easier. Moving to .NET would also hopefully make any UI work considerably easier than C++/MFC…

I think I’m pretty close with the C++ though, I just got the render to appear in the viewport, thanks again for the advice!

I’m still a bit unsure though on safely synchronizing the data between the ChangeQueue and my render thread; there are a couple of comments in the ChangeQueue header that mention threading like this one:

You should not call functions in the Rhino document during the Apply functions unless you are rendering on the main thread.

Does that imply that the ChangeQueue Apply and Flush functions are thread-safe?

As long as you don’t access the RhinoDoc your viewport session is running for. The data you receive in the Apply functions as part of the Flush call are all accessible for the duration of this flush, but no other guarantees are given.

Does that help?

Yup that helps a lot, thanks! I’ve got it up and working, though it still needs a lot of tuning to be usable. Here’s a quick video of it in action with 132255 model components:

4 Likes

Bravo!

1 Like

Thanks! The ChangeQueue definitely made for a nice implementation. Do you intend to add other geometry types at some point, like curves and points?

I’ve checked in all of the code and made a new release on GitHub if anyone would like to try it out:

I also made a new video to show off the denoiser in OSPRay, the denoiser now only starts after the first couple passes so you can see the difference between when it is off and on. It also helps interactivity since the denoiser takes a bit of time.

2 Likes

Until your request there hasn’t really been a need for that. We generally let Rhino do the rendering of these. For instance Raytraced and in v7 Rhino Render doesn’t do any curves, points, annotations - that is all Rhino. If you add chanWireframe in addition to the channels you already have then Rhino handles them for you.

Anyway, I’m adding a feature request for this: RH-57819

Nice.

1 Like

That’s nothing less than very impressive!
:open_mouth:

1 Like

Anyway, I’m adding a feature request for this: RH-57819

Thanks! It’s not a pressing issue but would be nice at some point, OSPRay seems to have good support for line primitives, including Bezier, b-spline, and others:

https://www.ospray.org/documentation.html#curves

If you add chanWireframe in addition to the channels you already have then Rhino handles them for you.

I didn’t know about that, I’ll check it out. It also seemed that by default Rhino rendered a wireframe over my rendered image, I could disable it by changing the display options edge thickness settings to zero. It looks like the same thing can be done in code by setting the DisplayAttrsMgrListDesc fields when the display mode is initialized?

pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowIsocurves = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowNakedEdges = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowEdgeEndpoints = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowNonmanifoldEdges = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowMeshWires = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowMeshVertices = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowMeshEdges = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowMeshNakedEdges = false;
pDisplayAttrsMgrListDesc->m_pAttrs->m_bShowMeshNonmanifoldEdges = false;