• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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