1 /*
   2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.scenario.effect.compiler;
  27 
  28 import com.sun.scenario.effect.compiler.backend.hw.ES2Backend;
  29 import com.sun.scenario.effect.compiler.backend.hw.HLSLBackend;
  30 import com.sun.scenario.effect.compiler.backend.prism.PrismBackend;
  31 import com.sun.scenario.effect.compiler.backend.sw.java.JSWBackend;
  32 import com.sun.scenario.effect.compiler.backend.sw.me.MEBackend;
  33 import com.sun.scenario.effect.compiler.backend.sw.sse.SSEBackend;
  34 import com.sun.scenario.effect.compiler.tree.ProgramUnit;
  35 import org.antlr.runtime.ANTLRInputStream;
  36 import org.antlr.runtime.CommonTokenStream;
  37 import org.antlr.stringtemplate.CommonGroupLoader;
  38 import org.antlr.stringtemplate.StringTemplateGroup;
  39 
  40 import java.io.*;
  41 import java.util.ArrayList;
  42 import java.util.HashMap;
  43 import java.util.List;
  44 import java.util.Map;
  45 
  46 /**
  47  */
  48 public class JSLC {
  49 
  50     public static final int OUT_NONE     = (0 << 0);
  51     public static final int OUT_D3D      = (1 << 0);
  52     public static final int OUT_ES2      = (1 << 1);
  53     public static final int OUT_JAVA     = (1 << 2);
  54     public static final int OUT_PRISM    = (1 << 3);
  55 
  56     public static final int OUT_SSE_JAVA        = (1 << 4);
  57     public static final int OUT_SSE_NATIVE      = (1 << 5);
  58     public static final int OUT_ME_JAVA         = (1 << 6);
  59     public static final int OUT_ME_NATIVE       = (1 << 7);
  60 
  61     public static final int OUT_ME       = OUT_ME_JAVA | OUT_ME_NATIVE;
  62     public static final int OUT_SSE      = OUT_SSE_JAVA | OUT_SSE_NATIVE;
  63 
  64     public static final int OUT_SW_PEERS   = OUT_JAVA | OUT_SSE;
  65     public static final int OUT_HW_PEERS   = OUT_PRISM;
  66     public static final int OUT_HW_SHADERS = OUT_D3D | OUT_ES2;
  67     public static final int OUT_ALL        = OUT_SW_PEERS | OUT_HW_PEERS | OUT_HW_SHADERS;
  68 
  69     private static final String rootPkg = "com/sun/scenario/effect";
  70     
  71     static {
  72         CommonGroupLoader loader = new CommonGroupLoader(rootPkg + "/compiler/backend", null);
  73         StringTemplateGroup.registerGroupLoader(loader);
  74     }
  75 
  76     public static class OutInfo {
  77         public String basePath;
  78         public String filePrefix;
  79         public String fileSuffix;
  80     }
  81 
  82     public static class ParserInfo {
  83         public JSLParser parser;
  84         public ProgramUnit program;
  85 
  86         public ParserInfo(JSLParser parser, ProgramUnit program) {
  87             this.parser = parser;
  88             this.program = program;
  89         }
  90     }
  91 
  92     public static ParserInfo getParserInfo(String source) throws Exception {
  93         return getParserInfo(new ByteArrayInputStream(source.getBytes()));
  94     }
  95 
  96     public static ParserInfo getParserInfo(InputStream stream) throws Exception {
  97         JSLParser parser = parse(stream);
  98         ProgramUnit program = parser.translation_unit();
  99         return new ParserInfo(parser, program);
 100     }
 101 
 102     /**
 103      * If trimToOutDir is provided by the user, then we will output all files
 104      * under the out directory, for example if outDir=/foo/bar:
 105      *   /foo/bar/ + rootPkg + /impl/sw/java
 106      *   /foo/bar/ + rootPkg + /impl/sw/sse
 107      *   /foo/bar/ + rootPkg + /impl/sw/me
 108      *   /foo/bar/ + rootPkg + /impl/hw/d3d/hlsl
 109      *   /foo/bar/ + rootPkg + /impl/es2/glsl
 110      *   /foo/bar/ + rootPkg + /impl/prism/ps
 111      * 
 112      * Otherwise, we use the layout currently expected by decora-runtime
 113      * for core effects:
 114      *   ../decora-jsw/build/gensrc/     + rootPkg + /impl/sw/java
 115      *   ../decora-sse/build/gensrc/     + rootPkg + /impl/sw/sse
 116      *   ../decora-me/build/gensrc/      + rootPkg + /impl/sw/me
 117      *   ../decora-d3d/build/gensrc/     + rootPkg + /impl/hw/d3d/hlsl
 118      *   ../decora-es2/build/gensrc/     + rootPkg + /impl/es2/glsl
 119      *   ../decora-prism-ps/build/gensrc/+ rootPkg + /impl/prism/ps
 120      */
 121     private static Map<Integer, String> initDefaultInfoMap() {
 122         Map<Integer, String> infoMap = new HashMap<Integer, String>();
 123         infoMap.put(OUT_D3D,        "decora-d3d/build/gensrc/{pkg}/impl/hw/d3d/hlsl/{name}.hlsl");
 124         infoMap.put(OUT_ES2,        "decora-es2/build/gensrc/{pkg}/impl/es2/glsl/{name}.frag");
 125         infoMap.put(OUT_JAVA,       "decora-jsw/build/gensrc/{pkg}/impl/sw/java/JSW{name}Peer.java");
 126         infoMap.put(OUT_PRISM,      "decora-prism-ps/build/gensrc/{pkg}/impl/prism/ps/PPS{name}Peer.java");
 127         infoMap.put(OUT_SSE_JAVA,   "decora-sse/build/gensrc/{pkg}/impl/sw/sse/SSE{name}Peer.java");
 128         infoMap.put(OUT_ME_JAVA,    "decora-me/build/gensrc/{pkg}/impl/sw/me/ME{name}Peer.java");
 129         infoMap.put(OUT_SSE_NATIVE, "decora-sse-native/build/gensrc/SSE{name}Peer.cc");
 130         infoMap.put(OUT_ME_NATIVE,  "decora-me-native/build/gensrc/ME{name}Peer.cc");
 131         return infoMap;
 132     }
 133 
 134     public static ParserInfo compile(JSLCInfo jslcinfo,
 135                                      String str,
 136                                      long sourceTime)
 137         throws Exception
 138     {
 139         return compile(jslcinfo,
 140                        new ByteArrayInputStream(str.getBytes()), sourceTime);
 141     }
 142 
 143     public static ParserInfo compile(JSLCInfo jslcinfo, File file)
 144         throws Exception
 145     {
 146         return compile(jslcinfo, new FileInputStream(file), file.lastModified());
 147     }
 148 
 149     public static JSLParser parse(String str)
 150         throws Exception
 151     {
 152         return parse(new ByteArrayInputStream(str.getBytes()));
 153     }
 154 
 155     private static JSLParser parse(InputStream stream)
 156         throws Exception
 157     {
 158         // Read input
 159         ANTLRInputStream input = new ANTLRInputStream(stream);
 160 
 161         // Lexer
 162         JSLLexer lexer = new JSLLexer(input);
 163         CommonTokenStream tokens = new CommonTokenStream(lexer);
 164 
 165         // Parser and AST construction
 166         return new JSLParser(tokens);
 167     }
 168 
 169     private static ParserInfo compile(JSLCInfo jslcinfo,
 170                                       InputStream stream,
 171                                       long sourceTime)
 172         throws Exception
 173     {
 174         return compile(jslcinfo, stream, sourceTime, null);
 175     }
 176 
 177     private static ParserInfo compile(JSLCInfo jslcinfo,
 178                                       InputStream stream,
 179                                       long sourceTime,
 180                                       ParserInfo pinfo)
 181         throws Exception
 182     {
 183         int outTypes = jslcinfo.outTypes;
 184         String interfaceName = jslcinfo.interfaceName;
 185         String peerName = jslcinfo.peerName;
 186         String shaderName = jslcinfo.shaderName;
 187         if (peerName == null) peerName = shaderName;
 188 
 189         // Compiler
 190         if ((outTypes & OUT_D3D) != 0) {
 191             File outFile = jslcinfo.getOutputFile(OUT_D3D);
 192             if (jslcinfo.force || outOfDate(outFile, sourceTime)) {
 193                 if (pinfo == null) pinfo = getParserInfo(stream);
 194                 HLSLBackend hlslBackend = new HLSLBackend(pinfo.parser, pinfo.program);
 195                 write(hlslBackend.getShader(), outFile);
 196             }
 197         }
 198 
 199         if ((outTypes & OUT_ES2) != 0) {
 200             File outFile = jslcinfo.getOutputFile(OUT_ES2);
 201             if (jslcinfo.force || outOfDate(outFile, sourceTime)) {
 202                 if (pinfo == null) pinfo = getParserInfo(stream);
 203                 ES2Backend es2Backend = new ES2Backend(pinfo.parser, pinfo.program);
 204                 write(es2Backend.getShader(), outFile);
 205             }
 206         }
 207 
 208         if ((outTypes & OUT_JAVA) != 0) {
 209             File outFile = jslcinfo.getOutputFile(OUT_JAVA);
 210             if (jslcinfo.force || outOfDate(outFile, sourceTime)) {
 211                 if (pinfo == null) pinfo = getParserInfo(stream);
 212                 JSWBackend javaBackend = new JSWBackend(pinfo.parser, pinfo.program);
 213                 String genCode = javaBackend.getGenCode(shaderName, peerName, interfaceName);
 214                 write(genCode, outFile);
 215             }
 216         }
 217         
 218         if ((outTypes & OUT_SSE) != 0) {
 219             File outFile = jslcinfo.getOutputFile(OUT_SSE_JAVA);
 220             // TODO: native code is always generated into the same
 221             // destination directory for now; need to make this more flexible
 222             File genCFile = jslcinfo.getOutputFile(OUT_SSE_NATIVE);
 223 
 224             boolean outFileStale = outOfDate(outFile, sourceTime);
 225             boolean genCFileStale = outOfDate(genCFile, sourceTime);
 226             if (jslcinfo.force || outFileStale || genCFileStale) {
 227                 if (pinfo == null) pinfo = getParserInfo(stream);
 228                 SSEBackend sseBackend = new SSEBackend(pinfo.parser, pinfo.program);
 229                 SSEBackend.GenCode gen =
 230                     sseBackend.getGenCode(shaderName, peerName, interfaceName);
 231 
 232                 // write impl class
 233                 if (outFileStale) {
 234                     write(gen.javaCode, outFile);
 235                 }
 236 
 237                 // write impl native code
 238                 if (genCFileStale) {
 239                     write(gen.nativeCode, genCFile);
 240                 }
 241             }
 242         }
 243         
 244         if ((outTypes & OUT_ME) != 0) {
 245             File outFile = jslcinfo.getOutputFile(OUT_ME_JAVA);
 246             // TODO: native code is always generated into the same
 247             // destination directory for now; need to make this more flexible
 248             File genCFile = jslcinfo.getOutputFile(OUT_ME_NATIVE);
 249 
 250             boolean outFileStale = outOfDate(outFile, sourceTime);
 251             boolean genCFileStale = outOfDate(genCFile, sourceTime);
 252             if (jslcinfo.force || outFileStale || genCFileStale) {
 253                 if (pinfo == null) pinfo = getParserInfo(stream);
 254                 MEBackend sseBackend = new MEBackend(pinfo.parser, pinfo.program);
 255                 MEBackend.GenCode gen =
 256                     sseBackend.getGenCode(shaderName, peerName, interfaceName);
 257 
 258                 // write impl class
 259                 if (outFileStale) {
 260                     write(gen.javaCode, outFile);
 261                 }
 262 
 263                 // write impl native code
 264                 if (genCFileStale) {
 265                     write(gen.nativeCode, genCFile);
 266                 }
 267             }
 268         }
 269         
 270         if ((outTypes & OUT_PRISM) != 0) {
 271             File outFile = jslcinfo.getOutputFile(OUT_PRISM);
 272             if (jslcinfo.force || outOfDate(outFile, sourceTime)) {
 273                 if (pinfo == null) pinfo = getParserInfo(stream);
 274                 PrismBackend prismBackend = new PrismBackend(pinfo.parser, pinfo.program);
 275                 String genCode = prismBackend.getGlueCode(shaderName, peerName, interfaceName);
 276                 write(genCode, outFile);
 277             }
 278         }
 279 
 280         return pinfo;
 281     }
 282 
 283     public static boolean outOfDate(File outFile, long sourceTime) {
 284         if (sourceTime < outFile.lastModified()) {
 285             return false;
 286         }
 287         return true;
 288     }
 289 
 290     public static void write(String str, File outFile) throws Exception {
 291         File outDir = outFile.getParentFile();
 292         if (!outDir.exists()) {
 293             outDir.mkdirs();
 294         }
 295         FileWriter fw = null;
 296         try {
 297             fw = new FileWriter(outFile);
 298             fw.write(str);
 299             fw.flush();
 300         } finally {
 301             if (fw != null) {
 302                 fw.close();
 303             }
 304         }
 305     }
 306 
 307     public static class JSLCInfo {
 308         public int outTypes;
 309         public boolean force;
 310         public String outDir;
 311         public boolean trimToOutDir;
 312         public List<String> srcDirs = new ArrayList<String>();
 313         public String shaderName;
 314         public String peerName;
 315         public String interfaceName;
 316         public String pkgName = rootPkg;
 317         public Map<Integer, String> outNameMap = initDefaultInfoMap();
 318 
 319         private String extraOpts;
 320 
 321         public JSLCInfo() {
 322         }
 323 
 324         public JSLCInfo(String extraOpts) {
 325             this.extraOpts = extraOpts;
 326         }
 327 
 328         public void usage(PrintStream out) {
 329             StackTraceElement callers[] = Thread.currentThread().getStackTrace();
 330             String prog = callers[callers.length - 1].getClassName();
 331             String prefix0 = "Usage: java "+prog+" ";
 332             String prefix1 = "";
 333             for (int i = 0; i < prefix0.length(); i++) prefix1 += " ";
 334             out.println(prefix0+"[-d3d | -es2 | -java | -sse | -me | -sw | -hw | -all]");
 335             out.println(prefix1+"[-o <outdir>] [-i <srcdir>] [-t]");
 336             out.println(prefix1+"[-name <name>] [-ifname <interface name>]");
 337             if (extraOpts != null) {
 338                 out.println(prefix1+extraOpts);
 339             }
 340         }
 341 
 342         public void error(String error) {
 343             System.err.println(error);
 344             usage(System.err);
 345             System.exit(1);
 346         }
 347 
 348         public void parseAllArgs(String args[]) {
 349             int index = parseArgs(args);
 350             if (index != args.length) {
 351                 error("unrecognized argument: "+args[index]);
 352             }
 353         }
 354 
 355         public int parseArgs(String args[]) {
 356             int i = 0;
 357             while (i < args.length) {
 358                 int consumed = parseArg(args, i);
 359                 if (consumed < 0) {
 360                     usage(System.err);
 361                     System.exit(1);
 362                 } else if (consumed == 0) {
 363                     break;
 364                 }
 365                 i += consumed;
 366             }
 367             return i;
 368         }
 369 
 370         public int parseArg(String args[], int index) {
 371             String arg = args[index++];
 372             if (arg.equals("-force")) {
 373                 force = true;
 374             } else if (arg.equals("-d3d")) {
 375                 outTypes |= OUT_D3D;
 376             } else if (arg.equals("-es2")) {
 377                 outTypes |= OUT_ES2;
 378             } else if (arg.equals("-java")) {
 379                 outTypes |= OUT_JAVA;
 380             } else if (arg.equals("-sse")) {
 381                 outTypes |= OUT_SSE;
 382             } else if (arg.equals("-me")) {
 383                 outTypes |= OUT_ME;
 384             } else if (arg.equals("-sw")) {
 385                 outTypes = OUT_SW_PEERS;
 386             } else if (arg.equals("-hw")) {
 387                 outTypes = OUT_HW_PEERS | OUT_HW_SHADERS;
 388             } else if (arg.equals("-all")) {
 389                 outTypes = OUT_ALL;
 390             } else if (arg.equals("-help")) {
 391                 usage(System.out);
 392                 System.exit(0);
 393             } else if (arg.equals("-t")) {
 394                 trimToOutDir = true;
 395             } else {
 396                 try {
 397                     // options with 1 argument
 398                     if (arg.equals("-o")) {
 399                         outDir = args[index];
 400                     } else if (arg.equals("-i")) {
 401                         srcDirs.add(args[index]);
 402                     } else if (arg.equals("-name")) {
 403                         shaderName = args[index];
 404                     } else if (arg.equals("-ifname")) {
 405                         interfaceName = args[index];
 406                     } else if (arg.equals("-pkg")) {
 407                         pkgName = args[index];
 408                     } else {
 409                         return 0;
 410                     }
 411                 } catch (ArrayIndexOutOfBoundsException e) {
 412                     return -1;
 413                 }
 414                 return 2;
 415             }
 416             return 1;
 417         }
 418 
 419         public File getJSLFile() {
 420             return getJSLFile(shaderName);
 421         }
 422 
 423         public File getJSLFile(String jslBaseName) {
 424             return getInputFile(jslBaseName + ".jsl");
 425         }
 426 
 427         public File getInputFile(String filename) {
 428             if (srcDirs.isEmpty()) {
 429                 File f = new File(filename);
 430                 if (f.exists()) {
 431                     return f;
 432                 }
 433             } else {
 434                 for (String dir : srcDirs) {
 435                     File f = new File(dir, filename);
 436                     if (f.exists()) {
 437                         return f;
 438                     }
 439                 }
 440             }
 441 
 442             error("Input file not found: "+filename);
 443             // NOT REACHED
 444             return null;
 445         }
 446 
 447         public File getOutputFile(String outName) {
 448             if (trimToOutDir) {
 449                 outName = outName.substring(outName.indexOf("gensrc") + 7);
 450             }
 451             outName = outName.replace("{pkg}", pkgName);
 452             outName = outName.replace("{name}", peerName == null ? shaderName : peerName);
 453             return outDir == null ? new File(outName) : new File(outDir, outName);
 454         }
 455 
 456         public File getOutputFile(int outType) {
 457             String fileName = outNameMap.get(outType);
 458             return getOutputFile(fileName);
 459         }
 460     }
 461 
 462     public static void main(String[] args) throws Exception {
 463         JSLCInfo jslcinfo = new JSLCInfo("<inputfile>");
 464         int index = jslcinfo.parseArgs(args);
 465 
 466         if (index != args.length - 1) {
 467             jslcinfo.error("Must specify one input file");
 468         }
 469         String arg = args[index];
 470         if (!arg.endsWith(".jsl") || arg.length() < 5) {
 471             jslcinfo.error("Input file name must end with '.jsl'");
 472         }
 473         File inFile = jslcinfo.getInputFile(arg);
 474         if (jslcinfo.shaderName == null) {
 475             jslcinfo.shaderName = arg.substring(0, arg.length()-4);
 476         }
 477 
 478         compile(jslcinfo, inFile);
 479     }
 480 }