diff --git a/Yafc.UI/Core/WindowMain.cs b/Yafc.UI/Core/WindowMain.cs index 4a96e7e1..a1fa296f 100644 --- a/Yafc.UI/Core/WindowMain.cs +++ b/Yafc.UI/Core/WindowMain.cs @@ -5,9 +5,9 @@ using Serilog; namespace Yafc.UI { - // Main window is resizable and hardware-accelerated + // Main window is resizable and hardware-accelerated unless forced to render via software by caller public abstract class WindowMain : Window { - protected void Create(string title, int display, float initialWidth, float initialHeight, bool maximized) { + protected void Create(string title, int display, float initialWidth, float initialHeight, bool maximized, bool forceSoftwareRenderer) { if (visible) { return; } @@ -31,7 +31,7 @@ protected void Create(string title, int display, float initialWidth, float initi ); SDL.SDL_SetWindowMinimumSize(window, minWidth, minHeight); WindowResize(); - surface = new MainWindowDrawingSurface(this); + surface = new MainWindowDrawingSurface(this, forceSoftwareRenderer); base.Create(); } @@ -86,9 +86,13 @@ internal class MainWindowDrawingSurface : DrawingSurface { /// The flags you were going to/are about to pass to SDL_CreateRenderer, just to make sure the function doesn't pick something /// incompatible (this is paranoia since the major renderers tend to support everything relevant). /// + /// + /// If set, always return the appropriate index for the software renderer. This can be useful if your graphics hardware doesn't support + /// the rendering API that would otherwise be returned. + /// /// The index of the selected render driver, including 0 (SDL autoselect) if no known-best driver exists on this machine. /// This value should be fed to the second argument of SDL_CreateRenderer() - private int PickRenderDriver(SDL.SDL_RendererFlags flags) { + private int PickRenderDriver(SDL.SDL_RendererFlags flags, bool forceSoftwareRenderer) { nint numRenderDrivers = SDL.SDL_GetNumRenderDrivers(); logger.Debug($"Render drivers available: {numRenderDrivers}"); int selectedRenderDriver = 0; @@ -105,31 +109,39 @@ private int PickRenderDriver(SDL.SDL_RendererFlags flags) { logger.Warning($"Render driver {thisRenderDriver} has an empty name, cannot compare, skipping"); continue; } - System.Diagnostics.Debug.WriteLine($"Render driver {thisRenderDriver} is {driverName} flags 0x{rendererInfo.flags.ToString("X")}"); - if ((rendererInfo.flags | (uint)flags) != rendererInfo.flags) { - logger.Debug($"Render driver {driverName} flags do not cover requested flags {flags}, skipping"); - continue; - } + logger.Debug($"Render driver {thisRenderDriver} is {driverName} flags 0x{rendererInfo.flags.ToString("X")}"); // SDL2 does actually have a fixed (from code) ordering of available render drivers, so doing a full list scan instead of returning // immediately is a bit paranoid, but paranoia comes well-recommended when dealing with graphics drivers - if (driverName == "direct3d12") { - logger.Debug($"Selecting render driver {thisRenderDriver} (DX12)"); - selectedRenderDriver = thisRenderDriver; + if (forceSoftwareRenderer) { + if (driverName == "software") { + logger.Debug($"Selecting render driver {thisRenderDriver} (software) because it was forced"); + selectedRenderDriver = thisRenderDriver; + } } - else if (driverName == "direct3d11" && selectedRenderDriver == 0) { - logger.Debug($"Selecting render driver {thisRenderDriver} (DX11)"); - selectedRenderDriver = thisRenderDriver; + else { + if ((rendererInfo.flags | (uint)flags) != rendererInfo.flags) { + logger.Debug($"Render driver {driverName} flags do not cover requested flags {flags}, skipping"); + continue; + } + if (driverName == "direct3d12") { + logger.Debug($"Selecting render driver {thisRenderDriver} (DX12)"); + selectedRenderDriver = thisRenderDriver; + } + else if (driverName == "direct3d11" && selectedRenderDriver == 0) { + logger.Debug($"Selecting render driver {thisRenderDriver} (DX11)"); + selectedRenderDriver = thisRenderDriver; + } } } logger.Debug($"Selected render driver index {selectedRenderDriver}"); return selectedRenderDriver; } - public MainWindowDrawingSurface(WindowMain window) : base(window.pixelsPerUnit) { + public MainWindowDrawingSurface(WindowMain window, bool forceSoftwareRenderer) : base(window.pixelsPerUnit) { this.window = window; - renderer = SDL.SDL_CreateRenderer(window.window, PickRenderDriver(SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC), SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC); + renderer = SDL.SDL_CreateRenderer(window.window, PickRenderDriver(SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC, forceSoftwareRenderer), SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC); nint result = SDL.SDL_GetRendererInfo(renderer, out SDL.SDL_RendererInfo info); logger.Information($"Driver: {SDL.SDL_GetCurrentVideoDriver()} Renderer: {Marshal.PtrToStringAnsi(info.name)}"); diff --git a/Yafc/Utils/Preferences.cs b/Yafc/Utils/Preferences.cs index b9274bdc..06360917 100644 --- a/Yafc/Utils/Preferences.cs +++ b/Yafc/Utils/Preferences.cs @@ -61,6 +61,13 @@ public void Save() { public float ingredientsColumWidth { get; set; } public float productsColumWidth { get; set; } public float modulesColumnWidth { get; set; } + /// + /// Set to always use a software renderer even where we'd normally use a hardware renderer if you know that we make bad decisions about hardware renderers for your system. + /// + /// The current known cases in which you should do this are: + /// - Your system has a very old graphics card that is not supported by Windows DX12 + /// + public bool forceSoftwareRenderer { get; set; } = false; public void AddProject(string path, string dataPath, string modsPath, bool expensiveRecipes, bool netProduction) { recentProjects = recentProjects.Where(x => string.Compare(path, x.path, StringComparison.InvariantCultureIgnoreCase) != 0) diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index c54e6eae..f9bdb907 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -57,7 +57,7 @@ public MainScreen(int display, Project project) : base(default) { allPages = new VirtualScrollList(30, new Vector2(0f, 2f), BuildPage, collapsible: true); Create("Yet Another Factorio Calculator CE v" + YafcLib.version.ToString(3), display, Preferences.Instance.initialMainScreenWidth, - Preferences.Instance.initialMainScreenHeight, Preferences.Instance.maximizeMainScreen); + Preferences.Instance.initialMainScreenHeight, Preferences.Instance.maximizeMainScreen, Preferences.Instance.forceSoftwareRenderer); SetProject(project); } diff --git a/Yafc/Windows/WelcomeScreen.cs b/Yafc/Windows/WelcomeScreen.cs index b41b5099..993894bd 100644 --- a/Yafc/Windows/WelcomeScreen.cs +++ b/Yafc/Windows/WelcomeScreen.cs @@ -153,6 +153,17 @@ protected override void BuildContents(ImGui gui) { """, false)) { gui.BuildCheckBox("Use net production/consumption when analyzing recipes", netProduction, out netProduction); } + using (gui.EnterRowWithHelpIcon(""" + If checked, the main project screen will not use hardware-accelerated rendering. + Enable this setting if YAFC crashes after loading without an error message, or if you know that your computer's graphics hardware does not support modern APIs (e.g. DirectX 12 on Windows). + """, false)) { + bool forceSoftwareRenderer = Preferences.Instance.forceSoftwareRenderer; + _ = gui.BuildCheckBox("Force software rendering in project screen", forceSoftwareRenderer, out forceSoftwareRenderer); + if (forceSoftwareRenderer != Preferences.Instance.forceSoftwareRenderer) { + Preferences.Instance.forceSoftwareRenderer = forceSoftwareRenderer; + Preferences.Instance.Save(); + } + } using (gui.EnterRow()) { if (Preferences.Instance.recentProjects.Length > 1) { diff --git a/changelog.txt b/changelog.txt index 9c7fa7e4..ac4e6479 100644 --- a/changelog.txt +++ b/changelog.txt @@ -22,6 +22,7 @@ Date: multi-page projects. Known issue: The button requires multiple clicks, which appears to be related to #169. - Add a right-click context menu to the tab header. - Allow fixed amounts on fuel, ingredients, and products, in addition to buildings. + - Add a setting to force software rendering if hardware rendering does not work on your system. Bugfixes: - Several fixes to the legacy summary page, including a regression in 0.8.1. Internal changes: