Skip to content

Commit

Permalink
Prep repo for PyPI push.
Browse files Browse the repository at this point in the history
  • Loading branch information
vaatsalya123 committed Jul 31, 2024
1 parent 704ed0f commit 0afb91f
Show file tree
Hide file tree
Showing 30 changed files with 5,521 additions and 0 deletions.
10 changes: 10 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include README.md
include LICENSE
include src/backend/DirectX/*.py
include src/backend/Metal/*.py
include src/backend/Opengl/*.py
include src/translator/*.py
include src/translator/codegen/*.py
include crosstl.py
include crosstl
include corsstl/src/
1 change: 1 addition & 0 deletions crosstl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._crosstl import *
59 changes: 59 additions & 0 deletions crosstl/_crosstl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from .src import translator
from .src.translator.lexer import Lexer
from .src.translator.parser import Parser
from .src.translator.codegen import directx_codegen, metal_codegen, opengl_codegen
from .src.translator.ast import ASTNode
from .src.backend.DirectX import *
from .src.backend.Metal import *
from .src.backend.Opengl import *

def translate(file_path: str, backend: str = "crossgl") -> str:
backend = backend.lower()

with open(file_path, 'r') as file:
shader_code = file.read()

# Determine the input shader type based on the file extension
if file_path.endswith(".cgl"):
lexer = Lexer(shader_code)
parser = Parser(lexer.tokens)
elif file_path.endswith(".hlsl"):
lexer = HLSLLexer(shader_code)
parser = HLSLParser(lexer.tokens)
elif file_path.endswith(".metal"):
lexer = MetalLexer(shader_code)
parser = MetalParser(lexer.tokens)
elif file_path.endswith(".glsl"):
lexer = GLSLLexer(shader_code)
parser = GLSLParser(lexer.tokens)
else:
raise ValueError(f"Unsupported shader file type: {file_path}")

ast = parser.parse()

if file_path.endswith(".cgl"):
if backend == "metal":
codegen = metal_codegen.MetalCodeGen()
return codegen.generate(ast)
elif backend == "directx":
codegen = directx_codegen.HLSLCodeGen()
return codegen.generate(ast)
elif backend == "opengl":
codegen = opengl_codegen.GLSLCodeGen()
return codegen.generate(ast)
else:
raise ValueError(f"Unsupported backend for CrossGL file: {backend}")
else:
if backend == "cgl":
if file_path.endswith(".hlsl"):
codegen = HLSLToCrossGLConverter()
elif file_path.endswith(".metal"):
codegen = MetalToCrossGLConverter()
elif file_path.endswith(".glsl"):
codegen = GLSLToCrossGLConverter()
else:
raise ValueError(f"Reverse translation not supported for: {file_path}")
return codegen.generate(ast)
else:
raise ValueError(f"Unsupported translation scenario: {file_path} to {backend}")

Empty file added crosstl/src/__init__.py
Empty file.
130 changes: 130 additions & 0 deletions crosstl/src/backend/DirectX/DirectxAst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
class ASTNode:
pass


class ShaderNode:
def __init__(self, input_struct, output_struct, functions):
self.input_struct = input_struct
self.output_struct = output_struct
self.functions = functions

def __repr__(self):
return f"ShaderNode(input_struct={self.input_struct}, output_struct={self.output_struct}, functions={self.functions})"


class StructNode:
def __init__(self, name, members):
self.name = name
self.members = members

def __repr__(self):
return f"StructNode(name={self.name}, members={self.members})"


class FunctionNode(ASTNode):
def __init__(self, return_type, name, params, body):
self.return_type = return_type
self.name = name
self.params = params
self.body = body

def __repr__(self):
return f"FunctionNode(return_type={self.return_type}, name={self.name}, params={self.params}, body={self.body})"


class VariableNode(ASTNode):
def __init__(self, vtype, name, semantic=None):
self.vtype = vtype
self.name = name
self.semantic = semantic

def __repr__(self):
return f"VariableNode(vtype='{self.vtype}', name='{self.name}', semantic={self.semantic})"


class AssignmentNode(ASTNode):
def __init__(self, left, right, operator="="):
self.left = left
self.right = right
self.operator = operator

def __repr__(self):
return f"AssignmentNode(left={self.left}, operator='{self.operator}', right={self.right})"


class IfNode(ASTNode):
def __init__(self, condition, if_body, else_body=None):
self.condition = condition
self.if_body = if_body
self.else_body = else_body

def __repr__(self):
return f"IfNode(condition={self.condition}, if_body={self.if_body}, else_body={self.else_body})"


class ForNode(ASTNode):
def __init__(self, init, condition, update, body):
self.init = init
self.condition = condition
self.update = update
self.body = body

def __repr__(self):
return f"ForNode(init={self.init}, condition={self.condition}, update={self.update}, body={self.body})"


class ReturnNode(ASTNode):
def __init__(self, value):
self.value = value

def __repr__(self):
return f"ReturnNode(value={self.value})"


class FunctionCallNode(ASTNode):
def __init__(self, name, args):
self.name = name
self.args = args

def __repr__(self):
return f"FunctionCallNode(name={self.name}, args={self.args})"


class BinaryOpNode(ASTNode):
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right

def __repr__(self):
return f"BinaryOpNode(left={self.left}, operator={self.op}, right={self.right})"


class MemberAccessNode(ASTNode):
def __init__(self, object, member):
self.object = object
self.member = member

def __repr__(self):
return f"MemberAccessNode(object={self.object}, member={self.member})"


class VectorConstructorNode:
def __init__(self, type_name, args):
self.type_name = type_name
self.args = args

def __repr__(self):
return f"VectorConstructorNode(type_name={self.type_name}, args={self.args})"


class UnaryOpNode(ASTNode):
def __init__(self, op, operand):
self.op = op
self.operand = operand

def __repr__(self):
return f"UnaryOpNode(operator={self.op}, operand={self.operand})"

def __str__(self):
return f"({self.op}{self.operand})"
157 changes: 157 additions & 0 deletions crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from .DirectxAst import *
from .DirectxParser import *
from .DirectxLexer import *


class HLSLToCrossGLConverter:
def __init__(self):
self.vertex_inputs = []
self.vertex_outputs = []
self.fragment_inputs = []
self.fragment_outputs = []
self.type_map = {
"float": "float",
"float2": "vec2",
"float3": "vec3",
"float4": "vec4",
"int": "int",
}

def convert(self, ast):
self.process_structs(ast)

code = "shader main {\n"

# Generate vertex shader
code += " // Vertex Shader\n"
code += " vertex {\n"
code += self.generate_io_declarations("vertex")
code += "\n"
code += self.generate_vertex_main(
next(f for f in ast.functions if f.name == "VSMain")
)
code += " }\n\n"

# Generate custom functions
for func in ast.functions:
if func.name not in ["VSMain", "PSMain"]:
code += self.generate_function(func)

# Generate fragment shader
code += " // Fragment Shader\n"
code += " fragment {\n"
code += self.generate_io_declarations("fragment")
code += "\n"
code += self.generate_fragment_main(
next(f for f in ast.functions if f.name == "PSMain")
)
code += " }\n"

code += "}\n"
return code

def process_structs(self, ast):
if ast.input_struct and ast.input_struct.name == "Vertex_INPUT":
for member in ast.input_struct.members:
self.vertex_inputs.append((member.vtype, member.name))
if ast.output_struct and ast.output_struct.name == "Vertex_OUTPUT":
for member in ast.output_struct.members:
if member.name != "position":
self.vertex_outputs.append((member.vtype, member.name))
self.fragment_inputs.append((member.vtype, member.name))
if ast.output_struct and ast.output_struct.name == "Fragment_OUTPUT":
for member in ast.output_struct.members:
self.fragment_outputs.append((member.vtype, member.name))

def generate_io_declarations(self, shader_type):
code = ""
if shader_type == "vertex":
for type, name in self.vertex_inputs:
code += f" input {self.map_type(type)} {name};\n"
for type, name in self.vertex_outputs:
code += f" output {self.map_type(type)} {name};\n"
elif shader_type == "fragment":
for type, name in self.fragment_inputs:
code += f" input {self.map_type(type)} {name};\n"
for type, name in self.fragment_outputs:
code += f" output {self.map_type(type)} {name};\n"
return code.rstrip()

def generate_function(self, func):
params = ", ".join(f"{self.map_type(p.vtype)} {p.name}" for p in func.params)
code = f" {self.map_type(func.return_type)} {func.name}({params}) {{\n"
code += self.generate_function_body(func.body, indent=2)
code += " }\n\n"
return code

def generate_vertex_main(self, func):
code = " void main() {\n"
code += self.generate_function_body(func.body, indent=3, is_main=True)
code += " }\n"
return code

def generate_fragment_main(self, func):
code = " void main() {\n"
code += self.generate_function_body(func.body, indent=3, is_main=True)
code += " }\n"
return code

def generate_function_body(self, body, indent=0, is_main=False):
code = ""
for stmt in body:
code += " " * indent
if isinstance(stmt, VariableNode):
if not is_main:
code += f"{self.map_type(stmt.vtype)} {stmt.name};\n"
elif isinstance(stmt, AssignmentNode):
code += self.generate_assignment(stmt, is_main) + ";\n"
elif isinstance(stmt, ReturnNode):
if not is_main:
code += f"return {self.generate_expression(stmt.value, is_main)};\n"
return code

def generate_assignment(self, node, is_main):
lhs = self.generate_expression(node.left, is_main)
rhs = self.generate_expression(node.right, is_main)
if (
is_main
and isinstance(node.left, MemberAccessNode)
and node.left.object == "output"
):
if node.left.member == "position":
return f"gl_Position = {rhs}"
return f"{node.left.member} = {rhs}"
return f"{lhs} = {rhs}"

def generate_expression(self, expr, is_main=False):
if isinstance(expr, str):
return expr
elif isinstance(expr, VariableNode):
return f"{expr.vtype} {expr.name}"
elif isinstance(expr, BinaryOpNode):
left = self.generate_expression(expr.left, is_main)
right = self.generate_expression(expr.right, is_main)
return f"({left} {expr.op} {right})"
elif isinstance(expr, UnaryOpNode):
operand = self.generate_expression(expr.operand, is_main)
return f"({expr.operator}{operand})"
elif isinstance(expr, FunctionCallNode):
args = ", ".join(
self.generate_expression(arg, is_main) for arg in expr.args
)
return f"{expr.name}({args})"
elif isinstance(expr, MemberAccessNode):
obj = self.generate_expression(expr.object)
if obj == "output" or obj == "input":
return expr.member
return f"{obj}.{expr.member}"
elif isinstance(expr, VectorConstructorNode):
args = ", ".join(
self.generate_expression(arg, is_main) for arg in expr.args
)
return f"{self.map_type(expr.type_name)}({args})"
else:
return str(expr)

def map_type(self, hlsl_type):
return self.type_map.get(hlsl_type, hlsl_type)
Loading

0 comments on commit 0afb91f

Please sign in to comment.