using System; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using SharpDX.D3DCompiler; namespace Blacklight.SharpDX9.PerformanceTests { /// /// Compiles the main pixel shader the way the backend does and reports its instruction and register counts, the /// numbers a GPU's occupancy and the ps_3_0 slot limit depend on. /// public static class ShaderStats { public const string Argument = "--shader-stats"; /// /// Gets the text of the main shader with its includes expanded. /// public static string PreprocessedMainShader() { Assembly assembly = typeof(Module).Assembly; string name = Module.ShaderNamespace + ".Main.fx"; using (Stream stream = assembly.GetManifestResourceStream(name)) using (var reader = new StreamReader(stream)) { return ShaderBytecode.Preprocess(reader.ReadToEnd(), null, new Module.IncludeHandler(), out string _); } } /// /// Gets the disassembly of one entry point of the main shader. /// public static string Disassemble(string source, string entryPoint, string profile) { // D3DX9's compiler, the one production D3D9 shaders go through. using (SharpDX.Direct3D9.ShaderBytecode compiled = SharpDX.Direct3D9.ShaderBytecode.Compile( source, entryPoint, profile, SharpDX.Direct3D9.ShaderFlags.OptimizationLevel3)) { byte[] bytes = compiled.Data.ReadRange((int)compiled.Data.Length); return new ShaderBytecode(bytes).Disassemble(DisassemblyFlags.None); } } /// /// Gets the highest temporary register a disassembly uses, plus one. /// public static int TemporaryRegisterCount(string disassembly) { int highest = -1; foreach (Match match in Regex.Matches(disassembly, @"\br(\d+)\b")) { highest = Math.Max(highest, int.Parse(match.Groups[1].Value)); } return highest + 1; } /// /// Writes the full disassembly of each entry point to a file in `directory`. /// public static int Dump(string directory) { string source = PreprocessedMainShader(); foreach ((string entryPoint, string profile) in new[] { ("MainPS", "ps_3_0"), ("MainVS", "vs_3_0") }) { string path = Path.Combine(directory, entryPoint + ".asm"); File.WriteAllText(path, Disassemble(source, entryPoint, profile)); } return 0; } public static int Run() { string source = PreprocessedMainShader(); foreach ((string entryPoint, string profile) in new[] { ("MainPS", "ps_3_0"), ("MainVS", "vs_3_0") }) { string disassembly = Disassemble(source, entryPoint, profile); Match slots = Regex.Match(disassembly, @"approximately (\d+) instruction slots used[^\r\n]*"); Console.WriteLine($"{entryPoint}: {slots.Value}"); Console.WriteLine($"{entryPoint}: {TemporaryRegisterCount(disassembly)} temporary registers"); } return 0; } } }