blob: b6db932705d71eaa5268611301f9acbec236e61a [file] [log] [blame]
/*
* Copyright (C) 2014 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.VBox}
*/
WebInspector.Layers3DView = function()
{
WebInspector.VBox.call(this);
this.element.classList.add("layers-3d-view");
this._initStatusBar();
this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("Not in the composited mode.\nConsider forcing composited mode in Settings."));
this._canvasElement = this.element.createChild("canvas");
this._canvasElement.tabIndex = 0;
this._transformController = new WebInspector.TransformController(this._canvasElement);
this._transformController.addEventListener(WebInspector.TransformController.Events.TransformChanged, this._update, this);
this._canvasElement.addEventListener("dblclick", this._onDoubleClick.bind(this), false);
this._canvasElement.addEventListener("mousedown", this._onMouseDown.bind(this), false);
this._canvasElement.addEventListener("mouseup", this._onMouseUp.bind(this), false);
this._canvasElement.addEventListener("mouseout", this._onMouseMove.bind(this), false);
this._canvasElement.addEventListener("mousemove", this._onMouseMove.bind(this), false);
this._canvasElement.addEventListener("contextmenu", this._onContextMenu.bind(this), false);
this._lastActiveObject = {};
this._picturesForLayer = {};
this._scrollRectQuadsForLayer = {};
this._isVisible = {};
this._layerTree = null;
this._textureManager = new WebInspector.LayerTextureManager();
this._textureManager.addEventListener(WebInspector.LayerTextureManager.Events.TextureUpdated, this._update, this);
WebInspector.settings.showPaintRects.addChangeListener(this._update, this);
}
/** @typedef {{borderColor: !Array.<number>, borderWidth: number}} */
WebInspector.Layers3DView.LayerStyle;
/** @typedef {{layerId: string, rect: !Array.<number>, snapshot: !WebInspector.PaintProfilerSnapshot, traceEvent: !WebInspector.TracingModel.Event}} */
WebInspector.Layers3DView.PaintTile;
/**
* @enum {string}
*/
WebInspector.Layers3DView.OutlineType = {
Hovered: "hovered",
Selected: "selected"
}
/**
* @enum {string}
*/
WebInspector.Layers3DView.Events = {
ObjectHovered: "ObjectHovered",
ObjectSelected: "ObjectSelected",
LayerSnapshotRequested: "LayerSnapshotRequested",
JumpToPaintEventRequested: "JumpToPaintEventRequested"
}
/**
* @enum {string}
*/
WebInspector.Layers3DView.ScrollRectTitles = {
RepaintsOnScroll: WebInspector.UIString("repaints on scroll"),
TouchEventHandler: WebInspector.UIString("touch event listener"),
WheelEventHandler: WebInspector.UIString("mousewheel event listener")
}
WebInspector.Layers3DView.FragmentShader = "\
precision mediump float;\
varying vec4 vColor;\
varying vec2 vTextureCoord;\
uniform sampler2D uSampler;\
void main(void)\
{\
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)) * vColor;\
}";
WebInspector.Layers3DView.VertexShader = "\
attribute vec3 aVertexPosition;\
attribute vec2 aTextureCoord;\
attribute vec4 aVertexColor;\
uniform mat4 uPMatrix;\
varying vec2 vTextureCoord;\
varying vec4 vColor;\
void main(void)\
{\
gl_Position = uPMatrix * vec4(aVertexPosition, 1.0);\
vColor = aVertexColor;\
vTextureCoord = aTextureCoord;\
}";
WebInspector.Layers3DView.SelectedBackgroundColor = [20, 40, 110, 0.66];
WebInspector.Layers3DView.BackgroundColor = [0, 0, 0, 0];
WebInspector.Layers3DView.HoveredBorderColor = [0, 0, 255, 1];
WebInspector.Layers3DView.SelectedBorderColor = [0, 255, 0, 1];
WebInspector.Layers3DView.BorderColor = [0, 0, 0, 1];
WebInspector.Layers3DView.ScrollRectBackgroundColor = [178, 0, 0, 0.4];
WebInspector.Layers3DView.SelectedScrollRectBackgroundColor = [178, 0, 0, 0.6];
WebInspector.Layers3DView.ScrollRectBorderColor = [178, 0, 0, 1];
WebInspector.Layers3DView.BorderWidth = 1;
WebInspector.Layers3DView.SelectedBorderWidth = 2;
WebInspector.Layers3DView.LayerSpacing = 20;
WebInspector.Layers3DView.ScrollRectSpacing = 4;
WebInspector.Layers3DView.prototype = {
/**
* @param {?WebInspector.LayerTreeBase} layerTree
*/
setLayerTree: function(layerTree)
{
this._layerTree = layerTree;
this._textureManager.reset();
this._update();
},
/**
* @param {?Array.<!WebInspector.Layers3DView.PaintTile>} tiles
*/
setTiles: function(tiles)
{
this._textureManager.setTiles(tiles);
},
/**
* @param {!WebInspector.Layer} layer
* @param {string=} imageURL
*/
showImageForLayer: function(layer, imageURL)
{
this._textureManager.createTexture(onTextureCreated.bind(this), imageURL);
/**
* @this {WebInspector.Layers3DView}
* @param {!WebGLTexture} texture
*/
function onTextureCreated(texture)
{
this._layerTexture = {layerId: layer.id(), texture: texture};
this._update();
}
},
onResize: function()
{
this._update();
},
wasShown: function()
{
if (this._needsUpdate)
this._update();
},
/**
* @param {!WebInspector.Layers3DView.OutlineType} type
* @param {?WebInspector.Layers3DView.ActiveObject} activeObject
*/
_setOutline: function(type, activeObject)
{
this._lastActiveObject[type] = activeObject;
this._update();
},
/**
* @param {?WebInspector.Layers3DView.ActiveObject} activeObject
*/
hoverObject: function(activeObject)
{
this._setOutline(WebInspector.Layers3DView.OutlineType.Hovered, activeObject);
},
/**
* @param {?WebInspector.Layers3DView.ActiveObject} activeObject
*/
selectObject: function(activeObject)
{
this._setOutline(WebInspector.Layers3DView.OutlineType.Hovered, null);
this._setOutline(WebInspector.Layers3DView.OutlineType.Selected, activeObject);
},
/**
* @param {!Element} canvas
* @return {!WebGLRenderingContext}
*/
_initGL: function(canvas)
{
var gl = canvas.getContext("webgl");
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.enable(gl.DEPTH_TEST);
return gl;
},
/**
* @param {!Object} type
* @param {string} script
*/
_createShader: function(type, script)
{
var shader = this._gl.createShader(type);
this._gl.shaderSource(shader, script);
this._gl.compileShader(shader);
this._gl.attachShader(this._shaderProgram, shader);
},
_initShaders: function()
{
this._shaderProgram = this._gl.createProgram();
this._createShader(this._gl.FRAGMENT_SHADER, WebInspector.Layers3DView.FragmentShader);
this._createShader(this._gl.VERTEX_SHADER, WebInspector.Layers3DView.VertexShader);
this._gl.linkProgram(this._shaderProgram);
this._gl.useProgram(this._shaderProgram);
this._shaderProgram.vertexPositionAttribute = this._gl.getAttribLocation(this._shaderProgram, "aVertexPosition");
this._gl.enableVertexAttribArray(this._shaderProgram.vertexPositionAttribute);
this._shaderProgram.vertexColorAttribute = this._gl.getAttribLocation(this._shaderProgram, "aVertexColor");
this._gl.enableVertexAttribArray(this._shaderProgram.vertexColorAttribute);
this._shaderProgram.textureCoordAttribute = this._gl.getAttribLocation(this._shaderProgram, "aTextureCoord");
this._gl.enableVertexAttribArray(this._shaderProgram.textureCoordAttribute);
this._shaderProgram.pMatrixUniform = this._gl.getUniformLocation(this._shaderProgram, "uPMatrix");
this._shaderProgram.samplerUniform = this._gl.getUniformLocation(this._shaderProgram, "uSampler");
},
_resizeCanvas: function()
{
this._canvasElement.width = this._canvasElement.offsetWidth * window.devicePixelRatio;
this._canvasElement.height = this._canvasElement.offsetHeight * window.devicePixelRatio;
this._gl.viewportWidth = this._canvasElement.width;
this._gl.viewportHeight = this._canvasElement.height;
},
/**
* @return {!CSSMatrix}
*/
_calculateProjectionMatrix: function()
{
var scaleFactorForMargins = 1.2;
var viewport = this._layerTree.viewportSize();
var baseWidth = viewport ? viewport.width : this._layerTree.contentRoot().width();
var baseHeight = viewport ? viewport.height : this._layerTree.contentRoot().height();
var canvasWidth = this._canvasElement.width;
var canvasHeight = this._canvasElement.height;
var scaleX = canvasWidth / baseWidth / scaleFactorForMargins;
var scaleY = canvasHeight / baseHeight / scaleFactorForMargins;
var viewScale = Math.min(scaleX, scaleY);
var scale = this._transformController.scale();
var offsetX = this._transformController.offsetX() * window.devicePixelRatio;
var offsetY = this._transformController.offsetY() * window.devicePixelRatio;
var rotateX = this._transformController.rotateX();
var rotateY = this._transformController.rotateY();
return new WebKitCSSMatrix().translate(offsetX, offsetY, 0).scale(scale, scale, scale).translate(canvasWidth / 2, canvasHeight / 2, 0)
.rotate(rotateX, rotateY, 0).scale(viewScale, viewScale, viewScale).translate(-baseWidth / 2, -baseHeight / 2, 0);
},
/**
* @param {!CSSMatrix} m
* @return {!Float32Array}
*/
_arrayFromMatrix: function(m)
{
return new Float32Array([m.m11, m.m12, m.m13, m.m14, m.m21, m.m22, m.m23, m.m24, m.m31, m.m32, m.m33, m.m34, m.m41, m.m42, m.m43, m.m44]);
},
_initProjectionMatrix: function()
{
var projectionMatrix = this._calculateProjectionMatrix();
this._pMatrix = new WebKitCSSMatrix().scale(1, -1, -1).translate(-1, -1, 0)
.scale(2 / this._canvasElement.width, 2 / this._canvasElement.height, 1 / 1000000).multiply(projectionMatrix);
this._gl.uniformMatrix4fv(this._shaderProgram.pMatrixUniform, false, this._arrayFromMatrix(this._pMatrix));
this._textureScale = Math.min(1, Math.max(projectionMatrix.m11, projectionMatrix.m22));
},
_initWhiteTexture: function()
{
this._whiteTexture = this._gl.createTexture();
this._gl.bindTexture(this._gl.TEXTURE_2D, this._whiteTexture);
var whitePixel = new Uint8Array([255, 255, 255, 255]);
this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, 1, 1, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, whitePixel);
},
_initGLIfNecessary: function()
{
if (this._gl)
return this._gl;
this._gl = this._initGL(this._canvasElement);
this._initShaders();
this._initWhiteTexture();
this._textureManager.setContext(this._gl);
return this._gl;
},
_calculateDepths: function()
{
this._depthByLayerId = {};
this._isVisible = {};
var depth = 0;
var root = this._layerTree.root();
var queue = [root];
this._depthByLayerId[root.id()] = 0;
this._isVisible[root.id()] = false;
while (queue.length > 0) {
var layer = queue.shift();
var children = layer.children();
for (var i = 0; i < children.length; ++i) {
this._depthByLayerId[children[i].id()] = ++depth;
this._isVisible[children[i].id()] = children[i] === this._layerTree.contentRoot() || this._isVisible[layer.id()];
queue.push(children[i]);
}
}
this._maxDepth = depth;
},
/**
* @param {!WebInspector.Layers3DView.OutlineType} type
* @param {!WebInspector.Layer} layer
* @param {number=} scrollRectIndex
*/
_isObjectActive: function(type, layer, scrollRectIndex)
{
var activeObject = this._lastActiveObject[type];
return activeObject && activeObject.layer && activeObject.layer.id() === layer.id() && (typeof scrollRectIndex !== "number" || activeObject.scrollRectIndex === scrollRectIndex);
},
/**
* @param {!WebInspector.Layer} layer
* @return {!WebInspector.Layers3DView.LayerStyle}
*/
_styleForLayer: function(layer)
{
var isSelected = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Selected, layer);
var isHovered = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Hovered, layer);
var borderColor;
if (isSelected)
borderColor = WebInspector.Layers3DView.SelectedBorderColor;
else if (isHovered)
borderColor = WebInspector.Layers3DView.HoveredBorderColor;
else
borderColor = WebInspector.Layers3DView.BorderColor;
var borderWidth = isSelected ? WebInspector.Layers3DView.SelectedBorderWidth : WebInspector.Layers3DView.BorderWidth;
return {borderColor: borderColor, borderWidth: borderWidth};
},
/**
* @param {!WebInspector.Layer} layer
* @return {number}
*/
_depthForLayer: function(layer)
{
return this._depthByLayerId[layer.id()] * WebInspector.Layers3DView.LayerSpacing;
},
/**
* @param {!WebInspector.Layer} layer
* @param {number} index
* @return {number}
*/
_calculateScrollRectDepth: function(layer, index)
{
return this._depthForLayer(layer) + index * WebInspector.Layers3DView.ScrollRectSpacing + 1;
},
/**
* @param {!WebInspector.Layer} layer
*/
_calculateLayerRect: function(layer)
{
if (!this._isVisible[layer.id()])
return;
var activeObject = WebInspector.Layers3DView.ActiveObject.createLayerActiveObject(layer);
var rect = new WebInspector.Layers3DView.Rectangle(activeObject);
var style = this._styleForLayer(layer);
rect.setVertices(layer.quad(), this._depthForLayer(layer));
rect.lineWidth = style.borderWidth;
rect.borderColor = style.borderColor;
this._rects.push(rect);
},
/**
* @param {!WebInspector.Layer} layer
*/
_calculateLayerScrollRects: function(layer)
{
var scrollRects = layer.scrollRects();
for (var i = 0; i < scrollRects.length; ++i) {
var activeObject = WebInspector.Layers3DView.ActiveObject.createScrollRectActiveObject(layer, i);
var rect = new WebInspector.Layers3DView.Rectangle(activeObject);
rect.calculateVerticesFromRect(layer, scrollRects[i].rect, this._calculateScrollRectDepth(layer, i));
var isSelected = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Selected, layer, i);
var color = isSelected ? WebInspector.Layers3DView.SelectedScrollRectBackgroundColor : WebInspector.Layers3DView.ScrollRectBackgroundColor;
rect.fillColor = color;
rect.borderColor = WebInspector.Layers3DView.ScrollRectBorderColor;
this._rects.push(rect);
}
},
/**
* @param {!WebInspector.Layer} layer
*/
_calculateLayerImageRect: function(layer)
{
var layerTexture = this._layerTexture;
if (layer.id() !== layerTexture.layerId)
return;
var activeObject = WebInspector.Layers3DView.ActiveObject.createLayerActiveObject(layer);
var rect = new WebInspector.Layers3DView.Rectangle(activeObject);
rect.setVertices(layer.quad(), this._depthForLayer(layer));
rect.texture = layerTexture.texture;
this._rects.push(rect);
},
/**
* @param {!WebInspector.Layer} layer
*/
_calculateLayerTileRects: function(layer)
{
var tiles = this._textureManager.tilesForLayer(layer.id());
for (var i = 0; i < tiles.length; ++i) {
var tile = tiles[i];
if (!tile.texture)
continue;
var activeObject = WebInspector.Layers3DView.ActiveObject.createTileActiveObject(layer, tile.traceEvent);
var rect = new WebInspector.Layers3DView.Rectangle(activeObject);
rect.calculateVerticesFromRect(layer, {x: tile.rect[0], y: tile.rect[1], width: tile.rect[2], height: tile.rect[3]}, this._depthForLayer(layer) + 1);
rect.texture = tile.texture;
this._rects.push(rect);
}
},
_calculateViewportRect: function()
{
var rect = new WebInspector.Layers3DView.Rectangle(null);
var viewport = this._layerTree.viewportSize();
var depth = (this._maxDepth + 1) * WebInspector.Layers3DView.LayerSpacing;
var vertices = [0, 0, depth, viewport.width, 0, depth, viewport.width, viewport.height, depth, 0, viewport.height, depth];
rect.vertices = vertices;
rect.borderColor = [0, 0, 0, 1];
rect.lineWidth = 3;
this._rects.push(rect);
},
_calculateRects: function()
{
this._rects = [];
this._layerTree.forEachLayer(this._calculateLayerRect.bind(this));
if (this._showSlowScrollRectsSetting.get())
this._layerTree.forEachLayer(this._calculateLayerScrollRects.bind(this));
if (this._showPaintsSetting.get()) {
if (this._layerTexture)
this._layerTree.forEachLayer(this._calculateLayerImageRect.bind(this));
else
this._layerTree.forEachLayer(this._calculateLayerTileRects.bind(this));
}
if (this._layerTree.viewportSize() && this._showViewportSetting.get())
this._calculateViewportRect();
},
/**
* @param {!Array.<number>} color
* @return {!Array.<number>}
*/
_makeColorsArray: function(color)
{
var colors = [];
var normalizedColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3]];
for (var i = 0; i < 4; i++) {
colors = colors.concat(normalizedColor);
}
return colors;
},
/**
* @param {!Object} attribute
* @param {!Array.<number>} array
* @param {!number} length
*/
_setVertexAttribute: function(attribute, array, length)
{
var gl = this._gl;
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), gl.STATIC_DRAW);
gl.vertexAttribPointer(attribute, length, gl.FLOAT, false, 0, 0);
},
/**
* @param {!Array.<number>} vertices
* @param {boolean} isBorder
* @param {!Array.<number>=} color
* @param {!Object=} texture
*/
_drawRectangle: function(vertices, isBorder, color, texture)
{
var gl = this._gl;
var glMode = isBorder ? gl.LINE_LOOP : gl.TRIANGLE_FAN;
var white = [255, 255, 255, 1];
this._setVertexAttribute(this._shaderProgram.vertexPositionAttribute, vertices, 3);
this._setVertexAttribute(this._shaderProgram.textureCoordAttribute, [0, 1, 1, 1, 1, 0, 0, 0], 2);
if (texture) {
this._setVertexAttribute(this._shaderProgram.vertexColorAttribute, this._makeColorsArray(white), white.length);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(this._shaderProgram.samplerUniform, 0);
} else {
this._setVertexAttribute(this._shaderProgram.vertexColorAttribute, this._makeColorsArray(color || white), color.length);
gl.bindTexture(gl.TEXTURE_2D, this._whiteTexture);
}
var numberOfVertices = 4;
gl.drawArrays(glMode, 0, numberOfVertices);
},
/**
* @param {!WebInspector.Layers3DView.Rectangle} rect
*/
_drawViewRect: function(rect)
{
var vertices = rect.vertices;
if (rect.texture)
this._drawRectangle(vertices, false, undefined, rect.texture);
else if (rect.fillColor)
this._drawRectangle(vertices, false, rect.fillColor);
this._gl.lineWidth(rect.lineWidth);
if (rect.borderColor)
this._drawRectangle(vertices, true, rect.borderColor);
},
_update: function()
{
if (!this.isShowing()) {
this._needsUpdate = true;
return;
}
var contentRoot = this._layerTree && this._layerTree.contentRoot();
if (!contentRoot || !this._layerTree.root()) {
this._emptyView.show(this.element);
return;
}
this._emptyView.detach();
var gl = this._initGLIfNecessary();
this._resizeCanvas();
this._initProjectionMatrix();
this._calculateDepths();
this._textureManager.setScale(this._textureScale);
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
this._calculateRects();
this._rects.forEach(this._drawViewRect.bind(this));
},
/**
* @param {!Event} event
* @return {?WebInspector.Layers3DView.ActiveObject}
*/
_activeObjectFromEventPoint: function(event)
{
if (!this._layerTree)
return null;
var closestIntersectionPoint = Infinity;
var closestObject = null;
var projectionMatrix = new WebKitCSSMatrix().scale(1, -1, -1).translate(-1, -1, 0).multiply(this._calculateProjectionMatrix());
var x0 = (event.clientX - this._canvasElement.totalOffsetLeft()) * window.devicePixelRatio;
var y0 = -(event.clientY - this._canvasElement.totalOffsetTop()) * window.devicePixelRatio;
/**
* @param {!WebInspector.Layers3DView.Rectangle} rect
*/
function checkIntersection(rect)
{
if (!rect.relatedObject)
return;
var t = rect.intersectWithLine(projectionMatrix, x0, y0);
if (t < closestIntersectionPoint) {
closestIntersectionPoint = t;
closestObject = rect.relatedObject;
}
}
this._rects.forEach(checkIntersection);
return closestObject;
},
/**
* @param {string} caption
* @param {string} name
* @param {boolean} value
* @param {!Element} statusBarElement
* @return {!WebInspector.Setting}
*/
_createVisibilitySetting: function(caption, name, value, statusBarElement)
{
var checkbox = new WebInspector.StatusBarCheckbox(WebInspector.UIString(caption))
statusBarElement.appendChild(checkbox.element);
var setting = WebInspector.settings.createSetting(name, value)
WebInspector.SettingsUI.bindCheckbox(checkbox.inputElement, setting);
setting.addChangeListener(this._update, this);
return setting;
},
_initStatusBar: function()
{
this._panelStatusBarElement = this.element.createChild("div", "panel-status-bar");
this._showViewportSetting = this._createVisibilitySetting("Viewport", "showViewport", true, this._panelStatusBarElement);
this._showSlowScrollRectsSetting = this._createVisibilitySetting("Slow scroll rects", "showSlowScrollRects", true, this._panelStatusBarElement);
this._showPaintsSetting = this._createVisibilitySetting("Paints", "showPaints", true, this._panelStatusBarElement);
},
/**
* @param {!Event} event
*/
_onContextMenu: function(event)
{
var activeObject = this._activeObjectFromEventPoint(event);
var node = activeObject && activeObject.layer && activeObject.layer.nodeForSelfOrAncestor();
var contextMenu = new WebInspector.ContextMenu(event);
contextMenu.appendItem(WebInspector.UIString("Reset View"), this._transformController.resetAndNotify.bind(this._transformController), false);
if (activeObject && activeObject.type() === WebInspector.Layers3DView.ActiveObject.Type.Tile)
contextMenu.appendItem(WebInspector.UIString("Jump to Paint Event"), this.dispatchEventToListeners.bind(this, WebInspector.Layers3DView.Events.JumpToPaintEventRequested, activeObject.traceEvent), false);
if (node)
contextMenu.appendApplicableItems(node);
contextMenu.show();
},
/**
* @param {!Event} event
*/
_onMouseMove: function(event)
{
if (event.which)
return;
this.dispatchEventToListeners(WebInspector.Layers3DView.Events.ObjectHovered, this._activeObjectFromEventPoint(event));
},
/**
* @param {!Event} event
*/
_onMouseDown: function(event)
{
this._mouseDownX = event.clientX;
this._mouseDownY = event.clientY;
},
/**
* @param {!Event} event
*/
_onMouseUp: function(event)
{
const maxDistanceInPixels = 6;
if (this._mouseDownX && Math.abs(event.clientX - this._mouseDownX) < maxDistanceInPixels && Math.abs(event.clientY - this._mouseDownY) < maxDistanceInPixels)
this.dispatchEventToListeners(WebInspector.Layers3DView.Events.ObjectSelected, this._activeObjectFromEventPoint(event));
delete this._mouseDownX;
delete this._mouseDownY;
},
/**
* @param {!Event} event
*/
_onDoubleClick: function(event)
{
var object = this._activeObjectFromEventPoint(event);
if (object) {
if (object.type() == WebInspector.Layers3DView.ActiveObject.Type.Tile)
this.dispatchEventToListeners(WebInspector.Layers3DView.Events.JumpToPaintEventRequested, object.traceEvent);
else if (object.layer)
this.dispatchEventToListeners(WebInspector.Layers3DView.Events.LayerSnapshotRequested, object.layer);
}
event.stopPropagation();
},
__proto__: WebInspector.VBox.prototype
}
/**
* @constructor
* @extends {WebInspector.Object}
*/
WebInspector.LayerTextureManager = function()
{
WebInspector.Object.call(this);
this.reset();
}
WebInspector.LayerTextureManager.Events = {
TextureUpdated: "TextureUpated"
}
WebInspector.LayerTextureManager.prototype = {
reset: function()
{
/** @type {!Object.<string, !Array.<!WebInspector.LayerTextureManager.Tile>>} */
this._tilesByLayerId = {};
this._scale = 0;
},
/**
* @param {!WebGLRenderingContext} glContext
*/
setContext: function(glContext)
{
this._gl = glContext;
if (this._scale)
this._updateTextures();
},
/**
* @param {?Array.<!WebInspector.Layers3DView.PaintTile>} paintTiles
*/
setTiles: function(paintTiles)
{
this._tilesByLayerId = {};
if (!paintTiles)
return;
for (var i = 0; i < paintTiles.length; ++i) {
var layerId = paintTiles[i].layerId;
var tilesForLayer = this._tilesByLayerId[layerId];
if (!tilesForLayer) {
tilesForLayer = [];
this._tilesByLayerId[layerId] = tilesForLayer;
}
var tile = new WebInspector.LayerTextureManager.Tile(paintTiles[i].snapshot, paintTiles[i].rect, paintTiles[i].traceEvent);
tilesForLayer.push(tile);
if (this._scale && this._gl)
this._updateTile(tile);
}
},
/**
* @param {number} scale
*/
setScale: function(scale)
{
if (this._scale && this._scale >= scale)
return;
this._scale = scale;
this._updateTextures();
},
/**
* @param {string} layerId
* @return {!Array.<!WebInspector.LayerTextureManager.Tile>}
*/
tilesForLayer: function(layerId)
{
return this._tilesByLayerId[layerId] || [];
},
_updateTextures: function()
{
if (!this._gl)
return;
if (!this._scale)
return;
for (var layerId in this._tilesByLayerId) {
for (var i = 0; i < this._tilesByLayerId[layerId].length; ++i) {
var tile = this._tilesByLayerId[layerId][i];
if (!tile.scale || tile.scale < this._scale)
this._updateTile(tile);
}
}
},
/**
* @param {!WebInspector.LayerTextureManager.Tile} tile
*/
_updateTile: function(tile)
{
console.assert(this._scale && this._gl);
tile.scale = this._scale;
tile.snapshot.requestImage(null, null, tile.scale, onGotImage.bind(this));
/**
* @this {WebInspector.LayerTextureManager}
* @param {string=} imageURL
*/
function onGotImage(imageURL)
{
this.createTexture(onTextureCreated.bind(this), imageURL);
}
/**
* @this {WebInspector.LayerTextureManager}
* @param {!WebGLTexture} texture
*/
function onTextureCreated(texture)
{
tile.texture = texture;
this.dispatchEventToListeners(WebInspector.LayerTextureManager.Events.TextureUpdated);
}
},
/**
* @param {!function(!WebGLTexture)} textureCreatedCallback
* @param {string=} imageURL
*/
createTexture: function(textureCreatedCallback, imageURL)
{
var image = new Image();
image.addEventListener("load", onImageLoaded.bind(this), false);
image.src = imageURL;
/**
* @this {WebInspector.LayerTextureManager}
*/
function onImageLoaded()
{
textureCreatedCallback(this._createTextureForImage(image));
}
},
/**
* @param {!Image} image
* @return {!WebGLTexture} texture
*/
_createTextureForImage: function(image)
{
var texture = this._gl.createTexture();
texture.image = image;
this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, true);
this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, texture.image);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
this._gl.bindTexture(this._gl.TEXTURE_2D, null);
return texture;
},
__proto__: WebInspector.Object.prototype
}
/**
* @constructor
* @param {?WebInspector.Layers3DView.ActiveObject} relatedObject
*/
WebInspector.Layers3DView.Rectangle = function(relatedObject)
{
this.relatedObject = relatedObject;
/** @type {number} */
this.lineWidth = 1;
/** @type {?Array.<number>} */
this.borderColor = null;
/** @type {?Array.<number>} */
this.fillColor = null;
/** @type {?WebGLTexture} */
this.texture = null;
}
WebInspector.Layers3DView.Rectangle.prototype = {
/**
* @param {!Array.<number>} quad
* @param {number} z
*/
setVertices: function(quad, z)
{
this.vertices = [quad[0], quad[1], z, quad[2], quad[3], z, quad[4], quad[5], z, quad[6], quad[7], z];
},
/**
* Finds coordinates of point on layer quad, having offsets (ratioX * width) and (ratioY * height)
* from the left corner of the initial layer rect, where width and heigth are layer bounds.
* @param {!Array.<number>} quad
* @param {number} ratioX
* @param {number} ratioY
* @return {!Array.<number>}
*/
_calculatePointOnQuad: function(quad, ratioX, ratioY)
{
var x0 = quad[0];
var y0 = quad[1];
var x1 = quad[2];
var y1 = quad[3];
var x2 = quad[4];
var y2 = quad[5];
var x3 = quad[6];
var y3 = quad[7];
// Point on the first quad side clockwise
var firstSidePointX = x0 + ratioX * (x1 - x0);
var firstSidePointY = y0 + ratioX * (y1 - y0);
// Point on the third quad side clockwise
var thirdSidePointX = x3 + ratioX * (x2 - x3);
var thirdSidePointY = y3 + ratioX * (y2 - y3);
var x = firstSidePointX + ratioY * (thirdSidePointX - firstSidePointX);
var y = firstSidePointY + ratioY * (thirdSidePointY - firstSidePointY);
return [x, y];
},
/**
* @param {!WebInspector.Layer} layer
* @param {!DOMAgent.Rect} rect
* @param {number} z
*/
calculateVerticesFromRect: function(layer, rect, z)
{
var quad = layer.quad();
var rx1 = rect.x / layer.width();
var rx2 = (rect.x + rect.width) / layer.width();
var ry1 = rect.y / layer.height();
var ry2 = (rect.y + rect.height) / layer.height();
var rectQuad = this._calculatePointOnQuad(quad, rx1, ry1).concat(this._calculatePointOnQuad(quad, rx2, ry1))
.concat(this._calculatePointOnQuad(quad, rx2, ry2)).concat(this._calculatePointOnQuad(quad, rx1, ry2));
this.setVertices(rectQuad, z);
},
/**
* Intersects quad with given transform matrix and line l(t) = (x0, y0, t)
* @param {!CSSMatrix} matrix
* @param {!number} x0
* @param {!number} y0
* @return {(number|undefined)}
*/
intersectWithLine: function(matrix, x0, y0)
{
var epsilon = 1e-8;
var i;
// Vertices of the quad with transform matrix applied
var points = [];
for (i = 0; i < 4; ++i)
points[i] = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(this.vertices[i * 3], this.vertices[i * 3 + 1], this.vertices[i * 3 + 2]), matrix);
// Calculating quad plane normal
var normal = WebInspector.Geometry.crossProduct(WebInspector.Geometry.subtract(points[1], points[0]), WebInspector.Geometry.subtract(points[2], points[1]));
// General form of the equation of the quad plane: A * x + B * y + C * z + D = 0
var A = normal.x;
var B = normal.y;
var C = normal.z;
var D = -(A * points[0].x + B * points[0].y + C * points[0].z);
// Finding t from the equation
var t = -(D + A * x0 + B * y0) / C;
// Point of the intersection
var pt = new WebInspector.Geometry.Vector(x0, y0, t);
// Vectors from the intersection point to vertices of the quad
var tVects = points.map(WebInspector.Geometry.subtract.bind(null, pt));
// Intersection point lies inside of the polygon if scalar products of normal of the plane and
// cross products of successive tVects are all nonstrictly above or all nonstrictly below zero
for (i = 0; i < tVects.length; ++i) {
var product = WebInspector.Geometry.scalarProduct(normal, WebInspector.Geometry.crossProduct(tVects[i], tVects[(i + 1) % tVects.length]));
if (product < 0)
return undefined;
}
return t;
}
}
/**
* @constructor
*/
WebInspector.Layers3DView.ActiveObject = function()
{
}
/**
* @enum {string}
*/
WebInspector.Layers3DView.ActiveObject.Type = {
Layer: "Layer",
ScrollRect: "ScrollRect",
Tile: "Tile",
};
/**
* @param {!WebInspector.Layer} layer
* @return {!WebInspector.Layers3DView.ActiveObject}
*/
WebInspector.Layers3DView.ActiveObject.createLayerActiveObject = function(layer)
{
var activeObject = new WebInspector.Layers3DView.ActiveObject();
activeObject._type = WebInspector.Layers3DView.ActiveObject.Type.Layer;
activeObject.layer = layer;
return activeObject;
}
/**
* @param {!WebInspector.Layer} layer
* @param {number} scrollRectIndex
* @return {!WebInspector.Layers3DView.ActiveObject}
*/
WebInspector.Layers3DView.ActiveObject.createScrollRectActiveObject = function(layer, scrollRectIndex)
{
var activeObject = new WebInspector.Layers3DView.ActiveObject();
activeObject._type = WebInspector.Layers3DView.ActiveObject.Type.ScrollRect;
activeObject.layer = layer;
activeObject.scrollRectIndex = scrollRectIndex;
return activeObject;
}
/**
* @param {!WebInspector.Layer} layer
* @param {!WebInspector.TracingModel.Event} traceEvent
* @return {!WebInspector.Layers3DView.ActiveObject}
*/
WebInspector.Layers3DView.ActiveObject.createTileActiveObject = function(layer, traceEvent)
{
var activeObject = new WebInspector.Layers3DView.ActiveObject();
activeObject._type = WebInspector.Layers3DView.ActiveObject.Type.Tile;
activeObject.layer = layer;
activeObject.traceEvent = traceEvent;
return activeObject;
}
WebInspector.Layers3DView.ActiveObject.prototype = {
/**
* @return {!WebInspector.Layers3DView.ActiveObject.Type}
*/
type: function()
{
return this._type;
}
};
/**
* @constructor
* @param {!WebInspector.PaintProfilerSnapshot} snapshot
* @param {!Array.<number>} rect
* @param {!WebInspector.TracingModel.Event} traceEvent
*/
WebInspector.LayerTextureManager.Tile = function(snapshot, rect, traceEvent)
{
this.snapshot = snapshot;
this.rect = rect;
this.traceEvent = traceEvent;
this.scale = 0;
/** @type {?WebGLTexture} */
this.texture = null;
}