Overview
This is part four in the series on render engine integration in Rhinoceros 3D using RhinoCommon.
- Setting up the plug-in
- Modal Rendering
- ChangeQueue
- Interactive render - viewport integration (this guide)
- Preview render (forthcoming)
If you have not already read the first three parts, please do so before proceeding.
Realtime Display
For this plug-in we are going to do things in a slightly different way. Not because it is a must, but because it gives an interesting possibility for plug-in developers who want to integrate their own render engines, but without exposing it to the _Render
command. We do that with a generic utility plug-in. There won’t be an API to implement for the _Render
command, instead we’ll implement two new classes. One derived from Rhino.Render.RealtimeDisplayMode
and one derived from Rhino.Render.RealtimeDisplayModeClassInfo
.
Together these will effectively create and register a conduit that is used during the drawing process of a viewport to display the result of the render engine.
For this example a ChangeQueue
implementation is used, but as said in earlier articles it is possible to do your data conversion directly from the RhinoDoc
. If the render engine to be integrated is one using mesh data for geometry I advise strongly to use the ChangeQueue
.
Utility plug-in
public class MockingViewportPlugIn : Rhino.PlugIns.PlugIn
{
public MockingViewportPlugIn()
{
if (Instance == null) Instance = this;
}
public static MockingViewportPlugIn Instance { get; private set; }
protected override LoadReturnCode OnLoad(ref string errorMessage)
{
// RealtimeDisplayMode.RegisterDisplayModes(this);
// call to RegisterDisplayModes no longer necessary, it is automatically called.
return LoadReturnCode.Success;
}
}
The plug-in code is very lean, only LoadRetunCode OnLoad()
needs to be overridden. With a proper RealtimeDisplayModeClassInfo and RealtimeDisplayMode implementation the new viewport mode will be registered with Rhino. It’ll show up in the viewport mode dropdown list.
Registering with Rhino
public class MockingRealtimeDisplayModeInfo : RealtimeDisplayModeClassInfo
{
public override string Name => "MockingRealtimeMode";
public override Guid GUID => new Guid("F14A3A24-C2FB-4216-9D2A-9636EF3869FA");
public override Type RealtimeDisplayModeType => typeof (MockingRealtimeDisplayMode);
}
Implement a class derived from Rhino.Render.RealtimeDisplayModeClassInfo
. When the plug-in is loaded the automatic registration procedure of the plug-in ensures that this information is used to identify the RealtimeDisplayMode
implementation of the plug-in.
All of the properties of the class are important, but pay especially close attention to Guid GUID
. This has to be unique from other plug-ins, so don’t ever copy-paste Guids from sample code.
The Type RealtimeDisplayModeType
property should return the type of your RealtimeDisplayMode
implementation.
After the plug-in is loaded the viewport mode can be found from the mode drop-down list.
The viewport implementation
The actual viewport integration is done with a class deriving from RealtimeDisplayMode
. When deriving from that class, which we do with MockingRealtimeDisplayMode
, Visual Studio will tell that several abstract methods need to be implemented. These methods are the minimum required functions to ensure proper functioning of the integration. The entire class can be found in the Git repository here. Lets step through the process what happens when the user selects the display mode for the viewport.
First of all an instance of our class will be created. If there is the need for initialisation a public default constructor can be implemented where such initialisation can be done. For MockingRealtimeDisplayMode
we don’t need that. During the start up phase of the mode switch the underlying system will be querying whether our engine is started, and whether there are results available. Because this can happen already before we’ve actually managed to start our engine and get some results we’ll be using a boolean flag _started
to communicate our state through the functions IsRendererStarted()
and IsFrameBufferAvailable()
. Our real entry into the rendering process happens with StartRenderer()
.
public override bool StartRenderer(int w, int h, RhinoDoc doc, ViewInfo view, ViewportInfo viewportInfo, bool forCapture,
RenderWindow renderWindow)
{
_started = true;
// prepare render, get a changequeue
_width = (int)w;
_height = (int)h;
reng = new MockingRender(Rhino.PlugIns.PlugIn.IdFromName("MockingBirdViewport"), doc.RuntimeSerialNumber, view, renderWindow)
{
MaxPasses = 50
};
reng.MaxPassesCompleted += Reng_MaxPassesCompleted;
reng.PassRendered += Reng_PassRendered;
reng.RenderReset += Reng_RenderReset;
reng.RenderStarted += Reng_RenderStarted;
MaxPassesChanged += MockingRealtimeDisplayMode_MaxPassesChanged;
// start rendering
_startTime = DateTime.Now;
_isCompleted = false;
_theThread = new Thread(reng.ColorPixels) {Name = "MockingBirdViewport thread"};
_theThread.Start();
return true;
}
For this example I opted to implement a very simple ‘render engine’ to show how working with threads in this environment can be done. So I start by creating an instance of that engine MockingRender
. This engine uses a ChangeQueue
internally, so I give the plug-in ID, Rhino document runtime serial number, the ViewInfo
instance and the RenderWindow
instance to it.
To make communication between the MockingRealtimeDisplayMode
and MockingRender
easy I have added several events to MockingRender
. I register necessary handlers for those.
Once the render engine has completed a pass it fires the PassRendered
event. In the handler the underlying system gets notified about that fact by calling the SignalDraw()
function provided by the base class RealtimeDisplayMode
.
private void Reng_PassRendered(object sender, PassRenderedEventArgs e)
{
_currentPass = e.Pass;
SignalRedraw();
}
The actual rendering
The MockingRender
class is responsible for generating the pixel data for the viewport. Its entry function is ColorPixels().
public void ColorPixels()
{
RenderStarting?.Invoke(this, EventArgs.Empty);
Passes = 0;
RenderStarted?.Invoke(this, EventArgs.Empty);
while (true)
{
while (Passes < MaxPasses)
{
ColorPass(Passes);
Thread.Sleep(10);
if (_shutdown) break;
Passes += 1;
}
if (!_completionTriggered && Passes >= MaxPasses)
{
_completionTriggered = true;
MaxPassesCompleted?.Invoke(this, EventArgs.Empty);
}
Thread.Sleep(10);
if (_shutdown) break;
}
}
It essentially goes into an eternal loop that runs until the _shutdown
flag is set. The render process goes through each pass (and simulates some extra workload by sleeping a whopping 10 milliseconds), then essentially waits for Passes
to get reset or MaxPasses
change such that Passes < MaxPasses
. ColorPass()
then fills the pixel buffer, presented to the render engine by means of the RenderWindow
. Increasing Passes
will also fire the PassRendered
event.
private void ColorPass(int pass)
{
var fac = pass/(float)MaxPasses;
var c = Color4f.FromArgb(1.0f, _color.R*fac, _color.G*fac, _color.B*fac);
using (var channel = rw.OpenChannel(RenderWindow.StandardChannels.RGBA))
{
var size = rw.Size();
for (var x = 0; x < size.Width; x++)
{
for (var y = 0; y < size.Height; y++)
{
channel.SetValue(x, y, c);
}
if (_shutdown) break;
}
}
}
In ColorPass()
above the most important part to look at is line 5. With the using
idiom the necessary channel from the RenderWindow
is opened (RGBA
in general, there are other channels too, though, but not in the scope of this article series), then filled with color data per pixel. The using idiom ensures the opened channel is properly disposed of.
Note that the simplest possible pixel buffer filling code would be to have the channel SetValue
loop directly in StartRenderer()
and leave out the entire MockingRender
and Thread
construct.
Next Steps
Congratulations! These are the steps necessary to integrate a new render engine into Rhino viewport for interactive, real-time rendering using the RhinoCommon SDK. Now what?
This is part four in the series on render engine integration in Rhinoceros using RhinoCommon. The next guide (forthcoming) will demonstrate implementing a preview render.
Related Topics
- Render Engine Integration - Introduction
- Render Engine Integration - Modal
- Render Engine Integration - ChangeQueue
- Preview render (forthcoming)