From 6c98fb060d4a7ed0803c536c3a6c079c767f583f Mon Sep 17 00:00:00 2001
From: Tim Felgentreff <timfelgentreff@gmail.com>
Date: Fri, 11 Dec 2020 11:14:34 +0100
Subject: [PATCH] make many shaders from libretro/glsl repo work directly

---
 src/video/shaders.cpp        |  19 +-
 src/video/shaders/cg2glsl.py | 728 +++++++++++++++++++++++++++++++++++
 2 files changed, 746 insertions(+), 1 deletion(-)
 create mode 100644 src/video/shaders/cg2glsl.py

diff --git a/src/video/shaders.cpp b/src/video/shaders.cpp
index 9c137501f..689f56c4f 100644
--- a/src/video/shaders.cpp
+++ b/src/video/shaders.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "shaders.h"
+#include <stdint.h>
 
 #ifndef __APPLE__
 #include <SDL.h>
@@ -31,7 +32,7 @@
 #include <SDL_opengl_glext.h>
 
 #include <stdlib.h>
-
+#include <regex>
 #include <iostream>
 #include <fstream>
 
@@ -89,6 +90,8 @@ static const char* shaderNames[MAX_SHADERS + 1] = { NULL };
 static char shadersLoaded = -1;
 static int currentShaderIdx = 0;
 
+static std::regex invalidQuoteReplaceRegex("\"([a-zA-Z0-9 -\\.]+)\"");
+
 const char* none =
 #include "./shaders/noshader.glsl"
 ;
@@ -135,6 +138,20 @@ static GLuint compileProgramSource(std::string source) {
 	GLuint programId = 0;
 	GLuint vtxShaderId, fragShaderId;
 
+	uint32_t offset = 0;
+	std::smatch m;
+	while (std::regex_search(source.cbegin() + offset, source.cend(), m, invalidQuoteReplaceRegex)) {
+		uint32_t next_offset = offset + m.position() + m.length();
+		for (std::string::iterator it = source.begin() + offset + m.position(); it < source.begin() + next_offset; it++) {
+			if (*it == '"') {
+				source.replace(it, it + 1, " ");
+			} else if (*it == ' ') {
+				source.replace(it, it + 1, "_");
+			}
+		}
+		offset = next_offset;
+	}
+
 	vtxShaderId = compileShader((std::string("#define VERTEX\n") + source).c_str(), GL_VERTEX_SHADER);
 	fragShaderId = compileShader((std::string("#define FRAGMENT\n") + source).c_str(), GL_FRAGMENT_SHADER);
 
diff --git a/src/video/shaders/cg2glsl.py b/src/video/shaders/cg2glsl.py
new file mode 100644
index 000000000..af8205f09
--- /dev/null
+++ b/src/video/shaders/cg2glsl.py
@@ -0,0 +1,728 @@
+#!/usr/bin/env python3
+"""
+Python 3 script which converts simple RetroArch Cg shaders to modern GLSL (ES) format.
+Author: Hans-Kristian Arntzen (Themaister)
+License: Public domain
+
+N.b.: This script works on some shader files from https://github.com/libretro/common-shaders
+Other directly converted shaders can be found here: https://github.com/libretro/glsl-shaders
+"""
+
+import os
+import errno
+import subprocess
+import sys
+
+if sys.version_info < (3, 0, 0):
+    sys.stderr.write("You need python 3.0 or later to run this script\n")
+    exit(1)
+
+batch_mode = False
+
+def log(*arg):
+    if not batch_mode:
+        # FIXME: This causes a syntax error in python2, preventing the version warning from displaying.
+        print(*arg)
+
+def keep_line_if(func, lines):
+    ret = []
+    for line in filter(func, lines):
+        ret.append(line)
+    return ret
+
+def remove_comments(source_lines):
+    lines_without_comments = [line.split('//')[0] for line in source_lines]
+    return keep_line_if(lambda line: line, lines_without_comments)
+
+def defines_var(line):
+    return ('//var' in line) or ('#var' in line)
+
+def replace_by_table(source, table):
+    for orig, new in table:
+        if orig:
+            source = source.replace(orig, new)
+
+    return source
+
+def replace_global_in(source):
+    replace_table = [
+        ('IN.video_size', 'InputSize'),
+        ('IN.texture_size', 'TextureSize'),
+        ('IN.output_size', 'OutputSize'),
+        ('IN.frame_count', 'FrameCount'),
+        ('IN.frame_direction', 'FrameDirection'),
+    ]
+
+    for line in source.splitlines():
+        if defines_var(line):
+            for index, replace in enumerate(replace_table):
+                orig = line.split()[2]
+                if replace[0] == orig:
+                    replace_table[index] = (line.split(':')[2].split(' ')[1], replace_table[index][1])
+
+    log('Replace globals:', replace_table)
+
+    return replace_by_table(source, replace_table)
+
+def replace_global_vertex(source):
+    source = replace_global_in(source)
+
+    replace_table = [
+        ('attribute', 'COMPAT_ATTRIBUTE'),
+        ('varying', 'COMPAT_VARYING'),
+        ('texture2D', 'COMPAT_TEXTURE'),
+        ('POSITION', 'VertexCoord'),
+        ('TEXCOORD1', 'LUTTexCoord'),
+        ('TEXCOORD0', 'TexCoord'),
+        ('TEXCOORD', 'TexCoord'),
+        ('uniform vec4 _modelViewProj1[4];', ''),
+        ('_modelViewProj1', 'MVPMatrix'),
+        ('_IN1._mvp_matrix[0]', 'MVPMatrix[0]'),
+        ('_IN1._mvp_matrix[1]', 'MVPMatrix[1]'),
+        ('_IN1._mvp_matrix[2]', 'MVPMatrix[2]'),
+        ('_IN1._mvp_matrix[3]', 'MVPMatrix[3]'),
+
+        ('FrameCount', 'float(FrameCount)'),
+        ('FrameDirection', 'float(FrameDirection)'),
+        ('input', 'input_dummy'),  # 'input' is reserved in GLSL.
+        ('output', 'output_dummy'),  # 'output' is reserved in GLSL.
+    ]
+
+    return replace_by_table(source, replace_table)
+
+def translate_varyings(varyings, source, direction):
+    dictionary = {}
+    for varying in varyings:
+        for line in source:
+            if defines_var(line) and (varying in line) and (direction in line):
+                log('Found line for', varying + ':', line)
+                dictionary[varying] = 'VAR' + line.split(':')[0].split('.')[-1].strip()
+                break
+
+    return dictionary
+
+def no_uniform(elem):
+    banned = [
+        '_video_size',
+        '_texture_size',
+        '_output_size',
+        '_output_dummy_size',
+        '_frame_count',
+        '_frame_direction',
+        '_mvp_matrix',
+        '_vertex_coord',
+        'sampler2D'
+    ]
+
+    for ban in banned:
+        if ban in elem:
+            return False
+
+    return True
+
+def destructify_varyings(source, direction):
+    # We have to change varying structs that Cg support to single varyings for GL.
+    # Varying structs aren't supported until later versions
+    # of GLSL.
+
+    # Global structs are sometimes used to store temporary data.
+    # Don't try to remove this as it breaks compile.
+    vout_lines = []
+    for line in source:
+        if defines_var(line) and (('$vout.' in line) or ('$vin.' in line)):
+            vout_lines.append(line)
+
+    struct_types = []
+    for line in source[1:]:
+        if 'struct' in line:
+            struct_type = line.split()[1]
+            if struct_type not in struct_types:
+                struct_types.append(struct_type)
+
+    log('Struct types:', struct_types)
+
+    last_struct_decl_line = 0
+    varyings = []
+    varyings_name = []
+    # Find all varyings in structs and make them "global" varyings.
+    for struct in struct_types:
+        for i, line in enumerate(source):
+            if ('struct ' + struct) in line:
+                j = i + 1
+                while (j < len(source)) and ('};' not in source[j]):
+                    j += 1
+
+                lines = ['COMPAT_VARYING ' + string for string in source[(i + 1):j]]
+                varyings.extend(lines)
+                names = [string.split()[1].split(';')[0].strip() for string in source[(i + 1):j]]
+                varyings_name.extend(names)
+                log('Found elements in struct', struct + ':', names)
+                last_struct_decl_line = j
+
+                # Must have explicit uniform sampler2D in struct.
+                for index in range(i, j + 1):
+                    if 'sampler2D' in source[index]:
+                        source[index] = 'float _placeholder{};'.format(index)
+
+    varyings_tmp = varyings
+    varyings = []
+    variables = []
+
+    # Don't include useless varyings like IN.video_size, IN.texture_size, etc as they are not actual varyings ...
+    for i in filter(no_uniform, varyings_tmp):
+        varyings.append(i)
+
+    # Find any global variable struct that is supposed to be the output varying, and redirect all references to it to
+    # the actual varyings we just declared ...
+    # Globals only come before main() ...
+    # Make sure to only look after all struct declarations as there might be overlap.
+    for line in remove_comments(source[last_struct_decl_line:]):
+        if 'void main()' in line:
+            break
+
+        for struct in struct_types:
+            if struct in line:
+                variable = line.split()[1].split(';')[0]
+
+                # Only redirect if the struct is actually used as vertex output.
+                for vout_line in vout_lines:
+                    if variable in vout_line:
+                        log('Found struct variable for', struct + ':', variable, 'in line:', line)
+                        variables.append(variable)
+                        break
+
+    varyings_dict = translate_varyings(varyings_name, source, direction)
+    log('Varyings dict:', varyings_dict)
+
+    # Append all varyings. Keep the structs as they might be used as regular values.
+    for varying in varyings:
+        source.insert(1, varying)
+
+    log('Variables:', variables)
+    log('Varying names:', varyings_name)
+
+    # Replace struct access with global access, e.g. (_co1._c00 => _c00)
+    # Also replace mangled Cg name with 'real' name.
+    for index, _ in enumerate(source):
+        for variable in variables:
+            for varying_name in varyings_dict:
+                trans_from = variable + '.' + varying_name
+                trans_to = varyings_dict[varying_name]
+                source[index] = source[index].replace(trans_from, trans_to)
+
+    for index, _ in enumerate(source):
+        for varying_name in varyings_name:
+            if varying_name in varyings_dict:
+                source[index] = source[index].replace(varying_name, varyings_dict[varying_name])
+
+    # Replace union <struct>. Sometimes we get collision in vertex and fragment.
+    for index, line in enumerate(source):
+        for struct_type in struct_types:
+            line = line.replace('uniform ' + struct_type, struct_type)
+        source[index] = line
+
+    return source
+
+def translate(cg, translations):
+    if cg in translations:
+        return translations[cg]
+    else:
+        return cg
+
+def translate_varying(cg):
+    translations = {
+        'IN.tex_coord': 'TexCoord',
+        'IN.vertex_coord': 'VertexCoord',
+        'IN.lut_tex_coord': 'LUTTexCoord',
+        'ORIG.tex_coord': 'OrigTexCoord',
+        'FEEDBACK.tex_coord': 'FeedbackTexCoord',
+        'PREV.tex_coord': 'PrevTexCoord',
+        'PREV1.tex_coord': 'Prev1TexCoord',
+        'PREV2.tex_coord': 'Prev2TexCoord',
+        'PREV3.tex_coord': 'Prev3TexCoord',
+        'PREV4.tex_coord': 'Prev4TexCoord',
+        'PREV5.tex_coord': 'Prev5TexCoord',
+        'PREV6.tex_coord': 'Prev6TexCoord',
+        'PASS1.tex_coord': 'Pass1TexCoord',
+        'PASS2.tex_coord': 'Pass2TexCoord',
+        'PASS3.tex_coord': 'Pass3TexCoord',
+        'PASS4.tex_coord': 'Pass4TexCoord',
+        'PASS5.tex_coord': 'Pass5TexCoord',
+        'PASS6.tex_coord': 'Pass6TexCoord',
+        'PASS7.tex_coord': 'Pass7TexCoord',
+        'PASS8.tex_coord': 'Pass8TexCoord',
+        'PASSPREV2.tex_coord': 'PassPrev2TexCoord',
+        'PASSPREV3.tex_coord': 'PassPrev3TexCoord',
+        'PASSPREV4.tex_coord': 'PassPrev4TexCoord',
+        'PASSPREV5.tex_coord': 'PassPrev5TexCoord',
+        'PASSPREV6.tex_coord': 'PassPrev6TexCoord',
+        'PASSPREV7.tex_coord': 'PassPrev7TexCoord',
+        'PASSPREV8.tex_coord': 'PassPrev8TexCoord',
+    }
+
+    return translate(cg, translations)
+
+def translate_texture_size(cg):
+    translations = {
+        'ORIG.texture_size': 'OrigTextureSize',
+        'FEEDBACK.texture_size': 'FeedbackTextureSize',
+        'PREV.texture_size': 'PrevTextureSize',
+        'PREV1.texture_size': 'Prev1TextureSize',
+        'PREV2.texture_size': 'Prev2TextureSize',
+        'PREV3.texture_size': 'Prev3TextureSize',
+        'PREV4.texture_size': 'Prev4TextureSize',
+        'PREV5.texture_size': 'Prev5TextureSize',
+        'PREV6.texture_size': 'Prev6TextureSize',
+        'PASS1.texture_size': 'Pass1TextureSize',
+        'PASS2.texture_size': 'Pass2TextureSize',
+        'PASS3.texture_size': 'Pass3TextureSize',
+        'PASS4.texture_size': 'Pass4TextureSize',
+        'PASS5.texture_size': 'Pass5TextureSize',
+        'PASS6.texture_size': 'Pass6TextureSize',
+        'PASS7.texture_size': 'Pass7TextureSize',
+        'PASS8.texture_size': 'Pass8TextureSize',
+        'PASSPREV2.texture_size': 'PassPrev2TextureSize',
+        'PASSPREV3.texture_size': 'PassPrev3TextureSize',
+        'PASSPREV4.texture_size': 'PassPrev4TextureSize',
+        'PASSPREV5.texture_size': 'PassPrev5TextureSize',
+        'PASSPREV6.texture_size': 'PassPrev6TextureSize',
+        'PASSPREV7.texture_size': 'PassPrev7TextureSize',
+        'PASSPREV8.texture_size': 'PassPrev8TextureSize',
+        'ORIG.video_size': 'OrigInputSize',
+        'FEEDBACK.video_size': 'FeedbackInputSize',
+        'PREV.video_size': 'PrevInputSize',
+        'PREV1.video_size': 'Prev1InputSize',
+        'PREV2.video_size': 'Prev2InputSize',
+        'PREV3.video_size': 'Prev3InputSize',
+        'PREV4.video_size': 'Prev4InputSize',
+        'PREV5.video_size': 'Prev5InputSize',
+        'PREV6.video_size': 'Prev6InputSize',
+        'PASS1.video_size': 'Pass1InputSize',
+        'PASS2.video_size': 'Pass2InputSize',
+        'PASS3.video_size': 'Pass3InputSize',
+        'PASS4.video_size': 'Pass4InputSize',
+        'PASS5.video_size': 'Pass5InputSize',
+        'PASS6.video_size': 'Pass6InputSize',
+        'PASS7.video_size': 'Pass7InputSize',
+        'PASS8.video_size': 'Pass8InputSize',
+        'PASSPREV2.video_size': 'PassPrev2InputSize',
+        'PASSPREV3.video_size': 'PassPrev3InputSize',
+        'PASSPREV4.video_size': 'PassPrev4InputSize',
+        'PASSPREV5.video_size': 'PassPrev5InputSize',
+        'PASSPREV6.video_size': 'PassPrev6InputSize',
+        'PASSPREV7.video_size': 'PassPrev7InputSize',
+        'PASSPREV8.video_size': 'PassPrev8InputSize',
+    }
+
+    return translate(cg, translations)
+
+def replace_varyings(source):
+    ret = []
+    translations = []
+    attribs = []
+    uniforms = []
+    for index, line in enumerate(source):
+        if defines_var(line):
+            func = translate_texture_size
+            collection = uniforms
+            if '$vin.' in line:
+                func = translate_varying
+                collection = attribs
+            orig = line.split()[2]
+            translated = func(orig)
+            if translated != orig and translated not in collection:
+                cg_var = line.split(':')[2].split(' ')[1]
+                if cg_var:
+                    translations.append((cg_var, translated))
+                    collection.append(translated)
+
+    for index, line in enumerate(source):
+        if 'void main()' in line:
+            for attrib in attribs:
+                if attrib == 'VertexCoord':
+                    source.insert(index, 'COMPAT_ATTRIBUTE vec4 ' + attrib + ';')
+                else:
+                    source.insert(index, 'COMPAT_ATTRIBUTE vec2 ' + attrib + ';')
+            for uniform in uniforms:
+                source.insert(index, 'uniform COMPAT_PRECISION vec2 ' + uniform + ';')
+            break
+
+    for line in source:
+        for trans in translations:
+            line = line.replace(trans[0], trans[1])
+        ret.append(line)
+
+    return ret
+
+def fix_samplers(log_prefix, ref_index, source):
+    translations = []
+    added_samplers = []
+    translated_samplers = []
+    uniforms = []
+    struct_texunit0 = False  # If True, we have to append uniform sampler2D Texture manually ...
+    for line in source:
+        if ('TEXUNIT0' in line) and ('semantic' not in line):
+            main_sampler = (line.split(':')[2].split(' ')[1], 'Texture')
+            if main_sampler[0]:
+                translations.append(main_sampler)
+                log(log_prefix, 'Sampler:', main_sampler[0], '->', main_sampler[1])
+                struct_texunit0 = '.' in main_sampler[0]
+        elif ('//var sampler2D' in line) or ('#var sampler2D' in line):
+            cg_texture = line.split()[2]
+            translated = translate_texture(cg_texture)
+            orig_name = translated
+            new_name = line.split(':')[2].split(' ')[1]
+            log(log_prefix, 'Sampler:', new_name, '->', orig_name)
+            if new_name:
+                if translated != cg_texture and translated not in translated_samplers:
+                    translated_samplers.append(translated)
+                    added_samplers.append('uniform sampler2D ' + translated + ';')
+                translations.append((new_name, orig_name))
+        elif defines_var(line):
+            orig = line.split()[2]
+            translated = translate_texture_size(orig)
+            if translated != orig and translated not in uniforms:
+                cg_uniform = line.split(':')[2].split(' ')[1]
+                if cg_uniform:
+                    translations.append((cg_uniform, translated))
+                    uniforms.append(translated)
+
+    for sampler in added_samplers:
+        source.insert(ref_index, sampler)
+    if struct_texunit0:
+        source.insert(ref_index, 'uniform sampler2D Texture;')
+    for uniform in uniforms:
+        source.insert(ref_index, 'uniform COMPAT_PRECISION vec2 ' + uniform + ';')
+    for index, line in enumerate(source):
+        for orig, new in translations:
+            log(log_prefix, 'Translation:', orig, '->', new)
+            source[index] = source[index].replace(orig, new)
+
+    return source
+
+def hack_source_vertex(source):
+    ref_index = 0
+    for index, line in enumerate(source):
+        if 'void main()' in line:
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 InputSize;')
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 TextureSize;')
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 OutputSize;')
+            source.insert(index, 'uniform int FrameCount;')
+            source.insert(index, 'uniform int FrameDirection;')
+            source.insert(index, 'uniform mat4 MVPMatrix;')
+
+            ref_index = index
+            break
+
+    # Fix samplers in vertex shader (supported by GLSL).
+    source = fix_samplers('Vertex:', ref_index, source)
+    source = destructify_varyings(source, '$vout.')
+    source = replace_varyings(source)
+
+    return source
+
+def replace_global_fragment(source):
+    source = replace_global_in(source)
+
+    replace_table = [
+        ('varying', 'COMPAT_VARYING'),
+        ('texture2D', 'COMPAT_TEXTURE'),
+        ('FrameCount', 'float(FrameCount)'),
+        ('FrameDirection', 'float(FrameDirection)'),
+        ('input', 'input_dummy'),
+        ('output', 'output_dummy'),  # 'output' is reserved in GLSL.
+        ('gl_FragColor', 'FragColor'),
+    ]
+
+    return replace_by_table(source, replace_table)
+
+def translate_texture(cg):
+    translations = {
+        'ORIG.texture': 'OrigTexture',
+        'FEEDBACK.texture': 'FeedbackTexture',
+        'PREV.texture': 'PrevTexture',
+        'PREV1.texture': 'Prev1Texture',
+        'PREV2.texture': 'Prev2Texture',
+        'PREV3.texture': 'Prev3Texture',
+        'PREV4.texture': 'Prev4Texture',
+        'PREV5.texture': 'Prev5Texture',
+        'PREV6.texture': 'Prev6Texture',
+        'PASS1.texture': 'Pass1Texture',
+        'PASS2.texture': 'Pass2Texture',
+        'PASS3.texture': 'Pass3Texture',
+        'PASS4.texture': 'Pass4Texture',
+        'PASS5.texture': 'Pass5Texture',
+        'PASS6.texture': 'Pass6Texture',
+        'PASS7.texture': 'Pass7Texture',
+        'PASS8.texture': 'Pass8Texture',
+        'PASSPREV2.texture': 'PassPrev2Texture',
+        'PASSPREV3.texture': 'PassPrev3Texture',
+        'PASSPREV4.texture': 'PassPrev4Texture',
+        'PASSPREV5.texture': 'PassPrev5Texture',
+        'PASSPREV6.texture': 'PassPrev6Texture',
+        'PASSPREV7.texture': 'PassPrev7Texture',
+        'PASSPREV8.texture': 'PassPrev8Texture',
+    }
+
+    return translate(cg, translations)
+
+def hack_source_fragment(source):
+    ref_index = 0
+    for index, line in enumerate(source):
+        if 'void main()' in line:
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 InputSize;')
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 TextureSize;')
+            source.insert(index, 'uniform COMPAT_PRECISION vec2 OutputSize;')
+            source.insert(index, 'uniform int FrameCount;')
+            source.insert(index, 'uniform int FrameDirection;')
+            ref_index = index
+            break
+
+    source = fix_samplers('Fragment:', ref_index, source)
+    source = destructify_varyings(source, '$vin.')
+
+    return source
+
+def validate_shader(source, target):
+    log('Shader:')
+    log('===')
+    log(source)
+    log('===')
+
+    command = ['cgc', '-noentry', '-ogles']
+    p = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout_ret, stderr_ret = p.communicate(source.encode())
+
+    log('CGC:', stderr_ret.decode())
+
+    return p.returncode == 0
+
+def preprocess_vertex(source_data):
+    input_data = source_data.splitlines()
+    ret = []
+    for line in input_data:
+        if ('uniform' in line) and (('float4x4' in line) or ('half4x4' in line)):
+            ret.append('#pragma pack_matrix(column_major)\n')
+            ret.append(line)
+            ret.append('#pragma pack_matrix(row_major)\n')
+        else:
+            ret.append(line)
+    return '\n'.join(ret)
+
+def convert(source, dest):
+    # Have to preprocess first to resolve #includes so we can hack potential vertex shaders.
+    inc_dir = os.path.split(source)[0]
+    vert_cmd_preprocess = ['cgc', '-E', '-I', '.' if inc_dir == '' else inc_dir, source]
+    p = subprocess.Popen(vert_cmd_preprocess, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+    source_data, stderr_ret = p.communicate()
+    log(stderr_ret.decode())
+
+    if p.returncode != 0:
+        log('Vertex preprocessing failed ...')
+
+    source_data = preprocess_vertex(source_data.decode())
+
+    vert_cmd = ['cgc', '-profile', 'glesv', '-entry', 'main_vertex', '-quiet']
+    p = subprocess.Popen(vert_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+    vertex_source, stderr_ret = p.communicate(input=source_data.encode())
+    log(stderr_ret.decode())
+    vertex_source = vertex_source.decode()
+
+    if p.returncode != 0:
+        log('Vertex compilation failed ...')
+        return 1
+
+    frag_cmd = ['cgc', '-profile', 'glesf', '-entry', 'main_fragment', '-quiet']
+    p = subprocess.Popen(frag_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+    fragment_source, stderr_ret = p.communicate(input=source_data.encode())
+    log(stderr_ret.decode())
+    fragment_source = fragment_source.decode()
+
+    if p.returncode != 0:
+        log('Vertex compilation failed ...')
+        return 1
+
+    vertex_source = replace_global_vertex(vertex_source)
+    fragment_source = replace_global_fragment(fragment_source)
+
+    vertex_source = vertex_source.splitlines()
+    fragment_source = fragment_source.splitlines()
+
+    # Cg think we're using row-major matrices, but we're using column major.
+    # Also, Cg tends to compile matrix multiplications as dot products in GLSL.
+    # Hack in a fix for this.
+    log('Hacking vertex')
+    vertex_source = hack_source_vertex(vertex_source)
+    log('Hacking fragment')
+    fragment_source = hack_source_fragment(fragment_source)
+
+    # We compile to GLES, but we really just want modern GL ...
+    vertex_source = keep_line_if(lambda line: 'precision' not in line, vertex_source)
+    fragment_source = keep_line_if(lambda line: 'precision' not in line, fragment_source)
+
+    # Kill all comments. Cg adds lots of useless comments.
+    # Remove first line. It contains the name of the cg program.
+    vertex_source = remove_comments(vertex_source[1:])
+    fragment_source = remove_comments(fragment_source[1:])
+
+    vert_hacks = []
+    vert_hacks.append('''
+#if __VERSION__ >= 130
+#define COMPAT_VARYING out
+#define COMPAT_ATTRIBUTE in
+#define COMPAT_TEXTURE texture
+#else
+#define COMPAT_VARYING varying
+#define COMPAT_ATTRIBUTE attribute
+#define COMPAT_TEXTURE texture2D
+#endif
+
+#ifdef GL_ES
+#define COMPAT_PRECISION mediump
+#else
+#define COMPAT_PRECISION
+#endif''')
+
+    out_vertex = '\n'.join(vert_hacks + vertex_source)
+
+    frag_hacks = []
+    frag_hacks.append('''
+#if __VERSION__ >= 130
+#define COMPAT_VARYING in
+#define COMPAT_TEXTURE texture
+out vec4 FragColor;
+#else
+#define COMPAT_VARYING varying
+#define FragColor gl_FragColor
+#define COMPAT_TEXTURE texture2D
+#endif
+
+#ifdef GL_ES
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+#define COMPAT_PRECISION mediump
+#else
+#define COMPAT_PRECISION
+#endif''')
+
+    out_fragment = '\n'.join(frag_hacks + fragment_source)
+
+    if not validate_shader(out_vertex, 'glesv'):
+        log('Vertex shader does not compile ...')
+        return 1
+
+    if not validate_shader(out_fragment, 'glesf'):
+        log('Fragment shader does not compile ...')
+        return 1
+
+    with open(dest, 'w') as f:
+        f.write('// GLSL shader autogenerated by cg2glsl.py.\n')
+        f.write('#if defined(VERTEX)\n')
+        f.write(out_vertex)
+        f.write('\n')
+        f.write('#elif defined(FRAGMENT)\n')
+        f.write(out_fragment)
+        f.write('\n')
+        f.write('#endif\n')
+    return 0
+
+def convert_cgp(source, dest):
+    string = ''
+    with open(source, 'r') as f:
+        string = f.read().replace('.cg', '.glsl')
+
+    open(dest, 'w').write(string)
+
+def path_ext(path):
+    _, ext = os.path.splitext(path)
+    return ext
+
+def convert_path(source, source_dir, dest_dir, conv):
+    index = 0 if source_dir[-1] == '/' else 1
+    return os.path.join(dest_dir, source.replace(source_dir, '')[index:]).replace(conv[0], conv[1])
+
+def main():
+    if len(sys.argv) != 3:
+        print('Usage: {} prog.cg(p) prog.glsl(p)'.format(sys.argv[0]))
+        print('Batch mode usage: {} cg-dir out-xml-shader-dir'.format(sys.argv[0]))
+        print('Requires Python 3 and cgc (nvidia-cg-toolkit) 3.1.')
+        return 1
+
+    if os.path.isdir(sys.argv[1]):
+        global batch_mode
+        batch_mode = True
+        try:
+            os.makedirs(sys.argv[2])
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+
+        failed_cnt = 0
+        success_cnt = 0
+        failed_files = []
+        for dirname, _, filenames in os.walk(sys.argv[1]):
+            for source in filter(lambda path: path_ext(path) == '.cg', [os.path.join(dirname, filename) for filename in filenames]):
+                dest = convert_path(source, sys.argv[1], sys.argv[2], ('.cg', '.glsl'))
+                dirpath = os.path.split(dest)[0]
+                print('Dirpath:', dirpath)
+                if not os.path.isdir(dirpath):
+                    try:
+                        os.makedirs(dirpath)
+                    except OSError as e:
+                        if e.errno != errno.EEXIST:
+                            raise
+
+                try:
+                    ret = convert(source, dest)
+                    print(source, '->', dest, '...', 'succeeded!' if ret == 0 else 'failed!')
+
+                    if ret == 0:
+                        success_cnt += 1
+                    else:
+                        failed_cnt += 1
+                        failed_files.append(source)
+                except Exception as e:
+                    print(e)
+                    failed_files.append(source)
+                    failed_cnt += 1
+
+            for source in filter(lambda path: path_ext(path) == '.cgp', [os.path.join(dirname, filename) for filename in filenames]):
+                dest = convert_path(source, sys.argv[1], sys.argv[2], ('.cgp', '.glslp'))
+                dirpath = os.path.split(dest)[0]
+                print('Dirpath:', dirpath)
+                if not os.path.isdir(dirpath):
+                    try:
+                        os.makedirs(dirpath)
+                    except OSError as e:
+                        if e.errno != errno.EEXIST:
+                            raise
+
+                try:
+                    convert_cgp(source, dest)
+                    success_cnt += 1
+                except Exception as e:
+                    print(e)
+                    failed_files.append(source)
+                    failed_cnt += 1
+
+        print(success_cnt, 'shaders converted successfully.')
+        print(failed_cnt, 'shaders failed.')
+        if failed_cnt > 0:
+            print('Failed shaders:')
+            for path in failed_files:
+                print(path)
+
+    else:
+        source = sys.argv[1]
+        dest = sys.argv[2]
+
+        if path_ext(source) == '.cgp':
+            sys.exit(convert_cgp(source, dest))
+        else:
+            sys.exit(convert(source, dest))
+
+if __name__ == '__main__':
+    sys.exit(main())