Skip to content

Commit

Permalink
feat: add option to force software rendering
Browse files Browse the repository at this point in the history
shpaass#196 switched the hardware-accelerated parts of YAFC (the main project screen) to use Direct3D12 where available to fix instability on modern systems. However, some users have systems that do not support Direct3D12, in a very particular way where SDL still reports that it's available but it just crashes when it tries to actually paint.

For those users, add a settings toggle that will force the project screen to use software rendering instead. This is exposed in the welcome screen, which is also software rendered, so that it will always be available.

Closes shpaass#233
  • Loading branch information
sfoster1 committed Aug 20, 2024
1 parent 8ae6bdf commit c92f6c3
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 18 deletions.
46 changes: 29 additions & 17 deletions Yafc.UI/Core/WindowMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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();
}

Expand Down Expand Up @@ -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).
/// </param>
/// <param name="forceSoftwareRenderer">
/// 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.
/// </param>
/// <returns>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()</returns>
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;
Expand All @@ -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)}");
Expand Down
7 changes: 7 additions & 0 deletions Yafc/Utils/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ public void Save() {
public float ingredientsColumWidth { get; set; }
public float productsColumWidth { get; set; }
public float modulesColumnWidth { get; set; }
/// <summary>
/// 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
/// </summary>
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)
Expand Down
2 changes: 1 addition & 1 deletion Yafc/Windows/MainScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public MainScreen(int display, Project project) : base(default) {
allPages = new VirtualScrollList<ProjectPage>(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);
}

Expand Down
11 changes: 11 additions & 0 deletions Yafc/Windows/WelcomeScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit c92f6c3

Please sign in to comment.