-
-
Notifications
You must be signed in to change notification settings - Fork 427
Error creating multiple windows in .NET 8 #2436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Using windowing on multiple threads is illegal. You must only call Silk.NET.Windowing functions on the thread that called your program’s Main function. |
Why is that? To my understanding OpenGL doesn't care about the thread it's running in, only the context it uses for it's calls. A quick google gave a few articles how multithreading windowing should be techincally possible: The contexts are already handled by the IWindow implementations through the |
Though let's assume that you are correct and instead of a multithreaded approach, could we expose the Run(Action onFrame) / Run() methods in a way that the developer could handle the game loop themselves? For example, instead of: // ViewImplementationBase.cs
// Game loop implementation
public virtual void Run(Action onFrame)
{
while (!IsClosing)
{
onFrame();
}
} we would do something similar to this (of course keeping the above the default): public virtual void Run(Action onFrame)
{
onFrame();
} |
This is what I've been doing to run multiple windows without blocking the main thread. It's being done in a directx12-based application, but I don't think that matters for the windowing portion. If you call The code below is a minimal version of what I've been working on. After the code is a little video showing it in action. Using this strategy, you can spawn long-running threads that perform other workloads (command-recording, etc.). You can ferry commands back to the main thread using a Queue similar to what I've done for creating windows. You'll see the console print out the Like I said, it's a minimal version, so all I've done is expose the Click method and whenever the user clicks on a window, a new window appears.... using System.Collections.Concurrent;
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.Windowing;
namespace System;
public static partial class Program
{
internal static partial Task InitialzeGenerated();
private static Task? runTask = null;
private static readonly List<IWindow> windows = [];
private static readonly List<IInputContext> inputContexts = [];
private static readonly ConcurrentQueue<WindowOptions> windowQueue = [];
private static readonly CancellationTokenSource cancellationTokenSource = new();
public static void Main(string[] _)
{
runTask = Task.Run(async () =>
{
await Task.Delay(1000);
windowQueue.Enqueue(WindowOptions.Default);
while(!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000);
Console.WriteLine("Running loop on background thread...");
}
});
while (!cancellationTokenSource.IsCancellationRequested)
{
foreach (var item in windows)
{
if (item is null) continue;
var window = item;
window.DoEvents();
window.DoUpdate();
window.DoRender();
if (window.IsClosing)
{
window.Reset();
window.Dispose();
windows.Remove(window);
if (windows.Count == 0)
cancellationTokenSource.Cancel();
break;
}
}
if (!cancellationTokenSource.IsCancellationRequested)
{
while(windowQueue.TryDequeue(out var options))
{
options.Title = "new window";
options.Position = new Vector2D<int>(
x: 500 + (50 * windows.Count),
y: 400 + (50 * windows.Count));
var window = Window.Create(options);
window.Initialize();
var inputContext = window.CreateInput();
foreach (var mouse in inputContext.Mice)
mouse.Click += Mouse_Click;
inputContexts.Add(inputContext);
windows.Add(window);
}
}
}
}
private static void Mouse_Click(IMouse arg1, MouseButton arg2, Numerics.Vector2 arg3)
{
windowQueue.Enqueue(WindowOptions.Default);
}
} 20250326-0104-20.7981625.mp4 |
But isn't the main thread still "blocked" / used for rendering. |
@Marco-Zechner Yes, though you can work around that. Here's what @michaeldawteck was doing with the workaround but in a simplified format for demonstration purposes: // Start the task that adds the first window into the queue and keeps the application alive.
StartWindowQueueTask();
// NOTE: This task is the change needed to unblock the main thread.
// Unblock the main thread to handle the window lifecycle synchronously.
Task.Run(() =>
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
for (int i = 0; i < _windows.Count; i++)
UpdateWindow(ref i); // Render etc
// Create any new windows created during the previous iteration
DequeueWindows();
}
});
// See the main thread actually does stuff while the windows are being updated by the task above.
Console.WriteLine("Hello World!");
// Wait for user input to exit the main loop and exit the application.
// This can be some other "main loop" depending on your needs
Console.ReadLine(); |
This isn't about OpenGL, this is about the requirements of the operating system's windowing system. Your main thread has to be the one calling Run. I'd generally recommend calling Run(Action) on your main window, and if you have additional windows calling the DoEvents/DoUpdate/DoRender calls within that callback for all of your windows. Now, obviously OpenGL contexts are thread-specific, and if you followed this approach exactly you'd be losing a lot of performance on constantly calling Hope this helps. |
Summary
I wanted to build a docking system using ImGui where, when the imgui window is undocked another silk.net window is created to host that tabs content. Just running the window was not possible in the same thread since the Run method would block the "main" window from running. After that I tried to move the new window into it's own thread and I noticed that something wasn't working since I was getting the following error:
Silk.NET.GLFW.GlfwException: 'PlatformError: Win32: Failed to register window class: Class already exists. '
I tried to reproduce the problem in a test project and using .NET 6 this works just fine. However using .NET 8 like the rest of the project, I got the same error using this code (NOTE: The 'usafe' block is just for debugging purposes, doesn't affect the problem itself):
System info:
The text was updated successfully, but these errors were encountered: