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; /// /// An implementation of the class that uses the DirectX 11 shader compiler from SharpDX /// // ReSharper disable once InconsistentNaming public sealed partial class DX11ShaderCompiler : ShaderCompiler { public Device Device { get; set; } protected override bool CompileShaderFromSource(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(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; } /// /// 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 /// [GeneratedRegex(@"(.*?)\((.*)\):(.*)")] private static partial Regex ShaderErrorPatternRegex(); private static readonly IReadOnlyDictionary > _shaderConstructors = new Dictionary>() { { 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 _shaderProfiles = new Dictionary() { { 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 AvailableResourcePackages { get; } public SymbolPackage Package { get; } public event Action Disposing; } }