Skip to content

Commit d00acb2

Browse files
authored
Merge pull request #3370 from LazyWorksZA/fix/material-new-unresolved-proxy
A material built by Material.New bound an empty texture for the environment DFG lookup table. Fixes #3369.
2 parents cdf6c0b + 60b2707 commit d00acb2

13 files changed

Lines changed: 510 additions & 42 deletions

File tree

sources/buildengine/Stride.Core.BuildEngine.Common/BuildTransaction.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,41 @@ public IEnumerable<KeyValuePair<string, ObjectId>> SearchValues(Func<KeyValuePai
3131
}
3232
}
3333

34+
public IEnumerable<KeyValuePair<string, ObjectId>> GetMergedIdMap()
35+
{
36+
var result = new Dictionary<string, ObjectId>();
37+
38+
lock (transactionOutputObjects)
39+
{
40+
foreach (var outputObject in transactionOutputObjects)
41+
{
42+
if (outputObject.Key.Type == UrlType.Content)
43+
result[outputObject.Key.Path] = outputObject.Value;
44+
}
45+
46+
foreach (var outputObjects in outputObjectsGroups)
47+
{
48+
// Lock underlying EnumerableBuildStep.OutputObjects
49+
lock (outputObjects)
50+
{
51+
foreach (var outputObject in outputObjects)
52+
{
53+
if (outputObject.Key.Type == UrlType.Content)
54+
result.TryAdd(outputObject.Key.Path, outputObject.Value.ObjectId);
55+
}
56+
}
57+
}
58+
59+
if (contentIndexMap != null)
60+
{
61+
foreach (var entry in contentIndexMap.GetMergedIdMap())
62+
result.TryAdd(entry.Key, entry.Value);
63+
}
64+
}
65+
66+
return result;
67+
}
68+
3469
public bool TryGetValue(string url, out ObjectId objectId)
3570
{
3671
var objUrl = new ObjectUrl(UrlType.Content, url);
@@ -114,8 +149,7 @@ public void WaitPendingOperations()
114149

115150
public IEnumerable<KeyValuePair<string, ObjectId>> GetMergedIdMap()
116151
{
117-
// Shouldn't be used
118-
throw new NotImplementedException();
152+
return buildTransaction.GetMergedIdMap();
119153
}
120154

121155
public void Dispose()

sources/core/Stride.Core.Serialization/Serialization/Contents/ContentManager.cs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -670,47 +670,54 @@ private void ThrowAssetNotFound(string url)
670670
{
671671
var errorMessage = $"The asset '{url}' could not be found. Asset path should be 'MyFolder/MyAssetName', or '/PackageName/MyFolder/MyAssetName' for an asset from a namespaced package. Check that the path is correct and that the asset has been included into the build.";
672672

673-
var contentIndexMap = FileProvider?.ContentIndexMap;
674-
if (contentIndexMap != null)
673+
try
675674
{
676-
var indexMap = contentIndexMap.GetMergedIdMap();
677-
678-
// First suggest assets differing from the requested URL only by the '/PackageName' root
679-
var isRooted = url.StartsWith('/');
680-
var candidates = new List<string>();
681-
foreach (var entry in indexMap)
682-
{
683-
if (isRooted ? DiffersByRoot(url, entry.Key) : DiffersByRoot(entry.Key, url))
684-
candidates.Add(entry.Key);
685-
}
686-
if (candidates.Count > 0)
687-
{
688-
candidates.Sort();
689-
errorMessage = $"The asset '{url}' could not be found. Did you mean:"
690-
+ string.Concat(candidates.Select(c => $"{Environment.NewLine} '{c}'"))
691-
+ $"{Environment.NewLine}An asset from a namespaced package is addressed by a rooted URL: '/PackageName/MyFolder/MyAssetName'.";
692-
}
693-
else
675+
var contentIndexMap = FileProvider?.ContentIndexMap;
676+
if (contentIndexMap != null)
694677
{
695-
// Otherwise fall back to assets with the same name in other folders
696-
var assetName = url.Substring(url.LastIndexOf('/') + 1);
697-
var sameNameCandidates = new List<string>();
678+
var indexMap = contentIndexMap.GetMergedIdMap();
679+
680+
// First suggest assets differing from the requested URL only by the '/PackageName' root
681+
var isRooted = url.StartsWith('/');
682+
var candidates = new List<string>();
698683
foreach (var entry in indexMap)
699684
{
700-
if (entry.Key.EndsWith(assetName, StringComparison.OrdinalIgnoreCase)
701-
&& (entry.Key.Length == assetName.Length || entry.Key[entry.Key.Length - assetName.Length - 1] == '/'))
702-
sameNameCandidates.Add(entry.Key);
685+
if (isRooted ? DiffersByRoot(url, entry.Key) : DiffersByRoot(entry.Key, url))
686+
candidates.Add(entry.Key);
687+
}
688+
if (candidates.Count > 0)
689+
{
690+
candidates.Sort();
691+
errorMessage = $"The asset '{url}' could not be found. Did you mean:"
692+
+ string.Concat(candidates.Select(c => $"{Environment.NewLine} '{c}'"))
693+
+ $"{Environment.NewLine}An asset from a namespaced package is addressed by a rooted URL: '/PackageName/MyFolder/MyAssetName'.";
703694
}
704-
if (sameNameCandidates.Count > 0)
695+
else
705696
{
706-
sameNameCandidates.Sort();
707-
var more = sameNameCandidates.Count > MaxSameNameCandidates ? $"{Environment.NewLine} (and {sameNameCandidates.Count - MaxSameNameCandidates} more)" : string.Empty;
708-
errorMessage = $"The asset '{url}' could not be found. Assets with the same name exist at:"
709-
+ string.Concat(sameNameCandidates.Take(MaxSameNameCandidates).Select(c => $"{Environment.NewLine} '{c}'"))
710-
+ more;
697+
// Otherwise fall back to assets with the same name in other folders
698+
var assetName = url.Substring(url.LastIndexOf('/') + 1);
699+
var sameNameCandidates = new List<string>();
700+
foreach (var entry in indexMap)
701+
{
702+
if (entry.Key.EndsWith(assetName, StringComparison.OrdinalIgnoreCase)
703+
&& (entry.Key.Length == assetName.Length || entry.Key[entry.Key.Length - assetName.Length - 1] == '/'))
704+
sameNameCandidates.Add(entry.Key);
705+
}
706+
if (sameNameCandidates.Count > 0)
707+
{
708+
sameNameCandidates.Sort();
709+
var more = sameNameCandidates.Count > MaxSameNameCandidates ? $"{Environment.NewLine} (and {sameNameCandidates.Count - MaxSameNameCandidates} more)" : string.Empty;
710+
errorMessage = $"The asset '{url}' could not be found. Assets with the same name exist at:"
711+
+ string.Concat(sameNameCandidates.Take(MaxSameNameCandidates).Select(c => $"{Environment.NewLine} '{c}'"))
712+
+ more;
713+
}
711714
}
712715
}
713716
}
717+
catch (Exception)
718+
{
719+
// Suggestions are best effort; an index map that cannot enumerate must not replace the real error
720+
}
714721

715722
// True when <rooted> is exactly '/PackageName' (a single segment) followed by '/<bare>'.
716723
static bool DiffersByRoot(string rooted, string bare)

sources/editor/Stride.Assets.Presentation/Preview/SkyboxPreview.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
22
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
33
using System.Threading.Tasks;
4+
using Stride.Core.Assets.Compiler;
45
using Stride.Core.Mathematics;
56
using Stride.Assets.Skyboxes;
67
using Stride.Editor.Annotations;
78
using Stride.Editor.Preview;
89
using Stride.Engine;
10+
using Stride.Graphics;
911
using Stride.Rendering.Skyboxes;
1012
using Stride.Rendering;
1113
using Stride.Rendering.Lights;
@@ -53,6 +55,23 @@ protected override void SetupLighting(Entity camera)
5355
// No default lighting
5456
}
5557

58+
protected override AssetCompilerResult Compile()
59+
{
60+
var result = base.Compile();
61+
62+
// The preview material is generated at run time, so the specular lookup table it references
63+
// is not a dependency of the skybox asset; compile it too so the preview database can serve it
64+
foreach (var profile in new[] { GraphicsProfile.Level_9_1, GraphicsProfile.Level_10_0 })
65+
{
66+
var lutReference = MaterialSpecularMicrofacetEnvironmentGGXLUT.CreateLookupTableReference(profile);
67+
var lutItem = AssetItem.Package.Session.FindAssetFromProxyObject(lutReference);
68+
if (lutItem != null)
69+
result.BuildSteps.Add(Builder.Compile(lutItem).BuildSteps);
70+
}
71+
72+
return result;
73+
}
74+
5675
protected override PreviewEntity CreatePreviewEntity()
5776
{
5877
var skybox = LoadAsset<Skybox>(AssetItem.Location);
@@ -123,7 +142,7 @@ private Material CreateMaterial(float metalness, float glossiness)
123142
},
124143
SpecularModel = new MaterialSpecularMicrofacetModelFeature()
125144
}
126-
});
145+
}, Game.Content);
127146
}
128147
}
129148
}

sources/editor/Stride.Assets.Presentation/Themes/Generic.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
<Slider Grid.Column="1" Value="{Binding Metalness}" Height="16" Minimum="0" Maximum="1" HorizontalAlignment="Stretch"
146146
TickFrequency="0.0333" TickPlacement="BottomRight" IsMoveToPointEnabled="True"/>
147147
</Grid>
148-
<Grid HorizontalAlignment="Stretch">
148+
<Grid HorizontalAlignment="Stretch" Margin="0,3,0,0">
149149
<Grid.ColumnDefinitions>
150150
<ColumnDefinition Width="70" />
151151
<ColumnDefinition Width="*" />

sources/engine/Stride.Assets.Tests/TestMaterialGenerator.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.IO;
77
using Xunit;
88
using Stride.Core.Mathematics;
9+
using Stride.Core.Serialization;
910
using Stride.Core.Yaml;
11+
using Stride.Graphics;
1012
using Stride.Rendering;
1113
using Stride.Rendering.Materials;
1214
using Stride.Rendering.Materials.ComputeColors;
@@ -914,6 +916,51 @@ public void Test2Layers3ShadingModels()
914916
}
915917

916918

919+
/// <summary>
920+
/// The generator on its own leaves texture references as proxies.
921+
/// </summary>
922+
/// <remarks>
923+
/// This is what the asset compiler serializes: the proxy carries the reference, and a content
924+
/// load turns it back into the texture. A caller that generates a material at run time is outside
925+
/// that path and has to resolve the references itself, which is what <c>Material.New</c> does.
926+
/// Pinning it here keeps a change made for the run-time path from breaking the build-time one.
927+
/// </remarks>
928+
[Fact]
929+
public void TestGeneratorLeavesTextureReferencesAsProxies()
930+
{
931+
// The graphics profile decides between the LUT16 and LUT8 lookup tables; pin it
932+
// so the URL asserted below does not depend on the context's default.
933+
var context = new MaterialGeneratorContextExtended { GraphicsProfile = GraphicsProfile.Level_10_0 };
934+
var materialDesc = new MaterialDescriptor
935+
{
936+
Attributes =
937+
{
938+
Diffuse = new MaterialDiffuseMapFeature(new ComputeColor(Color.White)),
939+
DiffuseModel = new MaterialDiffuseLambertModelFeature(),
940+
Specular = new MaterialMetalnessMapFeature(new ComputeFloat(1.0f)),
941+
MicroSurface = new MaterialGlossinessMapFeature(new ComputeFloat(0.9f)),
942+
943+
// Defaults its environment function to MaterialSpecularMicrofacetEnvironmentGGXLUT,
944+
// which attaches a reference to a lookup table texture.
945+
SpecularModel = new MaterialSpecularMicrofacetModelFeature(),
946+
},
947+
};
948+
949+
var result = MaterialGenerator.Generate(materialDesc, context, "test_material");
950+
Assert.False(result.HasErrors, result.ToText());
951+
952+
var lookupTable = result.Material.Passes[0].Parameters
953+
.Get(MaterialSpecularMicrofacetEnvironmentGGXLUTKeys.EnvironmentLightingDFG_LUT);
954+
955+
Assert.NotNull(lookupTable);
956+
957+
var reference = AttachedReferenceManager.GetAttachedReference(lookupTable);
958+
959+
Assert.NotNull(reference);
960+
Assert.True(reference.IsProxy, "The generator resolved the reference, which the asset compiler relies on staying a proxy.");
961+
Assert.Equal("/Stride.Engine/StrideEnvironmentLightingDFGLUT16", reference.Url);
962+
}
963+
917964
private class MaterialGeneratorContextExtended : MaterialGeneratorContext
918965
{
919966
private readonly Dictionary<object, object> assetMap = new Dictionary<object, object>();
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
using Xunit;
8+
9+
using Stride.Core.Assets;
10+
using Stride.Core.Diagnostics;
11+
using Stride.Core.Serialization;
12+
using Stride.Rendering;
13+
14+
namespace Stride.Engine.Tests
15+
{
16+
/// <summary>
17+
/// Tests how a <see cref="ParameterCollection"/> handles object values that are unresolved
18+
/// attached references (proxies made by <see cref="AttachedReferenceManager.CreateProxyObject{T}(AssetId, string)"/>).
19+
/// </summary>
20+
public class ParameterCollectionAttachedReferenceTest
21+
{
22+
public static readonly ObjectParameterKey<object> ObjectKey = ParameterKeys.NewObject<object>();
23+
24+
[Fact]
25+
public void TestResolveWithoutContentManagerWarnsAndKeepsProxy()
26+
{
27+
var parameters = new ParameterCollection();
28+
var url = $"test/unresolved/{Guid.NewGuid()}";
29+
var proxy = AttachedReferenceManager.CreateProxyObject<TestReferencedContent>(AssetId.New(), url);
30+
parameters.Set(ObjectKey, proxy);
31+
32+
var log = new LoggerResult();
33+
parameters.ResolveAttachedReferences(null, log);
34+
35+
Assert.Contains(log.Messages, message => message.Type == LogMessageType.Warning && message.Text.Contains(url));
36+
Assert.Same(proxy, parameters.Get(ObjectKey));
37+
}
38+
39+
[Fact]
40+
public void TestUpdateLayoutReportsUnresolvedReference()
41+
{
42+
var parameters = new ParameterCollection();
43+
// The report deduplicates per URL for the process lifetime, so make the URL unique
44+
var url = $"test/unresolved/{Guid.NewGuid()}";
45+
parameters.Set(ObjectKey, AttachedReferenceManager.CreateProxyObject<TestReferencedContent>(AssetId.New(), url));
46+
47+
var warnings = CollectGlobalWarningsDuring(() => parameters.UpdateLayout(LayoutWith(ObjectKey)));
48+
49+
Assert.Contains(warnings, text => text.Contains(url));
50+
}
51+
52+
[Fact]
53+
public void TestUpdateLayoutIgnoresResolvedObjects()
54+
{
55+
var parameters = new ParameterCollection();
56+
parameters.Set(ObjectKey, new TestReferencedContent());
57+
58+
var warnings = CollectGlobalWarningsDuring(() => parameters.UpdateLayout(LayoutWith(ObjectKey)));
59+
60+
Assert.DoesNotContain(warnings, text => text.Contains("unloaded content"));
61+
}
62+
63+
private static ParameterCollectionLayout LayoutWith(ParameterKey key)
64+
{
65+
var layout = new ParameterCollectionLayout { ResourceCount = 1 };
66+
layout.LayoutParameterKeyInfos.Add(new ParameterKeyInfo(key, 0));
67+
return layout;
68+
}
69+
70+
private static List<string> CollectGlobalWarningsDuring(Action action)
71+
{
72+
var warnings = new List<string>();
73+
74+
void Collect(ILogMessage message)
75+
{
76+
if (message.Type >= LogMessageType.Warning)
77+
{
78+
lock (warnings) warnings.Add(message.Text);
79+
}
80+
}
81+
82+
GlobalLogger.GlobalMessageLogged += Collect;
83+
try
84+
{
85+
action();
86+
}
87+
finally
88+
{
89+
GlobalLogger.GlobalMessageLogged -= Collect;
90+
}
91+
92+
return warnings;
93+
}
94+
95+
private class TestReferencedContent
96+
{
97+
}
98+
}
99+
}

sources/engine/Stride.Engine.Tests/Stride.Engine.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Compile Include="Build\TestBuilder.cs" />
2424
<Compile Include="EngineTestBase.cs" />
2525
<Compile Include="ForwardRendererTransparentTargetsTest.cs" />
26+
<Compile Include="ParameterCollectionAttachedReferenceTest.cs" />
2627
<Compile Include="ParameterCollectionUpdateEngineTest.cs" />
2728
<Compile Include="EntityUpdateEngineTest.cs" />
2829
<Compile Include="AnimatedModelTests.cs" />

sources/engine/Stride.Engine/Stride.Engine.sdpkg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ RootAssets:
99
- c90f3988-0544-4cbe-993f-13af7d9c23c6:/Stride.Engine/StrideDefaultFont
1010
- d26edb11-10bd-403c-b3c2-9c7fcccf25e5:/Stride.Engine/StrideDefaultSplashScreen
1111
- FF02239B-3697-4EBB-9F37-FE880659E64B:/Stride.Engine/StrideDebugSpriteFont
12+
- a49995f8-2380-4baa-a03e-f8d1da35b79a:/Stride.Engine/StrideEnvironmentLightingDFGLUT16
13+
- 87540190-ab97-4b4e-b3c2-d57d2fbb1ff3:/Stride.Engine/StrideEnvironmentLightingDFGLUT8

0 commit comments

Comments
 (0)