diff --git a/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md b/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md index 45dfa3bb4..4887a339c 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md @@ -86,3 +86,6 @@ CMPSD2D0076 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github CMPSD2D0077 | ComputeSharp.D2D1.Shaders | Info | [Documentation](https://github.com/Sergio0694/ComputeSharp) CMPSD2D0078 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp) CMPSD2D0079 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp) +CMPSD2D0080 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp) +CMPSD2D0081 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp) +CMPSD2D0082 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp) diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj b/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj index 1644d727d..a17cda56a 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj +++ b/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj @@ -1,6 +1,7 @@ netstandard2.0 + $(DefineConstants);D2D1_GENERATOR diff --git a/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs index 5f1420fa4..bbe3d69c5 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs @@ -209,6 +209,11 @@ static unsafe HlslBytecodeInfo GetInfo(HlslBytecodeInfoKey key, CancellationToke token.ThrowIfCancellationRequested(); + // Check whether double precision operations are required + bool requiresDoublePrecisionSupport = D3DCompiler.IsDoublePrecisionSupportRequired(dxcBlobBytecode.Get()); + + token.ThrowIfCancellationRequested(); + byte* buffer = (byte*)dxcBlobBytecode.Get()->GetBufferPointer(); int length = checked((int)dxcBlobBytecode.Get()->GetBufferSize()); @@ -216,7 +221,7 @@ static unsafe HlslBytecodeInfo GetInfo(HlslBytecodeInfoKey key, CancellationToke ImmutableArray bytecode = Unsafe.As>(ref array); - return new HlslBytecodeInfo.Success(bytecode); + return new HlslBytecodeInfo.Success(bytecode, requiresDoublePrecisionSupport); } catch (Win32Exception e) { @@ -267,5 +272,45 @@ public static void GetInfoDiagnostics( diagnostics.Add(diagnostic); } } + + /// + /// Gets the diagnostics for when double precision support is configured incorrectly. + /// + /// The input instance to process. + /// The source instance. + /// The collection of produced instances. + public static void GetDoublePrecisionSupportDiagnostics( + INamedTypeSymbol structDeclarationSymbol, + HlslBytecodeInfo info, + ImmutableArrayBuilder diagnostics) + { + // If we have no compiled HLSL bytecode, there is nothing more to do + if (info is not HlslBytecodeInfo.Success success) + { + return; + } + + bool hasD2DRequiresDoublePrecisionSupportAttribute = structDeclarationSymbol.TryGetAttributeWithFullyQualifiedMetadataName( + "ComputeSharp.D2D1.D2DRequiresDoublePrecisionSupportAttribute", + out AttributeData? attributeData); + + // Check the two cases where diagnostics are necessary: + // - The shader does not have [D2DRequiresDoublePrecisionSupport], but it needs it + // - The shader has [D2DRequiresDoublePrecisionSupport], but it does not need it + if (!hasD2DRequiresDoublePrecisionSupportAttribute && success.RequiresDoublePrecisionSupport) + { + diagnostics.Add(DiagnosticInfo.Create( + MissingD2DRequiresDoublePrecisionSupportAttribute, + structDeclarationSymbol, + structDeclarationSymbol)); + } + else if (hasD2DRequiresDoublePrecisionSupportAttribute && !success.RequiresDoublePrecisionSupport) + { + diagnostics.Add(DiagnosticInfo.Create( + UnnecessaryD2DRequiresDoublePrecisionSupportAttribute, + attributeData!.GetLocation(), + structDeclarationSymbol)); + } + } } } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs index 619708f19..d46dc2c06 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs @@ -169,6 +169,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Append any diagnostic for the shader compilation HlslBytecode.GetInfoDiagnostics(typeSymbol, hlslInfo, diagnostics); + HlslBytecode.GetDoublePrecisionSupportDiagnostics(typeSymbol, hlslInfo, diagnostics); token.ThrowIfCancellationRequested(); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DRequiresDoublePrecisionSupportAnalyzer.cs b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DRequiresDoublePrecisionSupportAnalyzer.cs new file mode 100644 index 000000000..fdfe86d19 --- /dev/null +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DRequiresDoublePrecisionSupportAnalyzer.cs @@ -0,0 +1,66 @@ +using System.Collections.Immutable; +using ComputeSharp.SourceGeneration.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors; + +namespace ComputeSharp.D2D1.SourceGenerators; + +/// +/// A diagnostic analyzer that generates diagnostics when [D2DRequiresDoublePrecisionSupport] is used incorrectly. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidD2DRequiresDoublePrecisionSupportAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidD2DRequiresDoublePrecisionSupportAttribute); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the [D2DRequiresDoublePrecisionSupport], [D2DShaderProfile] and [D2DGeneratedPixelShaderDescriptor] symbols + if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DRequiresDoublePrecisionSupportAttribute") is not { } d2DRequiresDoublePrecisionSupportAttributeSymbol || + context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DShaderProfileAttribute") is not { } d2DShaderProfileAttributeSymbol || + context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DGeneratedPixelShaderDescriptorAttribute") is not { } d2DGeneratedPixelShaderDescriptorAttributeSymbol) + { + return; + } + + context.RegisterSymbolAction(context => + { + // Only struct types are possible targets + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Struct } typeSymbol) + { + return; + } + + // Skip the type if it's not a generated shader descriptor target + if (!typeSymbol.HasAttributeWithType(d2DGeneratedPixelShaderDescriptorAttributeSymbol)) + { + return; + } + + // If the shader is precompiled, there is nothing left to do + if (typeSymbol.HasAttributeWithType(d2DShaderProfileAttributeSymbol) || + typeSymbol.ContainingAssembly.HasAttributeWithType(d2DShaderProfileAttributeSymbol)) + { + return; + } + + // Emit a diagnostic if the type is not precompiled and has [D2DRequiresDoublePrecisionSupport] + if (typeSymbol.TryGetAttributeWithType(d2DRequiresDoublePrecisionSupportAttributeSymbol, out AttributeData? attributeData)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidD2DRequiresDoublePrecisionSupportAttribute, + attributeData.GetLocation(), + typeSymbol)); + } + }, SymbolKind.NamedType); + }); + } +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index e76019d23..9d9f0d367 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -1190,4 +1190,52 @@ partial class DiagnosticDescriptors description: "If an assembly is using [D2DShaderProfile] (meaning that all shaders declared within it will be precompiled), also using [D2DEnableRuntimeCompilation] is unnecessary.", helpLinkUri: "https://github.com/Sergio0694/ComputeSharp", customTags: WellKnownDiagnosticTags.CompilationEnd); + + /// + /// Gets a for a shader missing [D2DRequiresDoublePrecisionSupport]. + /// + /// Format: "The shader {0} requires double precision support, but it does not have the [D2DRequiresDoublePrecisionSupport] attribute on it (adding the attribute is necessary to explicitly opt-in into that functionality)". + /// + /// + public static readonly DiagnosticDescriptor MissingD2DRequiresDoublePrecisionSupportAttribute = new( + id: "CMPSD2D0080", + title: "Missing [D2DRequiresDoublePrecisionSupport] attribute", + messageFormat: "The shader {0} requires double precision support, but it does not have the [D2DRequiresDoublePrecisionSupport] attribute on it (adding the attribute is necessary to explicitly opt-in into that functionality)", + category: "ComputeSharp.D2D1.Shaders", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Shaders performing double precision operations must be annotated with [D2DRequiresDoublePrecisionSupport] to explicitly opt-in into that functionality.", + helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); + + /// + /// Gets a for a shader is unnecessarily using [UnnecessaryD2DRequiresDoublePrecisionSupportAttribute]. + /// + /// Format: "The shader {0} does not require double precision support, but it has the [D2DRequiresDoublePrecisionSupport] attribute on it (using the attribute is not needed if the shader is not performing any double precision operations)". + /// + /// + public static readonly DiagnosticDescriptor UnnecessaryD2DRequiresDoublePrecisionSupportAttribute = new( + id: "CMPSD2D0081", + title: "Unnecessary [D2DRequiresDoublePrecisionSupport] attribute", + messageFormat: "The shader {0} does not require double precision support, but it has the [D2DRequiresDoublePrecisionSupport] attribute on it (using the attribute is not needed if the shader is not performing any double precision operations)", + category: "ComputeSharp.D2D1.Shaders", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Shaders not performing any double precision operations should not be annotated with [D2DRequiresDoublePrecisionSupport], as the attribute is not needed in that case.", + helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); + + /// + /// Gets a for a shader is using [UnnecessaryD2DRequiresDoublePrecisionSupportAttribute] incorrectly. + /// + /// Format: "The shader {0} has the [D2DRequiresDoublePrecisionSupport] attribute on it, but it is not precompiled, so validation for use of double precision operations cannot be performed". + /// + /// + public static readonly DiagnosticDescriptor InvalidD2DRequiresDoublePrecisionSupportAttribute = new( + id: "CMPSD2D0082", + title: "Invalid [D2DRequiresDoublePrecisionSupport] attribute", + messageFormat: "The shader {0} has the [D2DRequiresDoublePrecisionSupport] attribute on it, but it is not precompiled, so validation for use of double precision operations cannot be performed", + category: "ComputeSharp.D2D1.Shaders", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Shaders can only be annotated with [D2DRequiresDoublePrecisionSupport] to perform validation for use of double precision operations if they are precompiled.", + helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Fxc/D3DCompiler.cs b/src/ComputeSharp.D2D1.SourceGenerators/Fxc/D3DCompiler.cs new file mode 100644 index 000000000..41d105cac --- /dev/null +++ b/src/ComputeSharp.D2D1.SourceGenerators/Fxc/D3DCompiler.cs @@ -0,0 +1,33 @@ +using System; +using Windows.Win32; +using Windows.Win32.Graphics.Direct3D; +using Windows.Win32.Graphics.Direct3D11; +using DirectX = Windows.Win32.PInvoke; + +namespace ComputeSharp.D2D1.Shaders.Translation; + +/// +partial class D3DCompiler +{ + /// + /// Checks whether double precision support is required. + /// + /// The input HLSL bytecode to inspect. + /// Whether double precision support is required for . + public static unsafe bool IsDoublePrecisionSupportRequired(ID3DBlob* d3DBlob) + { + using ComPtr d3D11ShaderReflection = default; + + Guid iidOfID3D11ShaderReflection = ID3D11ShaderReflection.IID_Guid; + + DirectX.D3DReflect( + pSrcData: d3DBlob->GetBufferPointer(), + SrcDataSize: d3DBlob->GetBufferSize(), + pInterface: &iidOfID3D11ShaderReflection, + ppReflector: (void**)d3D11ShaderReflection.GetAddressOf()).Assert(); + + ulong doublePrecisionFlags = DirectX.D3D_SHADER_REQUIRES_DOUBLES | DirectX.D3D_SHADER_REQUIRES_11_1_DOUBLE_EXTENSIONS; + + return (d3D11ShaderReflection.Get()->GetRequiresFlags() & doublePrecisionFlags) != 0; + } +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/NativeMethods.txt b/src/ComputeSharp.D2D1.SourceGenerators/NativeMethods.txt index a1c61091e..eb4271bfe 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/NativeMethods.txt +++ b/src/ComputeSharp.D2D1.SourceGenerators/NativeMethods.txt @@ -1,4 +1,5 @@ D3DCompile +D3DReflect D3DSetBlobPart D3DStripShader D2D1_BUFFER_PRECISION @@ -21,6 +22,9 @@ D3DCOMPILE_OPTIMIZATION_LEVEL2 D3DCOMPILE_OPTIMIZATION_LEVEL3 D3DCOMPILE_WARNINGS_ARE_ERRORS D3DCOMPILER_STRIP_FLAGS +D3D_SHADER_REQUIRES_DOUBLES +D3D_SHADER_REQUIRES_11_1_DOUBLE_EXTENSIONS +ID3D11ShaderReflection IUnknown S_OK S_FALSE \ No newline at end of file diff --git a/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs b/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs index 9bd4c07f1..bf27291f2 100644 --- a/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs +++ b/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs @@ -24,12 +24,12 @@ namespace ComputeSharp.D2D1; /// This attribute can also be added to a whole assembly, and will be used by default if not overridden by a shader type. /// /// -/// The compiler options to use to compile the shader. +/// The compile options to use to compile the shader. [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Assembly | AttributeTargets.Method, AllowMultiple = false)] public sealed class D2DCompileOptionsAttribute(D2D1CompileOptions options) : Attribute { /// - /// Gets the number of threads in each thread group for the X axis + /// Gets the compile options to use to compile the shader. /// public D2D1CompileOptions Options { get; } = options; } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1/Attributes/D2DRequiresDoublePrecisionSupportAttribute.cs b/src/ComputeSharp.D2D1/Attributes/D2DRequiresDoublePrecisionSupportAttribute.cs new file mode 100644 index 000000000..dde64edd1 --- /dev/null +++ b/src/ComputeSharp.D2D1/Attributes/D2DRequiresDoublePrecisionSupportAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace ComputeSharp.D2D1; + +/// +/// An attribute for a D2D1 shader indicating that the shader requires support for double precision operations. +/// +/// +/// +/// This attribute does not map to any HLSL feature or compile option directly, but it is needed to make using double +/// precision operations opt-in. It is easy to accidentally introduce them in a shader when that is not intented, and +/// doing so can make the shader run slower and not being compatible with some GPUs (eg. many arm64 GPUs lack support +/// for double precision operations). To avoid this, support is disabled by default, and it is necessary to add this +/// attribute over a shader type to explicitly allow using these operations. +/// +/// +/// Validation can only be performed when the shader is being precompiled. If that is not the case (ie. if the shader +/// is using and not ), then +/// no build time check for double precision operations can be done. Using this attribute in that scenarios is not valid. +/// +/// +[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] +public sealed class D2DRequiresDoublePrecisionSupportAttribute : Attribute; \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration.Hlsl/Models/HlslBytecodeInfo.cs b/src/ComputeSharp.SourceGeneration.Hlsl/Models/HlslBytecodeInfo.cs index cb8b9bba2..96a5601b5 100644 --- a/src/ComputeSharp.SourceGeneration.Hlsl/Models/HlslBytecodeInfo.cs +++ b/src/ComputeSharp.SourceGeneration.Hlsl/Models/HlslBytecodeInfo.cs @@ -11,7 +11,17 @@ internal abstract record HlslBytecodeInfo /// A successfully compiled HLSL shader. /// /// The resulting HLSL bytecode. - public sealed record Success(EquatableArray Bytecode) : HlslBytecodeInfo; + public sealed record Success( +#if D3D12_GENERATOR + EquatableArray Bytecode) +#else + +#pragma warning disable CS1573 + EquatableArray Bytecode, + bool RequiresDoublePrecisionSupport) // Whether the shader requires support for double precision operations. +#pragma warning restore CS1573 +#endif + : HlslBytecodeInfo; /// /// An HLSL shader that failed to compile due to a Win32 error. diff --git a/src/ComputeSharp.SourceGeneration/Models/DiagnosticInfo.cs b/src/ComputeSharp.SourceGeneration/Models/DiagnosticInfo.cs index de3ae0ed6..72970bd54 100644 --- a/src/ComputeSharp.SourceGeneration/Models/DiagnosticInfo.cs +++ b/src/ComputeSharp.SourceGeneration/Models/DiagnosticInfo.cs @@ -33,6 +33,18 @@ public Diagnostic ToDiagnostic() return Diagnostic.Create(Descriptor, null, Arguments.ToArray()); } + /// + /// Creates a new instance with the specified parameters. + /// + /// The input for the diagnostics to create. + /// The location to use for the diagnostic. + /// The optional arguments for the formatted message to include. + /// A new instance with the specified parameters. + public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location? location, params object[] args) + { + return new(descriptor, location?.SourceTree, location?.SourceSpan ?? default, args.Select(static arg => arg.ToString()).ToImmutableArray()); + } + /// /// Creates a new instance with the specified parameters. /// @@ -42,9 +54,7 @@ public Diagnostic ToDiagnostic() /// A new instance with the specified parameters. public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, ISymbol symbol, params object[] args) { - Location location = symbol.Locations.First(); - - return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray()); + return Create(descriptor, symbol.Locations.First(), args); } /// @@ -56,8 +66,6 @@ public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, ISymbol sym /// A new instance with the specified parameters. public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, SyntaxNode node, params object[] args) { - Location location = node.GetLocation(); - - return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray()); + return Create(descriptor, node.GetLocation(), args); } } \ No newline at end of file diff --git a/src/ComputeSharp.SourceGenerators/ComputeSharp.SourceGenerators.csproj b/src/ComputeSharp.SourceGenerators/ComputeSharp.SourceGenerators.csproj index 330353f5b..c6ecff102 100644 --- a/src/ComputeSharp.SourceGenerators/ComputeSharp.SourceGenerators.csproj +++ b/src/ComputeSharp.SourceGenerators/ComputeSharp.SourceGenerators.csproj @@ -1,6 +1,7 @@ netstandard2.0 + $(DefineConstants);D3D12_GENERATOR diff --git a/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_Analyzers.cs b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_Analyzers.cs index cc4e3c5fb..903e6549c 100644 --- a/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_Analyzers.cs +++ b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_Analyzers.cs @@ -346,4 +346,73 @@ public async Task UnnecessaryD2DEnableRuntimeCompilation_OnAssembly_Warns() await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); } + + [TestMethod] + public async Task ShaderWithPrecompiledBytecode_FromAssembly_WithD2DRequiresDoublePrecisionSupport_DoesNotWarn() + { + const string source = """ + using ComputeSharp; + using ComputeSharp.D2D1; + + [assembly: D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] + + [D2DInputCount(0)] + [D2DRequiresDoublePrecisionSupport] + [D2DGeneratedPixelShaderDescriptor] + internal partial struct MyType : ID2D1PixelShader + { + public Float4 Execute() + { + return 0; + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); + } + + [TestMethod] + public async Task ShaderWithPrecompiledBytecode_FromType_WithD2DRequiresDoublePrecisionSupport_DoesNotWarn() + { + const string source = """ + using ComputeSharp; + using ComputeSharp.D2D1; + + [D2DInputCount(0)] + [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] + [D2DRequiresDoublePrecisionSupport] + [D2DGeneratedPixelShaderDescriptor] + internal partial struct MyType : ID2D1PixelShader + { + public Float4 Execute() + { + return 0; + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); + } + + [TestMethod] + public async Task ShaderWithNoPrecompiledBytecode_WithD2DRequiresDoublePrecisionSupport_Warns() + { + const string source = """ + using ComputeSharp; + using ComputeSharp.D2D1; + + [D2DInputCount(0)] + [{|CMPSD2D0082:D2DRequiresDoublePrecisionSupport|}] + [D2DGeneratedPixelShaderDescriptor] + internal partial struct MyType : ID2D1PixelShader + { + public Float4 Execute() + { + return 0; + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); + } } \ No newline at end of file diff --git a/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator.cs b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator.cs index d9d604118..ef7fdf3c3 100644 --- a/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator.cs +++ b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator.cs @@ -1817,6 +1817,7 @@ private static async Task VerifyGeneratedDiagnosticsAsync(string source, (string await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync(source); diff --git a/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator_Diagnostics.cs b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator_Diagnostics.cs new file mode 100644 index 000000000..4a6169d88 --- /dev/null +++ b/tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderSourceGenerator_Diagnostics.cs @@ -0,0 +1,121 @@ +extern alias Core; +extern alias D2D1; + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Basic.Reference.Assemblies; +using ComputeSharp.D2D1.SourceGenerators; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ComputeSharp.D2D1.Tests.SourceGenerators; + +[TestClass] +public class Test_D2DPixelShaderSourceGenerator_DIagnostics +{ + [TestMethod] + public void MissingD2DRequiresDoublePrecisionSupportAttribute() + { + const string source = """ + using ComputeSharp; + using ComputeSharp.D2D1; + using float4 = global::ComputeSharp.Float4; + + namespace MyNamespace; + + [D2DInputCount(0)] + [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] + [D2DGeneratedPixelShaderDescriptor] + internal readonly partial struct MyShader : ID2D1PixelShader + { + private readonly float time; + + public float4 Execute() + { + return (float)(time * 2.0); + } + } + """; + + VerifyGeneratedDiagnostics(source, "CMPSD2D0080"); + } + + [TestMethod] + public void UnnecessaryD2DRequiresDoublePrecisionSupportAttribute() + { + const string source = """ + using ComputeSharp; + using ComputeSharp.D2D1; + using float4 = global::ComputeSharp.Float4; + + namespace MyNamespace; + + [D2DInputCount(0)] + [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] + [D2DRequiresDoublePrecisionSupport] + [D2DGeneratedPixelShaderDescriptor] + internal readonly partial struct MyShader : ID2D1PixelShader + { + private readonly float time; + + public float4 Execute() + { + return (float)(time * 2.0f); + } + } + """; + + VerifyGeneratedDiagnostics(source, "CMPSD2D0081"); + } + + /// + /// Verifies the output of a source generator. + /// + /// The input source to process. + /// The expected diagnostics ids to be generated. + /// The task for the operation. + private static void VerifyGeneratedDiagnostics(string source, params string[] diagnosticsIds) + { + // Get all assembly references for the .NET TFM and ComputeSharp + IEnumerable metadataReferences = + [ + .. Net80.References.All, + MetadataReference.CreateFromFile(typeof(Core::ComputeSharp.Hlsl).Assembly.Location), + MetadataReference.CreateFromFile(typeof(D2D1::ComputeSharp.D2D1.ID2D1PixelShader).Assembly.Location) + ]; + + // Parse the source text (C# 12) + SyntaxTree sourceTree = CSharpSyntaxTree.ParseText( + source, + CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12)); + + // Create the original compilation + CSharpCompilation compilation = CSharpCompilation.Create( + "original", + [sourceTree], + metadataReferences, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); + + // Create the generator driver with the D2D shader generator + GeneratorDriver driver = CSharpGeneratorDriver.Create(new D2DPixelShaderDescriptorGenerator()).WithUpdatedParseOptions((CSharpParseOptions)sourceTree.Options); + + // Run all source generators on the input source code + _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics); + + Dictionary diagnosticMap = diagnostics.DistinctBy(diagnostic => diagnostic.Id).ToDictionary(diagnostic => diagnostic.Id); + + // Check that the diagnostics match + Assert.IsTrue(diagnosticMap.Keys.ToHashSet().SetEquals(diagnosticsIds), $"Diagnostics didn't match. {string.Join(", ", diagnosticMap.Values)}"); + + // If the compilation was supposed to succeed, ensure that no further errors were generated + if (diagnosticsIds.Length == 0) + { + // Compute diagnostics for the final compiled output (just include errors) + List outputCompilationDiagnostics = outputCompilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToList(); + + Assert.IsTrue(outputCompilationDiagnostics.Count == 0, $"resultingIds: {string.Join(", ", outputCompilationDiagnostics)}"); + } + } +} \ No newline at end of file diff --git a/tests/ComputeSharp.D2D1.Tests/D2D1ReflectionServicesTests.cs b/tests/ComputeSharp.D2D1.Tests/D2D1ReflectionServicesTests.cs index d9ce6d074..99dc004da 100644 --- a/tests/ComputeSharp.D2D1.Tests/D2D1ReflectionServicesTests.cs +++ b/tests/ComputeSharp.D2D1.Tests/D2D1ReflectionServicesTests.cs @@ -125,6 +125,7 @@ public void GetShaderInfoWithDoublePrecisionFeature() } [D2DInputCount(1)] + [D2DRequiresDoublePrecisionSupport] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] [D2DGeneratedPixelShaderDescriptor] [AutoConstructor]