1 /*
2  * Copyright (C) 2012 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.ide.eclipse.gltrace.editors;
18 
19 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
20 import com.android.ide.eclipse.gltrace.model.GLCall;
21 import com.android.ide.eclipse.gltrace.model.GLTrace;
22 
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Stack;
27 
28 public class GLCallGroups {
29     /**
30      * A {@link GLCallNode} is a simple wrapper around a {@link GLCall} that
31      * adds the notion of hierarchy.
32      */
33     public interface GLCallNode {
34         /** Does this call have child nodes? */
hasChildren()35         boolean hasChildren();
36 
37         /** Returns a list of child nodes of this call. */
getChildren()38         List<GLCallNode> getChildren();
39 
40         /** Returns the {@link GLCall} that is wrapped by this node. */
getCall()41         GLCall getCall();
42 
43         /** Returns the parent of this node, the parent is null if this is a top level node */
getParent()44         GLCallNode getParent();
45 
46         /** Set the parent node. */
setParent(GLCallNode parent)47         void setParent(GLCallNode parent);
48     }
49 
50     private static class GLTreeNode implements GLCallNode {
51         private final GLCall mCall;
52         private GLCallNode mParent;
53         private List<GLCallNode> mGLCallNodes;
54 
GLTreeNode(GLCall call)55         public GLTreeNode(GLCall call) {
56             mCall = call;
57             mGLCallNodes = new ArrayList<GLCallNode>();
58         }
59 
60         @Override
hasChildren()61         public boolean hasChildren() {
62             return true;
63         }
64 
65         @Override
getParent()66         public GLCallNode getParent() {
67             return mParent;
68         }
69 
70         @Override
setParent(GLCallNode parent)71         public void setParent(GLCallNode parent) {
72             mParent = parent;
73         }
74 
75         @Override
getChildren()76         public List<GLCallNode> getChildren() {
77             return mGLCallNodes;
78         }
79 
addChild(GLCallNode n)80         public void addChild(GLCallNode n) {
81             mGLCallNodes.add(n);
82             n.setParent(this);
83         }
84 
85         @Override
getCall()86         public GLCall getCall() {
87             return mCall;
88         }
89     }
90 
91     private static class GLLeafNode implements GLCallNode {
92         private final GLCall mCall;
93         private GLCallNode mParent;
94 
GLLeafNode(GLCall call)95         public GLLeafNode(GLCall call) {
96             mCall = call;
97         }
98 
99         @Override
hasChildren()100         public boolean hasChildren() {
101             return false;
102         }
103 
104         @Override
getChildren()105         public List<GLCallNode> getChildren() {
106             return null;
107         }
108 
109         @Override
getParent()110         public GLCallNode getParent() {
111             return mParent;
112         }
113 
114         @Override
setParent(GLCallNode parent)115         public void setParent(GLCallNode parent) {
116             mParent = parent;
117         }
118 
119         @Override
getCall()120         public GLCall getCall() {
121             return mCall;
122         }
123     }
124 
125     /**
126      * Impose a hierarchy on a list of {@link GLCall}'s based on the presence of
127      * {@link Function#glPushGroupMarkerEXT} and {@link Function#glPopGroupMarkerEXT} calls.
128      * Such a hierarchy is possible only if calls from a single context are considered.
129      * @param trace trace to look at
130      * @param start starting call index
131      * @param end ending call index
132      * @param contextToGroup context from which calls should be grouped. If no such context
133      *        is present, then all calls in the given range will be returned back as a flat
134      *        list.
135      * @return a tree structured list of {@link GLCallNode} objects
136      */
constructCallHierarchy(GLTrace trace, int start, int end, int contextToGroup)137     public static List<GLCallNode> constructCallHierarchy(GLTrace trace, int start, int end,
138             int contextToGroup) {
139         if (trace == null) {
140             return Collections.emptyList();
141         }
142 
143         if (contextToGroup < 0 || contextToGroup > trace.getContexts().size()) {
144             return flatHierarchy(trace, start, end);
145         }
146 
147         List<GLCall> calls = trace.getGLCalls();
148 
149         Stack<GLTreeNode> hierarchyStack = new Stack<GLTreeNode>();
150         List<GLCallNode> items = new ArrayList<GLCallNode>();
151 
152         for (int i = start; i < end; i++) {
153             GLCall c = calls.get(i);
154             if (c.getContextId() != contextToGroup) {
155                 // skip this call if it is not part of the context we need to display
156                 continue;
157             }
158 
159             if (c.getFunction() == Function.glPushGroupMarkerEXT) {
160                 GLTreeNode group = new GLTreeNode(c);
161                 if (hierarchyStack.size() > 0) {
162                     hierarchyStack.peek().addChild(group);
163                 } else {
164                     items.add(group);
165                 }
166                 hierarchyStack.push(group);
167             } else if (c.getFunction() == Function.glPopGroupMarkerEXT) {
168                 if (hierarchyStack.size() > 0) {
169                     hierarchyStack.pop();
170                 } else {
171                     // FIXME: If we are attempting to pop from an empty stack,
172                     // that implies that a push marker was seen in a prior frame
173                     // (in a call before @start). In such a case, we simply continue
174                     // adding further calls to the root of the hierarchy rather than
175                     // searching backwards in the call list for the corresponding
176                     // push markers.
177                     items.add(new GLLeafNode(c));
178                 }
179             } else {
180               GLLeafNode leaf = new GLLeafNode(c);
181               if (hierarchyStack.size() > 0) {
182                   hierarchyStack.peek().addChild(leaf);
183               } else {
184                   items.add(leaf);
185               }
186             }
187         }
188 
189         return items;
190     }
191 
flatHierarchy(GLTrace trace, int start, int end)192     private static List<GLCallNode> flatHierarchy(GLTrace trace, int start, int end) {
193         List<GLCallNode> items = new ArrayList<GLCallNode>();
194 
195         List<GLCall> calls = trace.getGLCalls();
196         for (int i = start; i < end; i++) {
197             items.add(new GLLeafNode(calls.get(i)));
198         }
199 
200         return items;
201     }
202 }
203