Skip to content

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

Description

@sasvdw

Release Type

  • Bug

Version

master at 2f6f79b4b. Observed on Windows, Direct3D11, Intel Arc 140V, at profile Level_10_0.

Description

A material created in code with Material.New binds an empty Texture for the environment DFG
lookup table. The default specular model uses that lookup table, so this applies to most materials
created at runtime.

Observed at run time. A probe script in a game built from the First Person Shooter template reports
the same asset resolving two different ways in the same frame, on the same device:

[built by Material.New] url=/Stride.Engine/StrideEnvironmentLightingDFGLUT16 IsProxy=True  size=0x0   format=None
[built by Material.New] CONFIRMED: the lookup table is an unresolved proxy with no pixels.
[loaded from an asset]  url=/Stride.Engine/StrideEnvironmentLightingDFGLUT16 IsProxy=False size=32x64 format=R16G16_Float
[loaded from an asset]  the lookup table is a real texture.

The material loaded from an asset holds a real 32x64 R16G16_Float texture. The material built by
Material.New holds a 0x0 texture with format None that is still marked IsProxy.

A third material that selects MaterialSpecularMicrofacetEnvironmentGGXPolynomial binds no texture at
all, which agrees with the cause below: only the lookup table version is affected.

Cause

MaterialSpecularMicrofacetEnvironmentGGXLUT.Generate binds the lookup table as a proxy:

https://github.com/stride3d/stride/blob/2f6f79b4b/sources/engine/Stride.Rendering/Rendering/Materials/MaterialSpecularMicrofacetEnvironmentGGXLUT.cs#L27-L29

AttachedReferenceManager.CreateProxyObject<T> is new T() plus reference metadata. Its own
documentation says it lets you "construct and save object references without actually loading them":

https://github.com/stride3d/stride/blob/2f6f79b4b/sources/core/Stride.Core.Serialization/Serialization/AttachedReferenceManager.cs#L78-L89

A proxy becomes a loaded object in one place only: ReferenceSerializer<T>.Serialize, and only when a
ContentSerializerContext is present — that is, during a ContentManager load.

Material.New runs MaterialGenerator.Generate outside that path:

https://github.com/stride3d/stride/blob/2f6f79b4b/sources/engine/Stride.Rendering/Rendering/Material.cs#L49-L68

Nothing resolves the proxy afterward. The parameter collection keeps a Texture with no GPU resource.

A material loaded from an asset is not affected. The generator runs at build time, and the resulting
material goes through the content pipeline, which resolves the reference.

Affected defaults

Two features default Environment to the lookup table version:

  • Materials/MaterialSpecularMicroFacetModelFeature.cs:44
  • Materials/Hair/MaterialSpecularHairModelFeature.cs:258

MaterialSpecularMicrofacetEnvironmentGGXPolynomial computes the term in the shader and needs no
texture, so a material that selects it is not affected.

Steps to reproduce

  1. Make a MaterialDescriptor with a MaterialSpecularMicrofacetModelFeature and keep the default
    Environment.
  2. Call Material.New(GraphicsDevice, descriptor).
  3. Read the lookup table back out of the material and look at it:
var texture = material.Passes[0].Parameters
    .Get(MaterialSpecularMicrofacetEnvironmentGGXLUTKeys.EnvironmentLightingDFG_LUT);
var reference = AttachedReferenceManager.GetAttachedReference(texture);
// reference.IsProxy is true, and texture.Width and texture.Height are 0.

To see both sides at once, put a material asset that uses the same environment function beside it and
read the same key from that one. It reports IsProxy=False and a 32x64 texture.

Expected: both give a real texture, because both name the same asset.

Possible directions

MaterialGeneratorContext holds a GraphicsDevice but no ContentManager, so Generate has no way
to load the asset. Options, roughly in order of size:

  • Give Material.New an optional ContentManager and resolve attached references after generation.
  • Let Generate load through a content manager on the context.
  • Document the limit, and have Material.New log a warning when generation leaves an unresolved proxy.

Related

Found while auditing GraphicsProfile comparisons for a capability model. The same Generate method
also selects between the 16-bit and the 8-bit lookup table on a profile comparison, when the real
question is texture format support. That part is a separate concern.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions