mirror of
https://github.com/OpenFusionProject/scripts.git
synced 2024-11-21 22:00:04 +00:00
Add dx2cg
This commit is contained in:
parent
08c7ec1278
commit
4ace5f065f
10
dx2cg/README.md
Normal file
10
dx2cg/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# dx2cg
|
||||
Tools for converting d3d9 shader assembly to HLSL/Cg.
|
||||
- `disassembler.py`: Takes in d3d9 assembly and gives back the HLSL equivalent.
|
||||
- `swapper.py`: Searches a shader file for d3d9 assembly and calls the disassembler to replace it with HLSL.
|
||||
- `main.py`: Executes the swapper on every file in a path, writing the changes to new files.
|
||||
|
||||
## Known issues
|
||||
- Only vertex shaders with profile `vs_1_1` are supported
|
||||
- No fragment shaders are supported yet
|
||||
- Only a limited set of instructions (those used by FF and Unity 2.6) are supported
|
283
dx2cg/disassembler.py
Normal file
283
dx2cg/disassembler.py
Normal file
@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
# d3d9 to cg crude dissassembler
|
||||
# ycc 08/08/2022
|
||||
|
||||
import re
|
||||
|
||||
legacy = False # True for 2.6
|
||||
|
||||
reserved = {
|
||||
"_Time",
|
||||
"_SinTime",
|
||||
"_CosTime",
|
||||
|
||||
"_ProjectionParams",
|
||||
|
||||
"_PPLAmbient",
|
||||
|
||||
"_ObjectSpaceCameraPos",
|
||||
"_ObjectSpaceLightPos0",
|
||||
"_ModelLightColor0",
|
||||
"_SpecularLightColor0",
|
||||
|
||||
"_Light2World0", "_World2Light0", "_Object2World", "_World2Object", "_Object2Light0",
|
||||
|
||||
"_LightDirectionBias",
|
||||
"_LightPositionRange",
|
||||
}
|
||||
|
||||
decls = {
|
||||
"dcl_position": "float4 {0} = vardat.vertex;",
|
||||
"dcl_normal": "float4 {0} = float4(vardat.normal.x, vardat.normal.y, vardat.normal.z, 0);",
|
||||
"dcl_texcoord0": "float4 {0} = vardat.texcoord;",
|
||||
"dcl_texcoord1": "float4 {0} = vardat.texcoord1;",
|
||||
"dcl_color": "float4 {0} = vardat.color;",
|
||||
"def": "const float4 {0} = float4({1}, {2}, {3}, {4});",
|
||||
}
|
||||
|
||||
ops = {
|
||||
"mov": "{0} = {1};",
|
||||
"add": "{0} = {1} + {2};",
|
||||
"mul": "{0} = {1} * {2};",
|
||||
"mad": "{0} = {1} * {2} + {3};",
|
||||
"dp4": "{0} = dot((float4){1}, (float4){2});",
|
||||
"dp3": "{0} = dot((float3){1}, (float3){2});",
|
||||
"min": "{0} = min({1}, {2});",
|
||||
"max": "{0} = max({1}, {2});",
|
||||
"rsq": "{0} = rsqrt({1});",
|
||||
"frc": "{0} = float4({1}.x - (float)floor({1}.x), {1}.y - (float)floor({1}.y), {1}.z - (float)floor({1}.z), {1}.w - (float)floor({1}.w));",
|
||||
"slt": "{0} = float4(({1}.x < {2}.x) ? 1.0f : 0.0f, ({1}.y < {2}.y) ? 1.0f : 0.0f, ({1}.z < {2}.z) ? 1.0f : 0.0f, ({1}.w < {2}.w) ? 1.0f : 0.0f);",
|
||||
"sge": "{0} = float4(({1}.x >= {2}.x) ? 1.0f : 0.0f, ({1}.y >= {2}.y) ? 1.0f : 0.0f, ({1}.z >= {2}.z) ? 1.0f : 0.0f, ({1}.w >= {2}.w) ? 1.0f : 0.0f);",
|
||||
"rcp": "{0} = ({1} == 0.0f) ? FLT_MAX : (({1} == 1.0f) ? {0} : (1 / {1}));",
|
||||
}
|
||||
|
||||
struct_appdata = """struct appdata {
|
||||
\tfloat4 vertex : POSITION;
|
||||
\tfloat3 normal : NORMAL;
|
||||
\tfloat4 texcoord : TEXCOORD0;
|
||||
\tfloat4 texcoord1 : TEXCOORD1;
|
||||
\tfloat4 tangent : TANGENT;
|
||||
\tfloat4 color : COLOR;
|
||||
};
|
||||
"""
|
||||
|
||||
v2f_postype = "POSITION" if legacy else "SV_POSITION"
|
||||
struct_v2f = f"""struct v2f {{
|
||||
\tfloat4 pos : {v2f_postype};
|
||||
\tfloat4 t0 : TEXCOORD0;
|
||||
\tfloat4 t1 : TEXCOORD1;
|
||||
\tfloat4 t2 : TEXCOORD2;
|
||||
\tfloat4 t3 : TEXCOORD3;
|
||||
\tfloat fog : FOG;
|
||||
\tfloat4 d0 : COLOR0;
|
||||
\tfloat4 d1 : COLOR1;
|
||||
}};
|
||||
"""
|
||||
|
||||
cg_header = """CGPROGRAM
|
||||
#include "UnityCG.cginc"
|
||||
#pragma exclude_renderers xbox360 ps3 gles
|
||||
"""
|
||||
|
||||
cg_footer = """ENDCG"""
|
||||
|
||||
vertex_header = """v2f vert(appdata vardat) {
|
||||
\tfloat4 r0, r1, r2, r3, r4;
|
||||
\tfloat4 tmp;
|
||||
\tv2f o;
|
||||
"""
|
||||
|
||||
vertex_footer = """\treturn o;
|
||||
}"""
|
||||
|
||||
def process_header(prog):
|
||||
keywords = []
|
||||
header = []
|
||||
loctab = {}
|
||||
locdecl = []
|
||||
binds = []
|
||||
i = 0
|
||||
lighting = False
|
||||
while i < len(prog):
|
||||
line = prog[i]
|
||||
if line.startswith("Keywords"):
|
||||
keywords = re.findall("\"[\w\d]+\"", line)
|
||||
del prog[i]
|
||||
i = i - 1
|
||||
elif line.startswith("Bind"):
|
||||
binds.append(line)
|
||||
del prog[i]
|
||||
i = i - 1
|
||||
elif line.startswith("Local") or line.startswith("Matrix"):
|
||||
dec = line.split(' ')
|
||||
key = int(dec[1][:-1])
|
||||
if dec[2][0] == '[':
|
||||
# singleton
|
||||
val = dec[2][1:-1]
|
||||
if val[0] == '_' and val not in reserved:
|
||||
loctype = "float4" if dec[0] == "Local" else "float4x4"
|
||||
locdecl.append(f"{loctype} {val};")
|
||||
elif dec[2][0] == '(':
|
||||
#components
|
||||
vals = dec[2][1:-1].split(',')
|
||||
for j, v in enumerate(vals):
|
||||
if v[0] == '[':
|
||||
vals[j] = v[1:-1]
|
||||
if vals[j][0] == '_' and vals[j] not in reserved:
|
||||
locdecl.append(f"float {vals[j]};")
|
||||
val = f"float4({vals[0]},{vals[1]},{vals[2]},{vals[3]})"
|
||||
|
||||
lightval = re.match("glstate_light(\d)_([a-zA-Z]+)", val)
|
||||
if lightval:
|
||||
val = f"glstate.light[{lightval[1]}].{lightval[2]}"
|
||||
lighting = True
|
||||
elif val == "glstate_lightmodel_ambient":
|
||||
val = "glstate.lightmodel.ambient"
|
||||
lighting = True
|
||||
elif val.startswith("glstate_matrix_texture"):
|
||||
val = f"glstate.matrix.texture[{val[-1]}]" if legacy else f"UNITY_MATRIX_TEXTURE{val[-1]}"
|
||||
elif val == "glstate_matrix_mvp":
|
||||
val = "glstate.matrix.mvp" if legacy else "UNITY_MATRIX_MVP"
|
||||
elif val == "glstate_matrix_modelview0":
|
||||
val = "glstate.matrix.modelview[0]" if legacy else "UNITY_MATRIX_MV"
|
||||
elif val == "glstate_matrix_transpose_modelview0":
|
||||
val = "glstate.matrix.transpose.modelview[0]" if legacy else "UNITY_MATRIX_T_MV"
|
||||
elif val == "glstate_matrix_invtrans_modelview0":
|
||||
val = "glstate.matrix.invtrans.modelview[0]" if legacy else "UNITY_MATRIX_IT_MV"
|
||||
elif val.startswith("glstate"):
|
||||
raise ValueError(f"Unrecognized glstate: {val}")
|
||||
|
||||
if dec[0] == "Local":
|
||||
loctab[key] = val
|
||||
elif dec[0] == "Matrix":
|
||||
for offset in range(0,4):
|
||||
loctab[key + offset] = f"{val}[{offset}]"
|
||||
|
||||
del prog[i]
|
||||
i = i - 1
|
||||
i = i + 1
|
||||
|
||||
if len(binds) > 0:
|
||||
header.append("BindChannels {")
|
||||
for b in binds:
|
||||
header.append(f"\t{b}")
|
||||
header.append("}")
|
||||
|
||||
if lighting:
|
||||
header.append("Lighting On")
|
||||
|
||||
return (keywords, header, loctab, locdecl)
|
||||
|
||||
def resolve_args(args, loctab, consts):
|
||||
for a in range(0, len(args)):
|
||||
arg = args[a]
|
||||
|
||||
neg = ""
|
||||
if arg[0] == '-':
|
||||
arg = arg[1:]
|
||||
neg = "-"
|
||||
|
||||
# save swizzler!
|
||||
dot = arg.find(".")
|
||||
if dot > -1:
|
||||
swiz = arg[dot:]
|
||||
arg = arg[:dot]
|
||||
else:
|
||||
swiz = ""
|
||||
|
||||
if arg[0] == 'r':
|
||||
pass
|
||||
elif arg[0] == 'v':
|
||||
pass
|
||||
elif arg[0] == 'c':
|
||||
if arg not in consts:
|
||||
key = int(arg[1:])
|
||||
arg = loctab[key]
|
||||
elif arg[0] == 'o':
|
||||
arg = f"o.{arg[1:].lower()}"
|
||||
elif re.match("[+-]?([0-9]*[.])?[0-9]+", arg):
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f"Unknown arg {arg}")
|
||||
|
||||
args[a] = neg + arg + swiz
|
||||
|
||||
def decode(code, args):
|
||||
if code in decls:
|
||||
return [decls[code].format(*args)]
|
||||
elif code in ops:
|
||||
target = args[0]
|
||||
if target == "o.fog":
|
||||
return [ops[code].format(*args)]
|
||||
|
||||
dot = re.search("\.[xyzw]+", target)
|
||||
if dot:
|
||||
swiz = target[dot.start()+1:]
|
||||
target = target[:dot.start()]
|
||||
else:
|
||||
swiz = "xyzw"
|
||||
|
||||
lines = [ops[code].format("tmp", *args[1:])]
|
||||
for c in swiz:
|
||||
lines.append(f"{target}.{c} = tmp.{c};")
|
||||
return lines
|
||||
else:
|
||||
raise ValueError(f"Unknown code {code}")
|
||||
|
||||
def process_asm(asm, loctab):
|
||||
shadertype = ""
|
||||
if asm[0] == "\"vs_1_1":
|
||||
shadertype = "vertex"
|
||||
else:
|
||||
raise ValueError(f"Unsupported shader type: {asm[0][1:]}")
|
||||
|
||||
consts = set()
|
||||
translated = []
|
||||
i = 1
|
||||
while i < len(asm):
|
||||
instruction = asm[i]
|
||||
if instruction == "\"":
|
||||
break
|
||||
|
||||
space = instruction.find(" ")
|
||||
if space == -1:
|
||||
code = instruction
|
||||
args = []
|
||||
else:
|
||||
code = instruction[:space]
|
||||
args = instruction[space+1:].split(", ")
|
||||
|
||||
if code == "def":
|
||||
consts.add(args[0])
|
||||
|
||||
resolve_args(args, loctab, consts)
|
||||
disasm = decode(code, args)
|
||||
# print(f"{instruction} \t==>\t{disasm}")
|
||||
disasm.insert(0, f"// {instruction}")
|
||||
translated.extend(disasm)
|
||||
i = i + 1
|
||||
|
||||
return (shadertype, translated)
|
||||
|
||||
def disassemble(text):
|
||||
asm = text.split('\n')[1:-1]
|
||||
(keywords, header, loctab, locdecl) = process_header(asm)
|
||||
(shadertype, disasm) = process_asm(asm, loctab)
|
||||
|
||||
text = "\n".join(header) + "\n"
|
||||
text += cg_header
|
||||
if keywords:
|
||||
text += "#pragma multi_compile " + " ".join(keywords)
|
||||
if shadertype == "vertex":
|
||||
text += "#pragma vertex vert\n\n"
|
||||
text += struct_appdata + "\n"
|
||||
text += struct_v2f + "\n"
|
||||
text += "\n".join(locdecl) + "\n"
|
||||
if shadertype == "vertex":
|
||||
text += vertex_header + "\n"
|
||||
text += "\t" + "\n\t".join(disasm) + "\n\n"
|
||||
if shadertype == "vertex":
|
||||
text += vertex_footer + "\n"
|
||||
text += cg_footer
|
||||
return text
|
37
dx2cg/main.py
Normal file
37
dx2cg/main.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
from swapper import process
|
||||
|
||||
def process_file(filename, suffix):
|
||||
dot = filename.rfind(".")
|
||||
if dot > -1:
|
||||
outfile_name = filename[:dot] + suffix + filename[dot:]
|
||||
else:
|
||||
outfile_name = filename + suffix
|
||||
return process(filename, outfile_name)
|
||||
|
||||
def process_batch(path, suffix="_hlsl"):
|
||||
files = os.listdir(path)
|
||||
for f in files:
|
||||
if os.path.isdir(f):
|
||||
process_batch(f"{path}/{f}")
|
||||
else:
|
||||
try:
|
||||
if process_file(f"{path}/{f}", suffix):
|
||||
print(f"Processed {f}")
|
||||
else:
|
||||
print(f"Skipping {f}")
|
||||
except ValueError as err:
|
||||
print(f"Failed to process {f}: {err}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: <shader-path> [outfile-suffix]")
|
||||
elif len(sys.argv) == 2:
|
||||
process_batch(sys.argv[1])
|
||||
else:
|
||||
process_batch(*sys.argv[1:2])
|
||||
|
64
dx2cg/swapper.py
Normal file
64
dx2cg/swapper.py
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
# parser for replacing d3d9 subprograms in shaderlab files with HLSL/CG
|
||||
# ycc 08/08/2022
|
||||
|
||||
import re
|
||||
from disassembler import disassemble
|
||||
|
||||
tabs = 3
|
||||
def indent(block):
|
||||
lines = block.split('\n')
|
||||
for i in range(0, len(lines)-1):
|
||||
lines[i] = tabs * "\t" + lines[i]
|
||||
return "\n".join(lines)
|
||||
|
||||
def find_closing_bracket(block, i):
|
||||
count = 0;
|
||||
while i < len(block):
|
||||
if block[i] == '{':
|
||||
count = count + 1
|
||||
if block[i] == '}':
|
||||
count = count - 1
|
||||
if count == 0:
|
||||
return i
|
||||
i = i + 1
|
||||
raise ValueError(f"Block at {i} has no closing bracket")
|
||||
|
||||
def process_program(prog):
|
||||
subprog_index = prog.find("SubProgram \"d3d9")
|
||||
if subprog_index == -1:
|
||||
raise ValueError(f"Program has no d3d9 subprogram")
|
||||
subprog_end_index = find_closing_bracket(prog, subprog_index)
|
||||
subprog = prog[subprog_index:subprog_end_index+1]
|
||||
processed = disassemble(subprog) + "\n"
|
||||
return indent(processed)
|
||||
|
||||
def process_shader(shader):
|
||||
buf = shader
|
||||
processed = ''
|
||||
program_index = buf.find('Program')
|
||||
while program_index > -1:
|
||||
processed = processed + buf[:program_index]
|
||||
buf = buf[program_index:]
|
||||
line = re.search("#LINE [0-9]+\n", buf)
|
||||
if not line:
|
||||
raise ValueError(f"Program at {program_index} has no #LINE marker")
|
||||
end_index = line.end() + 1
|
||||
program_section = buf[program_index:end_index+1]
|
||||
processed = processed + process_program(program_section)
|
||||
buf = buf[end_index+1:]
|
||||
|
||||
program_index = buf.find('Program')
|
||||
processed = processed + buf
|
||||
return processed
|
||||
|
||||
def process(fn_in, fn_out):
|
||||
with open(fn_in, "r") as fi:
|
||||
buf = fi.read()
|
||||
processed = process_shader(buf)
|
||||
if buf != processed:
|
||||
with open(fn_out, "w") as fo:
|
||||
fo.write(processed)
|
||||
return True
|
||||
return False
|
Loading…
Reference in New Issue
Block a user