Skip to content

Commit 3aff8cd

Browse files
authored
fix: use modern Direct3D on windows (#196)
When we call SDL_CreateRenderer(..., 0, <flags>), SDL2 will pick "the most appropriate renderer" from its list of defaults that support the passed flags. The thing is that its list of defaults is straight out of 1998 on Windows, and in most cases will end up selecting Direct3d/DirectX 9. Stack traces for crashes and freezes in #178 tend to show NT waiter deadlocks in d3d9 drivers (at least on my machine); implementing this change means (again at least on my machine) those crashes and hangs go away. This change should be safe on non-windows machines (and on windows machines that for whatever reason don't support DX12 or 11) because it will default to picking 0 (autoselect) and only opt in to the higher directx versions if SDL2 says they're available. ## Testing - If we can get a build, would love more people to run it for more time to see if it helps more generally than on my PC (but it does seem plausible that this would fix the issue) - I'm like 90% sure this will work on other platforms but I'm slightly concerned about whether proton and wine present DX drivers to SDL2 that might cause linux installs of the tool to use them instead of gles. On the other hand maybe that's fine since those tools work will and this is mostly developed for windows anyway.
2 parents 1a6fa44 + ea6a3ff commit 3aff8cd

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

Yafc.UI/Core/WindowMain.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Numerics;
33
using System.Runtime.InteropServices;
44
using SDL2;
5+
using Serilog;
56

67
namespace Yafc.UI {
78
// Main window is resizable and hardware-accelerated
@@ -63,14 +64,75 @@ protected WindowMain(Padding padding) : base(padding) { }
6364
}
6465

6566
internal class MainWindowDrawingSurface : DrawingSurface {
67+
private static readonly ILogger logger = Logging.GetLogger<MainWindowDrawingSurface>();
6668
private readonly IconAtlas atlas = new IconAtlas();
6769
private readonly IntPtr circleTexture;
6870

6971
public override Window window { get; }
7072

73+
/// <summary>
74+
/// Function <c>PickRenderDriver()</c> picks the best rendering backend available on the platform.
75+
///
76+
/// This seems like something that SDL2 should do on its own, since the point of SDL2 is to abstract across platform render
77+
/// APIs, and it sort of does - but SDL2 has been around for a really long time and its defaults reflect that. On Windows,
78+
/// it will autoselect DirectX/Direct3D 9 if given half a chance; DX9 was of course the version that shipped in 2002 and
79+
/// supported Windows 98. This is despite the fact that SDL2 supports DX12 and DX11 where possible, and in 2024 "where possible"
80+
/// is really going to be "everywhere" - it just doesn't seem to default select them.
81+
///
82+
/// Instead, we can specify the render driver to use when building a renderer instance; to figure out which one, you have to
83+
/// go iterate through all the render drivers the library supports and pick the right one by string comparing its name.
84+
/// </summary>
85+
/// <param name="flags">
86+
/// The flags you were going to/are about to pass to SDL_CreateRenderer, just to make sure the function doesn't pick something
87+
/// incompatible (this is paranoia since the major renderers tend to support everything relevant).
88+
/// </param>
89+
/// <returns>The index of the selected render driver, including 0 (SDL autoselect) if no known-best driver exists on this machine.
90+
/// This value should be fed to the second argument of SDL_CreateRenderer()</returns>
91+
private int PickRenderDriver(SDL.SDL_RendererFlags flags) {
92+
nint numRenderDrivers = SDL.SDL_GetNumRenderDrivers();
93+
logger.Debug($"Render drivers available: {numRenderDrivers}");
94+
int selectedRenderDriver = 0;
95+
for (int thisRenderDriver = 0; thisRenderDriver < numRenderDrivers; thisRenderDriver++) {
96+
nint res = SDL.SDL_GetRenderDriverInfo(thisRenderDriver, out SDL.SDL_RendererInfo rendererInfo);
97+
if (res != 0) {
98+
string reason = SDL.SDL_GetError();
99+
logger.Warning($"Render driver {thisRenderDriver} GetInfo failed: {res}: {reason}");
100+
continue;
101+
}
102+
// This is for some reason the one data structure that the dotnet library doesn't provide a native unmarshal for
103+
string? driverName = Marshal.PtrToStringAnsi(rendererInfo.name);
104+
if (driverName is null) {
105+
logger.Warning($"Render driver {thisRenderDriver} has an empty name, cannot compare, skipping");
106+
continue;
107+
}
108+
System.Diagnostics.Debug.WriteLine($"Render driver {thisRenderDriver} is {driverName} flags 0x{rendererInfo.flags.ToString("X")}");
109+
if ((rendererInfo.flags | (uint)flags) != rendererInfo.flags) {
110+
logger.Debug($"Render driver {driverName} flags do not cover requested flags {flags}, skipping");
111+
continue;
112+
}
113+
114+
// SDL2 does actually have a fixed (from code) ordering of available render drivers, so doing a full list scan instead of returning
115+
// immediately is a bit paranoid, but paranoia comes well-recommended when dealing with graphics drivers
116+
if (driverName == "direct3d12") {
117+
logger.Debug($"Selecting render driver {thisRenderDriver} (DX12)");
118+
selectedRenderDriver = thisRenderDriver;
119+
}
120+
else if (driverName == "direct3d11" && selectedRenderDriver == 0) {
121+
logger.Debug($"Selecting render driver {thisRenderDriver} (DX11)");
122+
selectedRenderDriver = thisRenderDriver;
123+
}
124+
}
125+
logger.Debug($"Selected render driver index {selectedRenderDriver}");
126+
return selectedRenderDriver;
127+
}
128+
71129
public MainWindowDrawingSurface(WindowMain window) : base(window.pixelsPerUnit) {
72130
this.window = window;
73-
renderer = SDL.SDL_CreateRenderer(window.window, 0, SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);
131+
132+
renderer = SDL.SDL_CreateRenderer(window.window, PickRenderDriver(SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC), SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);
133+
134+
nint result = SDL.SDL_GetRendererInfo(renderer, out SDL.SDL_RendererInfo info);
135+
logger.Information($"Driver: {SDL.SDL_GetCurrentVideoDriver()} Renderer: {Marshal.PtrToStringAnsi(info.name)}");
74136
circleTexture = SDL.SDL_CreateTextureFromSurface(renderer, RenderingUtils.CircleSurface);
75137
byte colorMod = RenderingUtils.darkMode ? (byte)255 : (byte)0;
76138
_ = SDL.SDL_SetTextureColorMod(circleTexture, colorMod, colorMod, colorMod);

0 commit comments

Comments
 (0)