| import argparse | |
| import glob | |
| import io | |
| 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") | |
| parser.add_argument("-m", "--msvc", action="store_true", help="set this to generate MSVC-compatible headers") | |
| 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", "rgb10_a2", | |
| } | |
| # 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, calling_token_type:str=None): | |
| # 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"] | |
| # Adding this calling_token_type != 'DEFINE' prevents us from adding a new line to stringify macros | |
| if is_directive and not is_newline and calling_token_type != 'DEFINE': | |
| 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,\ | |
| calling_token_type=tok.type) | |
| 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,\ | |
| calling_token_type=tok.type) | |
| 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,\ | |
| calling_token_type=tok.type) | |
| 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") | |
| if args.msvc: | |
| # MSVC cannot compile raw shaders as strings because of an internal | |
| # string length limit. There is, however, no limit on arrays so | |
| # instead write it out as an array of individual characters. | |
| out.write(f'const char {cpp_name}[] = {{\n ') | |
| codeIO = io.StringIO() | |
| self.emit_tokens_to_rewritten_glsl(codeIO, preserve_exported_switches=False) | |
| out_ch_count = 0 | |
| for ch in codeIO.getvalue(): | |
| # use `repr` to escape the characters (but we need to handle single | |
| # quote manually, since it will represent that as "'" - which will | |
| # definitely not compile) | |
| out.write(f" {repr(ch)},") if ch != "'" else out.write(" '\\'',") | |
| out_ch_count = out_ch_count + 1 | |
| CHAR_COUNT_PER_LINE = 12 | |
| if (out_ch_count % CHAR_COUNT_PER_LINE) == 0: | |
| out.write('\n ') | |
| # Null-terminate so the array is a valid C-string when passed to | |
| # APIs like ostream::operator<<(const char*), which read until '\0'. | |
| out.write(" '\\0'\n};\n") | |
| else: | |
| # For non-MSVC outputs just output as a raw string | |
| 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) |