diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 72d9f9ea8..3fcb049c0 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -118,6 +118,11 @@ jobs: - name: Run ComputeSharp.D2D1.Tests.AssemblyLevelAttributes run: dotnet test tests\ComputeSharp.D2D1.Tests.AssemblyLevelAttributes\ComputeSharp.D2D1.Tests.AssemblyLevelAttributes.csproj -c Release -f ${{matrix.framework}} -v n -l "console;verbosity=detailed" + # Run the D2D1 source generators tests as well (only on .NET 6, like for the DX12 ones) + - if: matrix.framework == 'net6.0' + name: Run ComputeSharp.D2D1.Tests.SourceGenerators + run: dotnet test tests\ComputeSharp.D2D1.Tests.SourceGenerators\ComputeSharp.D2D1.Tests.SourceGenerators.csproj -v n -l "console;verbosity=detailed" + # Run all unit tests using D3D12MA run-tests-d3d12ma: needs: [build-solution] diff --git a/ComputeSharp.sln b/ComputeSharp.sln index a6a67a140..5094cf24c 100644 --- a/ComputeSharp.sln +++ b/ComputeSharp.sln @@ -156,7 +156,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComputeSharp.D2D1.Uwp.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComputeSharp.D2D1.WinUI.Tests", "tests\ComputeSharp.D2D1.WinUI.Tests\ComputeSharp.D2D1.WinUI.Tests.csproj", "{73C32D0F-64DB-4674-84E9-8FCC41228474}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComputeSharp.D3D12MemoryAllocator", "src\ComputeSharp.D3D12MemoryAllocator\ComputeSharp.D3D12MemoryAllocator.csproj", "{E017B3B6-C7D3-4E53-A7F6-1036178E98C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComputeSharp.D3D12MemoryAllocator", "src\ComputeSharp.D3D12MemoryAllocator\ComputeSharp.D3D12MemoryAllocator.csproj", "{E017B3B6-C7D3-4E53-A7F6-1036178E98C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComputeSharp.D2D1.CodeFixers", "src\ComputeSharp.D2D1.CodeFixers\ComputeSharp.D2D1.CodeFixers.csproj", "{54654960-54B5-4E19-B3ED-993591CA39E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ComputeSharp.D2D1.Tests.SourceGenerators", "tests\ComputeSharp.D2D1.Tests.SourceGenerators\ComputeSharp.D2D1.Tests.SourceGenerators.csproj", "{59A17380-24A0-4BD7-9012-B105A9E7D46F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -454,6 +458,22 @@ Global {E017B3B6-C7D3-4E53-A7F6-1036178E98C3}.Release|ARM64.Build.0 = Release|Any CPU {E017B3B6-C7D3-4E53-A7F6-1036178E98C3}.Release|x64.ActiveCfg = Release|Any CPU {E017B3B6-C7D3-4E53-A7F6-1036178E98C3}.Release|x64.Build.0 = Release|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Debug|ARM64.Build.0 = Debug|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Debug|x64.ActiveCfg = Debug|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Debug|x64.Build.0 = Debug|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Release|ARM64.ActiveCfg = Release|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Release|ARM64.Build.0 = Release|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Release|x64.ActiveCfg = Release|Any CPU + {54654960-54B5-4E19-B3ED-993591CA39E5}.Release|x64.Build.0 = Release|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Debug|ARM64.Build.0 = Debug|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Debug|x64.ActiveCfg = Debug|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Debug|x64.Build.0 = Debug|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Release|ARM64.ActiveCfg = Release|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Release|ARM64.Build.0 = Release|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Release|x64.ActiveCfg = Release|Any CPU + {59A17380-24A0-4BD7-9012-B105A9E7D46F}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -494,6 +514,7 @@ Global {346AEB10-A4EA-427E-8578-F60915DB3053} = {F8EFBB27-4EE2-4463-A75B-7EFDFB55D0F7} {4D4BB2F6-5653-4DB5-A8DD-90D58D8FE4D3} = {F8EFBB27-4EE2-4463-A75B-7EFDFB55D0F7} {73C32D0F-64DB-4674-84E9-8FCC41228474} = {F8EFBB27-4EE2-4463-A75B-7EFDFB55D0F7} + {59A17380-24A0-4BD7-9012-B105A9E7D46F} = {F8EFBB27-4EE2-4463-A75B-7EFDFB55D0F7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4664C5E3-0340-4E22-BCFD-98AAEDF5F2DC} diff --git a/build/Directory.Build.props b/build/Directory.Build.props index a9bcf4a60..e5dcc07eb 100644 --- a/build/Directory.Build.props +++ b/build/Directory.Build.props @@ -142,8 +142,12 @@ $(ProjectDirectoryPathFromSourceRoot.StartsWith('src\')) false true + false + true + false + true false - true + true $(ProjectDirectoryPathFromSourceRoot.StartsWith('tests\')) false true diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ColorfulInfinity.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ColorfulInfinity.cs index 94ac59d22..846712197 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ColorfulInfinity.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ColorfulInfinity.cs @@ -11,6 +11,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct ColorfulInfinity : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ContouredLayers.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ContouredLayers.cs index 63b01ac66..d2cf799e6 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ContouredLayers.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ContouredLayers.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct ContouredLayers : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/FractalTiling.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/FractalTiling.cs index db30a226e..9b3e49957 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/FractalTiling.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/FractalTiling.cs @@ -11,6 +11,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct FractalTiling : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/HelloWorld.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/HelloWorld.cs index 497277206..f69707050 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/HelloWorld.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/HelloWorld.cs @@ -9,6 +9,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct HelloWorld : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/MengerJourney.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/MengerJourney.cs index ff9758216..ef1a0d13c 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/MengerJourney.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/MengerJourney.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct MengerJourney : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/Octagrams.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/Octagrams.cs index aa16a6bba..2e83fe790 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/Octagrams.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/Octagrams.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct Octagrams : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ProteanClouds.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ProteanClouds.cs index 762bf8106..00527c5c0 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ProteanClouds.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/ProteanClouds.cs @@ -11,6 +11,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct ProteanClouds : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/PyramidPattern.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/PyramidPattern.cs index a272f95ce..93bff4b01 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/PyramidPattern.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/PyramidPattern.cs @@ -11,6 +11,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct PyramidPattern : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TerracedHills.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TerracedHills.cs index 6669651c7..b45fe7c17 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TerracedHills.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TerracedHills.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct TerracedHills : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TriangleGridContouring.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TriangleGridContouring.cs index 970bd7757..f2d3b62a8 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TriangleGridContouring.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TriangleGridContouring.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct TriangleGridContouring : ID2D1PixelShader { diff --git a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TwoTiledTruchet.cs b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TwoTiledTruchet.cs index 00c763740..4e61a9a15 100644 --- a/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TwoTiledTruchet.cs +++ b/samples/ComputeSharp.SwapChain.Shaders.D2D1.Shared/TwoTiledTruchet.cs @@ -10,6 +10,7 @@ namespace ComputeSharp.SwapChain.Shaders.D2D1; [D2DInputCount(0)] [D2DRequiresScenePosition] [D2DShaderProfile(D2D1ShaderProfile.PixelShader50)] +[D2DGeneratedPixelShaderDescriptor] [AutoConstructor] internal readonly partial struct TwoTiledTruchet : ID2D1PixelShader { diff --git a/src/ComputeSharp.D2D1.CodeFixers/ComputeSharp.D2D1.CodeFixers.csproj b/src/ComputeSharp.D2D1.CodeFixers/ComputeSharp.D2D1.CodeFixers.csproj new file mode 100644 index 000000000..41ca05c4e --- /dev/null +++ b/src/ComputeSharp.D2D1.CodeFixers/ComputeSharp.D2D1.CodeFixers.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + + + + + + + + + + diff --git a/src/ComputeSharp.D2D1.CodeFixers/MissingPixelShaderDescriptorOnPixelShaderCodeFixer.cs b/src/ComputeSharp.D2D1.CodeFixers/MissingPixelShaderDescriptorOnPixelShaderCodeFixer.cs new file mode 100644 index 000000000..bddd91ada --- /dev/null +++ b/src/ComputeSharp.D2D1.CodeFixers/MissingPixelShaderDescriptorOnPixelShaderCodeFixer.cs @@ -0,0 +1,208 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using ComputeSharp.SourceGeneration.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors; + +namespace ComputeSharp.D2D1.CodeFixers; + +/// +/// A code fixer that adds the [D2DGeneratedPixelShaderDescriptor] to D2D1 shader types with no descriptor. +/// +[ExportCodeFixProvider(LanguageNames.CSharp)] +[Shared] +public sealed class MissingPixelShaderDescriptorOnPixelShaderCodeFixer : CodeFixProvider +{ + /// + /// The set of type names for all D2D attributes that can be over shader types. + /// + private static readonly ImmutableArray D2DAttributeTypeNames = ImmutableArray.Create( + "ComputeSharp.D2D1.D2DCompileOptionsAttribute", + "ComputeSharp.D2D1.D2DEffectAuthorAttribute", + "ComputeSharp.D2D1.D2DEffectCategoryAttribute", + "ComputeSharp.D2D1.D2DEffectDescriptionAttribute", + "ComputeSharp.D2D1.D2DEffectDisplayNameAttribute", + "ComputeSharp.D2D1.D2DEffectIdAttribute", + "ComputeSharp.D2D1.D2DInputComplexAttribute", + "ComputeSharp.D2D1.D2DInputCountAttribute", + "ComputeSharp.D2D1.D2DInputDescriptionAttribute", + "ComputeSharp.D2D1.D2DInputSimpleAttribute", + "ComputeSharp.D2D1.D2DOutputBufferAttribute", + "ComputeSharp.D2D1.D2DPixelOptionsAttribute", + "ComputeSharp.D2D1.D2DRequiresScenePositionAttribute", + "ComputeSharp.D2D1.D2DShaderProfileAttribute"); + + /// + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(MissingPixelShaderDescriptorOnPixelShaderTypeId); + + /// + public override Microsoft.CodeAnalysis.CodeFixes.FixAllProvider? GetFixAllProvider() + { + return new FixAllProvider(); + } + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Diagnostic diagnostic = context.Diagnostics[0]; + TextSpan diagnosticSpan = context.Span; + + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // Get the struct declaration from the target diagnostic + if (root?.FindNode(diagnosticSpan) is StructDeclarationSyntax structDeclaration) + { + // Register the code fix to update the return type to be Task instead + context.RegisterCodeFix( + CodeAction.Create( + title: "Add [D2DGeneratedPixelShaderDescriptor] attribute", + createChangedDocument: token => AddMissingD2DGeneratedPixelShaderDescriptorAttribute(context.Document, root, structDeclaration, token), + equivalenceKey: "Add [D2DGeneratedPixelShaderDescriptor] attribute"), + diagnostic); + } + } + + /// + /// Applies the code fix to add the [D2DGeneratedPixelShaderDescriptor] attribute to a target type. + /// + /// The original document being fixed. + /// The original tree root belonging to the current document. + /// The to update. + /// The cancellation token for the operation. + /// An updated document with the applied code fix, and the return type of the method being . + private static async Task AddMissingD2DGeneratedPixelShaderDescriptorAttribute( + Document document, + SyntaxNode root, + StructDeclarationSyntax structDeclaration, + CancellationToken cancellationToken) + { + // Get the new struct declaration + SyntaxNode updatedStructDeclaration = await AddMissingD2DGeneratedPixelShaderDescriptorAttribute( + document, + structDeclaration, + cancellationToken); + + // Replace the node in the document tree + return document.WithSyntaxRoot(root.ReplaceNode(structDeclaration, updatedStructDeclaration)); + } + + /// + /// Applies the code fix to add the [D2DGeneratedPixelShaderDescriptor] attribute to a target type. + /// + /// The original document being fixed. + /// The to update. + /// The cancellation token for the operation. + /// An updated document with the applied code fix, and the return type of the method being . + private static async Task AddMissingD2DGeneratedPixelShaderDescriptorAttribute( + Document document, + StructDeclarationSyntax structDeclaration, + CancellationToken cancellationToken) + { + // Get the semantic model (bail if it's not available) + if (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false) is not SemanticModel semanticModel) + { + return structDeclaration; + } + + // Build the map of D2D attributes to look for + if (!semanticModel.Compilation.TryBuildNamedTypeSymbolSet(D2DAttributeTypeNames, out ImmutableHashSet? d2DAttributeTypeSymbols)) + { + return structDeclaration; + } + + // Also bail if we can't resolve the [D2DGeneratedPixelShaderDescriptor] attribute symbol (this should really never happen) + if (semanticModel.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DGeneratedPixelShaderDescriptorAttribute") is not INamedTypeSymbol attributeSymbol) + { + return structDeclaration; + } + + int index = 0; + + // Find the index to use to insert the attribute. We want to make it so that if the struct declaration + // has a bunch of D2D attributes, the new one will be inserted right after that. This way the final list + // will be nicely sorted, instead of having D2D attributes interleaving other unrelated attributes, if any. + foreach (AttributeListSyntax attributeList in structDeclaration.AttributeLists) + { + // Make sure we have an attribute to check + if (attributeList.Attributes is not [AttributeSyntax attribute, ..]) + { + continue; + } + + // Resolve the symbol for the attribute (stop here if this failed for whatever reason) + if (!semanticModel.GetSymbolInfo(attribute, cancellationToken).TryGetAttributeTypeSymbol(out INamedTypeSymbol? attributeTypeSymbol)) + { + break; + } + + // If the attribute is D2D one, increment the index and continue + if (d2DAttributeTypeSymbols.Contains(attributeTypeSymbol)) + { + index++; + } + else + { + // Otherwise, stop here, we reached the end of the sequence + break; + } + } + + SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(document); + + // Create the attribute syntax for the new attribute. Also annotate it + // to automatically add using directives to the document, if needed. + // Then create the attribute syntax and insert it at the right position. + SyntaxNode attributeTypeSyntax = syntaxGenerator.TypeExpression(attributeSymbol).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation); + SyntaxNode attributeSyntax = syntaxGenerator.Attribute(attributeTypeSyntax); + SyntaxNode updatedStructDeclarationSyntax = syntaxGenerator.InsertAttributes(structDeclaration, index, attributeSyntax); + + // Replace the node in the syntax tree + return updatedStructDeclarationSyntax; + } + + /// + /// A custom with the logic from . + /// + private sealed class FixAllProvider : DocumentBasedFixAllProvider + { + /// + protected override async Task FixAllAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + { + // Get the document root (this should always succeed) + if (await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false) is not SyntaxNode root) + { + return document; + } + + SyntaxEditor syntaxEditor = new(root, fixAllContext.Solution.Services); + + foreach (Diagnostic diagnostic in diagnostics) + { + // Get the current struct declaration for the diagnostic + if (root.FindNode(diagnostic.Location.SourceSpan) is not StructDeclarationSyntax structDeclaration) + { + continue; + } + + // Get the syntax node with the updated declaration + SyntaxNode updatedStructDeclaration = await AddMissingD2DGeneratedPixelShaderDescriptorAttribute( + document, + structDeclaration, + fixAllContext.CancellationToken); + + // Replace the node via the editor + syntaxEditor.ReplaceNode(structDeclaration, updatedStructDeclaration); + } + + return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); + } + } +} diff --git a/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md b/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md index 7a211015b..53d2153b9 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/src/ComputeSharp.D2D1.SourceGenerators/AnalyzerReleases.Shipped.md @@ -71,3 +71,5 @@ CMPSD2D0061 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github CMPSD2D0062 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp) CMPSD2D0063 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp) CMPSD2D0064 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp) +CMPSD2D0065 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp) +CMPSD2D0066 | ComputeSharp.D2D1.Shaders | Error | [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 8240a9ff3..934ad6442 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj +++ b/src/ComputeSharp.D2D1.SourceGenerators/ComputeSharp.D2D1.SourceGenerators.csproj @@ -108,4 +108,9 @@ + + + + + diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateCreateFromConstantBufferMethod.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.CreateFromConstantBuffer.Syntax.cs similarity index 97% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateCreateFromConstantBufferMethod.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.CreateFromConstantBuffer.Syntax.cs index aa1be8d8b..333864120 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateCreateFromConstantBufferMethod.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.CreateFromConstantBuffer.Syntax.cs @@ -6,7 +6,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class CreateFromConstantBuffer @@ -21,7 +21,7 @@ public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) string typeName = info.Hierarchy.Hierarchy[0].QualifiedName; writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine("[global::System.Runtime.CompilerServices.SkipLocalsInit]"); writer.WriteLine($"readonly unsafe {typeName} global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{typeName}>.CreateFromConstantBuffer(global::System.ReadOnlySpan data)"); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.Syntax.cs similarity index 97% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.Syntax.cs index 1a783458f..7d4f82843 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class EffectId @@ -18,7 +18,7 @@ partial class EffectId public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly ref readonly global::System.Guid global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.EffectId"); using (writer.WriteBlock()) diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.cs similarity index 99% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.cs index a38c5f47d..c57150184 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectIdProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectId.cs @@ -11,7 +11,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the EffectId property. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.Syntax.cs similarity index 97% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.Syntax.cs index 860de917c..954b31662 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class EffectMetadata @@ -60,7 +60,7 @@ public static void WriteEffectAuthorSyntax(D2D1ShaderInfo info, IndentedTextWrit private static void WriteEffectMetadataSyntax(string qualifiedName, string propertyName, string? metadataValue, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.Write($"readonly string? global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{qualifiedName}>.{propertyName} => "); // Append null or the metadata value as a string literal diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.cs similarity index 98% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.cs index d2158878d..5ffcb5024 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateEffectMetadataProperties.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.EffectMetadata.cs @@ -4,7 +4,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the effect metadata properties. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.Syntax.cs similarity index 94% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.Syntax.cs index 6c2b84c7c..2f65635cc 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.Syntax.cs @@ -7,7 +7,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class HlslBytecode @@ -20,8 +20,8 @@ partial class HlslBytecode public static void WriteShaderProfileSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); - writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1ShaderProfile global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ShaderProfile => global::ComputeSharp.D2D1.D2D1ShaderProfile.{info.HlslInfoKey.EffectiveShaderProfile};"); + writer.WriteGeneratedAttributes(GeneratorName); + writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1ShaderProfile global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ShaderProfile => global::ComputeSharp.D2D1.D2D1ShaderProfile.{info.HlslInfoKey.ShaderProfile};"); } /// @@ -33,14 +33,14 @@ public static void WriteCompileOptionsSyntax(D2D1ShaderInfo info, IndentedTextWr { // Get a formatted representation of the compile options being used string compileOptionsExpression = - info.HlslInfoKey.EffectiveCompileOptions + info.HlslInfoKey.CompileOptions .ToString() .Split(',') .Select(static name => $"global::ComputeSharp.D2D1.D2D1CompileOptions.{name.Trim()}") .Aggregate("", static (left, right) => left.Length > 0 ? $"{left} | {right}" : right); writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1CompileOptions global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.CompileOptions => {compileOptionsExpression};"); } @@ -52,7 +52,7 @@ public static void WriteCompileOptionsSyntax(D2D1ShaderInfo info, IndentedTextWr public static void WriteHlslBytecodeSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.Write($"readonly global::System.ReadOnlyMemory global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.HlslBytecode => "); // If there is no bytecode, just return a default expression. @@ -98,7 +98,7 @@ static void Callback(D2D1ShaderInfo info, IndentedTextWriter writer) writer.WriteLine($$"""/// """); writer.WriteLine($$"""/// implementation to get the HLSL bytecode."""); writer.WriteLine($$"""/// """); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator), useFullyQualifiedTypeNames: false); + writer.WriteGeneratedAttributes(GeneratorName, useFullyQualifiedTypeNames: false); writer.WriteLine($$"""file sealed class HlslBytecodeMemoryManager : MemoryManager"""); using (writer.WriteBlock()) diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs similarity index 94% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs index fcf764fae..7289f8591 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslBytecodeProperties.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslBytecode.cs @@ -18,7 +18,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the HLSL bytecode properties. @@ -87,9 +87,13 @@ internal static partial class HlslBytecode /// Gets the effective shader profile to use. /// /// The requested shader profile. + /// Whether compilation should be performed with the input profile. /// The effective shader profile. - public static D2D1ShaderProfile GetEffectiveShaderProfile(D2D1ShaderProfile? shaderProfile) + public static D2D1ShaderProfile GetEffectiveShaderProfile(D2D1ShaderProfile? shaderProfile, out bool isCompilationEnabled) { + // Compilation is only enabled if the user explicitly selected a shader profile + isCompilationEnabled = shaderProfile is not null; + // The effective shader profile is either be the requested one, or the default value (which maps to PS5.0) return shaderProfile ?? D2D1ShaderProfile.PixelShader50; } @@ -150,11 +154,10 @@ public static HlslBytecodeInfo GetInfo(ref HlslBytecodeInfoKey key, Cancellation { static unsafe HlslBytecodeInfo GetInfo(HlslBytecodeInfoKey key, CancellationToken token) { - // No embedded shader was requested, or there were some errors earlier in the pipeline. + // Check if the compilation is not enabled (eg. if there's been errors earlier in the pipeline). // In this case, skip the compilation, as diagnostic will be emitted for those anyway. // Compiling would just add overhead and result in more errors, as the HLSL would be invalid. - // We also skip compilation if no shader profile has been requested (we never just assume one). - if (key.HasErrors || key.RequestedShaderProfile is null) + if (!key.IsCompilationEnabled) { return HlslBytecodeInfo.Missing.Instance; } @@ -166,8 +169,8 @@ static unsafe HlslBytecodeInfo GetInfo(HlslBytecodeInfoKey key, CancellationToke // Compile the shader bytecode using the effective parameters using ComPtr dxcBlobBytecode = D3DCompiler.Compile( key.HlslSource.AsSpan(), - key.EffectiveShaderProfile, - key.EffectiveCompileOptions); + key.ShaderProfile, + key.CompileOptions); token.ThrowIfCancellationRequested(); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.Syntax.cs similarity index 91% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.Syntax.cs index b065bca39..b9650bea7 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class HlslSource @@ -18,7 +18,7 @@ partial class HlslSource public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly string global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.HlslSource =>"); writer.IncreaseIndent(); writer.WriteLine("\"\"\""); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.cs similarity index 99% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.cs index 73ece91a3..bf547c42c 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateHlslSourceProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.HlslSource.cs @@ -17,7 +17,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the HlslSource property. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.Syntax.cs similarity index 96% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.Syntax.cs index f7bde651e..c2a6b125e 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.Syntax.cs @@ -7,7 +7,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// private static partial class InputDescriptions @@ -20,7 +20,7 @@ private static partial class InputDescriptions public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.Write($"readonly global::System.ReadOnlyMemory global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.InputDescriptions => "); // If there are no input descriptions, just return a default expression. @@ -66,7 +66,7 @@ static void Callback(D2D1ShaderInfo info, IndentedTextWriter writer) writer.WriteLine($$"""/// """); writer.WriteLine($$"""/// A container type for additional data needed by the shader."""); writer.WriteLine($$"""/// """); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator), useFullyQualifiedTypeNames: false); + writer.WriteGeneratedAttributes(GeneratorName, useFullyQualifiedTypeNames: false); writer.WriteLine($$"""file static class Data"""); using (writer.WriteBlock()) diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.cs similarity index 98% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.cs index dbe31d176..f6786a195 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputDescriptionsProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputDescriptions.cs @@ -10,7 +10,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the InputDescriptions property. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.Syntax.cs similarity index 96% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.Syntax.cs index 4774ef8f1..5c0255fba 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class InputTypes @@ -18,7 +18,7 @@ partial class InputTypes public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.Write($"readonly global::System.ReadOnlyMemory global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.InputTypes => "); // If there are no inputs, simply return a default expression. Otherwise, create @@ -65,7 +65,7 @@ static void Callback(D2D1ShaderInfo info, IndentedTextWriter writer) writer.WriteLine($$"""/// """); writer.WriteLine($$"""/// A implementation to get the input types."""); writer.WriteLine($$"""/// """); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator), useFullyQualifiedTypeNames: false); + writer.WriteGeneratedAttributes(GeneratorName, useFullyQualifiedTypeNames: false); writer.WriteLine($$"""file sealed class InputTypesMemoryManager : MemoryManager"""); using (writer.WriteBlock()) diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.cs similarity index 99% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.cs index 7b2d19a7e..74e902d8f 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateInputTypesProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.InputTypes.cs @@ -9,7 +9,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the InputTypes properties. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.Syntax.cs similarity index 97% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.Syntax.cs index 04fa769a2..a6232d492 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.Syntax.cs @@ -6,7 +6,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class LoadConstantBuffer @@ -21,7 +21,7 @@ public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) string typeName = info.Hierarchy.Hierarchy[0].QualifiedName; writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine("[global::System.Runtime.CompilerServices.SkipLocalsInit]"); writer.WriteLine($"readonly unsafe void global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{typeName}>.LoadConstantBuffer(in {typeName} shader, ref TLoader loader)"); @@ -105,7 +105,7 @@ static void Callback(D2D1ShaderInfo info, IndentedTextWriter writer) writer.WriteLine($$"""/// """); writer.WriteLine($$"""/// A type representing the constant buffer native layout for ."""); writer.WriteLine($$"""/// """); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator), useFullyQualifiedTypeNames: false); + writer.WriteGeneratedAttributes(GeneratorName, useFullyQualifiedTypeNames: false); writer.WriteLine($$"""[StructLayout(LayoutKind.Explicit, Size = {{info.ConstantBufferSizeInBytes}})]"""); writer.WriteLine($$"""file struct ConstantBuffer"""); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.cs similarity index 99% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.cs index dc12107f4..b950c73be 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateLoadConstantBufferMethod.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.LoadConstantBuffer.cs @@ -15,7 +15,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the LoadConstantBuffer method. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateNumericProperties.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.NumericProperties.Syntax.cs similarity index 90% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateNumericProperties.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.NumericProperties.Syntax.cs index 4c9ca529a..a4896594b 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateNumericProperties.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.NumericProperties.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the available numeric properties. @@ -20,7 +20,7 @@ private static partial class NumericProperties public static void WriteConstantBufferSizeSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly int global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ConstantBufferSize => {info.ConstantBufferSizeInBytes};"); } @@ -32,7 +32,7 @@ public static void WriteConstantBufferSizeSyntax(D2D1ShaderInfo info, IndentedTe public static void WriteInputCountSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly int global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.InputCount => {info.InputTypes.Length};"); } @@ -44,7 +44,7 @@ public static void WriteInputCountSyntax(D2D1ShaderInfo info, IndentedTextWriter public static void WriteResourceTextureCountSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly int global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ResourceTextureCount => {info.ResourceTextureDescriptions.Length};"); } } diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.Syntax.cs similarity index 93% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.Syntax.cs index 6d536f8cf..3767bb783 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.Syntax.cs @@ -6,7 +6,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class OutputBuffer @@ -27,7 +27,7 @@ public static void WriteBufferPrecisionSyntax(D2D1ShaderInfo info, IndentedTextW }; writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1BufferPrecision global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.BufferPrecision => {bufferPrecisionExpression};"); } @@ -46,7 +46,7 @@ public static void WriteChannelDepthSyntax(D2D1ShaderInfo info, IndentedTextWrit }; writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1ChannelDepth global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ChannelDepth => {channelDepthExpression};"); } } diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.cs similarity index 98% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.cs index 9c01e195c..aae4ada1b 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateOutputBufferProperties.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.OutputBuffer.cs @@ -4,7 +4,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the output buffer properties. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.Syntax.cs similarity index 92% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.Syntax.cs index a87afa5a2..a469a3421 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.Syntax.cs @@ -5,7 +5,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// partial class PixelOptions @@ -25,7 +25,7 @@ public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) }; writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.WriteLine($"readonly ComputeSharp.D2D1.D2D1PixelOptions global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.PixelOptions => {pixelOptionsExpression};"); } } diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.cs similarity index 95% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.cs index 089078150..5c9743521 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreatePixelOptionsProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.PixelOptions.cs @@ -4,7 +4,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the PixelOptions property. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.Syntax.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.Syntax.cs similarity index 97% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.Syntax.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.Syntax.cs index 94d202a3a..d0bb8a8b5 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.Syntax.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.Syntax.cs @@ -7,7 +7,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// private static partial class ResourceTextureDescriptions @@ -20,7 +20,7 @@ private static partial class ResourceTextureDescriptions public static void WriteSyntax(D2D1ShaderInfo info, IndentedTextWriter writer) { writer.WriteLine("/// "); - writer.WriteGeneratedAttributes(typeof(ID2D1ShaderGenerator)); + writer.WriteGeneratedAttributes(GeneratorName); writer.Write($"readonly global::System.ReadOnlyMemory global::ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor<{info.Hierarchy.Hierarchy[0].QualifiedName}>.ResourceTextureDescriptions => "); // If there are no resource texture descriptions, just return a default expression. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.cs similarity index 99% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.cs index 79dbdce9e..6282b6066 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.CreateResourceTextureDescriptionsProperty.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.ResourceTextureDescriptions.cs @@ -11,7 +11,7 @@ namespace ComputeSharp.D2D1.SourceGenerators; /// -partial class ID2D1ShaderGenerator +partial class D2DPixelShaderDescriptorGenerator { /// /// A helper with all logic to generate the ResourceTextureDescriptions property. diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.cs b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs similarity index 92% rename from src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.cs rename to src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs index 810264f2e..9342c22ec 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/D2DPixelShaderDescriptorGenerator.cs @@ -5,24 +5,29 @@ using ComputeSharp.SourceGeneration.Helpers; using ComputeSharp.SourceGeneration.Models; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace ComputeSharp.D2D1.SourceGenerators; /// -/// A source generator creating data loaders for the type. +/// A source generator creating pixel shader descriptors for annotated D2D1 pixel shader types. /// [Generator(LanguageNames.CSharp)] -public sealed partial class ID2D1ShaderGenerator : IIncrementalGenerator +public sealed partial class D2DPixelShaderDescriptorGenerator : IIncrementalGenerator { + /// + /// The name of generator to include in the generated code. + /// + private const string GeneratorName = "ComputeSharp.D2D1.D2DPixelShaderDescriptorGenerator"; + /// public void Initialize(IncrementalGeneratorInitializationContext context) { // Discover all shader types and extract all the necessary info from each of them IncrementalValuesProvider shaderInfo = context.SyntaxProvider - .CreateSyntaxProvider( + .ForAttributeWithMetadataName( + "ComputeSharp.D2D1.D2DGeneratedPixelShaderDescriptorAttribute", static (node, _) => node.IsTypeDeclarationWithOrPotentiallyWithBaseTypes(), static (context, token) => { @@ -32,16 +37,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return default; } - StructDeclarationSyntax typeDeclaration = (StructDeclarationSyntax)context.Node; - // If the type symbol doesn't have at least one interface, it can't possibly be a shader type - if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration, token) is not INamedTypeSymbol { AllInterfaces.Length: > 0 } typeSymbol) + if (context.TargetSymbol is not INamedTypeSymbol { AllInterfaces.Length: > 0 } typeSymbol) { return default; } + StructDeclarationSyntax typeDeclaration = (StructDeclarationSyntax)context.TargetNode; + // Check that the shader implements the ID2D1PixelShader interface - if (!IsD2D1PixelShaderType(typeSymbol, context.SemanticModel.Compilation)) + if (!typeSymbol.HasInterfaceWithType(context.SemanticModel.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.ID2D1PixelShader")!)) { return default; } @@ -135,9 +140,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) bool isLinkingSupported = HlslBytecode.IsSimpleInputShader(typeSymbol, inputCount); D2D1ShaderProfile? requestedShaderProfile = HlslBytecode.GetRequestedShaderProfile(typeSymbol); D2D1CompileOptions? requestedCompileOptions = HlslBytecode.GetRequestedCompileOptions(diagnostics, typeSymbol); - D2D1ShaderProfile effectiveShaderProfile = HlslBytecode.GetEffectiveShaderProfile(requestedShaderProfile); + D2D1ShaderProfile effectiveShaderProfile = HlslBytecode.GetEffectiveShaderProfile(requestedShaderProfile, out bool isCompilationEnabled); D2D1CompileOptions effectiveCompileOptions = HlslBytecode.GetEffectiveCompileOptions(requestedCompileOptions, isLinkingSupported); - bool hasErrors = diagnostics.Count > 0; token.ThrowIfCancellationRequested(); @@ -145,13 +149,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // This is done last so that it can be skipped if any errors happened before. HlslBytecodeInfoKey hlslInfoKey = new( hlslSource, - requestedShaderProfile, - requestedCompileOptions, effectiveShaderProfile, effectiveCompileOptions, - hasErrors); + isCompilationEnabled); - // TODO: cache this across transform runs + // Get the existing compiled shader, or compile the processed HLSL code HlslBytecodeInfo hlslInfo = HlslBytecode.GetInfo(ref hlslInfoKey, token); token.ThrowIfCancellationRequested(); diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DGeneratedPixelShaderDescriptorAttributeTargetAnalyzer.cs b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DGeneratedPixelShaderDescriptorAttributeTargetAnalyzer.cs new file mode 100644 index 000000000..4dc12f691 --- /dev/null +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/InvalidD2DGeneratedPixelShaderDescriptorAttributeTargetAnalyzer.cs @@ -0,0 +1,53 @@ +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 an error whenever the [D2DGeneratedPixelShaderDescriptor] attribute is used on an invalid target type. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidD2DGeneratedPixelShaderDescriptorAttributeTargetAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidD2DGeneratedPixelShaderDescriptorAttributeTarget); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the ID2D1PixelShader and [D2DGeneratedPixelShaderDescriptor] symbols + if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.ID2D1PixelShader") is not { } d2D1PixelShaderSymbol || + 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; + } + + // Emit a diagnostic if the target type is using [D2DGeneratedPixelShaderDescriptor] but does not implement ID2D1PixelShader + if (typeSymbol.TryGetAttributeWithType(d2DGeneratedPixelShaderDescriptorAttributeSymbol, out AttributeData? attribute) && + !typeSymbol.HasInterfaceWithType(d2D1PixelShaderSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidD2DGeneratedPixelShaderDescriptorAttributeTarget, + attribute.GetLocation(), + typeSymbol)); + } + }, SymbolKind.NamedType); + }); + } +} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/MissingPixelShaderDescriptorOnPixelShaderAnalyzer.cs b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/MissingPixelShaderDescriptorOnPixelShaderAnalyzer.cs new file mode 100644 index 000000000..2f125cf00 --- /dev/null +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/MissingPixelShaderDescriptorOnPixelShaderAnalyzer.cs @@ -0,0 +1,61 @@ +using System.Collections.Immutable; +using System.Linq; +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 a warning whenever a D2D1 shader type does not have an associated descriptor. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class MissingPixelShaderDescriptorOnPixelShaderAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(MissingPixelShaderDescriptorOnPixelShaderType); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the ID2D1PixelShader, ID2D1PixelShaderDescriptor and [D2DGeneratedPixelShaderDescriptor] symbols + if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.ID2D1PixelShader") is not { } d2D1PixelShaderSymbol || + context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.Descriptors.ID2D1PixelShaderDescriptor`1") is not { } d2D1PixelShaderDescriptorSymbol || + 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; + } + + // If the type is not a pixel shader type, immediately bail out + if (!typeSymbol.HasInterfaceWithType(d2D1PixelShaderSymbol)) + { + return; + } + + // Emit a diagnostic if the descriptor is missing for the shader type + if (!typeSymbol.HasInterfaceWithType(d2D1PixelShaderDescriptorSymbol) && + !typeSymbol.HasAttributeWithType(d2DGeneratedPixelShaderDescriptorAttributeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + MissingPixelShaderDescriptorOnPixelShaderType, + typeSymbol.Locations.FirstOrDefault(), + 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 a5110fe2c..7b8844205 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -7,6 +7,11 @@ namespace ComputeSharp.SourceGeneration.Diagnostics; /// partial class DiagnosticDescriptors { + /// + /// The diagnostic id for . + /// + public const string MissingPixelShaderDescriptorOnPixelShaderTypeId = "CMPSD2D0065"; + /// /// Gets a for an invalid shader field. /// @@ -954,4 +959,37 @@ partial class DiagnosticDescriptors description: "Unsafe blocks must be enabled for the source generators to emit valid code (the true option must be set in the .csproj/.props file).", helpLinkUri: "https://github.com/Sergio0694/ComputeSharp", customTags: WellKnownDiagnosticTags.CompilationEnd); + + /// + /// Gets a for when a pixel shader type doesn't have an associated descriptor. + /// + /// Format: "The D2D1 shader of type {0} does not have an associated descriptor (it can be autogenerated via the [D2DGeneratedPixelShaderDescriptor] attribute)". + /// + /// + public static readonly DiagnosticDescriptor MissingPixelShaderDescriptorOnPixelShaderType = new DiagnosticDescriptor( + id: MissingPixelShaderDescriptorOnPixelShaderTypeId, + title: "Missing descriptor for D2D1 pixel shader type", + messageFormat: "The D2D1 shader of type {0} does not have an associated descriptor (it can be autogenerated via the [D2DGeneratedPixelShaderDescriptor] attribute)", + category: "ComputeSharp.D2D1.Shaders", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "All D2D1 shader types must have an associated descriptor (it can be autogenerated via the [D2DGeneratedPixelShaderDescriptor] attribute).", + helpLinkUri: "https://github.com/Sergio0694/ComputeSharp", + customTags: WellKnownDiagnosticTags.CompilationEnd); + + /// + /// Gets a for when the [D2DGeneratedPixelShaderDescriptor] attribute is being used on an invalid target type. + /// + /// Format: "The type {0} is not a valid target for the [D2DGeneratedPixelShaderDescriptor] attribute (only types implementing the ID2D1PixelShader interface are valid)". + /// + /// + public static readonly DiagnosticDescriptor InvalidD2DGeneratedPixelShaderDescriptorAttributeTarget = new DiagnosticDescriptor( + id: "CMPSD2D0066", + title: "Invalid [D2DGeneratedPixelShaderDescriptor] attribute target", + messageFormat: "The type {0} is not a valid target for the [D2DGeneratedPixelShaderDescriptor] attribute (only types implementing ID2D1PixelShader interface are valid)", + category: "ComputeSharp.D2D1.Shaders", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "The [D2DGeneratedPixelShaderDescriptor] attribute must be used on types that implement the ID2D1PixelShader interface.", + helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); } \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Suppressors/D2D1ResourceTextureUninitializedFieldDiagnosticSuppressor.cs b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Suppressors/D2D1ResourceTextureUninitializedFieldDiagnosticSuppressor.cs index a570e68f7..664da5675 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Suppressors/D2D1ResourceTextureUninitializedFieldDiagnosticSuppressor.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Suppressors/D2D1ResourceTextureUninitializedFieldDiagnosticSuppressor.cs @@ -52,15 +52,13 @@ public override void ReportSuppressions(SuppressionAnalysisContext context) HlslKnownTypes.IsResourceTextureType(fieldSymbol.Type.GetFullyQualifiedMetadataName())) { // Get the ID2D1PixelShader interface symbol to the check the containing type of the field - INamedTypeSymbol? pixelShaderInterfaceSymbol = semanticModel.Compilation.GetTypeByMetadataName(typeof(ID2D1PixelShader).FullName)!; - - if (pixelShaderInterfaceSymbol is null) + if (semanticModel.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.ID2D1PixelShader") is not { } d2D1PixelShaderSymbol) { continue; } // Also check if the containing type is in fact a D2D1 pixel shader type - if (ID2D1ShaderGenerator.IsD2D1PixelShaderType(structSymbol, semanticModel.Compilation)) + if (structSymbol.HasInterfaceWithType(d2D1PixelShaderSymbol)) { context.ReportSuppression(Suppression.Create(UninitializedD2D1ResourceTextureField, diagnostic)); } diff --git a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.Helpers.cs b/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.Helpers.cs deleted file mode 100644 index ff29a0e69..000000000 --- a/src/ComputeSharp.D2D1.SourceGenerators/ID2D1ShaderGenerator.Helpers.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace ComputeSharp.D2D1.SourceGenerators; - -/// -partial class ID2D1ShaderGenerator -{ - /// - /// Gets the shader type for a given shader, if any. - /// - /// The input instance to check. - /// The instance currently in use. - /// Whether or not is a D2D1 interface type. - public static bool IsD2D1PixelShaderType(INamedTypeSymbol typeSymbol, Compilation compilation) - { - foreach (INamedTypeSymbol interfaceSymbol in typeSymbol.AllInterfaces) - { - if (interfaceSymbol.Name == nameof(ID2D1PixelShader)) - { - INamedTypeSymbol d2D1PixelShaderSymbol = compilation.GetTypeByMetadataName("ComputeSharp.D2D1.ID2D1PixelShader")!; - - if (SymbolEqualityComparer.Default.Equals(interfaceSymbol, d2D1PixelShaderSymbol)) - { - return true; - } - } - } - - return false; - } -} \ No newline at end of file diff --git a/src/ComputeSharp.D2D1.SourceGenerators/Models/HlslBytecodeInfoKey.cs b/src/ComputeSharp.D2D1.SourceGenerators/Models/HlslBytecodeInfoKey.cs index 772e9aea3..035f71446 100644 --- a/src/ComputeSharp.D2D1.SourceGenerators/Models/HlslBytecodeInfoKey.cs +++ b/src/ComputeSharp.D2D1.SourceGenerators/Models/HlslBytecodeInfoKey.cs @@ -4,15 +4,11 @@ namespace ComputeSharp.D2D1.SourceGenerators.Models; /// A model with info to be a unique key for instances. /// /// The input HLSL source code. -/// The requested shader profile, if available. -/// The requested compile options, if available. -/// The effective shader profile. -/// The effective compile options. -/// Whether any errors were produced when analyzing the shader. +/// The shader profile. +/// The compile options. +/// Whether compilation should be attempted for the current info. internal sealed record HlslBytecodeInfoKey( string HlslSource, - D2D1ShaderProfile? RequestedShaderProfile, - D2D1CompileOptions? RequestedCompileOptions, - D2D1ShaderProfile EffectiveShaderProfile, - D2D1CompileOptions EffectiveCompileOptions, - bool HasErrors); \ No newline at end of file + D2D1ShaderProfile ShaderProfile, + D2D1CompileOptions CompileOptions, + bool IsCompilationEnabled); \ No newline at end of file diff --git a/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs b/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs index a2ef6fee2..fe4568a05 100644 --- a/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs +++ b/src/ComputeSharp.D2D1/Attributes/D2DCompileOptionsAttribute.cs @@ -15,8 +15,6 @@ namespace ComputeSharp.D2D1; /// /// /// -/// -/// /// Note that the is always enabled automatically and does not need to be /// specified. This option is mandatory, as the generated code to load the constant buffer from a shader assumes the layout /// for matrix types is row major. For the same reason, using is not diff --git a/src/ComputeSharp.D2D1/Attributes/D2DGeneratedPixelShaderDescriptorAttribute.cs b/src/ComputeSharp.D2D1/Attributes/D2DGeneratedPixelShaderDescriptorAttribute.cs new file mode 100644 index 000000000..da27d2fe7 --- /dev/null +++ b/src/ComputeSharp.D2D1/Attributes/D2DGeneratedPixelShaderDescriptorAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace ComputeSharp.D2D1; + +/// +/// An attribute that indicates that a given D2D1 shader should have its associated descriptor code being generated. +/// +/// This attribute can be used on shader types that are declared as : +/// +/// [D2DGeneratedPixelShaderDescriptor] +/// partial struct MyShader : ID2D1PixelShader +/// { +/// } +/// +/// +/// +/// When a shader is annotated with , the source generator will also add +/// to its implemented interfaces, and implement its APIs automatically. +/// +/// +[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] +public sealed class D2DGeneratedPixelShaderDescriptorAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems b/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems index 3a2bf8d37..413e50cc8 100644 --- a/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems +++ b/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems @@ -18,6 +18,7 @@ + diff --git a/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs index b0a317243..a0dd6fbe3 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs @@ -27,6 +27,17 @@ public static string GetFullyQualifiedName(this ISymbol symbol) return symbol.ToDisplayString(FullyQualifiedWithoutGlobalFormat); } + /// + /// Checks whether or not a given symbol has an attribute with the specified type. + /// + /// The input instance to check. + /// The instance for the attribute type to look for. + /// Whether or not has an attribute with the specified type. + public static bool HasAttributeWithType(this ISymbol symbol, ITypeSymbol typeSymbol) + { + return TryGetAttributeWithType(symbol, typeSymbol, out _); + } + /// /// Tries to get an attribute with the specified type. /// diff --git a/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs index a9fd5b261..5149f647a 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs @@ -24,6 +24,25 @@ public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string return builder.WrittenSpan.SequenceEqual(name.AsSpan()); } + /// + /// Checks whether or not a given implements an interface of a specified type. + /// + /// The target instance to check. + /// The instane to check for inheritance from. + /// Whether or not has an interface of type . + public static bool HasInterfaceWithType(this ITypeSymbol typeSymbol, ITypeSymbol interfaceSymbol) + { + foreach (INamedTypeSymbol interfaceType in typeSymbol.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(interfaceType, interfaceSymbol)) + { + return true; + } + } + + return false; + } + /// /// Gets the fully qualified metadata name for a given instance. /// diff --git a/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs index 9803e9981..262e6aa2b 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs @@ -17,19 +17,23 @@ internal static class IndentedTextWriterExtensions /// /// /// The instance to write into. - /// The type of the running generator. + /// The name of the generator. /// Whether to use fully qualified type names or not. - public static void WriteGeneratedAttributes(this IndentedTextWriter writer, Type generatorType, bool useFullyQualifiedTypeNames = true) + public static void WriteGeneratedAttributes(this IndentedTextWriter writer, string generatorName, bool useFullyQualifiedTypeNames = true) { + // We can use this class to get the assembly, as all files for generators are just included + // via shared projects. As such, the assembly will be the same as the generator type itself. + Version assemblyVersion = typeof(IndentedTextWriterExtensions).Assembly.GetName().Version; + if (useFullyQualifiedTypeNames) { - writer.WriteLine($$"""[global::System.CodeDom.Compiler.GeneratedCode("{{generatorType.FullName}}", "{{generatorType.Assembly.GetName().Version}}")]"""); + writer.WriteLine($$"""[global::System.CodeDom.Compiler.GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); writer.WriteLine($$"""[global::System.Diagnostics.DebuggerNonUserCode]"""); writer.WriteLine($$"""[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"""); } else { - writer.WriteLine($$"""[GeneratedCode("{{generatorType.FullName}}", "{{generatorType.Assembly.GetName().Version}}")]"""); + writer.WriteLine($$"""[GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); writer.WriteLine($$"""[DebuggerNonUserCode]"""); writer.WriteLine($$"""[ExcludeFromCodeCoverage]"""); } diff --git a/src/ComputeSharp.SourceGeneration/Extensions/SymbolInfoExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/SymbolInfoExtensions.cs new file mode 100644 index 000000000..2317a5d1f --- /dev/null +++ b/src/ComputeSharp.SourceGeneration/Extensions/SymbolInfoExtensions.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace ComputeSharp.SourceGeneration.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class SymbolInfoExtensions +{ + /// + /// Tries to get the resolved attribute type symbol from a given value. + /// + /// The value to check. + /// The resulting attribute type symbol, if correctly resolved. + /// Whether is resolved to a symbol. + /// + /// This can be used to ensure users haven't eg. spelled names incorrecty or missed a using directive. Normally, code would just + /// not compile if that was the case, but that doesn't apply for attributes using invalid targets. In that case, Roslyn will ignore + /// any errors, meaning the generator has to validate the type symbols are correctly resolved on its own. + /// + public static bool TryGetAttributeTypeSymbol(this SymbolInfo symbolInfo, [NotNullWhen(true)] out INamedTypeSymbol? typeSymbol) + { + ISymbol? attributeSymbol = symbolInfo.Symbol; + + // If no symbol is selected and there is a single candidate symbol, use that + if (attributeSymbol is null && symbolInfo.CandidateSymbols is [ISymbol candidateSymbol]) + { + attributeSymbol = candidateSymbol; + } + + // Extract the symbol from either the current one or the containing type + if ((attributeSymbol as INamedTypeSymbol ?? attributeSymbol?.ContainingType) is not INamedTypeSymbol resultingSymbol) + { + typeSymbol = null; + + return false; + } + + typeSymbol = resultingSymbol; + + return true; + } +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8395dc9ca..19fabcadd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -45,11 +45,11 @@ - - false + + false - - true + + true false @@ -76,7 +76,7 @@ diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 5fb368ada..a19860024 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -2,11 +2,18 @@ - + + + false true $(TargetFrameworks.Split(';')[0].Trim()) + + + false + true + $(TargetFrameworks.Split(';')[0].Trim()) @@ -24,6 +31,21 @@ Visible="false" /> + + + + + + + + + +