Files
2026-07-13 13:15:05 +08:00

197 lines
8.4 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using T3.Core.DataTypes;
using T3.Core.IO;
using T3.Core.Settings;
using T3.Core.Logging;
using T3.Core.Model;
using T3.Core.Resource.Assets;
using ComputeShader = SharpDX.Direct3D11.ComputeShader;
using GeometryShader = SharpDX.Direct3D11.GeometryShader;
using PixelShader = SharpDX.Direct3D11.PixelShader;
using VertexShader = SharpDX.Direct3D11.VertexShader;
namespace T3.Core.Resource.ShaderCompiling;
/// <summary>
/// An implementation of the <see cref="ShaderCompiler"/> class that uses the DirectX 11 shader compiler from SharpDX
/// </summary>
// ReSharper disable once InconsistentNaming
public sealed partial class DX11ShaderCompiler : ShaderCompiler
{
public Device Device { get; set; }
protected override bool CompileShaderFromSource<TShader>(ShaderCompilationArgs args, out byte[] blob, out string errorMessage)
{
CompilationResult compilationResult = null;
string resultMessage;
bool success;
var profile = _shaderProfiles[typeof(TShader)];
try
{
ShaderFlags flags = ShaderFlags.None;
#if DEBUG || FORCE_SHADER_DEBUG
flags |= ShaderFlags.Debug;
#endif
if (CoreSettings.Config.SkipOptimization)
{
flags |= ShaderFlags.SkipOptimization;
}
compilationResult = ShaderBytecode.Compile(args.SourceCode,
args.EntryPoint,
profile,
flags,
EffectFlags.None,
null,
new IncludeHandler(args.Owner));
success = compilationResult.ResultCode == Result.Ok;
resultMessage = compilationResult.Message;
}
catch (Exception ce)
{
success = false;
resultMessage = ce.Message;
}
if (success)
{
blob = compilationResult.Bytecode.Data;
errorMessage = string.Empty;
}
else
{
resultMessage = ExtractMeaningfulShaderErrorMessage(resultMessage);
errorMessage = resultMessage;
blob = null;
}
return success;
}
protected override void CreateShaderInstance<TShader>(string name, in byte[] blob, out TShader shader)
{
// As shader type is generic we've to use Activator and PropertyInfo to create/set the shader object
var shaderType = typeof(TShader);
shader = (TShader)_shaderConstructors[shaderType].Invoke(Device, blob);
var debugNameInfo = shaderType.GetProperty("DebugName");
debugNameInfo?.SetValue(shader, name);
}
public static string ExtractMeaningfulShaderErrorMessage(string message)
{
var lines = message.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
(string ShaderName, string LineNumber, string ErrorMessage)? firstWarning = null;
foreach (var line in lines)
{
var shaderErrorMatch = ShaderErrorPatternRegex().Match(line);
if (shaderErrorMatch.Success)
{
var shaderName = shaderErrorMatch.Groups[1].Value;
var lineNumber = shaderErrorMatch.Groups[2].Value;
var errorMessage = shaderErrorMatch.Groups[3].Value.Trim();
if (errorMessage.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0)
return $"Line {lineNumber}: {errorMessage}\n\n{shaderName}";
if (firstWarning == null && errorMessage.IndexOf("warning", StringComparison.OrdinalIgnoreCase) >= 0)
firstWarning = (shaderName, lineNumber, errorMessage);
continue;
}
// Some compiler lines don't contain file/line info. Still prefer an error if one is present.
if (line.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0)
return line.Trim();
if (firstWarning == null && line.IndexOf("warning", StringComparison.OrdinalIgnoreCase) >= 0)
firstWarning = (string.Empty, string.Empty, line.Trim());
}
if (firstWarning != null)
{
if (!string.IsNullOrEmpty(firstWarning.Value.ShaderName))
return $"Line {firstWarning.Value.LineNumber}: {firstWarning.Value.ErrorMessage}\n\n{firstWarning.Value.ShaderName}";
return firstWarning.Value.ErrorMessage;
}
return message;
}
/// <summary>
/// Matches errors like....
///
/// Failed to compile shader 'ComputeWobble': C:\Users\pixtur\coding\t3\Resources\compute-ColorGrade.hlsl(32,12-56): warning X3206: implicit truncation of vector type
/// </summary>
[GeneratedRegex(@"(.*?)\((.*)\):(.*)")]
private static partial Regex ShaderErrorPatternRegex();
private static readonly IReadOnlyDictionary<Type, Func<Device, byte[], AbstractShader> > _shaderConstructors = new Dictionary<Type, Func<Device, byte[], AbstractShader>>()
{
{ typeof(T3.Core.DataTypes.VertexShader), (device, data) => new DataTypes.VertexShader(new VertexShader(device, data, null), data) },
{ typeof(T3.Core.DataTypes.PixelShader), (device, data) => new DataTypes.PixelShader(new PixelShader(device, data, null), data) },
{ typeof(T3.Core.DataTypes.ComputeShader), (device, data) => new DataTypes.ComputeShader(new ComputeShader(device, data, null), data) },
{ typeof(T3.Core.DataTypes.GeometryShader), (device, data) => new DataTypes.GeometryShader(new GeometryShader(device, data, null), data) },
};
private static readonly IReadOnlyDictionary<Type, string> _shaderProfiles = new Dictionary<Type, string>()
{
{ typeof(T3.Core.DataTypes.VertexShader), "vs_5_0" },
{ typeof(T3.Core.DataTypes.PixelShader), "ps_5_0" },
{ typeof(T3.Core.DataTypes.ComputeShader), "cs_5_0" },
{ typeof(T3.Core.DataTypes.GeometryShader), "gs_5_0" },
};
private sealed class IncludeHandler : SharpDX.D3DCompiler.Include, IResourceConsumer
{
private StreamReader _streamReader;
private readonly IResourceConsumer _owner;
public IncludeHandler(IResourceConsumer owner)
{
_owner = owner;
}
public void Dispose()
{
Disposing?.Invoke(this); // Notify subscribers
_streamReader?.Dispose();
}
public IDisposable Shadow { get; set; }
public Stream Open(IncludeType type, string sharedIncludeFileName, Stream parentStream)
{
if(TryResolveSharedIncludeAsset(sharedIncludeFileName, out var asset))
{
_streamReader = new StreamReader(asset.FullPath);
return _streamReader.BaseStream;
}
Log.Error($"Could not locate include file '{sharedIncludeFileName}'");
_streamReader = null;
return null;
}
public void Close(Stream stream)
{
_streamReader.Close();
}
public IReadOnlyList<IResourcePackage> AvailableResourcePackages { get; }
public SymbolPackage Package { get; }
public event Action<IResourceConsumer> Disposing;
}
}