Skip to content

fix: map GraphicsProfile.Level_11_2 to a valid Direct3D feature level - #3302

Open
sasvdw wants to merge 3 commits into
stride3d:masterfrom
LazyWorksZA:fix/graphicsprofile-level-11-2-mapping
Open

fix: map GraphicsProfile.Level_11_2 to a valid Direct3D feature level#3302
sasvdw wants to merge 3 commits into
stride3d:masterfrom
LazyWorksZA:fix/graphicsprofile-level-11-2-mapping

Conversation

@sasvdw

@sasvdw sasvdw commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

PR Details

SummaryGraphicsProfile.Level_11_2 is unusable on the Direct3D backends; this maps it to the
real feature level it represents (FL 11_1) so it behaves like every other profile.

DescriptionGraphicsProfile.Level_11_2 = 0xB200 has no corresponding native Direct3D feature
level (real levels jump 11_1 (0xB100) → 12_0 (0xC000); Direct3D 11.2 was an API revision that runs
on FL 11_1 hardware, not a new feature level). GraphicsProfileHelper.ToFeatureLevel raw-cast the
profile to the non-existent 0xB200, so selecting Level 11.2 crashed at startup on Direct3D11
(rejected in GraphicsAdapter.IsProfileSupported) and Direct3D12 (passed its unconditional
IsProfileSupported, then failed at CreateDevice(0xB200)), while Vulkan — which ignores the
profile — ran.

Three Direct3D code paths change; the GraphicsProfile enum is untouched, so this is non-breaking
with no serialization impact:

  • GraphicsProfileHelper.ToFeatureLevel maps Level_11_2 → 11_1 instead of emitting 0xB200.
    Every other profile equals a real D3DFeatureLevel and keeps the direct cast.
  • GraphicsAdapter.Direct3D.cs (Direct3D11 IsProfileSupported) resolves the feature level via
    ToFeatureLevel() rather than a raw cast, so the exact-match check tests the real FL 11_1.
  • ShaderCompiler.cs (ShaderProfileFromGraphicsProfile) lists Level_11_2 alongside
    11_0/11_1 in the shader-model 5_0 case so it no longer throws.

Direct3D11 and Direct3D12 now accept Level_11_2 and run at FL 11_1, matching Vulkan. Legitimate
rejection is preserved
: profiles a device genuinely cannot provide (e.g. Level_11_x on FL10-only
hardware) still fail the capability check as before — only the invalid-value failure is removed.

Motivation and context — see #3301. The failure is hardware-independent: a WARP
software-renderer proof (in the repro) shows WARP reporting FL 12_1 yet rejecting 0xB200 with
E_INVALIDARG while accepting 0xB100. The mapping has been wrong since the 2018 open-source commit;
a June-2025 documentation pass added the [Display("Level 11.2 ~ …")] tooltip that makes the unusable
option look legitimate in Game Settings.

Related Issue

Fixes #3301. Repro (minimal code-only game + WARP proof):
https://github.com/sasvdw/stride-graphicsprofile-level112-repro

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My change requires a change to the documentation.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have built and run the editor to try this change out.

Validation status (kept honest — check the boxes above as each is satisfied):

  • Docs: no documentation change required — the profile simply works now, and the existing tooltip
    becomes accurate.
  • Tests: sources/engine/Stride.Graphics.Tests/TestGraphicsProfileHelper.cs covers the mapping —
    Level_11_2 resolves to FL 11_1 (not 0xB200), and every profile maps to a defined
    D3DFeatureLevel. Verified red/green (both facts fail on the raw cast, pass on the fix).
  • Build: Stride.Graphics (Direct3D11 + Direct3D12) and Stride.Shaders.Compilers build clean;
    the full test suite runs in CI.
  • Editor: outstanding — build + run the editor with a project set to Level 11.2 to confirm it
    launches (this is the mandatory personal-testing step before marking the PR ready).

sasvdw and others added 2 commits July 28, 2026 22:39
GraphicsProfile.Level_11_2 (0xB200) has no corresponding native Direct3D
feature level: real feature levels jump from 11_1 (0xB100) to 12_0 (0xC000)
(Direct3D 11.2 was an API revision that runs on FL 11_1 hardware, not a new
feature level). ToFeatureLevel raw-cast the profile straight to 0xB200, so
selecting Level_11_2 failed on every machine: Direct3D11 rejected it in
GraphicsAdapter.IsProfileSupported (the exact-match check can never match a
non-existent level), and Direct3D12 passed its unconditional IsProfileSupported
check but then failed at CreateDevice(0xB200). Vulkan ignored the profile and
ran, which is the tell that the fault was in the Direct3D mapping, not the enum.

Map Level_11_2 to its real capability tier (FL 11_1) in ToFeatureLevel, drive
the Direct3D11 IsProfileSupported check through ToFeatureLevel (instead of a raw
cast), and list Level_11_2 alongside 11_0/11_1 in the shader-model 5_0 case so
the compiler no longer throws for it. Direct3D11 and Direct3D12 now accept
Level_11_2 and run at FL 11_1, matching Vulkan. Legitimate rejection is
preserved: profiles a device genuinely cannot provide (e.g. Level_11_x on
FL10-only hardware) still fail the capability check as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a regression test for the Direct3D GraphicsProfile -> D3DFeatureLevel
mapping: Level_11_2 must resolve to the real FL 11_1 (not the non-existent
0xB200), and every profile must map to a defined D3DFeatureLevel. Verified
red/green against the ToFeatureLevel fix (both facts fail on the raw cast, pass
on the fix).

Reference Silk.NET.Direct3D11 in the test project so D3DFeatureLevel is visible
at compile time (Stride.Graphics hides it via PrivateAssets); the test is guarded
by STRIDE_GRAPHICS_API_DIRECT3D since Stride.Graphics.Tests builds per graphics
API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Draft PR — automatic CI is skipped to save runner minutes.

  • Mark the PR ready for review to run the full automatic CI — or add a ci-run-on-draft label to run it now without leaving draft.
  • Or arm a specific opt-in suite: ci-enduser, ci-editor, ci-ios, ci-android.

@sasvdw
sasvdw marked this pull request as ready for review July 28, 2026 22:51
@Ethereal77

Copy link
Copy Markdown
Contributor

LGTM. Good catch!

A little nitpick: The comments look a bit too verbose for my taste (even though I tend to write big explanatory comments 😁)

@sasvdw

sasvdw commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@Ethereal77, happy to take suggestions... Was a really tough one from a comments point of view. There's a genuine footgun highlighted in the comments that is bound to cause issues if someone just looks at the changes at surface level.

@Ethereal77

Copy link
Copy Markdown
Contributor

I agree with you. Better documentation is always preferable, specially around conflicting code. But, I think that if you have already created and used a helper ToFeatureLevel(), and that helper already documents why the mapping 11_2 is special, then the call site of the helper does not need the same comment, as the potential bug is not there anymore.

In the shader compiler, as it does not use the helper, the comment makes sense.

Address review feedback on the verbose comments by removing the reason they
were needed. The helper relied on GraphicsProfile values happening to equal
D3D feature levels, so every conversion was a cast and the Level_11_2 special
case had to be explained in prose at each site.

An explicit mapping table documents itself: Level_11_1 and Level_11_2 sharing
a row states the fix outright, so the call-site comment in GraphicsAdapter and
the shader model comment in ShaderCompiler are both dropped, and the helper's
own explanation shrinks to a one-line remark.

Also removes the unused array overload, whose span reinterpret bypassed the
mapping entirely and would still have produced the non-existent 0xB200, and
extends the tests to pin every profile so a mis-mapping cannot pass.

FromFeatureLevel is left as-is; it is a separate direction with its own
unmapped-value question and no bearing on this fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sasvdw

sasvdw commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@Ethereal77, after further consideration, I think I've landed on something more concise while also adding tests to ensure everything stays up to date.

I also removed the comment from the shader compiler because it's an entirely different case and should be straightforward knowledge for anyone with an understanding of this domain.

@Ethereal77

Copy link
Copy Markdown
Contributor

👍 Looks nice

@xen2 xen2 self-assigned this Aug 3, 2026
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.

GraphicsProfile.Level_11_2 is unusable on Direct3D (invalid feature-level mapping)

3 participants