/// <summary>
        /// Creates a <see cref="MethodDeclarationSyntax"/> instance for the <c>BuildHlslSource</c> method.
        /// </summary>
        /// <param name="hlslSourceInfo">The input <see cref="HlslShaderSourceInfo"/> instance to use.</param>
        /// <param name="supportsDynamicShaders">Indicates whether or not dynamic shaders are supported.</param>
        /// <returns>The resulting <see cref="MethodDeclarationSyntax"/> instance for the <c>BuildHlslSource</c> method.</returns>
        public static MethodDeclarationSyntax GetSyntax(HlslShaderSourceInfo hlslSourceInfo, bool supportsDynamicShaders)
        {
            // Generate the necessary body statements depending on whether dynamic shaders are supported
            ImmutableArray <StatementSyntax> bodyStatements = supportsDynamicShaders
                ? GenerateRenderMethodBody(hlslSourceInfo)
                : GenerateEmptyMethodBody();

            // This code produces a method declaration as follows:
            //
            // readonly void global::ComputeSharp.__Internals.IShader.BuildHlslSource(out global::ComputeSharp.__Internals.ArrayPoolStringBuilder builder, int threadsX, int threadsY, int threadsZ)
            // {
            //     <BODY>
            // }
            return
                (MethodDeclaration(
                     PredefinedType(Token(SyntaxKind.VoidKeyword)),
                     Identifier("BuildHlslSource"))
                 .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier(IdentifierName($"global::ComputeSharp.__Internals.{nameof(IShader)}")))
                 .AddModifiers(Token(SyntaxKind.ReadOnlyKeyword))
                 .AddParameterListParameters(
                     Parameter(Identifier("builder")).AddModifiers(Token(SyntaxKind.OutKeyword)).WithType(IdentifierName("global::ComputeSharp.__Internals.ArrayPoolStringBuilder")),
                     Parameter(Identifier("threadsX")).WithType(PredefinedType(Token(SyntaxKind.IntKeyword))),
                     Parameter(Identifier("threadsY")).WithType(PredefinedType(Token(SyntaxKind.IntKeyword))),
                     Parameter(Identifier("threadsZ")).WithType(PredefinedType(Token(SyntaxKind.IntKeyword))))
                 .WithBody(Block(bodyStatements)));
        }
Beispiel #2
0
        /// <summary>
        /// Gets a <see cref="BlockSyntax"/> instance with the logic to try to get a compiled shader bytecode.
        /// </summary>
        /// <param name="sourceInfo">The source info for the shader to compile.</param>
        /// <param name="token">The <see cref="CancellationToken"/> used to cancel the operation, if needed.</param>
        /// <param name="options">The effective compile options used to create the shader bytecode.</param>
        /// <param name="diagnostic">The resulting diagnostic from the processing operation, if any.</param>
        /// <returns>The <see cref="ImmutableArray{T}"/> instance with the compiled shader bytecode.</returns>
        public static unsafe ImmutableArray <byte> GetBytecode(
            HlslShaderSourceInfo sourceInfo,
            CancellationToken token,
            out D2D1CompileOptions options,
            out DiagnosticInfo?diagnostic)
        {
            ImmutableArray <byte> bytecode = ImmutableArray <byte> .Empty;

            // No embedded shader was requested, or there were some 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.
            if (sourceInfo is { HasErrors: true } or {
                ShaderProfile: null
            })
        /// <summary>
        /// Produces the series of statements to build the current HLSL source.
        /// </summary>
        /// <param name="hlslSourceInfo">The input <see cref="HlslShaderSourceInfo"/> instance to use.</param>
        /// <returns>The series of statements to build the HLSL source to compile to execute the current shader.</returns>
        private static ImmutableArray <StatementSyntax> GenerateRenderMethodBody(HlslShaderSourceInfo hlslSourceInfo)
        {
            ImmutableArray <StatementSyntax> .Builder statements = ImmutableArray.CreateBuilder <StatementSyntax>();
            StringBuilder textBuilder        = new();
            int           capturedDelegates  = 0;
            int           prologueStatements = 0;
            int           sizeHint           = 64;

            void AppendLF()
            {
                textBuilder.Append('\n');
            }

            void AppendLine(string text)
            {
                textBuilder.Append(text);
            }

            void AppendParsedStatement(string text)
            {
                FlushText();

                statements.Add(ParseStatement(text));
            }

            void FlushText()
            {
                if (textBuilder.Length > 0)
                {
                    string text = textBuilder.ToString();

                    textBuilder.Append(text);

                    sizeHint += textBuilder.Length;

                    statements.Add(
                        ExpressionStatement(
                            InvocationExpression(
                                MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("builder"), IdentifierName("Append")))
                            .AddArgumentListArguments(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(text))))));

                    textBuilder.Clear();
                }
            }

            // Declare the hashsets to track imported members and types from delegates, if needed
            if (!hlslSourceInfo.Delegates.IsEmpty)
            {
                void DeclareMapping(int index, string name, IEnumerable <string> items)
                {
                    // global::System.Collections.Generic.HashSet<string> <NAME> = new();
                    statements.Insert(index,
                                      LocalDeclarationStatement(VariableDeclaration(
                                                                    GenericName(Identifier("global::System.Collections.Generic.HashSet"))
                                                                    .AddTypeArgumentListArguments(PredefinedType(Token(SyntaxKind.StringKeyword))))
                                                                .AddVariables(
                                                                    VariableDeclarator(Identifier(name))
                                                                    .WithInitializer(EqualsValueClause(ImplicitObjectCreationExpression())))));

                    prologueStatements++;

                    // <NAME>.Add("<ITEM>");
                    foreach (var item in items)
                    {
                        statements.Add(
                            ExpressionStatement(
                                InvocationExpression(
                                    MemberAccessExpression(
                                        SyntaxKind.SimpleMemberAccessExpression,
                                        IdentifierName(name),
                                        IdentifierName("Add")))
                                .AddArgumentListArguments(Argument(
                                                              LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(item))))));

                        prologueStatements++;
                    }
                }

                DeclareMapping(0, "__typeNames", hlslSourceInfo.DefinedTypes);
                DeclareMapping(1, "__constantNames", hlslSourceInfo.DefinedConstants);
                DeclareMapping(2, "__methodNames", hlslSourceInfo.MethodSignatures);

                // Go through all existing delegate fields, if any
                foreach (string fieldName in hlslSourceInfo.Delegates)
                {
                    // global::ComputeSharp.__Internals.ShaderMethodSourceAttribute __<DELEGATE_NAME>Attribute = global::ComputeSharp.__Internals.ShaderMethodSourceAttribute.GetForDelegate(<DELEGATE_NAME>, "<DELEGATE_NAME>");
                    statements.Add(
                        LocalDeclarationStatement(VariableDeclaration(IdentifierName($"global::ComputeSharp.__Internals.{nameof(ShaderMethodSourceAttribute)}"))
                                                  .AddVariables(
                                                      VariableDeclarator(Identifier($"__{fieldName}Attribute"))
                                                      .WithInitializer(
                                                          EqualsValueClause(
                                                              InvocationExpression(
                                                                  MemberAccessExpression(
                                                                      SyntaxKind.SimpleMemberAccessExpression,
                                                                      IdentifierName($"global::ComputeSharp.__Internals.{nameof(ShaderMethodSourceAttribute)}"),
                                                                      IdentifierName(nameof(ShaderMethodSourceAttribute.GetForDelegate))))
                                                              .AddArgumentListArguments(
                                                                  Argument(IdentifierName(fieldName)),
                                                                  Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(fieldName)))))))));

                    capturedDelegates++;
                    prologueStatements++;
                }
            }

            // Header and thread ids
            AppendLine(hlslSourceInfo.HeaderAndThreadsX);
            AppendParsedStatement("builder.Append(threadsX);");
            AppendLine(hlslSourceInfo.ThreadsY);
            AppendParsedStatement("builder.Append(threadsY);");
            AppendLine(hlslSourceInfo.ThreadsZ);
            AppendParsedStatement("builder.Append(threadsZ);");

            // Define declarations
            AppendLine(hlslSourceInfo.Defines);

            // Defines from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendConstants(ref builder, __constantNames);");
            }

            // Static fields and declared types
            AppendLine(hlslSourceInfo.StaticFieldsAndDeclaredTypes);

            // Declared types from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendTypes(ref builder, __typeNames);");
            }

            // Captured variables
            AppendLine(hlslSourceInfo.CapturedFieldsAndResourcesAndForwardDeclarations);

            // Forward declarations from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendForwardDeclarations(ref builder, __methodNames);");
            }

            // Remove all forward declarations from methods that are embedded into the shader.
            // This is necessary to avoid duplicate definitions from methods from delegates.
            if (capturedDelegates > 0)
            {
                // <NAME>.Add("<ITEM>");
                foreach (string forwardDeclaration in hlslSourceInfo.MethodSignatures)
                {
                    FlushText();

                    statements.Add(
                        ExpressionStatement(
                            InvocationExpression(
                                MemberAccessExpression(
                                    SyntaxKind.SimpleMemberAccessExpression,
                                    IdentifierName("__methodNames"),
                                    IdentifierName("Remove")))
                            .AddArgumentListArguments(Argument(
                                                          LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(forwardDeclaration))))));
                }
            }

            // Captured methods
            AppendLine(hlslSourceInfo.CapturedMethods);

            // Captured methods from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendMethods(ref builder, __methodNames);");
            }

            // Captured delegate methods
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendLF();
                AppendParsedStatement($"__{fieldName}Attribute.AppendMappedInvokeMethod(ref builder, \"{fieldName}\");");
                AppendLF();
            }

            // Entry point
            AppendLine(hlslSourceInfo.EntryPoint);

            FlushText();

            // builder = global::ComputeSharp.__Internals.ArrayPoolStringBuilder.Create(<SIZE_HINT>);
            statements.Insert(
                prologueStatements,
                ExpressionStatement(
                    AssignmentExpression(
                        SyntaxKind.SimpleAssignmentExpression,
                        IdentifierName("builder"),
                        InvocationExpression(
                            MemberAccessExpression(
                                SyntaxKind.SimpleMemberAccessExpression,
                                IdentifierName("global::ComputeSharp.__Internals.ArrayPoolStringBuilder"),
                                IdentifierName("Create")))
                        .AddArgumentListArguments(
                            Argument(LiteralExpression(
                                         SyntaxKind.NumericLiteralExpression,
                                         Literal(sizeHint)))))));

            return(statements.ToImmutable());
        }
        /// <summary>
        /// Produces the series of statements to build the current HLSL source.
        /// </summary>
        /// <param name="hlslSourceInfo">The input <see cref="HlslShaderSourceInfo"/> instance to use.</param>
        /// <param name="hierarchyDepth">The depth of the hierarchy for this type (used to calculate the right indentation).</param>
        /// <param name="useRawMultiLineStringLiteralExpression">Whether to use a raw multiline string literal expression</param>
        /// <returns>The series of statements to build the HLSL source to compile to execute the current shader.</returns>
        private static ImmutableArray <StatementSyntax> GenerateRenderMethodBody(HlslShaderSourceInfo hlslSourceInfo, int hierarchyDepth, bool useRawMultiLineStringLiteralExpression)
        {
            ImmutableArray <StatementSyntax> .Builder statements = ImmutableArray.CreateBuilder <StatementSyntax>();
            StringBuilder textBuilder        = new();
            int           capturedDelegates  = 0;
            int           prologueStatements = 0;
            int           sizeHint           = 64;

            void AppendLF()
            {
                textBuilder.Append('\n');
            }

            void AppendLine(string text)
            {
                textBuilder.Append(text);
            }

            void AppendParsedStatement(string text)
            {
                FlushText();

                statements.Add(ParseStatement(text));
            }

            void FlushText()
            {
                if (textBuilder.Length > 0)
                {
                    string text = textBuilder.ToString();

                    textBuilder.Append(text);

                    sizeHint += textBuilder.Length;

                    // If raw multiline string literal expressions are used, create a token to represent it. Here some spaces are
                    // also added to properly align the resulting text with one indentation below the declaring string constant.
                    // The spaces are: 4 for each containing type, 4 for the containing method, and 4 for the one additional indentation.
                    // An extra newline and indentation has to be added to the raw text when there is no trailing newline, as not doing
                    // so would otherwise case the terminating """ token to fall on the same line, which is invalid syntax. This is only
                    // needed to make the code valid, as the actual literal context of the string is not affected and remains the same.
                    string      indentation = new(' ', 4 * hierarchyDepth + 4 + 4);
                    SyntaxToken textToken   = useRawMultiLineStringLiteralExpression switch
                    {
                        true =>
                        Token(
                            TriviaList(),
                            SyntaxKind.MultiLineRawStringLiteralToken,
                            $"\"\"\"\n{indentation}{text.Replace("\n", $"\n{indentation}")}{(text.EndsWith("\n") ? "" : $"\n{indentation}")}\"\"\"",
                            text,
                            TriviaList()),
                        false => Literal(text)
                    };

                    statements.Add(
                        ExpressionStatement(
                            InvocationExpression(
                                MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("builder"), IdentifierName("Append")))
                            .AddArgumentListArguments(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, textToken)))));

                    textBuilder.Clear();
                }
            }

            // Declare the hashsets to track imported members and types from delegates, if needed
            if (!hlslSourceInfo.Delegates.IsEmpty)
            {
                void DeclareMapping(int index, string name, IEnumerable <string> items)
                {
                    // global::System.Collections.Generic.HashSet<string> <NAME> = new();
                    statements.Insert(index,
                                      LocalDeclarationStatement(VariableDeclaration(
                                                                    GenericName(Identifier("global::System.Collections.Generic.HashSet"))
                                                                    .AddTypeArgumentListArguments(PredefinedType(Token(SyntaxKind.StringKeyword))))
                                                                .AddVariables(
                                                                    VariableDeclarator(Identifier(name))
                                                                    .WithInitializer(EqualsValueClause(ImplicitObjectCreationExpression())))));

                    prologueStatements++;

                    // <NAME>.Add("<ITEM>");
                    foreach (var item in items)
                    {
                        statements.Add(
                            ExpressionStatement(
                                InvocationExpression(
                                    MemberAccessExpression(
                                        SyntaxKind.SimpleMemberAccessExpression,
                                        IdentifierName(name),
                                        IdentifierName("Add")))
                                .AddArgumentListArguments(Argument(
                                                              LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(item))))));

                        prologueStatements++;
                    }
                }

                DeclareMapping(0, "__typeNames", hlslSourceInfo.DefinedTypes);
                DeclareMapping(1, "__constantNames", hlslSourceInfo.DefinedConstants);
                DeclareMapping(2, "__methodNames", hlslSourceInfo.MethodSignatures);

                // Go through all existing delegate fields, if any
                foreach (string fieldName in hlslSourceInfo.Delegates)
                {
                    // global::ComputeSharp.__Internals.ShaderMethodSourceAttribute __<DELEGATE_NAME>Attribute = global::ComputeSharp.__Internals.ShaderMethodSourceAttribute.GetForDelegate(<DELEGATE_NAME>, "<DELEGATE_NAME>");
                    statements.Add(
                        LocalDeclarationStatement(VariableDeclaration(IdentifierName($"global::ComputeSharp.__Internals.{nameof(ShaderMethodSourceAttribute)}"))
                                                  .AddVariables(
                                                      VariableDeclarator(Identifier($"__{fieldName}Attribute"))
                                                      .WithInitializer(
                                                          EqualsValueClause(
                                                              InvocationExpression(
                                                                  MemberAccessExpression(
                                                                      SyntaxKind.SimpleMemberAccessExpression,
                                                                      IdentifierName($"global::ComputeSharp.__Internals.{nameof(ShaderMethodSourceAttribute)}"),
                                                                      IdentifierName(nameof(ShaderMethodSourceAttribute.GetForDelegate))))
                                                              .AddArgumentListArguments(
                                                                  Argument(IdentifierName(fieldName)),
                                                                  Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(fieldName)))))))));

                    capturedDelegates++;
                    prologueStatements++;
                }
            }

            // Header and thread ids
            AppendLine(hlslSourceInfo.HeaderAndThreadsX);
            AppendParsedStatement("builder.Append(threadsX);");
            AppendLine(hlslSourceInfo.ThreadsY);
            AppendParsedStatement("builder.Append(threadsY);");
            AppendLine(hlslSourceInfo.ThreadsZ);
            AppendParsedStatement("builder.Append(threadsZ);");

            // Define declarations
            AppendLine(hlslSourceInfo.Defines);

            // Defines from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendConstants(ref builder, __constantNames);");
            }

            // Static fields and declared types
            AppendLine(hlslSourceInfo.StaticFieldsAndDeclaredTypes);

            // Declared types from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendTypes(ref builder, __typeNames);");
            }

            // Captured variables
            AppendLine(hlslSourceInfo.CapturedFieldsAndResourcesAndForwardDeclarations);

            // Forward declarations from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendForwardDeclarations(ref builder, __methodNames);");
            }

            // Remove all forward declarations from methods that are embedded into the shader.
            // This is necessary to avoid duplicate definitions from methods from delegates.
            if (capturedDelegates > 0)
            {
                // <NAME>.Add("<ITEM>");
                foreach (string forwardDeclaration in hlslSourceInfo.MethodSignatures)
                {
                    FlushText();

                    statements.Add(
                        ExpressionStatement(
                            InvocationExpression(
                                MemberAccessExpression(
                                    SyntaxKind.SimpleMemberAccessExpression,
                                    IdentifierName("__methodNames"),
                                    IdentifierName("Remove")))
                            .AddArgumentListArguments(Argument(
                                                          LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(forwardDeclaration))))));
                }
            }

            // Captured methods
            AppendLine(hlslSourceInfo.CapturedMethods);

            // Captured methods from captured delegates
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendParsedStatement($"__{fieldName}Attribute.AppendMethods(ref builder, __methodNames);");
            }

            // Captured delegate methods
            foreach (string fieldName in hlslSourceInfo.Delegates)
            {
                AppendLF();
                AppendParsedStatement($"__{fieldName}Attribute.AppendMappedInvokeMethod(ref builder, \"{fieldName}\");");
                AppendLF();
            }

            // Entry point
            AppendLine(hlslSourceInfo.EntryPoint);

            FlushText();

            // builder = global::ComputeSharp.__Internals.ArrayPoolStringBuilder.Create(<SIZE_HINT>);
            statements.Insert(
                prologueStatements,
                ExpressionStatement(
                    AssignmentExpression(
                        SyntaxKind.SimpleAssignmentExpression,
                        IdentifierName("builder"),
                        InvocationExpression(
                            MemberAccessExpression(
                                SyntaxKind.SimpleMemberAccessExpression,
                                IdentifierName("global::ComputeSharp.__Internals.ArrayPoolStringBuilder"),
                                IdentifierName("Create")))
                        .AddArgumentListArguments(
                            Argument(LiteralExpression(
                                         SyntaxKind.NumericLiteralExpression,
                                         Literal(sizeHint)))))));

            return(statements.ToImmutable());
        }