1 /* 2 ** Copyright 2011, The Android Open Source Project 3 ** 4 ** Licensed under the Apache License, Version 2.0 (the "License"); 5 ** you may not use this file except in compliance with the License. 6 ** You may obtain a copy of the License at 7 ** 8 ** http://www.apache.org/licenses/LICENSE-2.0 9 ** 10 ** Unless required by applicable law or agreed to in writing, software 11 ** distributed under the License is distributed on an "AS IS" BASIS, 12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 ** See the License for the specific language governing permissions and 14 ** limitations under the License. 15 */ 16 17 package com.android.glesv2debugger; 18 19 import com.android.glesv2debugger.DebuggerMessage.Message; 20 import com.android.glesv2debugger.DebuggerMessage.Message.Function; 21 import com.android.glesv2debugger.DebuggerMessage.Message.Type; 22 23 import org.eclipse.jface.dialogs.MessageDialog; 24 import org.eclipse.swt.SWT; 25 import org.eclipse.swt.custom.ExtendedModifyEvent; 26 import org.eclipse.swt.custom.ExtendedModifyListener; 27 import org.eclipse.swt.custom.StyleRange; 28 import org.eclipse.swt.custom.StyledText; 29 import org.eclipse.swt.events.SelectionEvent; 30 import org.eclipse.swt.events.SelectionListener; 31 import org.eclipse.swt.graphics.Color; 32 import org.eclipse.swt.graphics.Font; 33 import org.eclipse.swt.layout.GridData; 34 import org.eclipse.swt.layout.GridLayout; 35 import org.eclipse.swt.widgets.Composite; 36 import org.eclipse.swt.widgets.Display; 37 import org.eclipse.swt.widgets.List; 38 import org.eclipse.swt.widgets.ToolBar; 39 import org.eclipse.swt.widgets.ToolItem; 40 41 import java.io.BufferedReader; 42 import java.io.File; 43 import java.io.FileWriter; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.io.InputStreamReader; 47 import java.util.ArrayList; 48 49 public class ShaderEditor extends Composite implements SelectionListener, ExtendedModifyListener, 50 ProcessMessage { 51 SampleView sampleView; 52 53 ToolBar toolbar; 54 ToolItem uploadShader, restoreShader, currentPrograms; 55 List list; 56 StyledText styledText; 57 58 GLShader current; 59 60 ArrayList<GLShader> shadersToUpload = new ArrayList<GLShader>(); 61 ShaderEditor(SampleView sampleView, Composite parent)62 ShaderEditor(SampleView sampleView, Composite parent) { 63 super(parent, 0); 64 this.sampleView = sampleView; 65 66 GridLayout gridLayout = new GridLayout(); 67 gridLayout.numColumns = 1; 68 this.setLayout(gridLayout); 69 70 toolbar = new ToolBar(this, SWT.BORDER); 71 72 uploadShader = new ToolItem(toolbar, SWT.PUSH); 73 uploadShader.setText("Upload Shader"); 74 uploadShader.addSelectionListener(this); 75 76 restoreShader = new ToolItem(toolbar, SWT.PUSH); 77 restoreShader.setText("Original Shader"); 78 restoreShader.addSelectionListener(this); 79 80 currentPrograms = new ToolItem(toolbar, SWT.PUSH); 81 currentPrograms.setText("Current Programs: "); 82 83 list = new List(this, SWT.V_SCROLL); 84 list.setFont(new Font(parent.getDisplay(), "Courier", 10, 0)); 85 list.addSelectionListener(this); 86 GridData gridData = new GridData(); 87 gridData.horizontalAlignment = SWT.FILL; 88 gridData.grabExcessHorizontalSpace = true; 89 gridData.verticalAlignment = SWT.FILL; 90 gridData.grabExcessVerticalSpace = true; 91 list.setLayoutData(gridData); 92 93 styledText = new StyledText(this, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI); 94 gridData = new GridData(); 95 gridData.horizontalAlignment = SWT.FILL; 96 gridData.grabExcessHorizontalSpace = true; 97 gridData.verticalAlignment = SWT.FILL; 98 gridData.grabExcessVerticalSpace = true; 99 gridData.verticalSpan = 2; 100 styledText.setLayoutData(gridData); 101 styledText.addExtendedModifyListener(this); 102 } 103 updateUI()104 public void updateUI() { 105 list.removeAll(); 106 String progs = "Current Programs: "; 107 for (int j = 0; j < sampleView.debugContexts.size(); j++) { 108 final Context context = sampleView.debugContexts.valueAt(j).currentContext; 109 110 if (context.serverShader.current != null) { 111 progs += context.serverShader.current.name + "(0x"; 112 progs += Integer.toHexString(context.contextId) + ") "; 113 } 114 for (int i = 0; i < context.serverShader.shaders.size(); i++) { 115 GLShader shader = context.serverShader.shaders.valueAt(i); 116 StringBuilder builder = new StringBuilder(); 117 builder.append(String.format("%08X", context.contextId)); 118 builder.append(' '); 119 builder.append(shader.type); 120 while (builder.length() < 30) 121 builder.append(" "); 122 builder.append(shader.name); 123 while (builder.length() < 40) 124 builder.append(" "); 125 builder.append(" : "); 126 for (Context ctx : context.shares) { 127 builder.append(String.format("%08X", ctx.contextId)); 128 builder.append(' '); 129 } 130 builder.append(": "); 131 for (int program : shader.programs) { 132 builder.append(program); 133 builder.append(" "); 134 } 135 list.add(builder.toString()); 136 } 137 138 } 139 140 currentPrograms.setText(progs); 141 toolbar.redraw(); 142 toolbar.pack(true); 143 toolbar.update(); 144 } 145 uploadShader()146 void uploadShader() { 147 current.source = styledText.getText(); 148 149 // optional syntax check by glsl_compiler, built from external/mesa3d 150 if (new File("./glsl_compiler").exists()) 151 try { 152 File file = File.createTempFile("shader", 153 current.type == GLEnum.GL_VERTEX_SHADER ? ".vert" : ".frag"); 154 FileWriter fileWriter = new FileWriter(file, false); 155 fileWriter.write(current.source); 156 fileWriter.close(); 157 158 ProcessBuilder processBuilder = new ProcessBuilder( 159 "./glsl_compiler", "--glsl-es", file.getAbsolutePath()); 160 final Process process = processBuilder.start(); 161 InputStream is = process.getInputStream(); 162 InputStreamReader isr = new InputStreamReader(is); 163 BufferedReader br = new BufferedReader(isr); 164 String line; 165 String infolog = ""; 166 167 styledText.setLineBackground(0, styledText.getLineCount(), null); 168 169 while ((line = br.readLine()) != null) { 170 infolog += line; 171 if (!line.startsWith("0:")) 172 continue; 173 String[] details = line.split(":|\\(|\\)"); 174 final int ln = Integer.parseInt(details[1]); 175 if (ln > 0) // usually line 0 means errors other than syntax 176 styledText.setLineBackground(ln - 1, 1, 177 new Color(Display.getCurrent(), 255, 230, 230)); 178 } 179 file.delete(); 180 if (infolog.length() > 0) { 181 if (!MessageDialog.openConfirm(getShell(), 182 "Shader Syntax Error, Continue?", infolog)) 183 return; 184 } 185 } catch (IOException e) { 186 sampleView.showError(e); 187 } 188 189 // add the initial command, which when read by server will set 190 // expectResponse for the message loop and go into message exchange 191 synchronized (shadersToUpload) { 192 for (GLShader shader : shadersToUpload) { 193 if (shader.context.context.contextId != current.context.context.contextId) 194 continue; 195 MessageDialog.openWarning(this.getShell(), "Context 0x" + 196 Integer.toHexString(current.context.context.contextId), 197 "Previous shader upload not complete, try again"); 198 return; 199 } 200 shadersToUpload.add(current); 201 final int contextId = current.context.context.contextId; 202 Message.Builder builder = getBuilder(contextId); 203 MessageParserEx.instance.parse(builder, 204 String.format("glShaderSource(%d,1,\"%s\",0)", current.name, current.source)); 205 sampleView.messageQueue.addCommand(builder.build()); 206 } 207 } 208 getBuilder(int contextId)209 Message.Builder getBuilder(int contextId) { 210 Message.Builder builder = Message.newBuilder(); 211 builder.setContextId(contextId); 212 builder.setType(Type.Response); 213 builder.setExpectResponse(true); 214 return builder; 215 } 216 exchangeMessage(final int contextId, final MessageQueue queue, String format, Object... args)217 Message exchangeMessage(final int contextId, final MessageQueue queue, 218 String format, Object... args) throws IOException { 219 Message.Builder builder = getBuilder(contextId); 220 MessageParserEx.instance.parse(builder, String.format(format, args)); 221 final Function function = builder.getFunction(); 222 queue.sendMessage(builder.build()); 223 final Message msg = queue.receiveMessage(contextId); 224 assert msg.getContextId() == contextId; 225 assert msg.getType() == Type.AfterGeneratedCall; 226 assert msg.getFunction() == function; 227 return msg; 228 } 229 230 // this is called from network thread processMessage(final MessageQueue queue, final Message msg)231 public boolean processMessage(final MessageQueue queue, final Message msg) 232 throws IOException { 233 GLShader shader = null; 234 final int contextId = msg.getContextId(); 235 synchronized (shadersToUpload) { 236 if (shadersToUpload.size() == 0) 237 return false; 238 boolean matchingContext = false; 239 for (int i = 0; i < shadersToUpload.size(); i++) { 240 shader = shadersToUpload.get(i); 241 for (Context ctx : shader.context.context.shares) 242 if (ctx.contextId == contextId) { 243 matchingContext = true; 244 break; 245 } 246 if (matchingContext) { 247 shadersToUpload.remove(i); 248 break; 249 } 250 } 251 if (!matchingContext) 252 return false; 253 } 254 255 // glShaderSource was already sent to trigger set expectResponse 256 assert msg.getType() == Type.AfterGeneratedCall; 257 assert msg.getFunction() == Function.glShaderSource; 258 259 exchangeMessage(contextId, queue, "glCompileShader(%d)", shader.name); 260 261 // the 0, "" and {0} are dummies for the parser 262 Message rcv = exchangeMessage(contextId, queue, 263 "glGetShaderiv(%d, GL_COMPILE_STATUS, {0})", shader.name); 264 assert rcv.hasData(); 265 if (rcv.getData().asReadOnlyByteBuffer().getInt() == 0) { 266 // compile failed 267 rcv = exchangeMessage(contextId, queue, 268 "glGetShaderInfoLog(%d, 0, 0, \"\")", shader.name); 269 final String title = String.format("Shader %d in 0x%s failed to compile", 270 shader.name, Integer.toHexString(shader.context.context.contextId)); 271 final String message = rcv.getData().toStringUtf8(); 272 sampleView.getSite().getShell().getDisplay().syncExec(new Runnable() { 273 @Override 274 public void run() 275 { 276 MessageDialog.openWarning(getShell(), title, message); 277 } 278 }); 279 } else 280 for (int programName : shader.programs) { 281 GLProgram program = shader.context.getProgram(programName); 282 exchangeMessage(contextId, queue, "glLinkProgram(%d)", program.name); 283 rcv = exchangeMessage(contextId, queue, 284 "glGetProgramiv(%d, GL_LINK_STATUS, {0})", program.name); 285 assert rcv.hasData(); 286 if (rcv.getData().asReadOnlyByteBuffer().getInt() != 0) 287 continue; 288 // link failed 289 rcv = exchangeMessage(contextId, queue, 290 "glGetProgramInfoLog(%d, 0, 0, \"\")", program.name); 291 final String title = String.format("Program %d in 0x%s failed to link", 292 program.name, Integer.toHexString(program.context.context.contextId)); 293 final String message = rcv.getData().toStringUtf8(); 294 sampleView.getSite().getShell().getDisplay().syncExec(new Runnable() { 295 @Override 296 public void run() 297 { 298 MessageDialog.openWarning(getShell(), title, message); 299 } 300 }); 301 // break; 302 } 303 304 // TODO: add to upload results if failed 305 306 Message.Builder builder = getBuilder(contextId); 307 builder.setExpectResponse(false); 308 if (queue.getPartialMessage(contextId) != null) 309 // the glShaderSource interrupted a BeforeCall, so continue 310 builder.setFunction(Function.CONTINUE); 311 else 312 builder.setFunction(Function.SKIP); 313 queue.sendMessage(builder.build()); 314 315 return true; 316 } 317 318 @Override widgetSelected(SelectionEvent e)319 public void widgetSelected(SelectionEvent e) { 320 if (e.getSource() == uploadShader && null != current) { 321 uploadShader(); 322 return; 323 } else if (e.getSource() == restoreShader && null != current) { 324 current.source = styledText.getText(); 325 styledText.setText(current.originalSource); 326 return; 327 } 328 329 if (list.getSelectionCount() < 1) 330 return; 331 if (null != current && !current.source.equals(styledText.getText())) { 332 String[] btns = { 333 "&Upload", "&Save", "&Discard" 334 }; 335 MessageDialog dialog = new MessageDialog(this.getShell(), "Shader Edited", 336 null, "Shader source has been edited", MessageDialog.QUESTION, btns, 0); 337 int rc = dialog.open(); 338 if (rc == SWT.DEFAULT || rc == 0) 339 uploadShader(); 340 else if (rc == 1) 341 current.source = styledText.getText(); 342 // else if (rc == 2) do nothing; selection is changing 343 } 344 String[] details = list.getSelection()[0].split("\\s+"); 345 final int contextId = Integer.parseInt(details[0], 16); 346 int name = Integer.parseInt(details[2]); 347 current = sampleView.debugContexts.get(contextId).currentContext.serverShader.shaders 348 .get(name); 349 styledText.setText(current.source); 350 } 351 352 @Override widgetDefaultSelected(SelectionEvent e)353 public void widgetDefaultSelected(SelectionEvent e) { 354 widgetSelected(e); 355 } 356 357 @Override modifyText(ExtendedModifyEvent event)358 public void modifyText(ExtendedModifyEvent event) { 359 final String[] keywords = { 360 "gl_Position", "gl_FragColor" 361 }; 362 // FIXME: proper scanner for syntax highlighting 363 String text = styledText.getText(); 364 int start = event.start; 365 int end = event.start + event.length; 366 start -= 20; // deleting chars from keyword causes rescan 367 end += 20; 368 if (start < 0) 369 start = 0; 370 if (end > text.length()) 371 end = text.length(); 372 if (null != styledText.getStyleRangeAtOffset(event.start)) { 373 StyleRange clearStyleRange = new StyleRange(); 374 clearStyleRange.start = start; 375 clearStyleRange.length = end - start; 376 clearStyleRange.foreground = event.display.getSystemColor(SWT.COLOR_BLACK); 377 styledText.setStyleRange(clearStyleRange); 378 } 379 380 while (start < end) { 381 for (final String keyword : keywords) { 382 if (!text.substring(start).startsWith(keyword)) 383 continue; 384 if (start > 0) { 385 final char before = text.charAt(start - 1); 386 if (Character.isLetterOrDigit(before)) 387 continue; 388 else if (before == '_') 389 continue; 390 } 391 if (start + keyword.length() < text.length()) { 392 final char after = text.charAt(start + keyword.length()); 393 if (Character.isLetterOrDigit(after)) 394 continue; 395 else if (after == '_') 396 continue; 397 } 398 StyleRange style1 = new StyleRange(); 399 style1.start = start; 400 style1.length = keyword.length(); 401 style1.foreground = event.display.getSystemColor(SWT.COLOR_BLUE); 402 styledText.setStyleRange(style1); 403 } 404 start++; 405 } 406 } 407 } 408