| import argparse |
| import glob |
| import os |
| import re |
| import sys |
| from collections import defaultdict |
| |
| parser = argparse.ArgumentParser(description=""" |
| Process a batch of .glsl files. Minify and export them to C++ strings. |
| |
| Performs the following transformations: |
| * Strip comments. |
| * Strip whitespace. |
| * Strip unused #defines. |
| * Rename stpq and rgba swizzles to xyzw. |
| * Rename variables. |
| - No new name begins with the '_' character, so internal code can begin names with '_' |
| without fear of renaming collisions. |
| - GLSL keywords and builtins are not renamed. |
| - Tokens beginning with '@' have their new name exported to a header file. |
| - Tokens beginning with '$' are not renamed, with the exception of removing the leading '$'. |
| |
| "file.glsl" gets exported to: |
| * "outdir/file.exports.h", with #defines for the rewritten names of each identifier that began |
| with '@' in the original shader. |
| * "outdir/file.glsl.cpp", with a global `const char file_glsl[]` in the rive::glsl |
| namespace that contains the minified shader code. This variable is intentionally declared as a |
| global in order to generate a link error if the user includes the string more than once in |
| the build process. |
| * "outdir/file.minified.glsl" for offline compiling, with all variables renamed except for |
| exported #defines names (since the offline compiling process will set those defines). |
| """) |
| parser.add_argument("files", type=str, nargs="+", help="a list of glsl files") |
| parser.add_argument("-o", "--outdir", required=True, |
| help="OUTPUT directory to store the header files") |
| parser.add_argument("-H", "--human-readable", action='store_true', |
| help="don't rename or strip out comments or whitespace") |
| parser.add_argument("-p", "--ply-path", type=str, help="path to ply module") |
| |
| args = parser.parse_args() |
| |
| if args.ply_path: |
| # --ply-path was specified, so add it to the sys path so we can locate the module. |
| # (if it was not specified we'll assume that it's already reachable via the path) |
| |
| # Convert posix path to windows |
| convertedPath = args.ply_path |
| if sys.platform.startswith('win32') and args.ply_path[:2] == '/c': |
| convertedPath = 'C:\\' + args.ply_path[2:] |
| print('Using ply path:' + convertedPath) |
| |
| sys.path.append(convertedPath) |
| |
| import ply.lex as lex |
| |
| # tokens used by PLY to run lexical analysis on our glsl files. |
| tokens = [ |
| "DEFINE", |
| "IFDEF", |
| "DEFINED_ID", |
| "TOKEN_PASTE", |
| "DIRECTIVE", |
| "LINE_COMMENT", |
| "BLOCK_COMMENT", |
| "WHITESPACE", |
| "OP", |
| "FLOAT", |
| "HEX", |
| "INT", |
| "ID", |
| "UNKNOWN", |
| ] |
| |
| # tracks which exported identifiers (identifiers whose @name begins with '@') are used as switches |
| # inside an #ifdef, #if defined(), etc. |
| exported_switches = set() |
| |
| # counts the number of times each ID is seen, to prioritize which IDs get the shortest names |
| all_id_counts = defaultdict(int); |
| all_id_reference_counts = defaultdict(int); |
| def parse_id(name, exports, *, is_reference): |
| all_id_counts[name] += 1 |
| if is_reference: |
| all_id_reference_counts[name] += 1 |
| # identifiers that begin with '@' get exported to C++ through #defines. |
| if name[0] == '@': |
| exports.add(name) |
| |
| # lexing functions used by PLY. |
| def t_DEFINE(tok): |
| r"\#[ \t]*define[ \t]+(?P<id>[\@\$]?[A-Za-z_][A-Za-z0-9_]*)(?P<arglist>\((\n|[^\)])*\))?(?P<val>(((\\\n|.)(?!\/[\/\*]))*))?" |
| tok.define_id = re.match(t_DEFINE.__doc__, tok.value)['id'] |
| arglist = re.match(t_DEFINE.__doc__, tok.value)['arglist'] |
| tok.define_arglist = Minifier(arglist, "", tok.lexer.exports) if arglist else None |
| val = re.match(t_DEFINE.__doc__, tok.value)['val'] |
| tok.define_val = Minifier(val, "", tok.lexer.exports) if val else None |
| parse_id(tok.define_id, tok.lexer.exports, is_reference=False) |
| tok.lexer.lineno += tok.value.count('\n') |
| return tok |
| |
| def t_IFDEF(tok): |
| r"\#[ \t]*(?P<tag>ifn?def)[ \t]+(?P<ifdef_id>[\@\$]?[A-Za-z_][A-Za-z0-9_]*)" |
| tok.ifdef_tag = re.match(t_IFDEF.__doc__, tok.value)['tag'] |
| tok.ifdef_id = re.match(t_IFDEF.__doc__, tok.value)['ifdef_id'] |
| if tok.ifdef_id[0] == '@': |
| exported_switches.add(tok.ifdef_id) |
| parse_id(tok.ifdef_id, tok.lexer.exports, is_reference=True) |
| return tok |
| |
| def t_DEFINED_ID(tok): |
| r"defined\((?P<defined_id>[\@\$]?[A-Za-z_][A-Za-z0-9_]*)\)" |
| tok.defined_id = re.match(t_DEFINED_ID.__doc__, tok.value)['defined_id'] |
| if tok.defined_id[0] == '@': |
| exported_switches.add(tok.defined_id) |
| parse_id(tok.defined_id, tok.lexer.exports, is_reference=True) |
| return tok |
| |
| def t_TOKEN_PASTE(tok): |
| r"\#\#" |
| return tok |
| |
| def t_DIRECTIVE(tok): |
| r"\#[ \t]*(?P<val>(((\\\n|.)(?!\/[\/\*]))*))" |
| val = re.match(t_DIRECTIVE.__doc__, tok.value)['val'] |
| tok.directive_val = Minifier(val, "", tok.lexer.exports) if val else None |
| tok.lexer.lineno += tok.value.count('\n') |
| return tok |
| |
| def t_LINE_COMMENT(tok): |
| r"//(\\\n|.)*" |
| tok.lexer.lineno += tok.value.count('\n') |
| return tok |
| |
| def t_BLOCK_COMMENT(tok): |
| r"\/\*(\*(?!\/)|[^*])*\*\/" |
| tok.lexer.lineno += tok.value.count('\n') |
| return tok |
| |
| def t_WHITESPACE(tok): |
| r"(\s|\\\r?\n)+" |
| tok.lexer.lineno += tok.value.count('\n') |
| return tok |
| |
| def t_OP(tok): |
| r"[~!%^&\*\(\)\-=+\/\[\]{}\?:\<\>\.\,|;]" |
| return tok |
| |
| def t_FLOAT(tok): |
| r"([0-9]*\.[0-9]+|[0-9]+\.)([eE][+\-]?[0-9]+)?|([0-9]+[eE][+\-]?[0-9]+)" |
| return tok |
| |
| def t_HEX(tok): |
| r"0x[0-9a-fA-F]+u?" |
| return tok |
| |
| def t_INT(tok): |
| r"[0-9]+u?" |
| return tok |
| |
| def t_ID(tok): |
| r"[\@\$]?[A-Za-z_][A-Za-z0-9_]*" |
| parse_id(tok.value, tok.lexer.exports, is_reference=True) |
| return tok |
| |
| def t_UNKNOWN(tok): |
| r"." |
| return tok |
| |
| def t_error(tok): |
| raise Exception("Illegal character '%s' at line %d" % (tok.value[0], tok.lexer.lineno)) |
| |
| # identifier names that cannot be renamed |
| glsl_reserved = { |
| "EmitStreamVertex", "EmitVertex", "EmitVertex", "EndPrimitive", "EndPrimitive", |
| "EndStreamPrimitive", "abs", "abs", "abs", "acos", "acosh", "all", "allInvocations", |
| "allInvocationsEqual", "any", "anyInvocation", "asin", "asinh", "atan", "atan", "atanh", |
| "atomicAdd", "atomicAdd", "atomicAnd", "atomicAnd", "atomicCompSwap", "atomicCompSwap", |
| "atomicCounter", "atomicCounterAdd", "atomicCounterAnd", "atomicCounterCompSwap", |
| "atomicCounterDecrement", "atomicCounterExchange", "atomicCounterIncrement", "atomicCounterMax", |
| "atomicCounterMin", "atomicCounterOr", "atomicCounterSubtract", "atomicCounterXor", |
| "atomicExchange", "atomicExchange", "atomicMax", "atomicMax", "atomicMin", "atomicMin", |
| "atomicOr", "atomicOr", "atomicXor", "atomicXor", "barrier", "barrier", |
| "beginFragmentShaderOrderingINTEL", "beginInvocationInterlockARB", "beginInvocationInterlockNV", |
| "binding", "bitCount", "bitCount", "bitfieldExtract", "bitfieldExtract", "bitfieldInsert", |
| "bitfieldInsert", "bitfieldReverse", "bitfieldReverse", "bool", "break", "bvec2", "bvec3", |
| "bvec4", "case", "ceil", "ceil", "centroid", "clamp", "clamp", "clamp", "clamp", "clamp", |
| "clamp", "clamp", "clamp", "coherent", "const", "continue", "cos", "cosh", "cross", "cross", |
| "dFdx", "dFdx", "dFdxCoarse", "dFdxFine", "dFdy", "dFdy", "dFdyCoarse", "dFdyFine", "default", |
| "degrees", "determinant", "determinant", "determinant", "discard", "distance", "distance", "do", |
| "dot", "dot", "else", "endInvocationInterlockARB", "endInvocationInterlockNV", "equal", "equal", |
| "equal", "equal", "exp", "exp2", "faceforward", "faceforward", "false", "findLSB", "findLSB", |
| "findMSB", "findMSB", "flat", "float", "floatBitsToInt", "floatBitsToUint", "floor", "floor", |
| "fma", "fma", "fma", "for", "fract", "fract", "frexp", "frexp", "ftransform", "fwidth", |
| "fwidth", "fwidthCoarse", "fwidthFine", "greaterThan", "greaterThan", "greaterThan", |
| "greaterThanEqual", "greaterThanEqual", "greaterThanEqual", "groupMemoryBarrier", "highp", "if", |
| "iimage2D", "image2D", "imageAtomicAdd", "imageAtomicAdd", "imageAtomicAdd", "imageAtomicAdd", |
| "imageAtomicAnd", "imageAtomicAnd", "imageAtomicAnd", "imageAtomicAnd", "imageAtomicCompSwap", |
| "imageAtomicCompSwap", "imageAtomicCompSwap", "imageAtomicCompSwap", "imageAtomicExchange", |
| "imageAtomicExchange", "imageAtomicExchange", "imageAtomicExchange", "imageAtomicExchange", |
| "imageAtomicMax", "imageAtomicMax", "imageAtomicMax", "imageAtomicMax", "imageAtomicMin", |
| "imageAtomicMin", "imageAtomicMin", "imageAtomicMin", "imageAtomicOr", "imageAtomicOr", |
| "imageAtomicOr", "imageAtomicOr", "imageAtomicXor", "imageAtomicXor", "imageAtomicXor", |
| "imageAtomicXor", "imageLoad", "imageLoad", "imageLoad", "imageLoad", "imageLoad", "imageLoad", |
| "imageLoad", "imageLoad", "imageSamples", "imageSamples", "imageSize", "imageSize", "imageSize", |
| "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", |
| "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", "imageSize", |
| "imageSize", "imageSize", "imageStore", "imageStore", "imageStore", "imageStore", "imageStore", |
| "imageStore", "imageStore", "imageStore", "imageStore", "imulExtended", "in", "inout", "int", |
| "intBitsToFloat", "interpolateAtCentroid", "interpolateAtCentroid", "interpolateAtCentroid", |
| "interpolateAtCentroid", "interpolateAtCentroid", "interpolateAtCentroid", |
| "interpolateAtCentroid", "interpolateAtCentroid", "interpolateAtOffset", "interpolateAtOffset", |
| "interpolateAtOffset", "interpolateAtOffset", "interpolateAtOffset", "interpolateAtOffset", |
| "interpolateAtOffset", "interpolateAtSample", "interpolateAtSample", "interpolateAtSample", |
| "interpolateAtSample", "interpolateAtSample", "interpolateAtSample", "interpolateAtSample", |
| "interpolateAtSample", "invariant", "inverse", "inverse", "inverse", "inversesqrt", |
| "inversesqrt", "isampler2D", "isampler2DArray", "isampler3D", "isamplerCube", "isinf", "isinf", |
| "isnan", "isnan", "ivec2", "ivec3", "ivec4", "layout", "ldexp", "ldexp", "length", "length", |
| "lessThan", "lessThan", "lessThan", "lessThanEqual", "lessThanEqual", "lessThanEqual", |
| "location", "log", "log2", "lowp", "main", "mat2", "mat2x2", "mat2x3", "mat2x4", "mat3", |
| "mat3x2", "mat3x3", "mat3x4", "mat4", "mat4x2", "mat4x3", "mat4x4", "matrixCompMult", |
| "matrixCompMult", "matrixCompMult", "matrixCompMult", "matrixCompMult", "matrixCompMult", |
| "matrixCompMult", "matrixCompMult", "matrixCompMult", "max", "max", "max", "max", "max", "max", |
| "max", "max", "mediump", "memoryBarrier", "memoryBarrierAtomicCounter", "memoryBarrierBuffer", |
| "memoryBarrierImage", "memoryBarrierShared", "min", "min", "min", "min", "min", "min", "min", |
| "min", "mix", "mix", "mix", "mix", "mix", "mix", "mix", "mix", "mix", "mod", "mod", "mod", |
| "mod", "modf", "modf", "noise1", "noise2", "noise3", "noise4", "noperspective", "normalize", |
| "normalize", "not", "notEqual", "notEqual", "notEqual", "notEqual", "out", "outerProduct", |
| "outerProduct", "outerProduct", "outerProduct", "outerProduct", "outerProduct", "outerProduct", |
| "outerProduct", "outerProduct", "packDouble2x32", "packHalf2x16", "packSnorm2x16", |
| "packSnorm4x8", "packUnorm2x16", "packUnorm4x8", "pixelLocalLoadANGLE", "pixelLocalStoreANGLE", |
| "pow", "precision", "r16f", "r32f", "r32i", "r32ui", "radians", "reflect", "reflect", "refract", |
| "refract", "return", "rg16f", "rgb_2_yuv", "rgba8", "rgba8i", "rgba8ui", "round", "round", |
| "roundEven", "roundEven", "sampler2D", "sampler2DArray", "sampler2DArrayShadow", |
| "sampler2DShadow", "sampler3D", "samplerCube", "samplerCubeShadow", "shadow1D", "shadow1DLod", |
| "shadow1DProj", "shadow1DProj", "shadow1DProjLod", "shadow2D", "shadow2D", "shadow2DEXT", |
| "shadow2DLod", "shadow2DProj", "shadow2DProj", "shadow2DProjEXT", "shadow2DProjLod", "sign", |
| "sign", "sign", "sin", "sinh", "smooth", "smoothstep", "smoothstep", "smoothstep", "smoothstep", |
| "sqrt", "sqrt", "std140", "std430", "step", "step", "step", "step", "struct", "subpassLoad", |
| "subpassLoad", "switch", "tan", "tanh", "texelFetch", "texelFetch", "texelFetch", "texelFetch", |
| "texelFetch", "texelFetch", "texelFetch", "texelFetch", "texelFetch", "texelFetch", |
| "texelFetch", "texelFetch", "texelFetch", "texelFetchOffset", "texelFetchOffset", |
| "texelFetchOffset", "texelFetchOffset", "texelFetchOffset", "texelFetchOffset", "texture", |
| "texture", "texture", "texture", "texture", "texture", "texture", "texture", "texture", |
| "texture", "texture", "texture", "texture", "texture", "texture", "texture", "texture", |
| "texture", "texture", "texture", "texture", "texture", "texture", "texture", "texture", |
| "texture", "texture", "texture", "texture", "texture", "texture", "texture", "texture", |
| "texture", "texture", "texture", "texture1D", "texture1D", "texture1DLod", "texture1DProj", |
| "texture1DProj", "texture1DProj", "texture1DProj", "texture1DProjLod", "texture1DProjLod", |
| "texture2D", "texture2D", "texture2D", "texture2DGradEXT", "texture2DLod", "texture2DLod", |
| "texture2DLodEXT", "texture2DProj", "texture2DProj", "texture2DProj", "texture2DProj", |
| "texture2DProj", "texture2DProj", "texture2DProjGradEXT", "texture2DProjGradEXT", |
| "texture2DProjLod", "texture2DProjLod", "texture2DProjLod", "texture2DProjLod", |
| "texture2DProjLodEXT", "texture2DProjLodEXT", "texture2DRect", "texture2DRectProj", |
| "texture2DRectProj", "texture3D", "texture3D", "texture3D", "texture3D", "texture3DLod", |
| "texture3DLod", "texture3DProj", "texture3DProj", "texture3DProj", "texture3DProj", |
| "texture3DProjLod", "texture3DProjLod", "textureCube", "textureCube", "textureCubeGradEXT", |
| "textureCubeLod", "textureCubeLod", "textureCubeLodEXT", "textureGather", "textureGather", |
| "textureGather", "textureGather", "textureGather", "textureGather", "textureGather", |
| "textureGather", "textureGather", "textureGather", "textureGather", "textureGather", |
| "textureGather", "textureGather", "textureGather", "textureGather", "textureGather", |
| "textureGather", "textureGather", "textureGather", "textureGather", "textureGatherOffset", |
| "textureGatherOffset", "textureGatherOffset", "textureGatherOffset", "textureGatherOffset", |
| "textureGatherOffset", "textureGatherOffset", "textureGatherOffset", "textureGatherOffset", |
| "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", |
| "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", |
| "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", |
| "textureGatherOffsets", "textureGatherOffsets", "textureGatherOffsets", "textureGrad", |
| "textureGrad", "textureGrad", "textureGrad", "textureGrad", "textureGrad", "textureGrad", |
| "textureGrad", "textureGrad", "textureGrad", "textureGrad", "textureGrad", "textureGrad", |
| "textureGrad", "textureGrad", "textureGradOffset", "textureGradOffset", "textureGradOffset", |
| "textureGradOffset", "textureGradOffset", "textureGradOffset", "textureGradOffset", |
| "textureGradOffset", "textureGradOffset", "textureGradOffset", "textureGradOffset", |
| "textureLod", "textureLod", "textureLod", "textureLod", "textureLod", "textureLod", |
| "textureLod", "textureLod", "textureLod", "textureLod", "textureLod", "textureLodOffset", |
| "textureLodOffset", "textureLodOffset", "textureLodOffset", "textureLodOffset", |
| "textureLodOffset", "textureLodOffset", "textureLodOffset", "textureOffset", "textureOffset", |
| "textureOffset", "textureOffset", "textureOffset", "textureOffset", "textureOffset", |
| "textureOffset", "textureOffset", "textureOffset", "textureOffset", "textureOffset", |
| "textureOffset", "textureOffset", "textureOffset", "textureOffset", "textureOffset", |
| "textureOffset", "textureOffset", "textureProj", "textureProj", "textureProj", "textureProj", |
| "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", |
| "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", |
| "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", "textureProj", |
| "textureProj", "textureProj", "textureProj", "textureProj", "textureProjGrad", |
| "textureProjGrad", "textureProjGrad", "textureProjGrad", "textureProjGrad", "textureProjGrad", |
| "textureProjGrad", "textureProjGrad", "textureProjGrad", "textureProjGrad", |
| "textureProjGradOffset", "textureProjGradOffset", "textureProjGradOffset", |
| "textureProjGradOffset", "textureProjGradOffset", "textureProjGradOffset", |
| "textureProjGradOffset", "textureProjGradOffset", "textureProjGradOffset", |
| "textureProjGradOffset", "textureProjLod", "textureProjLod", "textureProjLod", "textureProjLod", |
| "textureProjLod", "textureProjLod", "textureProjLod", "textureProjLodOffset", |
| "textureProjLodOffset", "textureProjLodOffset", "textureProjLodOffset", "textureProjLodOffset", |
| "textureProjLodOffset", "textureProjLodOffset", "textureProjOffset", "textureProjOffset", |
| "textureProjOffset", "textureProjOffset", "textureProjOffset", "textureProjOffset", |
| "textureProjOffset", "textureProjOffset", "textureProjOffset", "textureProjOffset", |
| "textureProjOffset", "textureProjOffset", "textureProjOffset", "textureProjOffset", |
| "textureProjOffset", "textureProjOffset", "textureProjOffset", "textureQueryLevels", |
| "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", |
| "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", |
| "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", "textureQueryLevels", |
| "textureQueryLod", "textureQueryLod", "textureQueryLod", "textureQueryLod", "textureQueryLod", |
| "textureQueryLod", "textureQueryLod", "textureQueryLod", "textureQueryLod", "textureQueryLod", |
| "textureQueryLod", "textureQueryLod", "textureQueryLod", "textureSamples", "textureSamples", |
| "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", |
| "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", |
| "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", |
| "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", "textureSize", |
| "textureVideoWEBGL", "transpose", "transpose", "transpose", "transpose", "transpose", |
| "transpose", "transpose", "transpose", "transpose", "true", "trunc", "trunc", "uaddCarry", |
| "uimage2D", "uint", "uintBitsToFloat", "umulExtended", "uniform", "unpackDouble2x32", |
| "unpackHalf2x16", "unpackSnorm2x16", "unpackSnorm4x8", "unpackUnorm2x16", "usampler2D", |
| "usampler2DArray", "usampler3D", "usamplerCube", "usubBorrow", "uvec2", "uvec3", "uvec4", |
| "vec2", "vec3", "vec4", "void", "volatile", "while", "yuv_2_rgb", "__pixel_localEXT", |
| "__pixel_local_inEXT", "__pixel_local_outEXT", "set", "texture2D", "utexture2D", "sampler", |
| "subpassInput", "subpassInputMS", "usubpassInput", "input_attachment_index", |
| "readonly", "buffer", "unpackUnorm4x8", "defined", "elif", "extension", |
| "enable", "require", "endif", "undef", "pragma", "__VERSION__", |
| "constant_id", "blend_support_all_equations", "blend_support_multiply", |
| "blend_support_screen", "blend_support_overlay", "blend_support_darken", |
| "blend_support_lighten", "blend_support_colordodge", |
| "blend_support_colorburn", "blend_support_hardlight", |
| "blend_support_softlight", "blend_support_difference", |
| "blend_support_exclusion", |
| } |
| |
| # rgba and stpq get rewritten to xyzw, so we only need to check xyzw here. This way we can keep |
| # renaming to names like, e.g., "rg". |
| xyzw_pattern = re.compile(r"^[xyzw]{1,4}$") |
| |
| # HLSL registers base names can't be overwritten by macro arguments if token pasting (e.g. t##IDX). |
| hlsl_register_base_names = ['t', 's', 'u', 'b'] |
| |
| # HLSL registers (e.g., t0, u1) can't be overwritten by a #define. |
| hlsl_register_pattern = re.compile(r"^[tsub]\d+$") |
| |
| # can we rename to or from 'name'? |
| def is_reserved_keyword(name): |
| return name in glsl_reserved\ |
| or xyzw_pattern.match(name)\ |
| or name in hlsl_register_base_names\ |
| or hlsl_register_pattern.match(name)\ |
| or name.startswith("$")\ |
| or name.startswith("gl_")\ |
| or name.startswith("GL_")\ |
| or name.startswith("__pixel_local")\ |
| or name.endswith("ANGLE") |
| |
| def remove_leading_annotation(name): |
| if name[0] == '@': |
| # A leading '@' indicates identifier names that should be exported. Rename '@my_var' to |
| # '_EXPORTED_my_var' to enforce that '@my_var' and 'my_var' are not interchangeable. |
| return '_EXPORTED_' + name[1:] |
| if name[0] == '$': |
| # A leading '$' indicates identifier names that should not be renamed. |
| return name[1:] |
| return name |
| |
| # Generates new identifier names to rewrite our variables. |
| class NameGenerator: |
| def __init__(self, first_letter_chars, additional_letter_chars): |
| self.first_letter_chars = first_letter_chars |
| self.additional_letter_chars = additional_letter_chars |
| self.name_index = 0 |
| |
| def next_name(self): |
| i = self.name_index |
| # Generate the first character from 'self.first_letter_chars' |
| name = self.first_letter_chars[i % len(self.first_letter_chars)] |
| i = i // len(self.first_letter_chars) |
| while i > 0: |
| # Generate the remaining characters from 'self.additional_letter_chars' |
| name += self.additional_letter_chars[i % len(self.additional_letter_chars)] |
| i = i // len(self.additional_letter_chars) |
| self.name_index = self.name_index + 1 |
| return name |
| |
| # Exported variables only use upper case letters in their names. HLSL semantics are not case |
| # sensitive and may also assign special meaning to numbers. |
| upper_case_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| upper_case_name_generator = NameGenerator(upper_case_chars, upper_case_chars + "_") |
| |
| # Don't begin new names with the the '_' character. Internal code can begin names with '_' without |
| # fear of renaming collisions. |
| lower_and_upper_chars = "abcdefghijklmnopqrstuvwxyz" + upper_case_chars |
| general_name_generator = NameGenerator(lower_and_upper_chars, "_0123456789" + lower_and_upper_chars) |
| |
| used_new_names = set() |
| |
| def generate_new_name(*, force_upper_case): |
| name_generator = upper_case_name_generator if force_upper_case else general_name_generator |
| while True: |
| name = name_generator.next_name() |
| if not is_reserved_keyword(name) and not name in used_new_names: |
| used_new_names.add(name) |
| return name |
| |
| # mapping from original identifiers to new names. |
| new_names = {} |
| def generate_new_names(): |
| for name,count in sorted(all_id_counts.items(), key=lambda x:x[1], reverse=True): |
| new_name = (remove_leading_annotation(name) |
| if args.human_readable or is_reserved_keyword(name) |
| # HLSL semantics are not case sensitive and can assign special meaning to |
| # numbers. Make all exported names upper case with no numbers. |
| else generate_new_name(force_upper_case=name[0] == '@')) |
| new_names[name] = new_name |
| |
| # used to rewrite rgba and stpq swizzles to xyzw. |
| rgba_stpq_pattern = re.compile(r"^([rgba]{1,4}|[stpq]{1,4})$") |
| rgba_stpq_remap = {'r':'x', 'g':'y', 'b':'z', 'a':'w', |
| 's':'x', 't':'y', 'p':'z', 'q':'w',} |
| |
| |
| # minifies a single GLSL file. |
| class Minifier: |
| def __init__(self, code, basename, exports=set()): |
| # parse tokens. |
| lexer = lex.lex() |
| lexer.exports = exports |
| lexer.input(code) |
| self.tokens = [tok for tok in lexer]; |
| self.exports = exports |
| self.basename = basename |
| |
| |
| # Strips unneeded code from the tokens. Called after all Minifiers have been parsed. |
| def strip_tokens(self): |
| assert(not args.human_readable) |
| |
| # strip comments. |
| self.tokens = \ |
| [tok for tok in self.tokens if "COMMENT" not in tok.type] |
| |
| # strip unused defines. |
| self.tokens = [tok for tok in self.tokens if tok.type != "DEFINE"\ |
| or all_id_reference_counts[tok.define_id] > 0] |
| |
| # merge whitespace. |
| unmerged,self.tokens = self.tokens,[] |
| for tok in unmerged: |
| if tok.type == "DEFINE": |
| if tok.define_arglist != None: |
| tok.define_arglist.strip_tokens() |
| if tok.define_val != None: |
| tok.define_val.strip_tokens() |
| if tok.type == "DIRECTIVE": |
| if tok.directive_val != None: |
| tok.directive_val.strip_tokens() |
| if (tok.type == "WHITESPACE" |
| and len(self.tokens) > 0 |
| and self.tokens[-1].type == "WHITESPACE"): |
| self.tokens[-1].value += tok.value |
| else: |
| self.tokens.append(tok) |
| |
| |
| # generates rewritten glsl from our tokens. |
| def emit_tokens_to_rewritten_glsl(self, out, *, preserve_exported_switches): |
| # stand-in for a null token. |
| lasttoken = lambda : None |
| lasttoken.type = "" |
| lasttoken.value = "" |
| |
| lasttoken_needs_whitespace = False |
| is_newline = True |
| |
| for tok in self.tokens: |
| if tok.type == "WHITESPACE": |
| if args.human_readable: |
| out.write(tok.value) |
| is_newline = tok.value[-1] == '\n' |
| lasttoken_needs_whitespace = False |
| continue |
| |
| is_directive = tok.type in ["DEFINE", "IFDEF", "DIRECTIVE"] |
| needs_whitespace = tok.type in ["FLOAT", "INT", "HEX", "ID", "DEFINED_ID"] |
| if is_directive and not is_newline: |
| out.write('\n') |
| elif needs_whitespace and lasttoken_needs_whitespace: |
| out.write(' ') |
| |
| # is_newline will be false once we output the token (unless this value otherwise gets |
| # updated). |
| is_newline = False |
| |
| if tok.type == "ID": |
| if (rgba_stpq_pattern.match(tok.value) |
| and lasttoken.type == "OP" |
| and lasttoken.value == "."): |
| # convert rgba and stpq to xyzw. |
| out.write(''.join([rgba_stpq_remap[ch] for ch in tok.value])) |
| else: |
| self.write_identifier(out, tok.value, preserve_exported_switches) |
| |
| elif tok.type == "DEFINE": |
| out.write("#define ") |
| self.write_identifier(out, tok.define_id, preserve_exported_switches) |
| if tok.define_arglist != None: |
| is_newline = tok.define_arglist.emit_tokens_to_rewritten_glsl(\ |
| out,\ |
| preserve_exported_switches=preserve_exported_switches) |
| assert(not is_newline) |
| if tok.define_val != None: |
| out.write(' ') |
| is_newline = tok.define_val.emit_tokens_to_rewritten_glsl(\ |
| out,\ |
| preserve_exported_switches=preserve_exported_switches) |
| |
| elif tok.type == "IFDEF": |
| out.write('#') |
| out.write(tok.ifdef_tag) |
| out.write(' ') |
| self.write_identifier(out, tok.ifdef_id, preserve_exported_switches) |
| |
| elif tok.type == "DEFINED_ID": |
| out.write('defined(') |
| self.write_identifier(out, tok.defined_id, preserve_exported_switches) |
| out.write(')') |
| |
| elif tok.type == "DIRECTIVE": |
| out.write("#") |
| if tok.directive_val != None: |
| is_newline = tok.directive_val.emit_tokens_to_rewritten_glsl(\ |
| out,\ |
| preserve_exported_switches=preserve_exported_switches) |
| |
| else: |
| out.write(tok.value) |
| |
| # Since we preserve whitespace in 'human_readable' mode, the newline after a |
| # preprocessor directive will happen for us automatically unless 'human_readable' is |
| # false. |
| if not args.human_readable and is_directive and not is_newline: |
| out.write('\n') |
| is_newline = True |
| |
| lasttoken = tok |
| lasttoken_needs_whitespace = needs_whitespace |
| |
| return is_newline |
| |
| |
| def write_identifier(self, out, identifier, preserve_exported_switches): |
| if preserve_exported_switches and identifier in exported_switches: |
| assert(identifier[0] == '@') |
| out.write(identifier[1:]) |
| else: |
| out.write(new_names[identifier]) |
| |
| |
| def write_exports(self, outdir): |
| output_path = os.path.join(outdir, f"{self.basename}.exports.h") |
| print(f"Exporting {output_path} <- {self.basename}") |
| out = open(output_path, "w", newline='\n') |
| out.write('#pragma once\n\n') |
| for exp in sorted(self.exports): |
| out.write('#define GLSL_%s "%s"\n' % (exp[1:], new_names[exp])) |
| out.write('#define GLSL_%s_raw %s\n' % (exp[1:], new_names[exp])) |
| out.close() |
| |
| |
| def write_embedded_glsl(self, outdir): |
| output_path = os.path.join(outdir, self.basename + ".hpp") |
| print("Embedding %s <- %s" % (output_path, self.basename)) |
| out = open(output_path, "w", newline='\n') |
| out.write("#pragma once\n\n") |
| |
| out.write(f'#include "{self.basename}.exports.h"\n\n') |
| |
| # emit shader code. |
| root, ext = os.path.splitext(self.basename) |
| cpp_name = root |
| if ext != '.glsl': |
| cpp_name = f"{root}_{ext[1:]}" |
| out.write("namespace rive {\n") |
| out.write("namespace gpu {\n") |
| out.write("namespace glsl {\n") |
| out.write(f'const char {cpp_name}[] = R"===(') |
| |
| is_newline = self.emit_tokens_to_rewritten_glsl(out, preserve_exported_switches=False) |
| if not is_newline: |
| out.write('\n') |
| |
| out.write(')===";\n') |
| out.write("} // namespace glsl\n") |
| out.write("} // namespace gpu\n") |
| out.write("} // namespace rive") |
| out.close() |
| |
| def write_offline_glsl(self, outdir): |
| root, ext = os.path.splitext(self.basename) |
| output_path = os.path.join(outdir, f"{root}.minified{ext}") |
| print(f"Minifying f{output_path} <- {self.basename}") |
| out = open(output_path, "w", newline='\n') |
| self.emit_tokens_to_rewritten_glsl(out, preserve_exported_switches=True) |
| out.close() |
| |
| |
| # parse all GLSL files before renaming. This keeps the renaming consistent across files. |
| minifiers = [Minifier(open(f).read(), os.path.basename(f)) for f in args.files] |
| generate_new_names() |
| |
| # minify all GLSL files. |
| if not os.path.exists(args.outdir): |
| os.makedirs(args.outdir) |
| for m in minifiers: |
| if not args.human_readable: |
| m.strip_tokens() |
| m.write_exports(args.outdir) |
| m.write_embedded_glsl(args.outdir) |
| m.write_offline_glsl(args.outdir) |