Skip to content

Games, Graphics, Profiling: pick the discrete GPU of a hybrid laptop, type the GPU preference, keep the profiler page at 1 or more - #3388

Open
Nicogo1705 wants to merge 3 commits into
stride3d:masterfrom
Nicogo1705:platform-fixes
Open

Games, Graphics, Profiling: pick the discrete GPU of a hybrid laptop, type the GPU preference, keep the profiler page at 1 or more#3388
Nicogo1705 wants to merge 3 commits into
stride3d:masterfrom
Nicogo1705:platform-fixes

Conversation

@Nicogo1705

@Nicogo1705 Nicogo1705 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

PR Details

Two related fixes to adapter selection. The profiler page clamp that was here moved to its own PR, as requested in review.

  • Games: stop skipping the discrete GPU of a hybrid laptop. FindBestDevices skipped every adapter with no display output, meant to keep WARP and the Basic Render Driver out. On a hybrid laptop every display is wired to the integrated GPU, so the discrete one - the adapter DXGI sorted to the front as the fastest - never has an output, and every Stride game landed on the integrated GPU. The skip now tests what it meant: a new GraphicsAdapter.IsSoftwareAdapter (the DXGI software flag plus the Microsoft vendor id on D3D, the CPU device type on Vulkan).
  • Graphics: type the GPU preference as API, keep the variable as override. GraphicsAdapterFactory.GpuPreference is a Stride enum (HighPerformance, the default; MinimumPower; Unspecified), set before the first adapter query or re-applied with the factory's Reset(); the Direct3D factory maps it to DXGI's preference. STRIDE_GPU_PREFERENCE stays as the machine's override, the same precedent as STRIDE_GRAPHICS_SOFTWARE_RENDERING.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • All new and existing tests passed.
  • I have built and run the editor to try this change out.

⚠️ Written with AI assistance (Claude Code), driven and verified by me; needs a real review. 🤖 Generated with Claude Code

@Ethereal77 Ethereal77 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice addition. This lacked configurability.

I must say however that I put the HighPerformance as default for the exact case you mention. I was developing this at the tame on an old laptop with integrated Intel Graphics + a dedicated nVidia GPU, and this was enough for Stride to always choose the nVidia adapter. Did not work for you?

And sorry if I'm being annoying 🙏 but... the profiler change maybe should have been a separate PR, even if small. Having several unrelated changes in a single PR makes it difficult in the future to locate them or understand them at a glance. Just my honest opinion, I like to have things tidy 😁

VendorId = (int) dxgiAdapterDesc.VendorId;
// DXGI_ADAPTER_FLAG_SOFTWARE (2) marks WARP; the Basic Render Driver predates the
// flag on some Windows builds, so its Microsoft vendor id is the fallback test.
IsSoftwareAdapter = (dxgiAdapterDesc.Flags & 0x2) != 0 || dxgiAdapterDesc.VendorId == 0x1414;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoding flags, prefer to use the Silk-provided enum: Silk.NET.DXGI.AdapterFlag.Software

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: (uint) AdapterFlag.Software, and the Microsoft vendor id moved to a named constant.

// Enumerates all the Graphics Adapters in the system, using GPU preference (DXGI 1.6+).
//
static List<GraphicsAdapter> EnumerateAdaptersPrefer(GpuPreference gpuPreference)
static List<GraphicsAdapter> EnumerateAdaptersPrefer(GpuPreference preference)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err... why rename this? 😀

Even though the type is different (it is now in Stride), the old name still made sense

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted, the parameter keeps its original name.

@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for Silicon Studio copyright in headers of new files

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, header trimmed to .NET Foundation and Contributors.

@xen2

xen2 commented Sep 8, 2026

Copy link
Copy Markdown
Member

General remark (apply to your other PRs): comments are overly long and verbose (some of those 9 lines!), giving way too much details, relevant only to previous code or your specific context/discussion.
I think most of the 5+ lines comments could written in a much shorter and simpler form (3~4 lines max if really necessary, but most can be 1/2 lines).
Also they shouldn't mention about the previous issues which are now gone (hard to understand).

cyrildelzandre-gif and others added 3 commits September 8, 2026 17:27
FindBestDevices skipped every adapter with no display output, meant to
keep WARP and the Basic Render Driver from being picked. On a hybrid
laptop that filter catches the wrong card: every display is wired to
the integrated GPU, so the discrete one - the adapter DXGI just sorted
to the front of the list as the fastest - never has an output, and
every Stride game lands on the integrated GPU with the fast one idle.
Windows' per-app GPU preference papers over it by re-attributing the
iGPU's outputs, which is why the problem only shows on binaries nobody
has configured.

The skip now tests what it meant: IsSoftwareAdapter, new on
GraphicsAdapter (the DXGI software flag plus the Microsoft vendor id on
D3D, the CPU device type on Vulkan). A hardware adapter with no output
is a fine renderer for a window - Windows composes across adapters
routinely - and exclusive fullscreen already requires
PreferredFullScreenOutputIndex to name a real output, so an output-less
adapter yields no fullscreen candidate on its own.

The enumeration preference also stops being hard-coded, answering the
TODO next to it: STRIDE_GPU_PREFERENCE=minimum-power puts the
battery-friendly GPU first and "unspecified" keeps the system's order,
machine-wide and without touching the game; RequiredAdapterUid remains
the per-game way to pick one exactly.

Measured on a muxless RTX 3050 Ti + Intel UHD laptop: the same demo
frame moves from the Intel (99% busy, TDR at its heaviest tier) to the
RTX by default, and back with minimum-power.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 06dabb4)
An environment variable is no API to review a GPU picker on: the game
itself had no typed way to say which GPU it wants first.
GraphicsAdapterFactory.GpuPreference is that way - a Stride enum
(HighPerformance, the default; MinimumPower; Unspecified), set before
the first adapter query, or re-applied with the Reset() the factory
already has. The Direct3D factory maps it to DXGI's own preference.

STRIDE_GPU_PREFERENCE stays, demoted to what an environment variable is
good at: the machine's word over the game's, for steering a title that
never exposes the choice - same precedent as
STRIDE_GRAPHICS_SOFTWARE_RENDERING.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 55dcfaa)
…shorter comments

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Nicogo1705

Copy link
Copy Markdown
Contributor Author

Thanks! HighPerformance does put the NVIDIA first in the enumeration, that part worked. What broke it is FindBestDevices right after, which skipped every adapter without a display output. On my laptop all outputs are wired to the Intel GPU, so the NVIDIA has none and was dropped, and every game landed on the Intel. The first commit fixes that by testing "software adapter" instead of "no output"; the enum only makes your default settable.

On the profiler: no problem, done, it moved to its own PR: #3405.

@Nicogo1705

Copy link
Copy Markdown
Contributor Author

All trimmed or removed: the 9-line comment in GamePlatform is one line now, and none mention the previous behaviour any more. Same pass done on #3387 and #3389.

@Ethereal77

Copy link
Copy Markdown
Contributor

What broke it is FindBestDevices right after, which skipped every adapter without a display output. On my laptop all outputs are wired to the Intel GPU, so the NVIDIA has none and was dropped, and every game landed on the Intel.

That's weird. My laptop's nVidia dGPU has no display attached either, yet using HighPerformance worked and Stride selected it instead of the Intel iGPU. At that's what happened back then when I was working on that part. I don't know if anything changed since.

Anyway, if your change is a more general solution, it's a nice addition.

@xen2 xen2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vulkan: nothing happens as current code in

// Software rendering adapters (e.g. WARP) have no outputs either, so allow them through
if (graphicsAdapter.Outputs.Length == 0
// Skip software rasterizers (WARP, Basic Render Driver) unless explicitly requested.
if (graphicsAdapter.IsSoftwareAdapter

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Vulkan, there's always a fake output graphicsOutputs = [ new GraphicsOutput() ];
So previous code with Outputs.Length == 0 never removed anything but the new one does.
Esp. software-only renderer like CI will only have IsSoftwareAdapter devices, which will be removed, so nothing left.

On D3D: same, since VendorId == 0x1414 (Microsoft) means any machine with only Microsoft Basic device and/or VM with virtual GPU will go through that path.

Maybe your fix should only be applied if something survived.
If graphicsDeviceInfos.Count == 0, run it again without software filtering.
As a result, it can still allow software rendering if nothing else.

Debug.Assert(dxgiFactoryVersion >= 6);
var dxgiFactory6 = (IDXGIFactory6*) dxgiFactory;

var dxgiPreference = gpuPreference == GpuPreference.MinimumPower

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vulkan? GPUPreference is ignored?

// if we want to switch to fullscreen, try to find only needed output, otherwise check them all
if (preferredParameters.IsFullScreen)
{
if (preferredParameters.PreferredFullScreenOutputIndex < graphicsAdapter.Outputs.Length)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An adapter with no outputs still produces no fullscreen candidate, so a game that starts fullscreen keeps landing on the iGPU.

On a muxless laptop (panel wired to the Intel, no MUX to switch it):
GPU 0: NVIDIA GPU (0 output)
GPU 1: Intel UHD Graphics (1 output)

GraphicsDeviceManager.IsFullScreen = true -> the NVIDIA adds nothing here, only the Intel does, so FindBestDevice returns the Intel and the device is created on it. Windowed gets the NVIDIA, fullscreen gets the Intel, on the same machine.

However, exclusive fullscreen on an output-less adapter is not really possible in DXGI...
So not sure what the expected behavior is? Is it to do borderless fullscreen rather than an output lookup?

/// </summary>
/// <remarks>
/// Set it before the first use of <see cref="Adapters"/> or <see cref="DefaultAdapter"/>, or call
/// <see cref="Reset"/> afterwards. The <c>STRIDE_GPU_PREFERENCE</c> environment variable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to be careful with Reset() recommendation: it dispose adapters, which could still be used by an alive GraphicsDevice.

I wonder if we should change our API so that GraphicsAdapterFactory list doesn't magically change depending on some state like GpuPreference being setup early enough.

It could simply report what the system has, untouched. With proper fact like IsSoftwareAdapter, and discrete/integrated hint (Vulkan has this info). Also possibly IsDefaultAdapter (item 0, could be Intel UHD) or PerformanceRank (from DXGI EnumAdapterByGpuPreference).

GPU preference can be stored like other preference on the appropriate object, like in GameGraphicsParameters then GraphicsDeviceManager (i.e. PreferredGpu).

As a result, creation won't use indirectly this mutable static anymore.

Example API for GraphicsAdapter:

public enum GraphicsAdapterType { Unknown, Integrated, Discrete, Virtual, Software }

public GraphicsAdapterType Type { get; private init; }        // set by each backend ctor
public bool IsSoftwareAdapter => Type == GraphicsAdapterType.Software; // maybe not needed, we can use Type

// Index of this adapter in the system enumeration order. 0 is the system default (use if GPU preference is unspecified -- on Windows it should return the OS pref depending on power saving settings)
public int AdapterOrder { get; private init; }
public bool IsDefaultAdapter => AdapterOrder == 0;

// Position in the platform's fastest-first order. 0 is the fastest
public int PerformanceRank { get; private init; }

// Position in the platform's most-efficient-first order. 0 is the most power efficient
public int PowerEfficiencyRank { get; private init; }

D3D can fill using DXGI ordering method like EnumerateLuidsByPreference
Vulkan can use stuff like Discrete > Integrated > Virtual > Cpu for perf (and swap Discrete/Integrated for efficiency)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants