2015-11-26 16:28:01 +00:00
|
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel;
|
2015-11-29 00:19:04 +00:00
|
|
|
|
using System.Diagnostics;
|
2015-11-26 16:28:01 +00:00
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
|
|
|
|
namespace shader_playground {
|
|
|
|
|
public partial class Editor : Form {
|
2015-11-29 00:19:04 +00:00
|
|
|
|
string compilerPath_ = @"..\..\..\..\..\build\bin\Windows\Debug\xenia-gpu-shader-compiler.exe";
|
|
|
|
|
|
|
|
|
|
FileSystemWatcher compilerWatcher_;
|
2015-11-30 00:55:42 +00:00
|
|
|
|
bool pendingTimer_ = false;
|
2015-11-29 00:19:04 +00:00
|
|
|
|
|
2015-11-26 16:28:01 +00:00
|
|
|
|
public Editor() {
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
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_)) {
|
2015-11-30 00:55:42 +00:00
|
|
|
|
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();
|
|
|
|
|
});
|
2015-11-29 00:19:04 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
compilerWatcher_.EnableRaisingEvents = true;
|
2015-11-26 16:28:01 +00:00
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
wordsTextBox.Click += (object sender, EventArgs e) => {
|
|
|
|
|
wordsTextBox.SelectAll();
|
|
|
|
|
wordsTextBox.Copy();
|
|
|
|
|
};
|
2015-11-26 16:28:01 +00:00
|
|
|
|
|
2015-11-30 00:55:42 +00:00
|
|
|
|
sourceCodeTextBox.Click += (object sender, EventArgs e) => {
|
2015-11-29 00:19:04 +00:00
|
|
|
|
Process(sourceCodeTextBox.Text);
|
|
|
|
|
};
|
|
|
|
|
sourceCodeTextBox.TextChanged += (object sender, EventArgs e) => {
|
|
|
|
|
Process(sourceCodeTextBox.Text);
|
|
|
|
|
};
|
2015-11-26 16:28:01 +00:00
|
|
|
|
|
2015-11-30 00:55:42 +00:00
|
|
|
|
translationComboBox.SelectedIndex = 0;
|
|
|
|
|
translationComboBox.SelectedIndexChanged += (object sender, EventArgs e) => {
|
|
|
|
|
Process(sourceCodeTextBox.Text);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sourceCodeTextBox.Text = string.Join(
|
2015-11-29 00:19:04 +00:00
|
|
|
|
"\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",
|
2015-11-30 00:55:42 +00:00
|
|
|
|
" mad oC0._, r0, r1.yyyy, c0",
|
|
|
|
|
" mad oC0.x1_, r0, r1.yyyy, c0",
|
|
|
|
|
" mad oC0.x10w, r0, r1.yyyy, c0",
|
2015-11-29 00:19:04 +00:00
|
|
|
|
" 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",
|
2015-11-30 00:55:42 +00:00
|
|
|
|
" mul r4.xyz, r[aL+1].xyzz, c[8 + a0].xyzz",
|
|
|
|
|
" adds r5.w, r[aL+0].zx",
|
2015-11-29 00:19:04 +00:00
|
|
|
|
" 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",
|
|
|
|
|
});
|
2015-11-26 16:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class NopIncludeHandler : CompilerIncludeHandler {
|
|
|
|
|
public override Stream Open(CompilerIncludeHandlerType includeType,
|
|
|
|
|
string filename) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
void Process(string shaderSourceCode) {
|
2015-11-26 16:28:01 +00:00
|
|
|
|
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;
|
2015-11-29 00:19:04 +00:00
|
|
|
|
compilerUcodeTextBox.Text = "";
|
|
|
|
|
wordsTextBox.Text = "";
|
2015-11-26 16:28:01 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var prefix = disassembledSourceCode.Substring(
|
2015-11-29 00:19:04 +00:00
|
|
|
|
0, disassembledSourceCode.IndexOf(
|
|
|
|
|
':', disassembledSourceCode.IndexOf(':') + 1));
|
2015-11-26 16:28:01 +00:00
|
|
|
|
disassembledSourceCode =
|
2015-11-29 00:19:04 +00:00
|
|
|
|
disassembledSourceCode.Replace(prefix + ": ", "");
|
2015-11-26 16:28:01 +00:00
|
|
|
|
disassembledSourceCode = disassembledSourceCode.Replace(
|
|
|
|
|
"// PDB hint 00000000-00000000-00000000\r\n", "");
|
|
|
|
|
var firstLine = disassembledSourceCode.IndexOf("//");
|
2015-11-29 00:19:04 +00:00
|
|
|
|
var warnings = "// " +
|
|
|
|
|
disassembledSourceCode.Substring(0, firstLine)
|
|
|
|
|
.Replace("\r\n", "\r\n// ");
|
|
|
|
|
disassembledSourceCode =
|
|
|
|
|
warnings + disassembledSourceCode.Substring(firstLine + 3);
|
2015-11-26 16:28:01 +00:00
|
|
|
|
disassembledSourceCode = disassembledSourceCode.Trim();
|
|
|
|
|
outputTextBox.Text = disassembledSourceCode;
|
2015-11-29 00:19:04 +00:00
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
2015-11-26 16:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
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");
|
2015-11-30 00:55:42 +00:00
|
|
|
|
string translatedDisasmPath = Path.Combine(Path.GetTempPath(), "shader_playground_disasm.translated.txt");
|
2015-11-29 00:19:04 +00:00
|
|
|
|
if (File.Exists(ucodePath)) {
|
|
|
|
|
File.Delete(ucodePath);
|
|
|
|
|
}
|
|
|
|
|
if (File.Exists(ucodeDisasmPath)) {
|
|
|
|
|
File.Delete(ucodeDisasmPath);
|
|
|
|
|
}
|
2015-11-30 00:55:42 +00:00
|
|
|
|
if (File.Exists(translatedDisasmPath)) {
|
|
|
|
|
File.Delete(translatedDisasmPath);
|
2015-11-29 00:19:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-30 00:55:42 +00:00
|
|
|
|
string outputType;
|
|
|
|
|
switch (translationComboBox.SelectedIndex) {
|
|
|
|
|
default:
|
|
|
|
|
case 0:
|
|
|
|
|
outputType = "spirvtext";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
startInfo = new ProcessStartInfo(compilerPath_);
|
|
|
|
|
startInfo.Arguments = string.Join(" ", new string[]{
|
|
|
|
|
"--shader_input=" + ucodePath,
|
|
|
|
|
"--shader_input_type=" + shaderType,
|
2015-11-30 00:55:42 +00:00
|
|
|
|
"--shader_output=" + translatedDisasmPath,
|
|
|
|
|
"--shader_output_type=" + outputType,
|
2015-11-29 00:19:04 +00:00
|
|
|
|
});
|
|
|
|
|
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
|
|
|
startInfo.CreateNoWindow = true;
|
|
|
|
|
try {
|
|
|
|
|
using (var process = System.Diagnostics.Process.Start(startInfo)) {
|
|
|
|
|
process.WaitForExit();
|
|
|
|
|
}
|
2015-11-30 00:55:42 +00:00
|
|
|
|
string disasmText = File.ReadAllText(translatedDisasmPath);
|
2015-11-29 00:19:04 +00:00
|
|
|
|
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) {
|
2015-11-26 16:28:01 +00:00
|
|
|
|
if (shaderCode == null || shaderCode.Length == 0) {
|
|
|
|
|
wordsTextBox.Text = "";
|
2015-11-29 00:19:04 +00:00
|
|
|
|
return null;
|
2015-11-26 16:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-29 00:19:04 +00:00
|
|
|
|
// 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);
|
2015-11-26 16:28:01 +00:00
|
|
|
|
for (int i = 0; i < swappedCode.Length; ++i) {
|
|
|
|
|
swappedCode[i] = SwapBytes(swappedCode[i]);
|
|
|
|
|
}
|
|
|
|
|
var sb = new StringBuilder();
|
2015-11-29 00:19:04 +00:00
|
|
|
|
sb.Append("const uint32_t shader_dwords[] = {");
|
2015-11-26 16:28:01 +00:00
|
|
|
|
for (int i = 0; i < swappedCode.Length; ++i) {
|
|
|
|
|
sb.AppendFormat("0x{0:X8}, ", swappedCode[i]);
|
|
|
|
|
}
|
2015-11-29 00:19:04 +00:00
|
|
|
|
sb.Append("};\r\n");
|
|
|
|
|
sb.Append("shader_type = ShaderType::" + (shaderType == "vs" ? "kVertex" : "kPixel") + ";\r\n");
|
2015-11-26 16:28:01 +00:00
|
|
|
|
wordsTextBox.Text = sb.ToString();
|
|
|
|
|
wordsTextBox.SelectAll();
|
2015-11-29 00:19:04 +00:00
|
|
|
|
|
|
|
|
|
return swappedCode;
|
2015-11-26 16:28:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint SwapBytes(uint x) {
|
|
|
|
|
return ((x & 0x000000ff) << 24) +
|
|
|
|
|
((x & 0x0000ff00) << 8) +
|
|
|
|
|
((x & 0x00ff0000) >> 8) +
|
|
|
|
|
((x & 0xff000000) >> 24);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|