Skip to content

fix: load the assets a run-time material references - #3370

Open
sasvdw wants to merge 2 commits into
stride3d:masterfrom
LazyWorksZA:fix/material-new-unresolved-proxy
Open

fix: load the assets a run-time material references#3370
sasvdw wants to merge 2 commits into
stride3d:masterfrom
LazyWorksZA:fix/material-new-unresolved-proxy

Conversation

@sasvdw

@sasvdw sasvdw commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

PR Details

Summary — A material built by Material.New bound an empty texture for the environment DFG lookup
table. Fixes #3369.

The two halves

The reported bug is that Material.New never resolves the references its features attach:

[built by Material.New] url=/Stride.Engine/StrideEnvironmentLightingDFGLUT16 IsProxy=True  size=0x0   format=None
[loaded from an asset]  url=/Stride.Engine/StrideEnvironmentLightingDFGLUT16 IsProxy=False size=32x64 format=R16G16_Float

Fixing that alone is not enough, and writing the test is what showed why. The first red run failed with
The asset ... could not be found, because the table may not be in the build at all. So there are
two problems, and a game whose materials are all built in code needs both fixed.

The reference was never resolved. MaterialSpecularMicrofacetEnvironmentGGXLUT.Generate attaches
the table with AttachedReferenceManager.CreateProxyObject, which is new T() plus reference metadata.
A proxy becomes the real asset in one place only, ReferenceSerializer, and only during a content load.
Material.New runs the generator outside that path.

Material.New now takes an optional ContentManager and, after generation, walks each pass's
parameters and loads anything still marked IsProxy. It resolves generically rather than special-casing
the lookup table, so Generate stays untouched and the asset compiler keeps serializing proxies exactly
as before.

The asset was never guaranteed to be there. The asset compiler starts from roots and follows
references. Package.RootAssets is the declared escape hatch for assets nothing references — "needs to
be compiled even if not directly or indirectly referenced (useful for explicit code references)"
.
Stride.Engine already declares the default font, the splash screen and the debug sprite font that way;
the splash screen is the identical pattern, named from code in GameSettingsFactory.cs:23. The two
lookup tables were never declared, so they reach a build only when some material asset happens to use
that environment function and drags them in.

Both are now declared. They ship in every bundle as a result, including games that never use that
environment function — 32x64 R16G16_Float plus the 8-bit variant, the same unconditional treatment the
splash screen already has.

When it still cannot resolve

Without a ContentManager, or when the asset is genuinely absent, the material keeps the empty object
and reports it, naming the parameter and the URL. It does not throw. Turning a subtly wrong render into
a crash would be the worse trade, and the roughly twelve existing callers — editor gizmos, previews,
debug shapes, fallbacks — pass no content manager and none of them uses a specular model.

Tests

  • TestMaterialProxyResolution in Stride.Graphics.Tests: the table resolves to a real texture, and the
    no-content-manager path reports rather than stays silent. That project holds no material asset
    referencing the tables, so it passes only if both halves hold. Reverting just the .sdpkg change puts
    it back to red with ContentManagerException, so it measures what it claims to.
  • TestGeneratorLeavesTextureReferencesAsProxies in Stride.Assets.Tests: pins that
    MaterialGenerator.Generate on its own still yields a proxy, which is what the asset compiler
    serializes. Four tests there already built this feature and asserted only on shader source.

Related Issue

Fixes #3369.

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

  • Build: Stride.Rendering clean under StrideGraphicsApi=Direct3D11, =Direct3D12 and =Vulkan,
    with -t:Rebuild rather than an incremental build. Not =Null: that backend does not compile on
    master today, with partial-method signature drift in Null/CommandList.Null.cs and
    Null/Texture.Null.cs. Unrelated to this change and worth its own issue.
  • Tests: 2 passing in Stride.Graphics.Tests, 7 in TestMaterialGenerator — the new contract test and
    the six that were already there, so the asset compiler's path is unchanged.
  • Gold images: none should move. The only rendering test that builds this material in code,
    MaterialLayerABBWithAPI, is [Fact(Skip = ...)] and never runs, so no baseline encodes the broken
    appearance. Left skipped; the stated reason is debugging convenience and I have no evidence it relates
    to this bug.
  • Editor: outstanding.

Not fixed here

Generate chooses between the 16-bit and 8-bit tables on a graphics profile when the real question is
texture format support. That is a separate concern, already listed under "found, not fixed" in #3368.

sasvdw and others added 2 commits August 23, 2026 16:44
Material.New runs the generator outside the content pipeline, so the
texture references its features attach stay proxies: empty objects with
reference data. Nothing resolved them, so a material built in code bound
a 0x0 texture for the environment lookup table.

Resolve them after generation when a content manager is given, and report
each one that stays empty otherwise. An asset reaches a build only when
something in the content references it, so a game that builds every
material in code can lack the asset; report that too rather than stop.

Fixes stride3d#3369

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An asset reaches a build only when something references it. Nothing
references these two, because MaterialSpecularMicrofacetEnvironmentGGXLUT
names them from code, so a game that builds every material in code never
gets them and the material cannot resolve them.

RootAssets is the declared way to say an asset is reached from code.
Stride.Engine already lists the default font and the splash screen, which
the engine reaches the same way.

The test moves to Stride.Graphics.Tests, which holds no material asset
that references the tables. It passes there only if both halves hold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

Material.New binds an empty texture for the default specular model

1 participant