using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace shader_playground { public partial class Editor : Form { string compilerPath_ = @"..\..\..\..\..\build\bin\Windows\Debug\xenia-gpu-shader-compiler.exe"; FileSystemWatcher compilerWatcher_; bool pendingTimer_ = false; public Editor() { InitializeComponent(); var scrollUpdateTimer = new Timer(); scrollUpdateTimer.Interval = 200; scrollUpdateTimer.Tick += (object sender, EventArgs e) => { UpdateScrollStates(); }; scrollUpdateTimer.Start(); var compilerBinPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetDirectoryName(compilerPath_)); compilerWatcher_ = new FileSystemWatcher(compilerBinPath, "*.exe"); compilerWatcher_.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size; compilerWatcher_.Changed += (object sender, FileSystemEventArgs e) => { if (e.Name == Path.GetFileName(compilerPath_)) { Invoke((MethodInvoker)delegate { if (pendingTimer_) { return; } pendingTimer_ = true; var timer = new Timer(); timer.Interval = 1000; timer.Tick += (object sender1, EventArgs e1) => { pendingTimer_ = false; timer.Dispose(); Process(sourceCodeTextBox.Text); }; timer.Start(); }); } }; compilerWatcher_.EnableRaisingEvents = true; wordsTextBox.Click += (object sender, EventArgs e) => { wordsTextBox.SelectAll(); wordsTextBox.Copy(); }; sourceCodeTextBox.Click += (object sender, EventArgs e) => { Process(sourceCodeTextBox.Text); }; sourceCodeTextBox.TextChanged += (object sender, EventArgs e) => { Process(sourceCodeTextBox.Text); }; translationComboBox.SelectedIndex = 0; translationComboBox.SelectedIndexChanged += (object sender, EventArgs e) => { Process(sourceCodeTextBox.Text); }; sourceCodeTextBox.Text = string.Join( Environment.NewLine, new string[] { "xps_3_0", "dcl_texcoord1 r0", "dcl_color r1.xy", "exec", "alloc colors", "exec", "tfetch1D r2, r0.y, tf0, FetchValidOnly=false", "tfetch1D r2, r0.x, tf2", "tfetch2D r3, r3.wx, tf13", "tfetch2D r[aL+3], r[aL+5].wx, tf13, FetchValidOnly=false, UnnormalizedTextureCoords=true, MagFilter=linear, MinFilter=linear, MipFilter=point, AnisoFilter=max1to1, UseRegisterGradients=true, UseComputedLOD=false, UseRegisterLOD=true, OffsetX=-1.5, OffsetY=1.0", "tfetch3D r31.w_01, r0.xyw, tf15", "tfetchCube r5, r1.xyw, tf31", " setTexLOD r1.z", " setGradientH r1.zyx", "(!p0) setGradientV r1.zyx", " getGradients r5, r1.xy, tf3", " mad oC0, r0, r1.yyyy, c0", " mad oC0._, r0, r1.yyyy, c0", " mad oC0.x1_, r0, r1.yyyy, c0", " mad oC0.x10w, r0, r1.yyyy, c0", " mul r4.xyz, r1.xyzz, c5.xyzz", " mul r4.xyz, r1.xyzz, c[0 + aL].xyzz", " mul r4.xyz, r1.xyzz, c[6 + aL].xyzz", " mul r4.xyz, r1.xyzz, c[0 + a0].xyzz", " mul r4.xyz, r1.xyzz, c[8 + a0].xyzz", " + adds r5.w, r0.xz", " cos r6.w, r0.x", " adds r5.w, r0.zx", " mul r4.xyz, r[aL+1].xyzz, c[8 + a0].xyzz", " adds r5.w, r[aL+0].zx", " jmp l5", "ccall b1, l5", "nop", " label l5", "(!p0) exec", "cexec b5, Yield=true", "cexec !b6", " mulsc r3.w, c1.z, r1.w", "loop i7, L4", " label L3", " exec", " setp_eq r15, c[aL].w", " (!p0) add r0, r0, c[aL]", "(p0) endloop i7, L3", "label L4", "exece", " mulsc r3.w, c3.z, r6.x", " mulsc r3.w, c200.z, r31.x", " mov oDepth.x, c3.w", " cnop", }); } class NopIncludeHandler : CompilerIncludeHandler { public override Stream Open(CompilerIncludeHandlerType includeType, string filename) { throw new NotImplementedException(); } } void Process(string shaderSourceCode) { shaderSourceCode += "\ncnop"; shaderSourceCode += "\ncnop"; var preprocessorDefines = new CompilerMacro[2]; preprocessorDefines[0].Name = "XBOX"; preprocessorDefines[0].Name = "XBOX360"; var includeHandler = new NopIncludeHandler(); var options = CompilerOptions.None; var compiledShader = ShaderCompiler.AssembleFromSource( shaderSourceCode, preprocessorDefines, includeHandler, options, Microsoft.Xna.Framework.TargetPlatform.Xbox360); var disassembledSourceCode = compiledShader.ErrorsAndWarnings; disassembledSourceCode = disassembledSourceCode.Replace("\n", Environment.NewLine); if (disassembledSourceCode.IndexOf("// PDB hint 00000000-00000000-00000000") == -1) { UpdateTextBox(outputTextBox, disassembledSourceCode, false); UpdateTextBox(compilerUcodeTextBox, "", false); UpdateTextBox(wordsTextBox, "", false); return; } var prefix = disassembledSourceCode.Substring( 0, disassembledSourceCode.IndexOf( ':', disassembledSourceCode.IndexOf(':') + 1)); disassembledSourceCode = disassembledSourceCode.Replace(prefix + ": ", ""); disassembledSourceCode = disassembledSourceCode.Replace( "// PDB hint 00000000-00000000-00000000" + Environment.NewLine, ""); var firstLine = disassembledSourceCode.IndexOf("//"); var warnings = "// " + disassembledSourceCode.Substring(0, firstLine) .Replace(Environment.NewLine, Environment.NewLine + "// "); disassembledSourceCode = warnings + disassembledSourceCode.Substring(firstLine + 3); disassembledSourceCode = disassembledSourceCode.Trim(); UpdateTextBox(outputTextBox, disassembledSourceCode, true); string shaderType = shaderSourceCode.IndexOf("xvs_") == -1 ? "ps" : "vs"; var ucodeWords = ExtractAndDumpWords(shaderType, compiledShader.GetShaderCode()); if (ucodeWords != null) { TryCompiler(shaderType, ucodeWords); } else { UpdateTextBox(compilerUcodeTextBox, "", false); } if (compilerUcodeTextBox.Text.Length > 0) { var sourcePrefix = disassembledSourceCode.Substring(0, disassembledSourceCode.IndexOf("/*")); TryRoundTrip(sourcePrefix, compilerUcodeTextBox.Text, compiledShader.GetShaderCode()); } } void TryCompiler(string shaderType, uint[] ucodeWords) { string ucodePath = Path.Combine(Path.GetTempPath(), "shader_playground_ucode.bin." + shaderType); string ucodeDisasmPath = Path.Combine(Path.GetTempPath(), "shader_playground_disasm.ucode.txt"); string translatedDisasmPath = Path.Combine(Path.GetTempPath(), "shader_playground_disasm.translated.txt"); if (File.Exists(ucodePath)) { File.Delete(ucodePath); } if (File.Exists(ucodeDisasmPath)) { File.Delete(ucodeDisasmPath); } if (File.Exists(translatedDisasmPath)) { File.Delete(translatedDisasmPath); } byte[] ucodeBytes = new byte[ucodeWords.Length * 4]; Buffer.BlockCopy(ucodeWords, 0, ucodeBytes, 0, ucodeWords.Length * 4); File.WriteAllBytes(ucodePath, ucodeBytes); if (!File.Exists(compilerPath_)) { UpdateTextBox(compilerUcodeTextBox, "Compiler not found: " + compilerPath_, false); return; } var startInfo = new ProcessStartInfo(compilerPath_); startInfo.Arguments = string.Join(" ", new string[]{ "--shader_input=" + ucodePath, "--shader_input_type=" + shaderType, "--shader_output=" + ucodeDisasmPath, "--shader_output_type=ucode", }); startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.CreateNoWindow = true; try { using (var process = System.Diagnostics.Process.Start(startInfo)) { process.WaitForExit(); } string disasmText = File.ReadAllText(ucodeDisasmPath); UpdateTextBox(compilerUcodeTextBox, disasmText.Replace("\n", Environment.NewLine), true); } catch { UpdateTextBox(compilerUcodeTextBox, "COMPILER FAILURE", false); } string outputType; switch (translationComboBox.SelectedIndex) { default: case 0: outputType = "glsl45"; break; case 1: outputType = "spirvtext"; break; } startInfo = new ProcessStartInfo(compilerPath_); startInfo.Arguments = string.Join(" ", new string[]{ "--shader_input=" + ucodePath, "--shader_input_type=" + shaderType, "--shader_output=" + translatedDisasmPath, "--shader_output_type=" + outputType, }); startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.CreateNoWindow = true; try { using (var process = System.Diagnostics.Process.Start(startInfo)) { process.WaitForExit(); } string disasmText = File.ReadAllText(translatedDisasmPath); UpdateTextBox(compilerTranslatedTextBox, disasmText.Replace("\n", Environment.NewLine), true); } catch { UpdateTextBox(compilerTranslatedTextBox, "COMPILER FAILURE", false); } } void TryRoundTrip(string sourcePrefix, string compilerSource, byte[] expectedBytes) { var shaderSourceCode = sourcePrefix + compilerSource; var preprocessorDefines = new CompilerMacro[2]; preprocessorDefines[0].Name = "XBOX"; preprocessorDefines[0].Name = "XBOX360"; var includeHandler = new NopIncludeHandler(); var options = CompilerOptions.None; var compiledShader = ShaderCompiler.AssembleFromSource( shaderSourceCode, preprocessorDefines, includeHandler, options, Microsoft.Xna.Framework.TargetPlatform.Xbox360); var compiledBytes = compiledShader.GetShaderCode(); if (compiledBytes == null || compiledBytes.Length != expectedBytes.Length || !MemCmp(compiledBytes, expectedBytes)) { compilerUcodeTextBox.BackColor = System.Drawing.Color.Red; } else { compilerUcodeTextBox.BackColor = System.Drawing.SystemColors.Control; } } void UpdateScrollStates() { foreach (var handle in scrollPreserve_.Keys) { if (scrollPreserve_[handle]) { var scrollInfo = new ScrollInfo(); scrollInfo.cbSize = Marshal.SizeOf(scrollInfo); scrollInfo.fMask = (uint)ScrollInfoMask.SIF_TRACKPOS; bool hasScrollInfo = GetScrollInfo(handle, SB_VERT, ref scrollInfo); scrollPositions_[handle] = scrollInfo.nTrackPos; } } } Dictionary scrollPreserve_ = new Dictionary(); Dictionary scrollPositions_ = new Dictionary(); void UpdateTextBox(TextBox textBox, string value, bool preserveScroll) { scrollPreserve_[textBox.Handle] = preserveScroll; textBox.Text = value; int previousScroll; if (!scrollPositions_.TryGetValue(textBox.Handle, out previousScroll)) { previousScroll = 0; } var scrollInfo = new ScrollInfo(); scrollInfo.cbSize = Marshal.SizeOf(scrollInfo); scrollInfo.fMask = (uint)ScrollInfoMask.SIF_TRACKPOS; scrollInfo.nTrackPos = previousScroll; SetScrollInfo(textBox.Handle, SB_VERT, ref scrollInfo, 1); var ptrWparam = new IntPtr(SB_THUMBPOSITION | previousScroll << 16); SendMessage(textBox.Handle, WM_VSCROLL, ptrWparam, IntPtr.Zero); } private const int SB_VERT = 1; private const uint SB_THUMBPOSITION = 4; private const uint WM_VSCROLL = 0x115; public enum ScrollInfoMask : uint { SIF_RANGE = 0x1, SIF_PAGE = 0x2, SIF_POS = 0x4, SIF_DISABLENOSCROLL = 0x8, SIF_TRACKPOS = 0x10, SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS), } private struct ScrollInfo { public int cbSize; public uint fMask; public int nMin; public int nMax; public int nPage; public int nPos; public int nTrackPos; } [DllImport("user32.dll", SetLastError = true)] private static extern bool GetScrollInfo(IntPtr hwnd, int bar, ref ScrollInfo scrollInfo); [DllImport("user32.dll", SetLastError = true)] private static extern bool SetScrollInfo(IntPtr hwnd, int bar, ref ScrollInfo scrollInfo, int redraw); [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); bool MemCmp(byte[] a1, byte[] b1) { if (a1 == null || b1 == null) { return false; } int length = a1.Length; if (b1.Length != length) { return false; } while (length > 0) { length--; if (a1[length] != b1[length]) { return false; } } return true; } uint[] ExtractAndDumpWords(string shaderType, byte[] shaderCode) { if (shaderCode == null || shaderCode.Length == 0) { UpdateTextBox(wordsTextBox, "", false); return null; } // Find shader code. int byteOffset = (shaderCode[4] << 24) | (shaderCode[5] << 16) | (shaderCode[6] << 8) | (shaderCode[7] << 0); int wordOffset = byteOffset / 4; uint[] shaderDwords = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)]; Buffer.BlockCopy(shaderCode, wordOffset * 4, shaderDwords, 0, shaderCode.Length - wordOffset * 4); var sb = new StringBuilder(); sb.Append("const uint32_t shader_dwords[] = {"); for (int i = 0; i < shaderDwords.Length; ++i) { sb.AppendFormat("0x{0:X8}, ", SwapByte(shaderDwords[i])); } sb.Append("};" + Environment.NewLine); sb.Append("shader_type = ShaderType::" + (shaderType == "vs" ? "kVertex" : "kPixel") + ";" + Environment.NewLine); UpdateTextBox(wordsTextBox, sb.ToString(), true); wordsTextBox.SelectAll(); return shaderDwords; } uint SwapByte(uint x) { return ((x & 0x000000ff) << 24) + ((x & 0x0000ff00) << 8) + ((x & 0x00ff0000) >> 8) + ((x & 0xff000000) >> 24); } } }