Repository: CrossGL/crosstl
Pull request #167 (2024-09-19 16:46:21)

fix: map hlsl semantics in crossgl
### PR Description
<!-- Provide a brief summary of the changes you have made. Explain the purpose and motivation behind these changes. -->

added the system semantics mapping from hlsl to crossgl.

### Related Issue
<!-- Link to the related issue(s) that this PR addresses. Example: #123 -->

### shader Sample
<!-- Provide a shader sample or snippet on which you have tested your changes. like

```crossgl
shader PerlinNoise {
    vertex {
        input vec3 position;
        output vec2 vUV;

        void main() {
            vUV = position.xy * 10.0;
            gl_Position = vec4(position, 1.0);
        }
    }

    // Perlin Noise Function
    float perlinNoise(vec2 p) {
        return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
    }

    // Fragment Shader
    fragment {
        input vec2 vUV;
        output vec4 fragColor;

        void main() {
            float noise = perlinNoise(vUV);
            float height = noise * 10.0;
            vec3 color = vec3(height / 10.0, 1.0 - height / 10.0, 0.0);
            fragColor = vec4(color, 1.0);
        }
    }
}
```-->


### Checklist
- [ ] Have you added the necessary tests?
- [ ] Only modified the files mentioned in the related issue(s)?
- [ ] Are all tests passing?





Patch:
diff --git a/crosstl/src/backend/DirectX/DirectxAst.py b/crosstl/src/backend/DirectX/DirectxAst.py
index b5d3ac2..b314f05 100644
--- a/crosstl/src/backend/DirectX/DirectxAst.py
+++ b/crosstl/src/backend/DirectX/DirectxAst.py
@@ -23,27 +23,6 @@ class ShaderNode:
         return f"ShaderNode(structs={self.structs}, functions={self.functions}, global_variables={self.global_variables}, cbuffers={self.cbuffers})"
 
 
-class shaderTypeNode:
-    """
-    Represents a shader type node in the AST.
-
-    Attributes:
-        vertex (bool): The vertex shader type
-        fragment (bool): The fragment shader type
-        compute (bool): The compute shader type
-
-    """
-
-    def __init__(self, vertex=False, fragment=False, compute=False, custom=False):
-        self.vertex = vertex
-        self.fragment = fragment
-        self.compute = compute
-        self.custom = custom
-
-    def __repr__(self):
-        return f"shaderTypeNode(vertex={self.vertex}, fragment={self.fragment}, compute={self.compute}, custom={self.custom})"
-
-
 class StructNode:
     def __init__(self, name, members):
         self.name = name
@@ -54,15 +33,16 @@ class StructNode:
 
 
 class FunctionNode(ASTNode):
-    def __init__(self, return_type, name, params, body, type_function="custom"):
+    def __init__(self, return_type, name, params, body, qualifier=None, semantic=None):
         self.return_type = return_type
         self.name = name
         self.params = params
         self.body = body
-        self.type_function = type_function
+        self.qualifier = qualifier
+        self.semantic = semantic
 
     def __repr__(self):
-        return f"FunctionNode(return_type={self.return_type}, name={self.name}, params={self.params}, body={self.body}, type_function={self.type_function})"
+        return f"FunctionNode(return_type={self.return_type}, name={self.name}, params={self.params}, body={self.body}, qualifier={self.qualifier}, semantic={self.semantic})"
 
 
 class VariableNode(ASTNode):
diff --git a/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py b/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
index 63ed820..4854cd0 100644
--- a/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
+++ b/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
@@ -34,6 +34,101 @@ class HLSLToCrossGLConverter:
             "Texture2D": "sampler2D",
             "TextureCube": "samplerCube",
         }
+        self.semantic_map = {
+            # Vertex inputs position
+            "POSITION": "in_Position",
+            "POSITION0": "in_Position0",
+            "POSITION1": "in_Position1",
+            "POSITION2": "in_Position2",
+            "POSITION3": "in_Position3",
+            "POSITION4": "in_Position4",
+            "POSITION5": "in_Position5",
+            "POSITION6": "in_Position6",
+            "POSITION7": "in_Position7",
+            # Vertex inputs normal
+            "NORMAL": "in_Normal",
+            "NORMAL0": "in_Normal0",
+            "NORMAL1": "in_Normal1",
+            "NORMAL2": "in_Normal2",
+            "NORMAL3": "in_Normal3",
+            "NORMAL4": "in_Normal4",
+            "NORMAL5": "in_Normal5",
+            "NORMAL6": "in_Normal6",
+            "NORMAL7": "in_Normal7",
+            # Vertex inputs tangent
+            "TANGENT": "in_Tangent",
+            "TANGENT0": "in_Tangent0",
+            "TANGENT1": "in_Tangent1",
+            "TANGENT2": "in_Tangent2",
+            "TANGENT3": "in_Tangent3",
+            "TANGENT4": "in_Tangent4",
+            "TANGENT5": "in_Tangent5",
+            "TANGENT6": "in_Tangent6",
+            "TANGENT7": "in_Tangent7",
+            # Vertex inputs binormal
+            "BINORMAL": "in_Binormal",
+            "BINORMAL0": "in_Binormal0",
+            "BINORMAL1": "in_Binormal1",
+            "BINORMAL2": "in_Binormal2",
+            "BINORMAL3": "in_Binormal3",
+            "BINORMAL4": "in_Binormal4",
+            "BINORMAL5": "in_Binormal5",
+            "BINORMAL6": "in_Binormal6",
+            "BINORMAL7": "in_Binormal7",
+            # Vertex inputs color
+            "COLOR": "Color",
+            "COLOR0": "Color0",
+            "COLOR1": "Color1",
+            "COLOR2": "Color2",
+            "COLOR3": "Color3",
+            "COLOR4": "Color4",
+            "COLOR5": "Color5",
+            "COLOR6": "Color6",
+            "COLOR7": "Color7",
+            # Vertex inputs texcoord
+            "TEXCOORD": "TexCoord",
+            "TEXCOORD0": "TexCoord0",
+            "TEXCOORD1": "TexCoord1",
+            "TEXCOORD2": "TexCoord2",
+            "TEXCOORD3": "TexCoord3",
+            "TEXCOORD4": "TexCoord4",
+            "TEXCOORD5": "TexCoord5",
+            "TEXCOORD6": "TexCoord6",
+            # Vertex inputs instance
+            "FRONT_FACE": "SV_IsFrontFace",
+            "PRIMITIVE_ID": "SV_PrimitiveID",
+            "INSTANCE_ID": "SV_InstanceID",
+            "VERTEX_ID": "SV_VertexID",
+            # Vertex outputs
+            "SV_Position": "Out_Position",
+            "SV_Position0": "Out_Position0",
+            "SV_Position1": "Out_Position1",
+            "SV_Position2": "Out_Position2",
+            "SV_Position3": "Out_Position3",
+            "SV_Position4": "Out_Position4",
+            "SV_Position5": "Out_Position5",
+            "SV_Position6": "Out_Position6",
+            "SV_Position7": "Out_Position7",
+            # Fragment inputs
+            "SV_Target": "Out_Color",
+            "SV_Target0": "Out_Color0",
+            "SV_Target1": "Out_Color1",
+            "SV_Target2": "Out_Color2",
+            "SV_Target3": "Out_Color3",
+            "SV_Target4": "Out_Color4",
+            "SV_Target5": "Out_Color5",
+            "SV_Target6": "Out_Color6",
+            "SV_Target7": "Out_Color7",
+            "SV_Depth": "Out_Depth",
+            "SV_Depth0": "Out_Depth0",
+            "SV_Depth1": "Out_Depth1",
+            "SV_Depth2": "Out_Depth2",
+            "SV_Depth3": "Out_Depth3",
+            "SV_Depth4": "Out_Depth4",
+            "SV_Depth5": "Out_Depth5",
+            "SV_Depth6": "Out_Depth6",
+            "SV_Depth7": "Out_Depth7",
+        }
 
     def generate(self, ast):
         code = "shader main {\n"
@@ -42,7 +137,7 @@ class HLSLToCrossGLConverter:
             if isinstance(node, StructNode):
                 code += f"    struct {node.name} {{\n"
                 for member in node.members:
-                    code += f"        {self.map_type(member.vtype)} {member.name};\n"
+                    code += f"        {self.map_type(member.vtype)} {member.name} {self.map_semantic(member.semantic)};\n"
                 code += "    }\n"
         # Generate global variables
         for node in ast.global_variables:
@@ -54,21 +149,24 @@ class HLSLToCrossGLConverter:
 
         # Generate custom functions
         for func in ast.functions:
-            function_type_node = func.type_function
-            if function_type_node.custom:
+            if func.qualifier == "vertex":
+                code += "    // Vertex Shader\n"
+                code += "    vertex {\n"
                 code += self.generate_function(func)
-            if function_type_node.vertex:
-                code += f"vertex {{\n"
+                code += "    }\n\n"
+            elif func.qualifier == "fragment":
+                code += "    // Fragment Shader\n"
+                code += "    fragment {\n"
                 code += self.generate_function(func)
-                code += f"}}\n"
-            elif function_type_node.fragment:
-                code += f"fragment {{\n"
+                code += "    }\n\n"
+
+            elif func.qualifier == "compute":
+                code += "    // Compute Shader\n"
+                code += "    compute {\n"
                 code += self.generate_function(func)
-                code += f"}}\n"
-            elif function_type_node.compute:
-                code += f"compute {{\n"
+                code += "    }\n\n"
+            else:
                 code += self.generate_function(func)
-                code += f"}}\n"
 
         code += "}\n"
         return code
@@ -83,10 +181,15 @@ class HLSLToCrossGLConverter:
                 code += "    }\n"
         return code
 
-    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)
+    def generate_function(self, func, indent=1):
+        code = " "
+        code += "  " * indent
+        params = ", ".join(
+            f"{self.map_type(p.vtype)} {p.name} {self.map_semantic(p.semantic)}"
+            for p in func.params
+        )
+        code += f"    {self.map_type(func.return_type)} {func.name}({params}) {self.map_semantic(func.semantic)} {{\n"
+        code += self.generate_function_body(func.body, indent=indent + 1)
         code += "    }\n\n"
         return code
 
@@ -187,3 +290,9 @@ class HLSLToCrossGLConverter:
         if hlsl_type:
             return self.type_map.get(hlsl_type, hlsl_type)
         return hlsl_type
+
+    def map_semantic(self, semantic):
+        if semantic is not None:
+            return f"@ {self.semantic_map.get(semantic, semantic)}"
+        else:
+            return ""
diff --git a/crosstl/src/backend/DirectX/DirectxLexer.py b/crosstl/src/backend/DirectX/DirectxLexer.py
index 6583503..07dbd6d 100644
--- a/crosstl/src/backend/DirectX/DirectxLexer.py
+++ b/crosstl/src/backend/DirectX/DirectxLexer.py
@@ -20,7 +20,6 @@ TOKENS = [
     ("ELSE", r"\belse\b"),
     ("FOR", r"\bfor\b"),
     ("REGISTER", r"\bregister\b"),
-    ("SEMANTIC", r": [A-Z_][A-Z0-9_]*"),
     ("IDENTIFIER", r"[a-zA-Z_][a-zA-Z0-9_]*"),
     ("NUMBER", r"\d+(\.\d+)?"),
     ("LBRACE", r"\{"),
diff --git a/crosstl/src/backend/DirectX/DirectxParser.py b/crosstl/src/backend/DirectX/DirectxParser.py
index e08eb47..b1ecfa6 100644
--- a/crosstl/src/backend/DirectX/DirectxParser.py
+++ b/crosstl/src/backend/DirectX/DirectxParser.py
@@ -12,7 +12,6 @@ from .DirectxAst import (
     UnaryOpNode,
     VariableNode,
     VectorConstructorNode,
-    shaderTypeNode,
 )
 from .DirectxLexer import HLSLLexer
 
@@ -38,21 +37,6 @@ class HLSLParser:
         else:
             raise SyntaxError(f"Expected {token_type}, got {self.current_token[0]}")
 
-    def parse_shader_type(self):
-        vertex = False
-        fragment = False
-        compute = False
-        custom = False
-        if self.current_token[1] == "VSMain":
-            vertex = True
-        elif self.current_token[1] == "PSMain":
-            fragment = True
-        elif self.current_token[1] == "CSMain":
-            compute = True
-        else:
-            custom = True
-        return shaderTypeNode(vertex, fragment, compute, custom)
-
     def parse(self):
         shader = self.parse_shader()
         self.eat("EOF")
@@ -137,9 +121,10 @@ class HLSLParser:
             var_name = self.current_token[1]
             self.eat("IDENTIFIER")
             semantic = None
-            if self.current_token[0] == "SEMANTIC":
+            if self.current_token[0] == "COLON":
+                self.eat("COLON")
                 semantic = self.current_token[1]
-                self.eat("SEMANTIC")
+                self.eat("IDENTIFIER")
             self.eat("SEMICOLON")
             members.append(VariableNode(vtype, var_name, semantic))
         self.eat("RBRACE")
@@ -148,16 +133,26 @@ class HLSLParser:
     def parse_function(self):
         return_type = self.current_token[1]
         self.eat(self.current_token[0])
-        type_function = self.parse_shader_type()
         name = self.current_token[1]
+        qualifier = None
+        if name == "VSMain":
+            qualifier = "vertex"
+        elif name == "PSMain":
+            qualifier = "fragment"
+        elif name == "CSMain":
+            qualifier = "compute"
+
         self.eat("IDENTIFIER")
         self.eat("LPAREN")
         params = self.parse_parameters()
         self.eat("RPAREN")
-        if self.current_token[0] == "SEMANTIC":
-            self.eat("SEMANTIC")
+        semantic = None
+        if self.current_token[0] == "COLON":
+            self.eat("COLON")
+            semantic = self.current_token[1]
+            self.eat("IDENTIFIER")
         body = self.parse_block()
-        return FunctionNode(return_type, name, params, body, type_function)
+        return FunctionNode(return_type, name, params, body, qualifier, semantic)
 
     def parse_parameters(self):
         params = []
@@ -166,9 +161,12 @@ class HLSLParser:
             self.eat(self.current_token[0])
             name = self.current_token[1]
             self.eat("IDENTIFIER")
-            if self.current_token[0] == "SEMANTIC":
-                self.eat("SEMANTIC")
-            params.append(VariableNode(vtype, name))
+            semantic = None
+            if self.current_token[0] == "COLON":
+                self.eat("COLON")
+                semantic = self.current_token[1]
+                self.eat("IDENTIFIER")
+            params.append(VariableNode(vtype, name, semantic))
             if self.current_token[0] == "COMMA":
                 self.eat("COMMA")
         return params


Test patch:
diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py
index 7250a7f..882e9f4 100644
--- a/tests/test_backend/test_directx/test_codegen.py
+++ b/tests/test_backend/test_directx/test_codegen.py
@@ -50,7 +50,7 @@ def test_struct_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -93,7 +93,7 @@ def test_if_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -139,7 +139,7 @@ def test_for_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -188,7 +188,7 @@ def test_else_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -238,7 +238,7 @@ def test_function_call_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -285,7 +285,7 @@ def test_else_if_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_TARGET0;
+        float4 out_color : SV_Target0;
     };
 
     PSOutput PSMain(PSInput input) {
diff --git a/tests/test_backend/test_directx/test_lexer.py b/tests/test_backend/test_directx/test_lexer.py
index 26a6885..62689be 100644
--- a/tests/test_backend/test_directx/test_lexer.py
+++ b/tests/test_backend/test_directx/test_lexer.py
@@ -6,13 +6,13 @@ from crosstl.src.backend.DirectX.DirectxLexer import HLSLLexer
 def tokenize_code(code: str) -> List:
     """Helper function to tokenize code."""
     lexer = HLSLLexer(code)
-    return lexer.tokenize()
+    return lexer.tokens
 
 
 def test_struct_tokenization():
     code = """
     struct VSInput {
-        float4 position : POSITION;
+        float4 position : SV_position;
         float4 color : TEXCOORD0;
     };
 
diff --git a/tests/test_backend/test_directx/test_parser.py b/tests/test_backend/test_directx/test_parser.py
index 2a0a580..17515d5 100644
--- a/tests/test_backend/test_directx/test_parser.py
+++ b/tests/test_backend/test_directx/test_parser.py
@@ -25,7 +25,7 @@ def tokenize_code(code: str) -> List:
 def test_struct_parsing():
     code = """
     struct VSInput {
-        float4 position : POSITION;
+        float4 position : SV_Position;
         float4 color : TEXCOORD0;
     };