/*
* Created on 14. March 2007, 23:51
*/
package net.java.nboglpack.glslcompiler;
import com.mbien.engine.glsl.GLSLCompileException;
import com.mbien.engine.glsl.CompilerMessage;
import com.mbien.engine.util.GLRunnable;
import com.mbien.engine.glsl.GLSLCompilerMessageParser;
import com.mbien.engine.glsl.CodeFragment;
import com.mbien.engine.glsl.GLSLLinkException;
import com.mbien.engine.glsl.GLSLProgram;
import com.mbien.engine.glsl.GLSLShader;
import com.mbien.engine.util.GLWorker;
import java.awt.Color;
import java.io.File;
import net.java.nboglpack.glslcompiler.annotation.CompilerAnnotations;
import net.java.nboglpack.glslcompiler.annotation.CompilerAnnotation;
import java.io.IOException;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.media.opengl.GL;
import javax.media.opengl.GL2GL3;
import javax.media.opengl.GLContext;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.text.Line;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;
import org.openide.windows.IOColorLines;
import org.openide.windows.IOProvider;
import org.openide.windows.InputOutput;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;
/**
* Implementation for the GLSL compiler service.
* @author Michael Bien
*/
public class GLSLCompilerImpl implements GLSLCompilerService {
private final GLSLCompilerMessageParser compilerMSGParser;
private final GLWorker glWorker;
private final InputOutput io;
private final Color DARK_GREEN = new Color(0, 153, 0);
public GLSLCompilerImpl() {
glWorker = Lookup.getDefault().lookup(GLWorker.class);
Preferences pref = NbPreferences.forModule(GLSLCompilerService.class);
String patternString = pref.get("GlslCompilerLogPattern", null);
if(patternString == null || patternString.trim().isEmpty()) {
final String[] buffer = new String[1];
glWorker.addWork(new GLRunnable() {
public void run(GLContext context) {
GL gl = context.getGL();
buffer[0] = gl.glGetString(GL.GL_VENDOR);
}
});
glWorker.work();
// samples of compiler errors:
// NV: "(267) : error C0000: syntax error, unexpected identifier, expecting ';' or ',' at token ius";
// ATI: "ERROR: 0:17: '-' : wrong operand types no operation '-' exists that takes a left-hand operand of type 'const int' and a right operand of type 'float' (or there is no acceptable conversion)"
if(buffer[0].contains("NVIDIA")) {
patternString = "\\((\\d+)\\)\\s*:\\s*(\\w+)";
}else if(buffer[0].contains("ATI") || buffer[0].contains("AMD") ) {
patternString = "(\\w+):\\s*\\d+:(\\d+):";
}else{
patternString = "";
}
pref.put("GlslCompilerLogPattern", patternString);
}
Pattern pattern = null;
if(patternString != null) {
try{
pattern = Pattern.compile(patternString);
}catch(PatternSyntaxException ex) {
Exceptions.printStackTrace(ex);
}
}
compilerMSGParser = new GLSLCompilerMessageParser(pattern);
io = IOProvider.getDefault().getIO("GLSL Compiler Output", false);
}
public boolean compileShader(DataObject[] daos, boolean printOut) {
boolean success = true;
if(printOut) {
try{
io.getOut().reset();
}catch(IOException ex) {
Exceptions.printStackTrace(ex);
}
}
for (DataObject dao : daos) {
try{
GLSLShader shader = compile(dao, printOut, true);
if(shader != null)
annotateMessage(dao, shader, printOut);
}catch(GLSLCompileException ex){
success = false;
GLSLShader source = (GLSLShader) ex.source;
if(source.getCompilerMsg() != null) {
if(printOut)
io.getErr().println("error compiling shader");
annotateMessage(dao, source, printOut);
}else if(printOut){
io.getErr().println(ex.getMessage());
}
}
}
if(success && printOut)
println("compilation successful", DARK_GREEN);
return success;
}
public boolean compileAndLinkProgram(DataObject[] daos, boolean printOut) {
boolean success = true;
if(printOut) {
try{
io.getOut().reset();
}catch(IOException ex) {
Exceptions.printStackTrace(ex);
}
}
GLSLShader[] shaders = new GLSLShader[daos.length];
for (int i = 0; i < shaders.length; i++) {
try{
GLSLShader shader = compile(daos[i], printOut, false);
shaders[i] = shader;
if(shaders[i] != null)
annotateMessage(daos[i], shader, printOut);
}catch(GLSLCompileException ex) {
success = false;
GLSLShader source = (GLSLShader) ex.source;
if(source.getCompilerMsg() != null) {
if(printOut)
io.getErr().println("error compiling shader");
annotateMessage(daos[i], source, printOut);
}else if(printOut){
io.getErr().println(ex.getMessage());
}
}
}
// success == true if all shaders compiled without errors
if(success) {
if(printOut){
println("compilation successful", DARK_GREEN);
println("linking shaders", Color.GRAY);
}
try{
link(shaders);
if(printOut)
println("link successful", DARK_GREEN);
}catch(GLSLLinkException ex) {
success = false;
if(printOut){
io.getErr().println("link error");
io.getErr().println(ex.getMessage()); // TODO we need a testcase for not linkable shader
}
// massageHandler.parse(ex.getMessage());
}
}
return success;
}
/**
* creates a new GLSLShader object from the given DataObject. This shader is
* not initialized or bound to any GL context.
*
* @param dao - the DataObject of the shader source
*/
private GLSLShader createShader(DataObject dao) {
GLSLShader shader = null;
NBShaderSourceProvider provider = new NBShaderSourceProvider();
CodeFragment<DataObject> main;
main = provider.loadShaderSource(dao);
if(main != null && dao.isValid()) {
FileObject foFolder = dao.getFolder().getPrimaryFile();
File folder = FileUtil.toFile(foFolder);
GLSLShader.TYPE shaderType = GLSLShader.TYPE.fromMime(dao.getPrimaryFile().getMIMEType());
shader = new GLSLShader(shaderType, main, folder.getAbsolutePath(), provider);
}
// shader.setThrowExceptionOnCompilerWarning(true);
return shader;
}
private GLSLShader compile(final DataObject dao, boolean printOut, final boolean deleteAfterCompilation) throws GLSLCompileException {
final GLSLShader shader = createShader(dao);
if(shader == null)
return null;
if(printOut) {
println("compiling shader:", Color.GRAY);
if(shader.getFragments() != null && shader.getFragments().length > 0) {
for (CodeFragment fragment : shader.getFragments()) {
println(" - including "+fragment.name, Color.GRAY);
}
}else{
println(shader.getName(), Color.GRAY);
}
}
CompilerAnnotations.removeAnnotations(dao);
final GLSLCompileException[] exception = new GLSLCompileException[] {null};
if(!shader.type.isSupported())
throw new GLSLCompileException(shader, shader.type.toString().toLowerCase()+" shaders not supported");
GLRunnable compilerTask = new GLRunnable(){
public void run(GLContext context) {
GL2GL3 gl = context.getGL().getGL2GL3();
try{
shader.initShader(gl);
}catch(GLSLCompileException ex) {
exception[0] = ex;
}finally{
if(deleteAfterCompilation)
shader.deleteShader(gl);
}
}
};
glWorker.work(compilerTask);
if(exception[0] != null) {
throw exception[0];
}
return shader;
}
private void link(final GLSLShader[] shaders) throws GLSLLinkException {
final GLSLLinkException[] exception = new GLSLLinkException[] {null};
GLRunnable linkerTask = new GLRunnable(){
public void run(GLContext context) {
GL2GL3 gl = context.getGL().getGL2GL3();
GLSLProgram program = new GLSLProgram();
try{
program.initProgram(gl, shaders);
}catch(GLSLLinkException ex) {
exception[0] = ex;
}finally{
// delete program and all attached shaders
program.deinitProgram(gl, true);
}
}
};
glWorker.work(linkerTask);
// rethrow exception from Runnable
if(exception[0] != null)
throw exception[0];
}
private final CompilerMessage[] parseMessage(DataObject dao, GLSLShader shader) {
int[] lines = countLines(dao, shader);
String msg = shader.getCompilerMsg();
return compilerMSGParser.parse(msg, lines);
}
private void annotateMessage(DataObject dao, GLSLShader shader, boolean printOut) {
CompilerMessage[] msgs = parseMessage(dao, shader);
for (CompilerMessage msg : msgs) {
if(msg.type == CompilerMessage.COMPILER_EVENT_TYPE.MSG) {
if(printOut)
println("compiler msg: " + msg.msg, Color.ORANGE);
}else{
// System.out.println(shader.getName() +" dep: "+shader.dependencies.length);
// System.out.println(" - frag: "+msg.fragment);
// the dao which caused the message (can be a dependency or the root dao)
DataObject dao2annotate;
if(msg.fragment < shader.getFragments().length && msg.fragment >= 0)
dao2annotate = (DataObject) shader.getFragments()[msg.fragment].sourceObj;
else
dao2annotate = dao;
CompilerAnnotation.AnnotationType type;
if(msg.type == CompilerMessage.COMPILER_EVENT_TYPE.ERROR)
type = CompilerAnnotation.AnnotationType.ERROR;
else
type = CompilerAnnotation.AnnotationType.WARNING;
// add editor annotation to dao
CompilerAnnotations.addAnnotation(dao2annotate, type, msg.msg, msg.line);
// add hyperlink in output window if print out enabled
if(printOut){
try {
String txt = dao2annotate.getName() + " (line "+msg.line+"): "+msg.msg;
io.getErr().println(txt, new HyperlinkProvider(dao2annotate, msg.line));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}
}
private final int[] countLines(DataObject dao, GLSLShader shader) {
int[] lines = new int[shader.getFragments().length+1];
EditorCookie cookie;
for (int i = 0; i < shader.getFragments().length; i++) {
CodeFragment<DataObject> d = shader.getFragments()[i];
cookie = d.sourceObj.getCookie(EditorCookie.class);
lines[i] = cookie.getLineSet().getLines().size();
}
cookie = dao.getCookie(EditorCookie.class);
lines[lines.length-1] = cookie.getLineSet().getLines().size();
return lines;
}
private final void println(String line, Color color) {
try {
IOColorLines.println(io, line, color);
} catch (IOException ex) {
io.getOut().print(line);
}
}
private static class HyperlinkProvider implements OutputListener{
private final int line;
private final DataObject dao;
private HyperlinkProvider(DataObject dao, int lineNumber) {
this.line = lineNumber;
this.dao = dao;
}
public void outputLineAction(OutputEvent e) {
//open file in editor and go to annotated line
dao.getCookie(OpenCookie.class).open();
LineCookie lineCookie = dao.getCookie(LineCookie.class);
if(line > 0 && line < lineCookie.getLineSet().getLines().size()) {
Line current = lineCookie.getLineSet().getCurrent(line - 1);
current.show(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FRONT);
}
}
public void outputLineCleared(OutputEvent arg0) {}
public void outputLineSelected(OutputEvent arg0) {}
}
}