• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2014 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.VBox}
34 */
35WebInspector.Layers3DView = function()
36{
37    WebInspector.VBox.call(this);
38    this.element.classList.add("layers-3d-view");
39    this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("Not in the composited mode.\nConsider forcing composited mode in Settings."));
40    this._canvasElement = this.element.createChild("canvas");
41    this._transformController = new WebInspector.TransformController(this._canvasElement);
42    this._transformController.addEventListener(WebInspector.TransformController.Events.TransformChanged, this._update, this);
43    this._canvasElement.addEventListener("dblclick", this._onDoubleClick.bind(this), false);
44    this._canvasElement.addEventListener("mousedown", this._onMouseDown.bind(this), false);
45    this._canvasElement.addEventListener("mouseup", this._onMouseUp.bind(this), false);
46    this._canvasElement.addEventListener("mouseout", this._onMouseMove.bind(this), false);
47    this._canvasElement.addEventListener("mousemove", this._onMouseMove.bind(this), false);
48    this._canvasElement.addEventListener("contextmenu", this._onContextMenu.bind(this), false);
49    this._lastActiveObject = {};
50    this._picturesForLayer = {};
51    this._scrollRectQuadsForLayer = {};
52    this._isVisible = {};
53    this._layerTree = null;
54    WebInspector.settings.showPaintRects.addChangeListener(this._update, this);
55}
56
57/** @typedef {{layer: !WebInspector.Layer, scrollRectIndex: number}|{layer: !WebInspector.Layer}} */
58WebInspector.Layers3DView.ActiveObject;
59
60/** @typedef {{color: !Array.<number>, borderColor: !Array.<number>, borderWidth: number}} */
61WebInspector.Layers3DView.LayerStyle;
62
63/** @typedef {{layerId: string, rect: !Array.<number>, imageURL: string}} */
64WebInspector.Layers3DView.Tile;
65
66/**
67 * @enum {string}
68 */
69WebInspector.Layers3DView.OutlineType = {
70    Hovered: "hovered",
71    Selected: "selected"
72}
73
74/**
75 * @enum {string}
76 */
77WebInspector.Layers3DView.Events = {
78    ObjectHovered: "ObjectHovered",
79    ObjectSelected: "ObjectSelected",
80    LayerSnapshotRequested: "LayerSnapshotRequested"
81}
82
83/**
84 * @enum {string}
85 */
86WebInspector.Layers3DView.ScrollRectTitles = {
87    RepaintsOnScroll: WebInspector.UIString("repaints on scroll"),
88    TouchEventHandler: WebInspector.UIString("touch event listener"),
89    WheelEventHandler: WebInspector.UIString("mousewheel event listener")
90}
91
92WebInspector.Layers3DView.FragmentShader = "\
93    precision mediump float;\
94    varying vec4 vColor;\
95    varying vec2 vTextureCoord;\
96    uniform sampler2D uSampler;\
97    void main(void)\
98    {\
99        gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)) * vColor;\
100    }";
101
102WebInspector.Layers3DView.VertexShader = "\
103    attribute vec3 aVertexPosition;\
104    attribute vec2 aTextureCoord;\
105    attribute vec4 aVertexColor;\
106    uniform mat4 uPMatrix;\
107    varying vec2 vTextureCoord;\
108    varying vec4 vColor;\
109    void main(void)\
110    {\
111        gl_Position = uPMatrix * vec4(aVertexPosition, 1.0);\
112        vColor = aVertexColor;\
113        vTextureCoord = aTextureCoord;\
114    }";
115
116WebInspector.Layers3DView.SelectedBackgroundColor = [20, 40, 110, 0.66];
117WebInspector.Layers3DView.BackgroundColor = [0, 0, 0, 0];
118WebInspector.Layers3DView.HoveredBorderColor = [0, 0, 255, 1];
119WebInspector.Layers3DView.SelectedBorderColor = [0, 255, 0, 1];
120WebInspector.Layers3DView.BorderColor = [0, 0, 0, 1];
121WebInspector.Layers3DView.ScrollRectBackgroundColor = [178, 0, 0, 0.4];
122WebInspector.Layers3DView.SelectedScrollRectBackgroundColor = [178, 0, 0, 0.6];
123WebInspector.Layers3DView.ScrollRectBorderColor = [178, 0, 0, 1];
124WebInspector.Layers3DView.BorderWidth = 1;
125WebInspector.Layers3DView.SelectedBorderWidth = 2;
126
127WebInspector.Layers3DView.LayerSpacing = 20;
128WebInspector.Layers3DView.ScrollRectSpacing = 4;
129
130WebInspector.Layers3DView.prototype = {
131    /**
132     * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(?Event=))} registerShortcutDelegate
133     */
134    registerShortcuts: function(registerShortcutDelegate)
135    {
136        this._transformController.registerShortcuts(registerShortcutDelegate);
137    },
138
139    onResize: function()
140    {
141        this._update();
142    },
143
144    willHide: function()
145    {
146    },
147
148    wasShown: function()
149    {
150        if (this._needsUpdate)
151            this._update();
152    },
153
154    /**
155     * @param {!WebInspector.Layers3DView.OutlineType} type
156     * @param {?WebInspector.Layers3DView.ActiveObject} activeObject
157     */
158    _setOutline: function(type, activeObject)
159    {
160        this._lastActiveObject[type] = activeObject;
161        this._update();
162    },
163
164    /**
165     * @param {?WebInspector.Layers3DView.ActiveObject} activeObject
166     */
167    hoverObject: function(activeObject)
168    {
169        this._setOutline(WebInspector.Layers3DView.OutlineType.Hovered, activeObject);
170    },
171
172    /**
173     * @param {?WebInspector.Layers3DView.ActiveObject} activeObject
174     */
175    selectObject: function(activeObject)
176    {
177        this._setOutline(WebInspector.Layers3DView.OutlineType.Hovered, null);
178        this._setOutline(WebInspector.Layers3DView.OutlineType.Selected, activeObject);
179    },
180
181    /**
182     * @param {!WebInspector.Layer} layer
183     * @param {string=} imageURL
184     */
185    showImageForLayer: function(layer, imageURL)
186    {
187        this.setTiles([{layerId: layer.id(), rect: [0, 0, layer.width(), layer.height()], imageURL: imageURL}])
188    },
189
190    /**
191     * @param {!Array.<!WebInspector.Layers3DView.Tile>} tiles
192     */
193    setTiles: function(tiles)
194    {
195        this._picturesForLayer = {};
196        tiles.forEach(this._setTile, this);
197    },
198
199    /**
200     * @param {!WebInspector.Layers3DView.Tile} tile
201     */
202    _setTile: function(tile)
203    {
204        var texture = this._gl.createTexture();
205        texture.image = new Image();
206        texture.image.addEventListener("load", this._handleLoadedTexture.bind(this, texture, tile.layerId, tile.rect), false);
207        texture.image.src = tile.imageURL;
208    },
209
210    /**
211     * @param {!Element} canvas
212     * @return {!Object}
213     */
214    _initGL: function(canvas)
215    {
216        var gl = canvas.getContext("webgl");
217        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
218        gl.enable(gl.BLEND);
219        gl.clearColor(0.0, 0.0, 0.0, 0.0);
220        gl.enable(gl.DEPTH_TEST);
221        return gl;
222    },
223
224    /**
225     * @param {!Object} type
226     * @param {string} script
227     */
228    _createShader: function(type, script)
229    {
230        var shader = this._gl.createShader(type);
231        this._gl.shaderSource(shader, script);
232        this._gl.compileShader(shader);
233        this._gl.attachShader(this._shaderProgram, shader);
234    },
235
236    /**
237     * @param {string} attributeName
238     * @param {string} glName
239     */
240    _enableVertexAttribArray: function(attributeName, glName)
241    {
242        this._shaderProgram[attributeName] = this._gl.getAttribLocation(this._shaderProgram, glName);
243        this._gl.enableVertexAttribArray(this._shaderProgram[attributeName]);
244    },
245
246    _initShaders: function()
247    {
248        this._shaderProgram = this._gl.createProgram();
249        this._createShader(this._gl.FRAGMENT_SHADER, WebInspector.Layers3DView.FragmentShader);
250        this._createShader(this._gl.VERTEX_SHADER, WebInspector.Layers3DView.VertexShader);
251        this._gl.linkProgram(this._shaderProgram);
252        this._gl.useProgram(this._shaderProgram);
253
254        this._shaderProgram.vertexPositionAttribute = this._gl.getAttribLocation(this._shaderProgram, "aVertexPosition");
255        this._gl.enableVertexAttribArray(this._shaderProgram.vertexPositionAttribute);
256        this._shaderProgram.vertexColorAttribute = this._gl.getAttribLocation(this._shaderProgram, "aVertexColor");
257        this._gl.enableVertexAttribArray(this._shaderProgram.vertexColorAttribute);
258        this._shaderProgram.textureCoordAttribute = this._gl.getAttribLocation(this._shaderProgram, "aTextureCoord");
259        this._gl.enableVertexAttribArray(this._shaderProgram.textureCoordAttribute);
260
261        this._shaderProgram.pMatrixUniform = this._gl.getUniformLocation(this._shaderProgram, "uPMatrix");
262        this._shaderProgram.samplerUniform = this._gl.getUniformLocation(this._shaderProgram, "uSampler");
263    },
264
265    _resizeCanvas: function()
266    {
267        this._canvasElement.width = this._canvasElement.offsetWidth * window.devicePixelRatio;
268        this._canvasElement.height = this._canvasElement.offsetHeight * window.devicePixelRatio;
269        this._gl.viewportWidth = this._canvasElement.width;
270        this._gl.viewportHeight = this._canvasElement.height;
271    },
272
273    /**
274     * @return {!CSSMatrix}
275     */
276    _calculateProjectionMatrix: function()
277    {
278        var scaleFactorForMargins = 1.2;
279        var viewport = this._layerTree.viewportSize();
280        var baseWidth = viewport ? viewport.width : this._layerTree.contentRoot().width();
281        var baseHeight = viewport ? viewport.height : this._layerTree.contentRoot().height();
282        var canvasWidth = this._canvasElement.width;
283        var canvasHeight = this._canvasElement.height;
284        var scaleX = canvasWidth / baseWidth / scaleFactorForMargins;
285        var scaleY = canvasHeight / baseHeight / scaleFactorForMargins;
286        var viewScale = Math.min(scaleX, scaleY);
287        var scale = this._transformController.scale();
288        var offsetX = this._transformController.offsetX() * window.devicePixelRatio;
289        var offsetY = this._transformController.offsetY() * window.devicePixelRatio;
290        var rotateX = this._transformController.rotateX();
291        var rotateY = this._transformController.rotateY();
292        return new WebKitCSSMatrix().translate(offsetX, offsetY, 0).scale(scale, scale, scale).translate(canvasWidth / 2, canvasHeight / 2, 0)
293            .rotate(rotateX, rotateY, 0).scale(viewScale, viewScale, viewScale).translate(-baseWidth / 2, -baseHeight / 2, 0);
294    },
295
296    _initProjectionMatrix: function()
297    {
298        this._pMatrix = new WebKitCSSMatrix().scale(1, -1, -1).translate(-1, -1, 0)
299            .scale(2 / this._canvasElement.width, 2 / this._canvasElement.height, 1 / 1000000).multiply(this._calculateProjectionMatrix());
300        this._gl.uniformMatrix4fv(this._shaderProgram.pMatrixUniform, false, this._arrayFromMatrix(this._pMatrix));
301    },
302
303    /**
304     * @param {!Object} texture
305     * @param {string} layerId
306     * @param {!Array.<number>} rect
307     */
308    _handleLoadedTexture: function(texture, layerId, rect)
309    {
310        this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
311        this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, true);
312        this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, texture.image);
313        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
314        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
315        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
316        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
317        this._gl.bindTexture(this._gl.TEXTURE_2D, null);
318        if (!this._picturesForLayer[layerId])
319            this._picturesForLayer[layerId] = [];
320        this._picturesForLayer[layerId].push({texture: texture, rect: rect});
321        this._update();
322    },
323
324    _initWhiteTexture: function()
325    {
326        this._whiteTexture = this._gl.createTexture();
327        this._gl.bindTexture(this._gl.TEXTURE_2D, this._whiteTexture);
328        var whitePixel = new Uint8Array([255, 255, 255, 255]);
329        this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, 1, 1, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, whitePixel);
330    },
331
332    _initGLIfNecessary: function()
333    {
334        if (this._gl)
335            return this._gl;
336        this._gl = this._initGL(this._canvasElement);
337        this._initShaders();
338        this._initWhiteTexture();
339        return this._gl;
340    },
341
342    /**
343     * @param {!CSSMatrix} m
344     * @return {!Float32Array}
345     */
346    _arrayFromMatrix: function(m)
347    {
348        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]);
349    },
350
351    /**
352     * @param {!Array.<number>} color
353     * @return {!Array.<number>}
354     */
355    _makeColorsArray: function(color)
356    {
357        var colors = [];
358        var normalizedColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3]];
359        for (var i = 0; i < 4; i++) {
360            colors = colors.concat(normalizedColor);
361        }
362        return colors;
363    },
364
365    /**
366     * @param {!Object} attribute
367     * @param {!Array.<number>} array
368     * @param {!number} length
369     */
370    _setVertexAttribute: function(attribute, array, length)
371    {
372        var gl = this._gl;
373        var buffer = gl.createBuffer();
374        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
375        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), gl.STATIC_DRAW);
376        gl.vertexAttribPointer(attribute, length, gl.FLOAT, false, 0, 0);
377    },
378
379    /**
380     * @param {!Array.<number>} vertices
381     * @param {!Array.<number>} color
382     * @param {!Object} glMode
383     * @param {!Object=} texture
384     */
385    _drawRectangle: function(vertices, color, glMode, texture)
386    {
387        this._setVertexAttribute(this._shaderProgram.vertexPositionAttribute, vertices, 3);
388        this._setVertexAttribute(this._shaderProgram.textureCoordAttribute, [0, 1, 1, 1, 1, 0, 0, 0], 2);
389
390        if (texture) {
391            var white = [255, 255, 255, 1];
392            this._setVertexAttribute(this._shaderProgram.vertexColorAttribute, this._makeColorsArray(white), white.length);
393            this._gl.activeTexture(this._gl.TEXTURE0);
394            this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
395            this._gl.uniform1i(this._shaderProgram.samplerUniform, 0);
396        } else {
397            this._setVertexAttribute(this._shaderProgram.vertexColorAttribute, this._makeColorsArray(color), color.length);
398            this._gl.bindTexture(this._gl.TEXTURE_2D, this._whiteTexture);
399        }
400
401        var numberOfVertices = 4;
402        this._gl.drawArrays(glMode, 0, numberOfVertices);
403    },
404
405    /**
406     * @param {!WebInspector.Layers3DView.OutlineType} type
407     * @param {!WebInspector.Layer} layer
408     * @param {number=} scrollRectIndex
409     */
410    _isObjectActive: function(type, layer, scrollRectIndex)
411    {
412        var activeObject = this._lastActiveObject[type];
413        return activeObject && activeObject.layer && activeObject.layer.id() === layer.id() && (typeof scrollRectIndex !== "number" || activeObject.scrollRectIndex === scrollRectIndex);
414    },
415
416    /**
417     * @param {!WebInspector.Layer} layer
418     * @return {!WebInspector.Layers3DView.LayerStyle}
419     */
420    _styleForLayer: function(layer)
421    {
422        var isSelected = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Selected, layer);
423        var isHovered = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Hovered, layer);
424        var color = isSelected ? WebInspector.Layers3DView.SelectedBackgroundColor : WebInspector.Layers3DView.BackgroundColor;
425        var borderColor;
426        if (isSelected)
427            borderColor = WebInspector.Layers3DView.SelectedBorderColor;
428        else if (isHovered)
429            borderColor = WebInspector.Layers3DView.HoveredBorderColor;
430        else
431            borderColor = WebInspector.Layers3DView.BorderColor;
432        var borderWidth = isSelected ? WebInspector.Layers3DView.SelectedBorderWidth : WebInspector.Layers3DView.BorderWidth;
433        return {color: color, borderColor: borderColor, borderWidth: borderWidth};
434    },
435
436    /**
437     * @param {!Array.<number>} quad
438     * @param {number} z
439     * @return {!Array.<number>}
440     */
441    _calculateVerticesForQuad: function(quad, z)
442    {
443        return [quad[0], quad[1], z, quad[2], quad[3], z, quad[4], quad[5], z, quad[6], quad[7], z];
444    },
445
446    /**
447     * Finds coordinates of point on layer quad, having offsets (ratioX * width) and (ratioY * height)
448     * from the left corner of the initial layer rect, where width and heigth are layer bounds.
449     * @param {!Array.<number>} quad
450     * @param {number} ratioX
451     * @param {number} ratioY
452     * @return {!Array.<number>}
453     */
454    _calculatePointOnQuad: function(quad, ratioX, ratioY)
455    {
456        var x0 = quad[0];
457        var y0 = quad[1];
458        var x1 = quad[2];
459        var y1 = quad[3];
460        var x2 = quad[4];
461        var y2 = quad[5];
462        var x3 = quad[6];
463        var y3 = quad[7];
464        // Point on the first quad side clockwise
465        var firstSidePointX = x0 + ratioX * (x1 - x0);
466        var firstSidePointY = y0 + ratioX * (y1 - y0);
467        // Point on the third quad side clockwise
468        var thirdSidePointX = x3 + ratioX * (x2 - x3);
469        var thirdSidePointY = y3 + ratioX * (y2 - y3);
470        var x = firstSidePointX + ratioY * (thirdSidePointX - firstSidePointX);
471        var y = firstSidePointY + ratioY * (thirdSidePointY - firstSidePointY);
472        return [x, y];
473    },
474
475    /**
476     * @param {!WebInspector.Layer} layer
477     * @param {!DOMAgent.Rect} rect
478     * @return {!Array.<number>}
479     */
480    _calculateRectQuad: function(layer, rect)
481    {
482        var quad = layer.quad();
483        var rx1 = rect.x / layer.width();
484        var rx2 = (rect.x + rect.width) / layer.width();
485        var ry1 = rect.y / layer.height();
486        var ry2 = (rect.y + rect.height) / layer.height();
487        return this._calculatePointOnQuad(quad, rx1, ry1).concat(this._calculatePointOnQuad(quad, rx2, ry1))
488            .concat(this._calculatePointOnQuad(quad, rx2, ry2)).concat(this._calculatePointOnQuad(quad, rx1, ry2));
489    },
490
491    /**
492     * @param {!WebInspector.Layer} layer
493     * @return {!Array.<!Array.<number>>}
494     */
495    _calculateScrollRectQuadsForLayer: function(layer)
496    {
497        var quads = [];
498        for (var i = 0; i < layer.scrollRects().length; ++i)
499            quads.push(this._calculateRectQuad(layer, layer.scrollRects()[i].rect));
500        return quads;
501    },
502
503    /**
504     * @param {!WebInspector.Layer} layer
505     * @param {number} index
506     * @return {number}
507     */
508    _calculateScrollRectDepth: function(layer, index)
509    {
510        return this._depthByLayerId[layer.id()] * WebInspector.Layers3DView.LayerSpacing + index * WebInspector.Layers3DView.ScrollRectSpacing + 1;
511    },
512
513    /**
514     * @param {!WebInspector.Layer} layer
515     */
516    _drawLayer: function(layer)
517    {
518        var gl = this._gl;
519        var vertices;
520        var style = this._styleForLayer(layer);
521        var layerDepth = this._depthByLayerId[layer.id()] * WebInspector.Layers3DView.LayerSpacing;
522        if (this._isVisible[layer.id()]) {
523            vertices = this._calculateVerticesForQuad(layer.quad(), layerDepth);
524            gl.lineWidth(style.borderWidth);
525            this._drawRectangle(vertices, style.borderColor, gl.LINE_LOOP);
526            gl.lineWidth(1);
527        }
528        this._scrollRectQuadsForLayer[layer.id()] = this._calculateScrollRectQuadsForLayer(layer);
529        var scrollRectQuads = this._scrollRectQuadsForLayer[layer.id()];
530        for (var i = 0; i < scrollRectQuads.length; ++i) {
531            vertices = this._calculateVerticesForQuad(scrollRectQuads[i], this._calculateScrollRectDepth(layer, i));
532            var isSelected = this._isObjectActive(WebInspector.Layers3DView.OutlineType.Selected, layer, i);
533            var color = isSelected ? WebInspector.Layers3DView.SelectedScrollRectBackgroundColor : WebInspector.Layers3DView.ScrollRectBackgroundColor;
534            this._drawRectangle(vertices, color, gl.TRIANGLE_FAN);
535            this._drawRectangle(vertices, WebInspector.Layers3DView.ScrollRectBorderColor, gl.LINE_LOOP);
536        }
537        var tiles = this._picturesForLayer[layer.id()] || [];
538        for (var i = 0; i < tiles.length; ++i) {
539            var tile = tiles[i];
540            var quad = this._calculateRectQuad(layer, {x: tile.rect[0], y: tile.rect[1], width: tile.rect[2] - tile.rect[0], height: tile.rect[3] - tile.rect[1]});
541            vertices = this._calculateVerticesForQuad(quad, layerDepth);
542            this._drawRectangle(vertices, style.color, gl.TRIANGLE_FAN, tile.texture);
543        }
544    },
545
546    _drawViewport: function()
547    {
548        var viewport = this._layerTree.viewportSize();
549        var vertices = [0, 0, 0, viewport.width, 0, 0, viewport.width, viewport.height, 0, 0, viewport.height, 0];
550        var color = [0, 0, 0, 1];
551        this._gl.lineWidth(3.0);
552        this._drawRectangle(vertices, color, this._gl.LINE_LOOP);
553        this._gl.lineWidth(1.0);
554    },
555
556    _calculateDepths: function()
557    {
558        this._depthByLayerId = {};
559        this._isVisible = {};
560        var depth = 0;
561        var root = this._layerTree.root();
562        var queue = [root];
563        this._depthByLayerId[root.id()] = 0;
564        this._isVisible[root.id()] = false;
565        while (queue.length > 0) {
566            var layer = queue.shift();
567            var children = layer.children();
568            for (var i = 0; i < children.length; ++i) {
569                this._depthByLayerId[children[i].id()] = ++depth;
570                this._isVisible[children[i].id()] = children[i] === this._layerTree.contentRoot() || this._isVisible[layer.id()];
571                queue.push(children[i]);
572            }
573        }
574    },
575
576
577    /**
578     * @param {?WebInspector.LayerTreeBase} layerTree
579     */
580    setLayerTree: function(layerTree)
581    {
582        this._layerTree = layerTree;
583        this._update();
584    },
585
586    _update: function()
587    {
588        if (!this.isShowing()) {
589            this._needsUpdate = true;
590            return;
591        }
592        var contentRoot = this._layerTree && this._layerTree.contentRoot();
593        if (!contentRoot || !this._layerTree.root()) {
594            this._emptyView.show(this.element);
595            return;
596        }
597        this._emptyView.detach();
598
599        var gl = this._initGLIfNecessary();
600        this._resizeCanvas();
601        this._initProjectionMatrix();
602        this._calculateDepths();
603
604        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
605        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
606
607        if (this._layerTree.viewportSize())
608            this._drawViewport();
609        this._layerTree.forEachLayer(this._drawLayer.bind(this));
610    },
611
612    /**
613     * Intersects quad with given transform matrix and line l(t) = (x0, y0, t)
614     * @param {!Array.<number>} vertices
615     * @param {!CSSMatrix} matrix
616     * @param {!number} x0
617     * @param {!number} y0
618     * @return {(number|undefined)}
619     */
620    _intersectLineAndRect: function(vertices, matrix, x0, y0)
621    {
622        var epsilon = 1e-8;
623        var i;
624        // Vertices of the quad with transform matrix applied
625        var points = [];
626        for (i = 0; i < 4; ++i)
627            points[i] = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix);
628        // Calculating quad plane normal
629        var normal = WebInspector.Geometry.crossProduct(WebInspector.Geometry.subtract(points[1], points[0]), WebInspector.Geometry.subtract(points[2], points[1]));
630        // General form of the equation of the quad plane: A * x + B * y + C * z + D = 0
631        var A = normal.x;
632        var B = normal.y;
633        var C = normal.z;
634        var D = -(A * points[0].x + B * points[0].y + C * points[0].z);
635        // Finding t from the equation
636        var t = -(D + A * x0 + B * y0) / C;
637        // Point of the intersection
638        var pt = new WebInspector.Geometry.Vector(x0, y0, t);
639        // Vectors from the intersection point to vertices of the quad
640        var tVects = points.map(WebInspector.Geometry.subtract.bind(null, pt));
641        // Intersection point lies inside of the polygon if scalar products of normal of the plane and
642        // cross products of successive tVects are all nonstrictly above or all nonstrictly below zero
643        for (i = 0; i < tVects.length; ++i) {
644            var product = WebInspector.Geometry.scalarProduct(normal, WebInspector.Geometry.crossProduct(tVects[i], tVects[(i + 1) % tVects.length]));
645            if (product < 0)
646                return undefined;
647        }
648        return t;
649    },
650
651    /**
652     * @param {?Event} event
653     * @return {?WebInspector.Layers3DView.ActiveObject}
654     */
655    _activeObjectFromEventPoint: function(event)
656    {
657        if (!this._layerTree)
658            return null;
659        var closestIntersectionPoint = Infinity;
660        var closestLayer = null;
661        var projectionMatrix = new WebKitCSSMatrix().scale(1, -1, -1).translate(-1, -1, 0).multiply(this._calculateProjectionMatrix());
662        var x0 = (event.clientX - this._canvasElement.totalOffsetLeft()) * window.devicePixelRatio;
663        var y0 = -(event.clientY - this._canvasElement.totalOffsetTop()) * window.devicePixelRatio;
664
665        /**
666         * @param {!WebInspector.Layer} layer
667         * @this {WebInspector.Layers3DView}
668         */
669        function checkIntersection(layer)
670        {
671            var t;
672            if (this._isVisible[layer.id()]) {
673                t = this._intersectLineAndRect(this._calculateVerticesForQuad(layer.quad(), this._depthByLayerId[layer.id()] * WebInspector.Layers3DView.LayerSpacing), projectionMatrix, x0, y0);
674                if (t < closestIntersectionPoint) {
675                    closestIntersectionPoint = t;
676                    closestLayer = {layer: layer};
677                }
678            }
679            var scrollRectQuads = this._scrollRectQuadsForLayer[layer.id()];
680            for (var i = 0; i < scrollRectQuads.length; ++i) {
681                t = this._intersectLineAndRect(this._calculateVerticesForQuad(scrollRectQuads[i], this._calculateScrollRectDepth(layer, i)), projectionMatrix, x0, y0);
682                if (t < closestIntersectionPoint) {
683                    closestIntersectionPoint = t;
684                    closestLayer = {layer: layer, scrollRectIndex: i};
685                }
686            }
687        }
688
689        this._layerTree.forEachLayer(checkIntersection.bind(this));
690        return closestLayer;
691    },
692
693    /**
694     * @param {?Event} event
695     */
696    _onContextMenu: function(event)
697    {
698        var activeObject = this._activeObjectFromEventPoint(event);
699        var node = activeObject && activeObject.layer && activeObject.layer.nodeForSelfOrAncestor();
700        var contextMenu = new WebInspector.ContextMenu(event);
701        contextMenu.appendItem("Reset view", this._transformController._resetAndNotify.bind(this._transformController), false);
702        if (node)
703            contextMenu.appendApplicableItems(node);
704        contextMenu.show();
705    },
706
707    /**
708     * @param {?Event} event
709     */
710    _onMouseMove: function(event)
711    {
712        if (event.which)
713            return;
714        this.dispatchEventToListeners(WebInspector.Layers3DView.Events.ObjectHovered, this._activeObjectFromEventPoint(event));
715    },
716
717    /**
718     * @param {?Event} event
719     */
720    _onMouseDown: function(event)
721    {
722        this._mouseDownX = event.clientX;
723        this._mouseDownY = event.clientY;
724    },
725
726    /**
727     * @param {?Event} event
728     */
729    _onMouseUp: function(event)
730    {
731        const maxDistanceInPixels = 6;
732        if (this._mouseDownX && Math.abs(event.clientX - this._mouseDownX) < maxDistanceInPixels && Math.abs(event.clientY - this._mouseDownY) < maxDistanceInPixels)
733            this.dispatchEventToListeners(WebInspector.Layers3DView.Events.ObjectSelected, this._activeObjectFromEventPoint(event));
734        delete this._mouseDownX;
735        delete this._mouseDownY;
736    },
737
738    /**
739     * @param {?Event} event
740     */
741    _onDoubleClick: function(event)
742    {
743        var object = this._activeObjectFromEventPoint(event);
744        if (object && object.layer)
745            this.dispatchEventToListeners(WebInspector.Layers3DView.Events.LayerSnapshotRequested, object.layer);
746        event.stopPropagation();
747    },
748
749    __proto__: WebInspector.VBox.prototype
750}
751