Unable to use two GlControls in the same form
antoinecla opened this issue · comments
Step to reproduce:
- Open HelloTriangle Project
- Open SampleForm
- Select RenderControl, set Dock to None
- Copy and paste GlControl somewhere else in Form
- Build and Run
On my system the original control is solid white and the second one is black. Nothing else is shown. Property ContextSharing
is set to OwnContext
for both controls.
I was not able to find what is causing the issue in the code. Here is what I have found:
- Setting the Animation to false for the second control fix the issue
- Resizing the window to mask the second control and show only the first one fix the issue!!
- Turning off animation for both controls and adding a timer that invalidates both controls works
A possible cause is the Invalidate you use inside OnPaint() to animate.
Very strange behavior, but seems very consistent. Seems that the first GlControl instance is excluded in drawing and update.
Ok, I found a way to workaround this issue. However, I have to admit that the actual culprit is not found yet. What I got is:
- Calling
Invalidate
on the firstGlControl
, prevent the second one to draw - Calling
Invalidate
on the second one, but not on the first one, allow drawing two controls at the same time
My understanding is that Invalidate will issue a WM_PAINT
message on the message loop, implementing a game loop without sacrificing other control messages (menus, buttons, ...). Instead what I see is that the second GlControl is affected by this message.
I tried to invoke ParentForm invalidation (ParentForm.Invalidate(true)
instead of Invalidate()
), but without success.
Here is the solution I came up:
Application.Idle += Application_Idle;
private void Application_Idle(object sender, EventArgs e)
{
while (IsApplicationIdle()) {
Invalidate(true);
}
}
bool IsApplicationIdle()
{
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr Handle;
public uint Message;
public IntPtr WParameter;
public IntPtr LParameter;
public uint Time;
public Point Location;
}
[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
The implementation is scoped at the Form level, in order to invalidate all children. The problem here is that this solution is valid only for Windows platforms. I have no idea if Linux/Mono implementations can handle multiple GlControl instances.
Anyway, using two GlControl instances is very, very slow.
Anyway, using two
GlControl
instances is very, very slow.
Can you explain? Is there an overhead in using two or more GlControls
?
Each GlControl
creates its own GL context by default. The function pointers relative to the underlying context may depends on the device context which the GL context is bound to. I say "may" because this is a Windows specific issue: if the two GL contexts are bound to different device contexts, they can share GL function pointers only if all device contexts have the same pixel format set; in the other case, function pointers must be reloaded.
Being said this, OpenGL.Net ignore the pixel format set on the GlControl
instances; this means that DeviceContext.MakeCurrent
will reload all function pointers anyway, causing sensible delays, each time a different GlControl
is updated.
With a single GlControl
instance, a single GL context is current on the thread. DeviceContext.MakeCurrent
optimize this case, avoiding function pointers reloading if the GL context made current does not change.
Remember that GL function pointer have are scoped to TLS.
Indeed, having more than one GlControl
instance cause updating all those, and because they have different device contexts, function pointers are continuously reloaded. This is noticeable applying animation to two GlControl
instances and apply the patch of the previous post: animations are no more fluid as the simple example HelloTriangle.
I've not profiled it, but I bet that Gl.BindAPI()
is the bottleneck: it uses reflection to query and set function pointers. Maybe it could be optimized by emitting optimized methods.
Indeed my experiments lead to some awkward conclusion: Invalidate(
) executed by OnPaint()
causes a synchronous refresh of the UserControl
, leading to other control to the previous contents. Calling Invalidate() on other contexts (i.e. in a timer callback) works correctly.
Invalidate()
invokes UnsafeNativeMethods.InvalidateRect
, which points to WIN32 InvalidateRect; the man says:
The system sends a
WM_PAINT
message to a window whenever its update region is not empty and there are no other messages in the application queue for that window.
I wonder why the message is processed "immediately". If the Animation property is set on the last GlControl within the form, both controls can be redrawn, with a resize operation, while animating; but invalidating the first controls will lead to the same behavior (only the invalidated control will be redrawn).
I tried to invoke asynchronously Invalidate()
using BeginInvoke
without luck. At the moment I'm out of ideas.