Skip to content

Commit

Permalink
Merge pull request #721 from Sergio0694/dev/d2d-requires-double-preci…
Browse files Browse the repository at this point in the history
…sion-support

Add [D2DRequiresDoublePrecisionSupport] attribute
  • Loading branch information
Sergio0694 authored Dec 17, 2023
2 parents c71770f + 5d58622 commit d98e57d
Show file tree
Hide file tree
Showing 17 changed files with 445 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,6 @@ CMPSD2D0076 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github
CMPSD2D0077 | ComputeSharp.D2D1.Shaders | Info | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0078 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0079 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0080 | ComputeSharp.D2D1.Shaders | Error | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0081 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
CMPSD2D0082 | ComputeSharp.D2D1.Shaders | Warning | [Documentation](https://github.com/Sergio0694/ComputeSharp)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>$(DefineConstants);D2D1_GENERATOR</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,19 @@ static unsafe HlslBytecodeInfo GetInfo(HlslBytecodeInfoKey key, CancellationToke

token.ThrowIfCancellationRequested();

// Check whether double precision operations are required
bool requiresDoublePrecisionSupport = D3DCompiler.IsDoublePrecisionSupportRequired(dxcBlobBytecode.Get());

token.ThrowIfCancellationRequested();

byte* buffer = (byte*)dxcBlobBytecode.Get()->GetBufferPointer();
int length = checked((int)dxcBlobBytecode.Get()->GetBufferSize());

byte[] array = new ReadOnlySpan<byte>(buffer, length).ToArray();

ImmutableArray<byte> bytecode = Unsafe.As<byte[], ImmutableArray<byte>>(ref array);

return new HlslBytecodeInfo.Success(bytecode);
return new HlslBytecodeInfo.Success(bytecode, requiresDoublePrecisionSupport);
}
catch (Win32Exception e)
{
Expand Down Expand Up @@ -267,5 +272,45 @@ public static void GetInfoDiagnostics(
diagnostics.Add(diagnostic);
}
}

/// <summary>
/// Gets the diagnostics for when double precision support is configured incorrectly.
/// </summary>
/// <param name="structDeclarationSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
/// <param name="info">The source <see cref="HlslBytecodeInfo"/> instance.</param>
/// <param name="diagnostics">The collection of produced <see cref="DiagnosticInfo"/> instances.</param>
public static void GetDoublePrecisionSupportDiagnostics(
INamedTypeSymbol structDeclarationSymbol,
HlslBytecodeInfo info,
ImmutableArrayBuilder<DiagnosticInfo> diagnostics)
{
// If we have no compiled HLSL bytecode, there is nothing more to do
if (info is not HlslBytecodeInfo.Success success)
{
return;
}

bool hasD2DRequiresDoublePrecisionSupportAttribute = structDeclarationSymbol.TryGetAttributeWithFullyQualifiedMetadataName(
"ComputeSharp.D2D1.D2DRequiresDoublePrecisionSupportAttribute",
out AttributeData? attributeData);

// Check the two cases where diagnostics are necessary:
// - The shader does not have [D2DRequiresDoublePrecisionSupport], but it needs it
// - The shader has [D2DRequiresDoublePrecisionSupport], but it does not need it
if (!hasD2DRequiresDoublePrecisionSupportAttribute && success.RequiresDoublePrecisionSupport)
{
diagnostics.Add(DiagnosticInfo.Create(
MissingD2DRequiresDoublePrecisionSupportAttribute,
structDeclarationSymbol,
structDeclarationSymbol));
}
else if (hasD2DRequiresDoublePrecisionSupportAttribute && !success.RequiresDoublePrecisionSupport)
{
diagnostics.Add(DiagnosticInfo.Create(
UnnecessaryD2DRequiresDoublePrecisionSupportAttribute,
attributeData!.GetLocation(),
structDeclarationSymbol));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

// Append any diagnostic for the shader compilation
HlslBytecode.GetInfoDiagnostics(typeSymbol, hlslInfo, diagnostics);
HlslBytecode.GetDoublePrecisionSupportDiagnostics(typeSymbol, hlslInfo, diagnostics);

token.ThrowIfCancellationRequested();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Immutable;
using ComputeSharp.SourceGeneration.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using static ComputeSharp.SourceGeneration.Diagnostics.DiagnosticDescriptors;

namespace ComputeSharp.D2D1.SourceGenerators;

/// <summary>
/// A diagnostic analyzer that generates diagnostics when [D2DRequiresDoublePrecisionSupport] is used incorrectly.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class InvalidD2DRequiresDoublePrecisionSupportAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidD2DRequiresDoublePrecisionSupportAttribute);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(static context =>
{
// Get the [D2DRequiresDoublePrecisionSupport], [D2DShaderProfile] and [D2DGeneratedPixelShaderDescriptor] symbols
if (context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DRequiresDoublePrecisionSupportAttribute") is not { } d2DRequiresDoublePrecisionSupportAttributeSymbol ||
context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DShaderProfileAttribute") is not { } d2DShaderProfileAttributeSymbol ||
context.Compilation.GetTypeByMetadataName("ComputeSharp.D2D1.D2DGeneratedPixelShaderDescriptorAttribute") is not { } d2DGeneratedPixelShaderDescriptorAttributeSymbol)
{
return;
}

context.RegisterSymbolAction(context =>
{
// Only struct types are possible targets
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Struct } typeSymbol)
{
return;
}

// Skip the type if it's not a generated shader descriptor target
if (!typeSymbol.HasAttributeWithType(d2DGeneratedPixelShaderDescriptorAttributeSymbol))
{
return;
}

// If the shader is precompiled, there is nothing left to do
if (typeSymbol.HasAttributeWithType(d2DShaderProfileAttributeSymbol) ||
typeSymbol.ContainingAssembly.HasAttributeWithType(d2DShaderProfileAttributeSymbol))
{
return;
}

// Emit a diagnostic if the type is not precompiled and has [D2DRequiresDoublePrecisionSupport]
if (typeSymbol.TryGetAttributeWithType(d2DRequiresDoublePrecisionSupportAttributeSymbol, out AttributeData? attributeData))
{
context.ReportDiagnostic(Diagnostic.Create(
InvalidD2DRequiresDoublePrecisionSupportAttribute,
attributeData.GetLocation(),
typeSymbol));
}
}, SymbolKind.NamedType);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1190,4 +1190,52 @@ partial class DiagnosticDescriptors
description: "If an assembly is using [D2DShaderProfile] (meaning that all shaders declared within it will be precompiled), also using [D2DEnableRuntimeCompilation] is unnecessary.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp",
customTags: WellKnownDiagnosticTags.CompilationEnd);

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a shader missing [D2DRequiresDoublePrecisionSupport].
/// <para>
/// Format: <c>"The shader {0} requires double precision support, but it does not have the [D2DRequiresDoublePrecisionSupport] attribute on it (adding the attribute is necessary to explicitly opt-in into that functionality)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor MissingD2DRequiresDoublePrecisionSupportAttribute = new(
id: "CMPSD2D0080",
title: "Missing [D2DRequiresDoublePrecisionSupport] attribute",
messageFormat: "The shader {0} requires double precision support, but it does not have the [D2DRequiresDoublePrecisionSupport] attribute on it (adding the attribute is necessary to explicitly opt-in into that functionality)",
category: "ComputeSharp.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Shaders performing double precision operations must be annotated with [D2DRequiresDoublePrecisionSupport] to explicitly opt-in into that functionality.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a shader is unnecessarily using [UnnecessaryD2DRequiresDoublePrecisionSupportAttribute].
/// <para>
/// Format: <c>"The shader {0} does not require double precision support, but it has the [D2DRequiresDoublePrecisionSupport] attribute on it (using the attribute is not needed if the shader is not performing any double precision operations)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor UnnecessaryD2DRequiresDoublePrecisionSupportAttribute = new(
id: "CMPSD2D0081",
title: "Unnecessary [D2DRequiresDoublePrecisionSupport] attribute",
messageFormat: "The shader {0} does not require double precision support, but it has the [D2DRequiresDoublePrecisionSupport] attribute on it (using the attribute is not needed if the shader is not performing any double precision operations)",
category: "ComputeSharp.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Shaders not performing any double precision operations should not be annotated with [D2DRequiresDoublePrecisionSupport], as the attribute is not needed in that case.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a shader is using [UnnecessaryD2DRequiresDoublePrecisionSupportAttribute] incorrectly.
/// <para>
/// Format: <c>"The shader {0} has the [D2DRequiresDoublePrecisionSupport] attribute on it, but it is not precompiled, so validation for use of double precision operations cannot be performed"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor InvalidD2DRequiresDoublePrecisionSupportAttribute = new(
id: "CMPSD2D0082",
title: "Invalid [D2DRequiresDoublePrecisionSupport] attribute",
messageFormat: "The shader {0} has the [D2DRequiresDoublePrecisionSupport] attribute on it, but it is not precompiled, so validation for use of double precision operations cannot be performed",
category: "ComputeSharp.D2D1.Shaders",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Shaders can only be annotated with [D2DRequiresDoublePrecisionSupport] to perform validation for use of double precision operations if they are precompiled.",
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
}
33 changes: 33 additions & 0 deletions src/ComputeSharp.D2D1.SourceGenerators/Fxc/D3DCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using Windows.Win32;
using Windows.Win32.Graphics.Direct3D;
using Windows.Win32.Graphics.Direct3D11;
using DirectX = Windows.Win32.PInvoke;

namespace ComputeSharp.D2D1.Shaders.Translation;

/// <inheritdoc/>
partial class D3DCompiler
{
/// <summary>
/// Checks whether double precision support is required.
/// </summary>
/// <param name="d3DBlob">The input HLSL bytecode to inspect.</param>
/// <returns>Whether double precision support is required for <paramref name="d3DBlob"/>.</returns>
public static unsafe bool IsDoublePrecisionSupportRequired(ID3DBlob* d3DBlob)
{
using ComPtr<ID3D11ShaderReflection> d3D11ShaderReflection = default;

Guid iidOfID3D11ShaderReflection = ID3D11ShaderReflection.IID_Guid;

DirectX.D3DReflect(
pSrcData: d3DBlob->GetBufferPointer(),
SrcDataSize: d3DBlob->GetBufferSize(),
pInterface: &iidOfID3D11ShaderReflection,
ppReflector: (void**)d3D11ShaderReflection.GetAddressOf()).Assert();

ulong doublePrecisionFlags = DirectX.D3D_SHADER_REQUIRES_DOUBLES | DirectX.D3D_SHADER_REQUIRES_11_1_DOUBLE_EXTENSIONS;

return (d3D11ShaderReflection.Get()->GetRequiresFlags() & doublePrecisionFlags) != 0;
}
}
4 changes: 4 additions & 0 deletions src/ComputeSharp.D2D1.SourceGenerators/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
D3DCompile
D3DReflect
D3DSetBlobPart
D3DStripShader
D2D1_BUFFER_PRECISION
Expand All @@ -21,6 +22,9 @@ D3DCOMPILE_OPTIMIZATION_LEVEL2
D3DCOMPILE_OPTIMIZATION_LEVEL3
D3DCOMPILE_WARNINGS_ARE_ERRORS
D3DCOMPILER_STRIP_FLAGS
D3D_SHADER_REQUIRES_DOUBLES
D3D_SHADER_REQUIRES_11_1_DOUBLE_EXTENSIONS
ID3D11ShaderReflection
IUnknown
S_OK
S_FALSE
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ namespace ComputeSharp.D2D1;
/// This attribute can also be added to a whole assembly, and will be used by default if not overridden by a shader type.
/// </para>
/// </summary>
/// <param name="options">The compiler options to use to compile the shader.</param>
/// <param name="options">The compile options to use to compile the shader.</param>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Assembly | AttributeTargets.Method, AllowMultiple = false)]
public sealed class D2DCompileOptionsAttribute(D2D1CompileOptions options) : Attribute
{
/// <summary>
/// Gets the number of threads in each thread group for the X axis
/// Gets the compile options to use to compile the shader.
/// </summary>
public D2D1CompileOptions Options { get; } = options;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace ComputeSharp.D2D1;

/// <summary>
/// An attribute for a D2D1 shader indicating that the shader requires support for double precision operations.
/// </summary>
/// <remarks>
/// <para>
/// This attribute does not map to any HLSL feature or compile option directly, but it is needed to make using double
/// precision operations opt-in. It is easy to accidentally introduce them in a shader when that is not intented, and
/// doing so can make the shader run slower and not being compatible with some GPUs (eg. many arm64 GPUs lack support
/// for double precision operations). To avoid this, support is disabled by default, and it is necessary to add this
/// attribute over a shader type to explicitly allow using these operations.
/// </para>
/// <para>
/// Validation can only be performed when the shader is being precompiled. If that is not the case (ie. if the shader
/// is using <see cref="D2DEnableRuntimeCompilationAttribute"/> and not <see cref="D2DShaderProfileAttribute"/>), then
/// no build time check for double precision operations can be done. Using this attribute in that scenarios is not valid.
/// </para>
/// </remarks>
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)]
public sealed class D2DRequiresDoublePrecisionSupportAttribute : Attribute;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ internal abstract record HlslBytecodeInfo
/// A successfully compiled HLSL shader.
/// </summary>
/// <param name="Bytecode">The resulting HLSL bytecode.</param>
public sealed record Success(EquatableArray<byte> Bytecode) : HlslBytecodeInfo;
public sealed record Success(
#if D3D12_GENERATOR
EquatableArray<byte> Bytecode)
#else

#pragma warning disable CS1573
EquatableArray<byte> Bytecode,
bool RequiresDoublePrecisionSupport) // Whether the shader requires support for double precision operations.
#pragma warning restore CS1573
#endif
: HlslBytecodeInfo;

/// <summary>
/// An HLSL shader that failed to compile due to a Win32 error.
Expand Down
20 changes: 14 additions & 6 deletions src/ComputeSharp.SourceGeneration/Models/DiagnosticInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ public Diagnostic ToDiagnostic()
return Diagnostic.Create(Descriptor, null, Arguments.ToArray());
}

/// <summary>
/// Creates a new <see cref="DiagnosticInfo"/> instance with the specified parameters.
/// </summary>
/// <param name="descriptor">The input <see cref="DiagnosticDescriptor"/> for the diagnostics to create.</param>
/// <param name="location">The location to use for the diagnostic.</param>
/// <param name="args">The optional arguments for the formatted message to include.</param>
/// <returns>A new <see cref="DiagnosticInfo"/> instance with the specified parameters.</returns>
public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location? location, params object[] args)
{
return new(descriptor, location?.SourceTree, location?.SourceSpan ?? default, args.Select(static arg => arg.ToString()).ToImmutableArray());
}

/// <summary>
/// Creates a new <see cref="DiagnosticInfo"/> instance with the specified parameters.
/// </summary>
Expand All @@ -42,9 +54,7 @@ public Diagnostic ToDiagnostic()
/// <returns>A new <see cref="DiagnosticInfo"/> instance with the specified parameters.</returns>
public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, ISymbol symbol, params object[] args)
{
Location location = symbol.Locations.First();

return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray());
return Create(descriptor, symbol.Locations.First(), args);
}

/// <summary>
Expand All @@ -56,8 +66,6 @@ public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, ISymbol sym
/// <returns>A new <see cref="DiagnosticInfo"/> instance with the specified parameters.</returns>
public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, SyntaxNode node, params object[] args)
{
Location location = node.GetLocation();

return new(descriptor, location.SourceTree, location.SourceSpan, args.Select(static arg => arg.ToString()).ToImmutableArray());
return Create(descriptor, node.GetLocation(), args);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>$(DefineConstants);D3D12_GENERATOR</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand Down
Loading

0 comments on commit d98e57d

Please sign in to comment.