What is the correct redraw code for the views, all flags considered?
-
Since CollieMouse just popped up again in a different topic, here's an issue I have been fighting since quite some time: What is the correct code for redrawing the views during an ongoing "camera movement operation", like "clicking and dragging on the camera movement icons in the view window"?
Yes, I know it's DrawViews() but I have never been able to make it work satisfactorily under all circumstances. Here's what I do:
-
I have a Space Mouse which sends messages (through the Windows window API) to an invisible window that is polled in a different thread (as I cannot receive Windows messages through the C4D API, and polling in the main thread with the usual loop would lock it up). This part is working fine.
-
This thread translates the coordinate systems, evaluates the camera settings and moves the camera according to the Space Mouse movement, and then sends a "do a refresh" message to my C4D Dialog which receives it through the CoreMessage() method. Works fine too, at first glance.
-
The following code is handling the redraw
BaseDocument* doc = GetActiveDocument(); if (doc != nullptr) { BaseContainer* storage = CollieMousePrefs::GetCurrentSettings(); if (storage != nullptr) { Float64 currentTime = GeGetMilliSeconds(); BaseDraw* basedraw = doc->GetActiveBaseDraw(); if (basedraw != nullptr) { DRAWFLAGS flags = DRAWFLAGS_0 | DRAWFLAGS_NO_THREAD; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_ONLY_BASEDRAW, STORAGEDEFAULT_DRAWFLAGS_ONLY_BASEDRAW)) flags |= DRAWFLAGS_ONLY_BASEDRAW; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_INMOVE, STORAGEDEFAULT_DRAWFLAGS_INMOVE)) flags |= DRAWFLAGS_INMOVE; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_NO_REDUCTION, STORAGEDEFAULT_DRAWFLAGS_NO_REDUCTION)) flags |= DRAWFLAGS_NO_REDUCTION; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_NO_EXPRESSIONS, STORAGEDEFAULT_DRAWFLAGS_NO_EXPRESSIONS)) flags |= DRAWFLAGS_NO_EXPRESSIONS; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_INDRAG, STORAGEDEFAULT_DRAWFLAGS_INDRAG)) flags |= DRAWFLAGS_INDRAG; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_ONLY_CAMERAEXPRESSION, STORAGEDEFAULT_DRAWFLAGS_ONLY_CAMERAEXPRESSION)) flags |= DRAWFLAGS_ONLY_CAMERAEXPRESSION; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_ONLY_HIGHLIGHT, STORAGEDEFAULT_DRAWFLAGS_ONLY_HIGHLIGHT)) flags |= DRAWFLAGS_ONLY_HIGHLIGHT; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_NO_HIGHLIGHT_PLANE, STORAGEDEFAULT_DRAWFLAGS_NO_HIGHLIGHT_PLANE)) flags |= DRAWFLAGS_NO_HIGHLIGHT_PLANE; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_FORCEFULLREDRAW, STORAGEDEFAULT_DRAWFLAGS_FORCEFULLREDRAW)) flags |= DRAWFLAGS_FORCEFULLREDRAW; if (storage->GetBool(IDD_COLLIEMOUSEDIALOG_DRAWFLAGS_NO_ANIMATION, STORAGEDEFAULT_DRAWFLAGS_NO_ANIMATION)) flags |= DRAWFLAGS_NO_ANIMATION; DrawViews(flags, basedraw); // EventAdd(); Int32 timerms = (Int32)storage->GetFloat(IDD_COLLIEMOUSEDIALOG_REDRAWAFTER, STORAGEDEFAULT_REDRAWAFTER); SetTimer(timerms); } lastTime = currentTime; } }
This is what I'd expect to be the most basic C4D redraw -- you can ignore the timer code, as it only serves to trigger a global refresh after n milliseconds to emulate a "mouse release" or "key release" which naturally don't exist in a Space Mouse. The flags are read from the user interface so I can experiment with them. Other than that, there is nothing special.
And under simple circumstances, this works fine. In fact, I had been working with the plugin for quite a while until I came to the point where the redraw started to show a distinctly worse behavior than C4D's own view buttons.
If there is an animated character (like the ones provided in the content library), the views start lagging, the reduced view with only the boxes is shown, and the status bar starts to show the progress bar for a fraction of a second. So, the system is clearly doing something that takes a lot of time which I do not want the system to do.
Here's what I know:
- The NO_ANIMATION flag needs to be set, otherwise C4D would evaluate the animation and overwrite any camera movement with the camera's current animation values.
- FORCEFULLREDRAW is normally not set. NO_REDUCTION and INMOVE are the flags I do set by default.
- The HIGHLIGHT flags are not what I want.
- The NO_EXPRESSIONS and ONLY_CAMERAEXPRESSION flags do what I think they do, but they do not have an effect on the slow redraw as far as my experiments went.
- It's not an issue of the amount of polygons - I can redraw millions of polys in a static scene without issue.
- It's not an issue of getting the values from the Preferences container either, or the issue would always be present.
- I am redrawing only the active window (any secondary view will be refreshed when the timer runs out) so the amount of redrawing should be minimized already.
- C4D is able to do the refresh on my system in the required way with no issues - as the C4D view buttons prove. If I use these, the refresh happens as expected.
- I do not need to EventAdd() after the redraw.
- DrawViews() must be called from the main thread, and due to the Windows polling the Space Mouse must be connected to a parallel thread so there is no way around the internal message sending to trigger the refresh.
Here's what I do not know, even after trying out many, many combinations of flags in the call:
- Which combination of flags is exactly to be used when I redraw the view in the middle of a user-driven camera movement? In other words, what exactly is the code for the redraw e.g. when I continuously use the move/rotate/scale icons in the view window frame, or any keyboard/mouse combinations to the same effect?
- I do not see the status bar showing a progress bar when I use C4D's own icons, so clearly in my code the system tries to do something really compute-intensive. But with NO_ANIMATION set, what would that be? (And more important, how do I get rid of it?)
- Is there perhaps any basic issue with calling DrawViews() in this way, from a dialog, in CoreMessage, triggered by a different thread? I would not expect so but perhaps there are side effects.
- Are there hidden flags that I need to set? I would not expect so either.
I am currently still compiling for the R19, but I don't think it makes a difference, as all previous C4D versions react similarly (as far as I still was able to verify).
Thanks for looking into the issue.
-
-
Hi Cairyn, thanks a lot for your complete description. First of all your issue might take longer than the others to be resolved, since it needs proper investigation.
With that's said could you try to remove your CollieMouse plugin and try with the Cinema 4D built-in Space Mouse support to see if the issue also appear with our current implementation (in order to be sure it's an issue in your implementation, and not something more global)
Thanks a lot.
Cheers,
Maxime. -
@m_adam I will try that on the weekend, I must look for the original config files first (I think I did that already anyway, but I'm going to verify).
We need keep in mind that CollieMouse works slightly different in its 3Dconnexion connection; the built-in controller (at least up to R19, I haven't checked the cfg for R20 yet but I think it's the same) is working with an old "transport model", while CollieMouse is using the most recent 3Dconnexion driver functionalities (so it can use the contexts and radial menus). For comparison, I may need to go back to an older CollieMouse version where I used the messaging system directly like the internal controller, but I'm not sure whether I still have versions from that time.
Anyway, I came to think of... is it maybe necessary to invoke StopAllThreads before issuing another redraw? Sort of stopping leftover processes that have been triggered but are not necessary to complete because we want another camera view already?
It feels funny to do so, as CollieMouse uses a thread as described above, and killing that thread during the redraw seems plain wrong. But I may just test that as well...
-
Actually, our Space Mouse integration, make only use of public API, so it's should be possible to achieve similar performance (that's also why I asked for testing our space Mouse integration).
Internally, StopAllThreads is called, since you are doing some scene modification (aka moving the camera of the BaseView).
Regarding StopAllThreads, find more information about it in this topic What Exactly does StopAllThreads, so normally it should not cancel your thread.
Cheers,
Maxime. -
So I spent some time researching the DrawViews() issue on the weekend, and I am almost sure that I have found the reason for the choppy behavior.
-
I started with testing the behavior of the built-in controller, which was fine. I am using "Sancho" from the content library as test case by the way. I didn't even have to change the config data of the 3Dconnexion drivers so perhaps Maxon has switched to the new libraries as well (at least by R19). So, it was not an issue of the hardware or the driver installation.
-
Then I went back to CollieMouse and reduced "Sancho" (with the intent to simplify until the thing works). I found that the view updates fine when I both delete the scene camera (which with "Sancho" is switched on by default) AND switch off the expressions (putting everything in a layer and cancel out the layer's expression evaluation).
-
Some experiments proved that the biggest issue is the scene camera. As long as I am working with the default camera of the viewport, DrawViews() behaves "as expected". Apparently the expressions of "Sancho" take too long to evaluate as the movement is stuttering but I can set the ONLY_CAMERAEXPRESSION or NO_EXPRESSIONS flags to suppress that evaluation, and lo! my controller suddenly is all fine. But when I switch to the scene camera, the view updates come only sporadical and the movement gets all choppy.
-
When I read your link about the StopAllThreads stuff, I stumbled over the remark that I am not allowed to make changes to the scene within a (non-main) thread. Now this would make perfect sense in the context, as I found that I am indeed changing the camera from within the handler thread. My guess for now: When I use the default camera of the viewport, this camera is not part of the scene and reacts beneficial to my changes. When I use a scene camera, changing it from within the thread can (not always, but with "Sancho" in any case) result in weird & unwanted behavior.
-
If my suspicion is correct, then the error was never with my DrawViews() code, but with the threading, specifically changing the camera in the thread itself. I will refactor the code to move the actual camera change out of the thread and into the CoreMessage that also performs the DrawViews(). If I'm right, this change, together with making "NO_EXPRESSIONS" the default, would fix the issue.
I'll post the results when I'm done...
-
-
Mmmkay. So I did change the code that the camera-changing calls happen in the main thread, and yes, this seems to be the main issue. Movement of a scene camera is now a lot better.
Sadly there is still something not in order. The scene camera behaves a bit choppy while the viewport camera is perfectly smooth. There should not really be a difference between both, as all calculations are the same (at least from a CollieMouse POV) with the exception of fetching the proper camera.
Indeed, checking timer values through GeGetMilliSeconds() show that neither the CollieMouse camera movement nor the actual redraw behave significantly different between scene and viewport camera. The scene camera is slightly slower for both process steps on the average, but the difference is so minimal that it should not be visible with the bare eye.
Thus, I have no idea at the moment where this choppyness comes from. I am going to refactor the code, build a few semaphores into it, and run timing tests on the handler thread side, perhaps I can get an indicator on what's still burning processor cycles here...
I am going to close this topic, as the redraw code apparently always was correct (just overlaid by an unrelated error with incalculable side effects which merely looked as if the DrawViews() was to blame), and the flags are doing what I expect them to do as far as I understand them (I am still unsure when to use INDRAG, INMOVE, and the HIGHLIGHT ones, but that is a question for another day).
-
Hi @Cairyn, actually in our implementation the camera is grabbed with this code
BaseDraw* view = doc->GetActiveBaseDraw(); if (!view) return false; cam = view->GetSceneCamera(doc); if (!cam) return false;
And our DrawView call
DrawViews(DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_ANIMATION | DRAWFLAGS::INMOVE | DRAWFLAGS::ONLY_BASEDRAW, view);
I'm waiting for your exploration, but please do not hesitate to open another topic if you feel the need.
Cheers,
Maxime. -
Hi again; just to close off the topic here's what I ultimately found:
- A lot of the delay that causes the choppyness of the motion is apparently an issue with EventAdd().
I require this call to update the Attribute Manager for the current camera, and to start the recalculation of the interactive render region. But as it seems to refresh about everything, the call causes a noticeable delay before the next viewport refresh comes through.
(It is surprising that this call takes longer than a redraw of the viewport, and it is very hard to time as EventAdd() just posts a message to the queue, so I cannot actually measure the execution duration.) - The scene camera is slower than the viewport camera. CollieMouse treats both the same, in fact, all handling is done through a CameraObject that I get in a similar way as the one you posted above. Yet, the redraw is slightly slower by itself, and there are apparently things indirectly happening for a scene camera that take even more time (even when expressions are switched off).
There is not much I can do about that except allow the user to suppress certain refreshs for the sake of a smoother Space Navigator experience.
I'm still not sure what some of the DrawViews() flags exactly mean, by the way. I guess I can ignore the HIGHLIGHT ones for now, but what are INDRAG, INMOVE and FORCEFULLREDRAW really doing, and when do I need to use them? I see that you use INMOVE in your code (but not NO_EXPRESSIONS which surprises me).
- A lot of the delay that causes the choppyness of the motion is apparently an issue with EventAdd().