xenia/tools/shader-playground/Editor.cs

319 lines
11 KiB
C#
Raw Normal View History

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.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 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(
"\r\n", 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", "\r\n");
if (disassembledSourceCode.IndexOf("// PDB hint 00000000-00000000-00000000") == -1) {
outputTextBox.Text = disassembledSourceCode;
compilerUcodeTextBox.Text = "";
wordsTextBox.Text = "";
return;
}
var prefix = disassembledSourceCode.Substring(
0, disassembledSourceCode.IndexOf(
':', disassembledSourceCode.IndexOf(':') + 1));
disassembledSourceCode =
disassembledSourceCode.Replace(prefix + ": ", "");
disassembledSourceCode = disassembledSourceCode.Replace(
"// PDB hint 00000000-00000000-00000000\r\n", "");
var firstLine = disassembledSourceCode.IndexOf("//");
var warnings = "// " +
disassembledSourceCode.Substring(0, firstLine)
.Replace("\r\n", "\r\n// ");
disassembledSourceCode =
warnings + disassembledSourceCode.Substring(firstLine + 3);
disassembledSourceCode = disassembledSourceCode.Trim();
outputTextBox.Text = disassembledSourceCode;
string shaderType =
shaderSourceCode.IndexOf("xvs_") == -1 ? "ps" : "vs";
var ucodeWords = ExtractAndDumpWords(shaderType, compiledShader.GetShaderCode());
if (ucodeWords != null) {
TryCompiler(shaderType, ucodeWords);
} else {
compilerUcodeTextBox.Text = "";
}
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_)) {
compilerUcodeTextBox.Text = "Compiler not found: " + compilerPath_;
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);
compilerUcodeTextBox.Text = disasmText;
} catch {
compilerUcodeTextBox.Text = "COMPILER FAILURE";
}
string outputType;
switch (translationComboBox.SelectedIndex) {
default:
case 0:
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);
compilerTranslatedTextBox.Text = disasmText;
} catch {
compilerTranslatedTextBox.Text = "COMPILER FAILURE";
}
}
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;
}
}
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) {
wordsTextBox.Text = "";
return null;
}
// Find shader code.
int byteOffset = (shaderCode[4] << 24) | (shaderCode[5] << 16) |
(shaderCode[6] << 8) | (shaderCode[7] << 0);
int wordOffset = byteOffset / 4;
uint[] swappedCode = new uint[(shaderCode.Length - wordOffset) / sizeof(uint)];
Buffer.BlockCopy(shaderCode, wordOffset * 4, swappedCode, 0, shaderCode.Length - wordOffset * 4);
for (int i = 0; i < swappedCode.Length; ++i) {
swappedCode[i] = SwapBytes(swappedCode[i]);
}
var sb = new StringBuilder();
sb.Append("const uint32_t shader_dwords[] = {");
for (int i = 0; i < swappedCode.Length; ++i) {
sb.AppendFormat("0x{0:X8}, ", swappedCode[i]);
}
sb.Append("};\r\n");
sb.Append("shader_type = ShaderType::" + (shaderType == "vs" ? "kVertex" : "kPixel") + ";\r\n");
wordsTextBox.Text = sb.ToString();
wordsTextBox.SelectAll();
return swappedCode;
}
uint SwapBytes(uint x) {
return ((x & 0x000000ff) << 24) +
((x & 0x0000ff00) << 8) +
((x & 0x00ff0000) >> 8) +
((x & 0xff000000) >> 24);
}
}
}