CSampleRenderer::StopRenderProcess(): WaitForSingleObject waits forever

I am building a custom test render in order to find out how the rendering pipeline works based on the SampleRealTimeRenderer. At the moment I am working on the resize events. While my custom renderer is rendering a heavy geometry it behaves exactly like the SampleRealTimeRenderer. I can resize anything and the system corresponds perfectly…I can actually play basketball with the Rhino window while rendering a big scene.

The strange is that if I load just a cube and I resize rapidly the Rhino window the system hangs forever at the WaitForSingleObject in void CSampleRenderer::StopRenderProcess()


void CSampleRenderer::StopRenderProcess()
{
	// Stop the render if it's running
	if (0 != m_pRenderThread)
	{
		// Set the flag to tell renderer that it should stop
		m_bRunning = false;

		// Wait until renderer checks the flag and returns
		WaitForSingleObject(m_pRenderThread->m_hThread, INFINITE);

		// Delete the thread
		delete m_pRenderThread;

		// And set the pointer to NULL so we don't try stopping it again
		m_pRenderThread = 0;
	}

	delete m_pRenderWnd;
	m_pRenderWnd = nullptr;
}

I have kept almost identical the DisplayMode/ChangeQueue/CSampleRenderer but somehow there is some thread related issue while stopping rendering process.
One basic difference in the renderer is that I use

pChannel->SetValueRect(0, 0, width, .height, width * sizeof(float4), ComponentOrder::RGBA, buffer.data());

to load directly the buffer data instead of using the for loop with SetValue:

		for (int px = 0; px < sz.cx; px++)
		{
			const float red = (float)(rand() % 256) / 255.0f;
			const float green = (float)(rand() % 256) / 255.0f;
			const float blue = (float)(rand() % 256) / 255.0f;

			float fColor[4];
			fColor[0] = red;
			fColor[1] = green;
			fColor[2] = blue;
			fColor[3] = 1.0f; 

			pChannel->SetValue(px, py, ComponentOrder::RGBA, fColor);
		}

I wonder if there is a workaround on this issue.

Well, after trying tons of alternative solutions I managed to find a workaround to make it work, but I don’t know why it works. As a reminder, resizing inside the Rhino window was working flawlessly, but resizing the whole Rhino window while holding left mouse button clicked would hang at:

WaitForSingleObject(m_pRenderThread->m_hThread, INFINITE);

while the renderer was trying to stop.

Resizing the rhino window basically works on SampleRealTimeRenderer by stopping the renderer and restarting it with the new window size. In my custom renderer it worked pretty well with scenes with large geometry or if I would resize the rhino window very slowly. If I would resize a scene with just a cube the window would hang/freeze.

So I focused a lot on mutexes and other sync techniques but actually none of them worked as when resizing (with the mouse button hold) you actually kill the renderer and you restart it. Then I noticed that, if in this scenario I set a breakpoint and continue I would have no problem whatsoever.

It seemed that something has to do with the thread management, killing and restarting the process too quickly was part of the problem. So I just added

// Renderer thread executes this static method. We pass in the pointer to our renderer object.
	unsigned int CMyCustomRenderer::RenderProcess(void* pData)
	{
		Sleep(200);

and the problem was solved! but I really don’t know why…

Since I am very close to the design of the SampleRealTimeRenderer, is there any indication what probably goes wrong on resizing?
Thanks! (and sorry for the long note :slight_smile: )

@fatecasino, I’ll be talking mostly from RhinoCycles point of view since that is what I know.

I appreciate the difficulties you’re having with the threading part of integrating a renderer. I have had much to deal with that while working on the RhinoCycles integration.

Cycles itself is written in C++, there is a wrapping layer in C around it that I wrote myself so it can be integrated in Rhino building on top of the RhinoCommon.

Cycles runs in a separate thread, spawning itself also threads. The main approach is to react only ever to events given by the changequeue. So for RhinoCycles the viewport changes are always triggered through the changequeue ApplyViewChange method.

Whenever any change is signalled by the changequeue I only ever try to acquire a lock to the Cycles renderer - I don’t fully stop the rendering process. You’ll need to have a way for your renderer to do the same: introduce a lock so that the renderer doesn’t access any data structures you’re going to be changing. Make the updates while locked, then unlock so you can continue rendering.

The RhinoCommon SDK is a thin wrapper around the C++ SDK, so it’ll be very similar. I’ll point out the important parts of my RhinoCycles implementation.

Getting notified by the changequeue of new changes via NotifyBeginUpdates()

and getting notified that those changes are ready for consumption via NotifyEndUpdates()

You’ll see that here I only set a flag Flush. I do that since these notifications are signalled on a different thread than what RhinoCycles is running in. My viewport renderer is implemented in ViewportRenderEngine.Renderer(). In its rendering and updating loop I check for this Flush flag - when it is true I’ll handle changes and synchronize.

Since I’m doing some preprocessing of the changes I don’t have to pause the renderer here just yet , but CheckFlushQueue() sees if there are actually any changes and gets them from the changequeue. Once that is done I can Synchronize() data with Cycles. You’ll see that here is where I wait until I get a lock. Once acquired I UploadData(), meaning I make necessary changes, including the viewport induced changes:

So your renderer needs to have a lock that your render loop is holding for each iteration, releases at the end and reacquires at the begin. In between those you controlling thread can try to acquire the lock, and when it succeeds make the necessary changes.

You should find that Raytraced updates properly without hanging while changing the Rhino main window continuously (at least in Rhino 8).

Thank you very much for your help! I actually have implemented a mutex lock on geometry change based on a previous post of yours and it works perfectly. Cycles is on the top of my list, but for the moment I have focused on the samples as there is so much to learn from each one of them!

SampleRealtimeRenderer gives me the opportunity to understand the ChangeQueue-DisplayMode-Renderer communication and slowly-slowly I am getting closer to a basic understanding. I even managed to locate a tiny error in changequeue :slight_smile:
https://mcneel.myjetbrains.com/youtrack/issue/RH-81660/ChangeQueue-Do-not-treat-copy-transforms-as-transforms

On the window resize event now, as it is already mentioned, my system freezes and after lots of headaches I managed to solve it!
First i made my plugin to look almost identical to the ChangeQueue/DisplayMode/Renderer of the samples. Then I applied many different sync techniques (flags, mutex, signals, etc) but still no progress.
Then I realized that something completely different makes my plugin to freeze and I found it. It was just a RhinoApp().Print line at the very beginning of the RenderProcess. I removed it and everything worked exactly like the SampleRealtimeRenderer. I can even play basketball with the rhino window and get no hang or crash or freeze.

You can very easily reproduce the problem in SampleRealtimeRenderer:

  1. add a RhinoApp().Print line at:
// Renderer thread executes this static method. We pass in the pointer to our renderer object.
unsigned int CSampleRenderer::RenderProcess(void* pData)
{
	RhinoApp().Print(L"Hello World from RenderProcess\n");
  1. Start Rhino
  2. Start SampleRealTimeRenderer
  3. Resize the rhino window a bit fast without releasing the mouse button.
    After a 3-4 resizes everything freezes on the screen. I have actually to press alt+tab in order to activate VS and stop the debug process.

I really would have never imagined that printing a message would cause such a mess in the thread management. Is this normal? Why does it happen?

RhinoApp().Print() needs to be run on the main thread, it is not safe to use in other threads. Print() causes pumping of the Windows message queue and using that in a thread other than main application thread easily causes lock-ups.

I would suggest creating a logging facility that writes to a file instead of the Rhino command-line history.