Release Type
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
- Make a
MaterialDescriptor with a MaterialSpecularMicrofacetModelFeature and keep the default
Environment.
- Call
Material.New(GraphicsDevice, descriptor).
- 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.
Release Type
Version
masterat2f6f79b4b. Observed on Windows, Direct3D11, Intel Arc 140V, at profileLevel_10_0.Description
A material created in code with
Material.Newbinds an emptyTexturefor the environment DFGlookup 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:
The material loaded from an asset holds a real 32x64
R16G16_Floattexture. The material built byMaterial.Newholds a 0x0 texture with formatNonethat is still markedIsProxy.A third material that selects
MaterialSpecularMicrofacetEnvironmentGGXPolynomialbinds no texture atall, which agrees with the cause below: only the lookup table version is affected.
Cause
MaterialSpecularMicrofacetEnvironmentGGXLUT.Generatebinds 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>isnew T()plus reference metadata. Its owndocumentation 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 aContentSerializerContextis present — that is, during aContentManagerload.Material.NewrunsMaterialGenerator.Generateoutside 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
Texturewith 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
Environmentto the lookup table version:Materials/MaterialSpecularMicroFacetModelFeature.cs:44Materials/Hair/MaterialSpecularHairModelFeature.cs:258MaterialSpecularMicrofacetEnvironmentGGXPolynomialcomputes the term in the shader and needs notexture, so a material that selects it is not affected.
Steps to reproduce
MaterialDescriptorwith aMaterialSpecularMicrofacetModelFeatureand keep the defaultEnvironment.Material.New(GraphicsDevice, descriptor).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=Falseand a 32x64 texture.Expected: both give a real texture, because both name the same asset.
Possible directions
MaterialGeneratorContextholds aGraphicsDevicebut noContentManager, soGeneratehas no wayto load the asset. Options, roughly in order of size:
Material.Newan optionalContentManagerand resolve attached references after generation.Generateload through a content manager on the context.Material.Newlog a warning when generation leaves an unresolved proxy.Related
Found while auditing
GraphicsProfilecomparisons for a capability model. The sameGeneratemethodalso 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.