Skip to content

Commit a5cad31

Browse files
authored
Merge pull request #811 from Sergio0694/dev/shader-linking-fixes2
Rework linking analyzer, remove generator heuristic, change D2D compile options
2 parents 86077ea + 2c36f2c commit a5cad31

11 files changed

+270
-295
lines changed
Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using ComputeSharp.SourceGeneration.Extensions;
32
using Microsoft.CodeAnalysis;
43

@@ -76,83 +75,12 @@ public static D2D1ShaderProfile GetEffectiveShaderProfile(D2D1ShaderProfile? sha
7675
/// Gets the effective compile options to use.
7776
/// </summary>
7877
/// <param name="compileOptions">The requested compile options.</param>
79-
/// <param name="isLinkingSupported">Whether the input shader supports linking.</param>
8078
/// <returns>The effective compile options.</returns>
81-
public static D2D1CompileOptions GetEffectiveCompileOptions(D2D1CompileOptions? compileOptions, bool isLinkingSupported)
79+
public static D2D1CompileOptions GetEffectiveCompileOptions(D2D1CompileOptions? compileOptions)
8280
{
8381
// If an explicit set of compile options is provided, use that directly. Otherwise, use the default
8482
// options plus the option to enable linking only if the shader can potentially support it.
85-
return compileOptions ?? (D2D1CompileOptions.Default | (isLinkingSupported ? D2D1CompileOptions.EnableLinking : 0));
86-
}
87-
88-
/// <summary>
89-
/// Extracts the metadata definition for the current shader.
90-
/// </summary>
91-
/// <param name="compilation">The input <see cref="Compilation"/> object currently in use.</param>
92-
/// <param name="structDeclarationSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
93-
/// <param name="inputCount">The number of inputs for the shader.</param>
94-
/// <returns>Whether the shader only has simple inputs.</returns>
95-
public static bool IsSimpleInputShader(
96-
Compilation compilation,
97-
INamedTypeSymbol structDeclarationSymbol,
98-
int inputCount)
99-
{
100-
return IsSimpleInputShader(
101-
structDeclarationSymbol,
102-
compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DInputSimpleAttribute")!,
103-
inputCount);
104-
}
105-
106-
/// <summary>
107-
/// Extracts the metadata definition for the current shader.
108-
/// </summary>
109-
/// <param name="structDeclarationSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
110-
/// <param name="d2DInputSimpleSymbolAttributeSymbol">The symbol for the <c>[D2DInputSimple]</c> attribute.</param>
111-
/// <param name="inputCount">The number of inputs for the shader.</param>
112-
/// <returns>Whether the shader only has simple inputs.</returns>
113-
public static bool IsSimpleInputShader(
114-
INamedTypeSymbol structDeclarationSymbol,
115-
INamedTypeSymbol d2DInputSimpleSymbolAttributeSymbol,
116-
int inputCount)
117-
{
118-
// We cannot trust the input count to be valid at this point (it may be invalid and
119-
// with diagnostic already emitted for it). So first, just clamp it in the right range.
120-
inputCount = Math.Max(0, Math.Min(8, inputCount));
121-
122-
// If there are no inputs, the shader is as if only had simple inputs
123-
if (inputCount == 0)
124-
{
125-
return true;
126-
}
127-
128-
// Build a map of all simple inputs (unmarked inputs default to being complex).
129-
// We can never have more than 8 inputs, and if there are it means the shader is
130-
// not valid. Just ignore them, and the generator will emit a separate diagnostic.
131-
Span<bool> simpleInputsMap = stackalloc bool[8];
132-
133-
// We first start with all inputs marked as complex (ie. not simple)
134-
simpleInputsMap.Clear();
135-
136-
foreach (AttributeData attributeData in structDeclarationSymbol.GetAttributes())
137-
{
138-
// Only retrieve indices of simple inputs that are in range. If an input is out of
139-
// range, the diagnostic for it will already be emitted by a previous generator step.
140-
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, d2DInputSimpleSymbolAttributeSymbol) &&
141-
attributeData.ConstructorArguments is [{ Value: >= 0 and < 8 and int index }])
142-
{
143-
simpleInputsMap[index] = true;
144-
}
145-
}
146-
147-
bool isSimpleInputShader = true;
148-
149-
// Validate all inputs in our range (filtered by the allowed one)
150-
foreach (bool isSimpleInput in simpleInputsMap[..inputCount])
151-
{
152-
isSimpleInputShader &= isSimpleInput;
153-
}
154-
155-
return isSimpleInputShader;
83+
return compileOptions ?? D2D1CompileOptions.Default;
15684
}
15785
}
15886
}

src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
128128
token.ThrowIfCancellationRequested();
129129

130130
// Get the shader profile and linking info for the HLSL bytecode properties
131-
bool isLinkingSupported = HlslBytecode.IsSimpleInputShader(context.SemanticModel.Compilation, typeSymbol, inputCount);
132131
D2D1ShaderProfile? requestedShaderProfile = HlslBytecode.GetRequestedShaderProfile(typeSymbol);
133132
D2D1CompileOptions? requestedCompileOptions = HlslBytecode.GetRequestedCompileOptions(typeSymbol);
134133
D2D1ShaderProfile effectiveShaderProfile = HlslBytecode.GetEffectiveShaderProfile(requestedShaderProfile, out bool isCompilationEnabled);
135-
D2D1CompileOptions effectiveCompileOptions = HlslBytecode.GetEffectiveCompileOptions(requestedCompileOptions, isLinkingSupported);
134+
D2D1CompileOptions effectiveCompileOptions = HlslBytecode.GetEffectiveCompileOptions(requestedCompileOptions);
136135

137136
token.ThrowIfCancellationRequested();
138137

src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2D1CompileOptionsEnableLinkingOnShaderTypeAnalyzer.cs

Lines changed: 0 additions & 67 deletions
This file was deleted.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using ComputeSharp.SourceGeneration.Extensions;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors;
7+
8+
namespace ComputeSharp.D2D1.SourceGenerators;
9+
10+
/// <summary>
11+
/// A diagnostic analyzer that generates an error whenever [D2DPixelOptions] is used on a shader type to incorrectly request trivial sampling.
12+
/// </summary>
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
public sealed class InvalidD2D1PixelOptionsTrivialSamplingOnShaderTypeAnalyzer : DiagnosticAnalyzer
15+
{
16+
/// <inheritdoc/>
17+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [InvalidD2D1PixelOptionsTrivialSamplingOnShaderType];
18+
19+
/// <inheritdoc/>
20+
public override void Initialize(AnalysisContext context)
21+
{
22+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
23+
context.EnableConcurrentExecution();
24+
25+
context.RegisterCompilationStartAction(static context =>
26+
{
27+
// Get the [D2DPixelOptions], [D2DInputCount] and [D2DInputSimple] symbols
28+
if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DPixelOptionsAttribute") is not { } d2DPixelOptionsAttributeSymbol ||
29+
context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DInputCountAttribute") is not { } d2DInputCountAttributeSymbol ||
30+
context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DInputSimpleAttribute") is not { } d2DInputSimpleAttributeSymbol)
31+
{
32+
return;
33+
}
34+
35+
context.RegisterSymbolAction(context =>
36+
{
37+
// Only struct types are possible targets
38+
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Struct } typeSymbol)
39+
{
40+
return;
41+
}
42+
43+
// If the type is not using [D2DPixelOptions] with D2D1PixelOptions.TrivialSampling, there's nothing to do
44+
if (!typeSymbol.TryGetAttributeWithType(d2DPixelOptionsAttributeSymbol, out AttributeData? pixelOptionsAttribute) ||
45+
!((D2D1PixelOptions)pixelOptionsAttribute.ConstructorArguments[0].Value!).HasFlag(D2D1PixelOptions.TrivialSampling))
46+
{
47+
return;
48+
}
49+
50+
// Make sure we have the [D2DInputCount] (if not present, the shader is invalid anyway) and with a valid value
51+
if (!typeSymbol.TryGetAttributeWithType(d2DInputCountAttributeSymbol, out AttributeData? inputCountAttribute) ||
52+
inputCountAttribute.ConstructorArguments is not [{ Value: >= 0 and < 8 and int inputCount }])
53+
{
54+
return;
55+
}
56+
57+
// Emit a diagnostic if the compile options are not valid for the shader type
58+
if (!IsSimpleInputShader(typeSymbol, d2DInputSimpleAttributeSymbol, inputCount))
59+
{
60+
context.ReportDiagnostic(Diagnostic.Create(
61+
InvalidD2D1PixelOptionsTrivialSamplingOnShaderType,
62+
pixelOptionsAttribute.GetLocation(),
63+
typeSymbol));
64+
}
65+
}, SymbolKind.NamedType);
66+
});
67+
}
68+
69+
/// <summary>
70+
/// Checks whether a given shader only has simple inputs.
71+
/// </summary>
72+
/// <param name="structDeclarationSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
73+
/// <param name="d2DInputSimpleSymbolAttributeSymbol">The symbol for the <c>[D2DInputSimple]</c> attribute.</param>
74+
/// <param name="inputCount">The number of inputs for the shader.</param>
75+
/// <returns>Whether the shader only has simple inputs.</returns>
76+
private static bool IsSimpleInputShader(
77+
INamedTypeSymbol structDeclarationSymbol,
78+
INamedTypeSymbol d2DInputSimpleSymbolAttributeSymbol,
79+
int inputCount)
80+
{
81+
// We cannot trust the input count to be valid at this point (it may be invalid and
82+
// with diagnostic already emitted for it). So first, just clamp it in the right range.
83+
inputCount = Math.Max(0, Math.Min(8, inputCount));
84+
85+
// If there are no inputs, the shader is as if only had simple inputs
86+
if (inputCount == 0)
87+
{
88+
return true;
89+
}
90+
91+
// Build a map of all simple inputs (unmarked inputs default to being complex).
92+
// We can never have more than 8 inputs, and if there are it means the shader is
93+
// not valid. Just ignore them, and the generator will emit a separate diagnostic.
94+
Span<bool> simpleInputsMap = stackalloc bool[8];
95+
96+
// We first start with all inputs marked as complex (ie. not simple)
97+
simpleInputsMap.Clear();
98+
99+
foreach (AttributeData attributeData in structDeclarationSymbol.GetAttributes())
100+
{
101+
// Only retrieve indices of simple inputs that are in range. If an input is out of
102+
// range, the diagnostic for it will already be emitted by a previous generator step.
103+
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, d2DInputSimpleSymbolAttributeSymbol) &&
104+
attributeData.ConstructorArguments is [{ Value: >= 0 and < 8 and int index }])
105+
{
106+
simpleInputsMap[index] = true;
107+
}
108+
}
109+
110+
bool isSimpleInputShader = true;
111+
112+
// Validate all inputs in our range (filtered by the allowed one)
113+
foreach (bool isSimpleInput in simpleInputsMap[..inputCount])
114+
{
115+
isSimpleInputShader &= isSimpleInput;
116+
}
117+
118+
return isSimpleInputShader;
119+
}
120+
}

src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,19 +1024,19 @@ partial class DiagnosticDescriptors
10241024
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
10251025

10261026
/// <summary>
1027-
/// Gets a <see cref="DiagnosticDescriptor"/> for an invalid use of <c>[D2DCompileOptions]</c> requesting to enable linking.
1027+
/// Gets a <see cref="DiagnosticDescriptor"/> for an invalid use of <c>[D2DPixelOptions]</c> indicating trivial sampling.
10281028
/// <para>
1029-
/// Format: <c>"The D2D1 shader of type {0} cannot use D2D1CompileOptions.EnableLinking in its [D2DCompileOptions] attribute, as it doesn't support linking (only D2D1 shaders with no complex inputs can use this option)"</c>.
1029+
/// Format: <c>"The D2D1 shader of type {0} shouldn't use D2D1PixelOptions.TrivialSampling in its [D2DPixelOptions] attribute, as it has one or more complex inputs (either mark the inputs as simple, or remove the trivial sampling option)"</c>.
10301030
/// </para>
10311031
/// </summary>
1032-
public static readonly DiagnosticDescriptor InvalidD2D1CompileOptionsEnableLinkingOnShaderType = new(
1032+
public static readonly DiagnosticDescriptor InvalidD2D1PixelOptionsTrivialSamplingOnShaderType = new(
10331033
id: "CMPSD2D0069",
1034-
title: "Invalid [D2DResourceTextureIndex] use",
1035-
messageFormat: """The D2D1 shader of type {0} cannot use D2D1CompileOptions.EnableLinking in its [D2DCompileOptions] attribute, as it doesn't support linking (only D2D1 shaders with no complex inputs can use this option)""",
1034+
title: "Invalid [D2DPixelOptions] use",
1035+
messageFormat: """The D2D1 shader of type {0} shouldn't use D2D1PixelOptions.TrivialSampling in its [D2DPixelOptions] attribute, as it has one or more complex inputs (either mark the inputs as simple, or remove the trivial sampling option)""",
10361036
category: "ComputeSharp.D2D1.Shaders",
10371037
defaultSeverity: DiagnosticSeverity.Warning,
10381038
isEnabledByDefault: true,
1039-
description: "A D2D1 shader cannot use D2D1CompileOptions.EnableLinking in its [D2DCompileOptions] attribute if it doesn't support linking (only D2D1 shaders with no complex inputs can use this option).",
1039+
description: "A D2D1 shader shouldn't use D2D1PixelOptions.TrivialSampling in its [D2DPixelOptions] attribute if it has one or more complex inputs (because trivial sampling shaders can only sample pixels at the same scene coordinate as the output pixel, a shader using this option should only have simple inputs).",
10401040
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
10411041

10421042
/// <summary>

0 commit comments

Comments
 (0)