diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java new file mode 100644 index 000000000..9b4c57cad --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.editors; + +import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function; +import com.android.ide.eclipse.gltrace.model.GLCall; +import com.android.ide.eclipse.gltrace.model.GLTrace; + +import org.eclipse.jface.resource.FontRegistry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +import java.util.ArrayList; +import java.util.List; + +public class DurationMinimap extends Canvas { + /** Default alpha value. */ + private static final int DEFAULT_ALPHA = 255; + + /** Alpha value for highlighting visible calls. */ + private static final int VISIBLE_CALLS_HIGHLIGHT_ALPHA = 50; + + /** Clamp call durations at this value. */ + private static final long CALL_DURATION_CLAMP = 20000; + + private static final String FONT_KEY = "default.font"; //$NON-NLS-1$ + + /** Scale font size by this amount to get the max display length of call duration. */ + private static final int MAX_DURATION_LENGTH_SCALE = 6; + + /** List of GL Calls in the trace. */ + private List<GLCall> mCalls; + + /** Number of GL contexts in the trace. */ + private int mContextCount; + + /** Starting call index of currently displayed frame. */ + private int mStartCallIndex; + + /** Ending call index of currently displayed frame. */ + private int mEndCallIndex; + + /** The top index that is currently visible in the table. */ + private int mVisibleCallTopIndex; + + /** The bottom index that is currently visible in the table. */ + private int mVisibleCallBottomIndex; + + private Color mBackgroundColor; + private Color mDurationLineColor; + private Color mGlDrawColor; + private Color mGlErrorColor; + private Color mContextHeaderColor; + private Color mVisibleCallsHighlightColor; + private Color mMouseMarkerColor; + + private FontRegistry mFontRegistry; + private int mFontWidth; + private int mFontHeight; + + // back buffers used for double buffering + private Image mBackBufferImage; + private GC mBackBufferGC; + + // mouse state + private boolean mMouseInSelf; + private int mMouseY; + + // helper object used to position various items on screen + private final PositionHelper mPositionHelper; + + public DurationMinimap(Composite parent, GLTrace trace) { + super(parent, SWT.NO_BACKGROUND); + + setInput(trace); + + initializeColors(); + initializeFonts(); + + mPositionHelper = new PositionHelper( + mFontHeight, + mContextCount, + mFontWidth * MAX_DURATION_LENGTH_SCALE, /* max display length for call. */ + CALL_DURATION_CLAMP /* max duration */); + + addPaintListener(new PaintListener() { + @Override + public void paintControl(PaintEvent e) { + draw(e.display, e.gc); + } + }); + + addListener(SWT.Resize, new Listener() { + @Override + public void handleEvent(Event event) { + controlResized(); + } + }); + + addMouseMoveListener(new MouseMoveListener() { + @Override + public void mouseMove(MouseEvent e) { + mouseMoved(e); + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + mouseClicked(e); + } + }); + + addMouseTrackListener(new MouseTrackListener() { + @Override + public void mouseHover(MouseEvent e) { + } + + @Override + public void mouseExit(MouseEvent e) { + mMouseInSelf = false; + redraw(); + } + + @Override + public void mouseEnter(MouseEvent e) { + mMouseInSelf = true; + redraw(); + } + }); + } + + public void setInput(GLTrace trace) { + if (trace != null) { + mCalls = trace.getGLCalls(); + mContextCount = trace.getContexts().size(); + } else { + mCalls = null; + mContextCount = 1; + } + } + + @Override + public void dispose() { + disposeColors(); + disposeBackBuffer(); + super.dispose(); + } + + private void initializeColors() { + mBackgroundColor = new Color(getDisplay(), 0x33, 0x33, 0x33); + mDurationLineColor = new Color(getDisplay(), 0x08, 0x51, 0x9c); + mGlDrawColor = new Color(getDisplay(), 0x6b, 0xae, 0xd6); + mContextHeaderColor = new Color(getDisplay(), 0xd1, 0xe5, 0xf0); + mVisibleCallsHighlightColor = new Color(getDisplay(), 0xcc, 0xcc, 0xcc); + mMouseMarkerColor = new Color(getDisplay(), 0xaa, 0xaa, 0xaa); + + mGlErrorColor = getDisplay().getSystemColor(SWT.COLOR_RED); + } + + private void disposeColors() { + mBackgroundColor.dispose(); + mDurationLineColor.dispose(); + mGlDrawColor.dispose(); + mContextHeaderColor.dispose(); + mVisibleCallsHighlightColor.dispose(); + mMouseMarkerColor.dispose(); + } + + private void initializeFonts() { + mFontRegistry = new FontRegistry(getDisplay()); + mFontRegistry.put(FONT_KEY, + new FontData[] { new FontData("Arial", 8, SWT.NORMAL) }); //$NON-NLS-1$ + + GC gc = new GC(getDisplay()); + gc.setFont(mFontRegistry.get(FONT_KEY)); + mFontWidth = gc.getFontMetrics().getAverageCharWidth(); + mFontHeight = gc.getFontMetrics().getHeight(); + gc.dispose(); + } + + private void initializeBackBuffer() { + Rectangle clientArea = getClientArea(); + + if (clientArea.width == 0 || clientArea.height == 0) { + mBackBufferImage = null; + mBackBufferGC = null; + return; + } + + mBackBufferImage = new Image(getDisplay(), + clientArea.width, + clientArea.height); + mBackBufferGC = new GC(mBackBufferImage); + } + + private void disposeBackBuffer() { + if (mBackBufferImage != null) { + mBackBufferImage.dispose(); + mBackBufferImage = null; + } + + if (mBackBufferGC != null) { + mBackBufferGC.dispose(); + mBackBufferGC = null; + } + } + + private void mouseMoved(MouseEvent e) { + mMouseY = e.y; + redraw(); + } + + private void mouseClicked(MouseEvent e) { + if (mMouseInSelf) { + int selIndex = mPositionHelper.getCallAt(mMouseY); + sendCallSelectedEvent(selIndex); + redraw(); + } + } + + private void draw(Display display, GC gc) { + if (mBackBufferImage == null) { + initializeBackBuffer(); + } + + if (mBackBufferImage == null) { + return; + } + + // draw contents onto the back buffer + drawBackground(mBackBufferGC, mBackBufferImage.getBounds()); + drawContextHeaders(mBackBufferGC); + drawCallDurations(mBackBufferGC); + drawVisibleCallHighlights(mBackBufferGC); + drawMouseMarkers(mBackBufferGC); + + // finally copy over the rendered back buffer onto screen + int width = getClientArea().width; + int height = getClientArea().height; + gc.drawImage(mBackBufferImage, + 0, 0, width, height, + 0, 0, width, height); + } + + private void drawBackground(GC gc, Rectangle bounds) { + gc.setBackground(mBackgroundColor); + gc.fillRectangle(bounds); + } + + private void drawContextHeaders(GC gc) { + if (mContextCount <= 1) { + return; + } + + gc.setForeground(mContextHeaderColor); + gc.setFont(mFontRegistry.get(FONT_KEY)); + for (int i = 0; i < mContextCount; i++) { + Point p = mPositionHelper.getHeaderLocation(i); + gc.drawText("CTX" + Integer.toString(i), p.x, p.y); + } + } + + /** Draw the call durations as a sequence of lines. + * + * Calls are arranged on the y-axis based on the sequence in which they were originally + * called by the application. If the display height is lesser than the number of calls, then + * not every call is shown - the calls are underscanned based the height of the display. + * + * The x-axis shows two pieces of information: the duration of the call, and the context + * in which the call was made. The duration controls how long the displayed line is, and + * the context controls the starting offset of the line. + */ + private void drawCallDurations(GC gc) { + if (mCalls == null || mCalls.size() < mEndCallIndex) { + return; + } + + gc.setBackground(mDurationLineColor); + + int callUnderScan = mPositionHelper.getCallUnderScanValue(); + for (int i = mStartCallIndex; i < mEndCallIndex; i += callUnderScan) { + boolean resetColor = false; + GLCall c = mCalls.get(i); + + long duration = c.getWallDuration(); + + if (c.hasErrors()) { + gc.setBackground(mGlErrorColor); + resetColor = true; + + // If the call has any errors, we want it to be visible in the minimap + // regardless of how long it took. + duration = mPositionHelper.getMaxDuration(); + } else if (c.getFunction() == Function.glDrawArrays + || c.getFunction() == Function.glDrawElements + || c.getFunction() == Function.eglSwapBuffers) { + gc.setBackground(mGlDrawColor); + resetColor = true; + + // render all draw calls & swap buffer at max length + duration = mPositionHelper.getMaxDuration(); + } + + Rectangle bounds = mPositionHelper.getDurationBounds( + i - mStartCallIndex, + c.getContextId(), + duration); + gc.fillRectangle(bounds); + + if (resetColor) { + gc.setBackground(mDurationLineColor); + } + } + } + + /** + * Draw a bounding box that highlights the currently visible range of calls in the + * {@link GLFunctionTraceViewer} table. + */ + private void drawVisibleCallHighlights(GC gc) { + gc.setAlpha(VISIBLE_CALLS_HIGHLIGHT_ALPHA); + gc.setBackground(mVisibleCallsHighlightColor); + gc.fillRectangle(mPositionHelper.getBoundsFramingCalls( + mVisibleCallTopIndex - mStartCallIndex, + mVisibleCallBottomIndex - mStartCallIndex)); + gc.setAlpha(DEFAULT_ALPHA); + } + + private void drawMouseMarkers(GC gc) { + if (!mMouseInSelf) { + return; + } + + if (mPositionHelper.getCallAt(mMouseY) < 0) { + return; + } + + gc.setForeground(mMouseMarkerColor); + gc.drawLine(0, mMouseY, getClientArea().width, mMouseY); + } + + private void controlResized() { + // regenerate back buffer on size changes + disposeBackBuffer(); + initializeBackBuffer(); + + redraw(); + } + + public int getMinimumWidth() { + return mPositionHelper.getMinimumWidth(); + } + + /** Set the GL Call start and end indices for currently displayed frame. */ + public void setCallRangeForCurrentFrame(int startCallIndex, int endCallIndex) { + mStartCallIndex = startCallIndex; + mEndCallIndex = endCallIndex; + mPositionHelper.updateCallDensity(mEndCallIndex - mStartCallIndex, getClientArea().height); + redraw(); + } + + /** + * Set the call range that is currently visible in the {@link GLFunctionTraceViewer} table. + * @param visibleTopIndex index of call currently visible at the top of the table. + * @param visibleBottomIndex index of call currently visible at the bottom of the table. + */ + public void setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex) { + mVisibleCallTopIndex = visibleTopIndex; + mVisibleCallBottomIndex = visibleBottomIndex; + redraw(); + } + + public interface ICallSelectionListener { + void callSelected(int selectedCallIndex); + } + + private List<ICallSelectionListener> mListeners = new ArrayList<ICallSelectionListener>(); + + public void addCallSelectionListener(ICallSelectionListener l) { + mListeners.add(l); + } + + private void sendCallSelectedEvent(int selectedCall) { + for (ICallSelectionListener l : mListeners) { + l.callSelected(selectedCall); + } + } + + /** Utility class to help with the positioning and sizes of elements in the canvas. */ + private static class PositionHelper { + /** Left Margin after which duration lines are drawn. */ + private static final int LEFT_MARGIN = 5; + + /** Top margin after which header is drawn. */ + private static final int TOP_MARGIN = 5; + + /** # of pixels of padding between duration markers for different contexts. */ + private static final int CONTEXT_PADDING = 10; + + private final int mHeaderMargin; + private final int mContextCount; + private final int mMaxDurationLength; + private final long mMaxDuration; + private final double mScale; + + private int mCallCount; + private int mNumCallsPerPixel = 1; + + public PositionHelper(int fontHeight, int contextCount, + int maxDurationLength, long maxDuration) { + mContextCount = contextCount; + mMaxDurationLength = maxDurationLength; + mMaxDuration = maxDuration; + mScale = (double) maxDurationLength / maxDuration; + + // header region is present only there are multiple contexts + if (mContextCount > 1) { + mHeaderMargin = fontHeight * 3; + } else { + mHeaderMargin = 0; + } + } + + /** Get the minimum width of the canvas. */ + public int getMinimumWidth() { + return LEFT_MARGIN + (mMaxDurationLength + CONTEXT_PADDING) * mContextCount; + } + + /** Get the bounds for a call duration line. */ + public Rectangle getDurationBounds(int callIndex, int context, long duration) { + if (duration <= 0) { + duration = 1; + } else if (duration > mMaxDuration) { + duration = mMaxDuration; + } + + int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context); + int y = (callIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin; + int w = (int) (duration * mScale); + int h = 1; + + return new Rectangle(x, y, w, h); + } + + public long getMaxDuration() { + return mMaxDuration; + } + + /** Get the bounds for calls spanning given range. */ + public Rectangle getBoundsFramingCalls(int startCallIndex, int endCallIndex) { + if (startCallIndex >= 0 && endCallIndex >= startCallIndex + && endCallIndex <= mCallCount) { + int x = LEFT_MARGIN; + int y = (startCallIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin; + int w = ((mMaxDurationLength + CONTEXT_PADDING) * mContextCount); + int h = (endCallIndex - startCallIndex)/mNumCallsPerPixel; + return new Rectangle(x, y, w, h); + } else { + return new Rectangle(0, 0, 0, 0); + } + } + + public Point getHeaderLocation(int context) { + int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context); + return new Point(x, TOP_MARGIN); + } + + /** Update the call density based on the number of calls to be displayed and + * the available height to display them in. */ + public void updateCallDensity(int callCount, int displayHeight) { + mCallCount = callCount; + + if (displayHeight <= 0) { + displayHeight = callCount + 1; + } + + mNumCallsPerPixel = (callCount / displayHeight) + 1; + } + + /** Get the underscan value. In cases where there are more calls to be displayed + * than there are availble pixels, we only display 1 out of every underscan calls. */ + public int getCallUnderScanValue() { + return mNumCallsPerPixel; + } + + /** Get the index of the call at given y offset. */ + public int getCallAt(int y) { + if (!isWithinBounds(y)) { + return -1; + } + + Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount); + return (y - displayBounds.y) * mNumCallsPerPixel; + } + + /** Does the provided y offset map to a valid call? */ + private boolean isWithinBounds(int y) { + Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount); + if (y < displayBounds.y) { + return false; + } + + if (y > (displayBounds.y + displayBounds.height)) { + return false; + } + + return true; + } + } +} |