From 31513558efb8664cb17a790c8883ea741bdaa01f Mon Sep 17 00:00:00 2001 From: Said Abou-Hallawa Date: Thu, 25 Apr 2024 17:51:35 -0700 Subject: [PATCH] Expand the grid size of the Multiply test automatically when needed https://github.com/WebKit/MotionMark/issues/52 Enlarge the grid size and reduce the tile size when a larger complexity is needed to lower the frame rate. Refactor the spiral iterator into a separate class. Keep calling its next() method to move to the next cell. When its isDone() returns true enlarge the iterator grid size and resize the already created tiles to fit in the new grid size. Add a new class for the roundedRect tile called "Tile". This class can handle the location, size and animation of the Tile. --- MotionMark/resources/extensions.js | 26 ++ MotionMark/tests/core/resources/multiply.js | 317 ++++++++++++++------ 2 files changed, 248 insertions(+), 95 deletions(-) diff --git a/MotionMark/resources/extensions.js b/MotionMark/resources/extensions.js index 3fb5794..8222c4f 100644 --- a/MotionMark/resources/extensions.js +++ b/MotionMark/resources/extensions.js @@ -265,18 +265,41 @@ Point = Utilities.createClass( return this.x; }, + // Used when the point object is used as a size object. + set width(w) + { + this.x = w; + }, + // Used when the point object is used as a size object. get height() { return this.y; }, + // Used when the point object is used as a size object. + set height(h) + { + this.y = h; + }, + // Used when the point object is used as a size object. get center() { return new Point(this.x / 2, this.y / 2); }, + // Used when the point object is used as a size object. + area: function() { + return this.x * this.y; + }, + + // Used when the point object is used as a size object. + expand(width, height) { + this.x += width; + this.y += height; + }, + str: function() { return "x = " + this.x + ", y = " + this.y; @@ -320,6 +343,9 @@ Point = Utilities.createClass( } }); +// FIXME: Add a seprate class for Size. +let Size = Point; + Utilities.extendObject(Point, { zero: new Point(0, 0), diff --git a/MotionMark/tests/core/resources/multiply.js b/MotionMark/tests/core/resources/multiply.js index c21febc..a1007ac 100644 --- a/MotionMark/tests/core/resources/multiply.js +++ b/MotionMark/tests/core/resources/multiply.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2017 Apple Inc. All rights reserved. + * Copyright (C) 2015-2024 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -24,120 +24,247 @@ */ (function() { +var Tile = Utilities.createClass( + function(stage, coordinate) + { + this.stage = stage; + this.coordinate = coordinate; + + this.roundedRect = Utilities.createElement('div', { + class: "div-" + Stage.randomInt(0, 5) + }, stage.element); + + this.distance = this.coordinate.length(); + this.step = Math.max(3, this.distance / 1.5); + this.rotate = Stage.randomInt(0, 359); + + this.move(); + this.resize(); + this.hide(); + }, { + + move: function() { + let tileSize = this.stage.tileSize; + + let location = this.stage.size.center; + location = location.add(this.coordinate.multiply(tileSize)); + location = location.subtract(tileSize.multiply(0.5)); + + this.roundedRect.style.left = location.x + 'px'; + this.roundedRect.style.top = location.y + 'px'; + }, + + resize: function() { + let tileSize = this.stage.tileSize; + + this.roundedRect.style.width = tileSize.width + 'px'; + this.roundedRect.style.height = tileSize.height + 'px'; + }, + + show: function() { + this.roundedRect.style.display = "block"; + }, + + hide: function() { + this.roundedRect.style.display = "none"; + }, + + backgroundColor: function() { + let influence = Math.max(.01, 1 - (this.distance * this.stage.distanceFactor)); + let l = this.stage.l * Math.tan(influence); + return this.stage.hslPrefix + l + "%," + influence + ")"; + }, + + animate: function(timestamp) { + this.rotate += this.step; + this.roundedRect.style.transform = "rotate(" + this.rotate + "deg)"; + this.roundedRect.style.backgroundColor = this.backgroundColor(); + } +}); + +var SpiralIterator = Utilities.createClass( + function(gridSize) + { + this.gridSize = gridSize; + this.current = Point.zero; + this.direction = this.directions.right; + this.size = new Size(1, 1); + this.count = 0; + }, { + + directions: { + top: 0, + left: 1, + bottom: 2, + right: 3 + }, + + moves: [ + new Size(0, -1), // top + new Size(-1, 0), // left + new Size(0, +1), // bottom + new Size(+1, 0) // right + ], + + isDone: function() { + return this.count >= this.gridSize.area(); + }, + + next: function() { + ++this.count; + + if (this.isDone()) + return; + + let direction = this.direction; + let move = this.moves[direction]; + + if (Math.abs(this.current.x) == Math.abs(this.current.y)) { + // Turn left. + direction = (direction + 1) % 4; + + if (this.current.x >= 0 && this.current.y >= 0) { + if (this.size.width < Math.min(this.gridSize.width, this.gridSize.height)) + this.size.expand(2, 2); + else if (this.size.width < this.gridSize.width) + ++this.size.width; + + move = this.moves[this.directions.right]; + } else + move = this.moves[direction]; + } + + if (this.count < this.size.area()) { + this.current = this.current.add(move); + this.direction = direction; + return; + } + + // Make a U-turn. + this.direction = (this.direction + 1) % 4; + + if (this.direction == this.directions.left || this.direction == this.directions.right) + this.current = this.current.add(this.moves[this.direction].multiply(this.size.width++)); + else + this.current = this.current.add(this.moves[this.direction].multiply(this.size.height++)); + + this.direction = (this.direction + 1) % 4; + } +}); + var MultiplyStage = Utilities.createSubclass(Stage, function() { Stage.call(this); this.tiles = []; - this._offsetIndex = 0; + this.activeLength = 0; }, { - visibleCSS: [ - ["display", "none", "block"] - ], - totalRows: 68, + initialRowsCount: 69, + extraExpandRows: 16, - initialize: function(benchmark, options) - { + initialize: function(benchmark, options) { Stage.prototype.initialize.call(this, benchmark, options); - var tileSize = Math.round(this.size.height / this.totalRows); - if (options.visibleCSS) - this.visibleCSS = options.visibleCSS; - - // Fill the scene with elements - var x = Math.round((this.size.width - tileSize) / 2); - var y = Math.round((this.size.height - tileSize) / 2); - var tileStride = tileSize; - var direction = 0; - var spiralCounter = 2; - var nextIndex = 1; - var maxSide = Math.floor(y / tileStride) * 2 + 1; - this._centerSpiralCount = maxSide * maxSide; - for (var i = 0; i < this._centerSpiralCount; ++i) { - this._addTile(x, y, tileSize, Stage.randomInt(0, 359)); - - if (i == nextIndex) { - direction = (direction + 1) % 4; - spiralCounter++; - nextIndex += spiralCounter >> 1; - } - if (direction == 0) - x += tileStride; - else if (direction == 1) - y -= tileStride; - else if (direction == 2) - x -= tileStride; - else - y += tileStride; - } - this._sidePanelCount = maxSide * Math.floor((this.size.width - x) / tileStride) * 2; - for (var i = 0; i < this._sidePanelCount; ++i) { - var sideX = x + Math.floor(Math.floor(i / maxSide) / 2) * tileStride; - var sideY = y - tileStride * (i % maxSide); + this.rowsCount = this.initialRowsCount; - if (Math.floor(i / maxSide) % 2 == 1) - sideX = this.size.width - sideX - tileSize + 1; - this._addTile(sideX, sideY, tileSize, Stage.randomInt(0, 359)); - } + this.calculateTileSize(); + this.calculateGridSize(); + this.iterator = new SpiralIterator(this.tileGrid); + + while (!this.iterator.isDone()) + this.tiles.push(this.createTile()); }, - _addTile: function(x, y, tileSize, rotateDeg) - { - var tile = Utilities.createElement("div", { class: "div-" + Stage.randomInt(0,6) }, this.element); - var halfTileSize = tileSize / 2; - tile.style.left = x + 'px'; - tile.style.top = y + 'px'; - tile.style.width = tileSize + 'px'; - tile.style.height = tileSize + 'px'; - var visibleCSS = this.visibleCSS[this.tiles.length % this.visibleCSS.length]; - tile.style[visibleCSS[0]] = visibleCSS[1]; - - var distance = 1 / tileSize * this.size.multiply(0.5).subtract(new Point(x + halfTileSize, y + halfTileSize)).length(); - this.tiles.push({ - element: tile, - rotate: rotateDeg, - step: Math.max(3, distance / 1.5), - distance: distance, - active: false, - visibleCSS: visibleCSS, - }); - }, - - complexity: function() - { - return this._offsetIndex; + calculateTileSize: function() { + let tileSide = Math.round(this.size.height / this.rowsCount); + this.tileSize = new Size(tileSide, tileSide); }, - tune: function(count) - { - this._offsetIndex = Math.max(0, Math.min(this._offsetIndex + count, this.tiles.length)); - this._distanceFactor = 1.5 * (1 - 0.5 * Math.max(this._offsetIndex - this._centerSpiralCount, 0) / this._sidePanelCount) / Math.sqrt(this._offsetIndex); + calculateGridSize: function() { + let columnsCount = Math.floor(this.size.width / this.tileSize.width); + if (columnsCount % 2 == 0) + --columnsCount; + this.tileGrid = new Size(columnsCount, this.rowsCount); }, - animate: function() - { - var progress = this._benchmark.timestamp % 10000 / 10000; - var bounceProgress = Math.sin(2 * Math.abs( 0.5 - progress)); - var l = Utilities.lerp(bounceProgress, 20, 50); - var hslPrefix = "hsla(" + Utilities.lerp(progress, 0, 360) + ",100%,"; - - for (var i = 0; i < this._offsetIndex; ++i) { - var tile = this.tiles[i]; - tile.active = true; - tile.element.style[tile.visibleCSS[0]] = tile.visibleCSS[2]; - tile.rotate += tile.step; - tile.element.style.transform = "rotate(" + tile.rotate + "deg)"; - - var influence = Math.max(.01, 1 - (tile.distance * this._distanceFactor)); - tile.element.style.backgroundColor = hslPrefix + l * Math.tan(influence / 1.25) + "%," + influence + ")"; + expandGrid: function(extraRows) { + this.rowsCount += this.extraExpandRows; + + this.calculateTileSize(); + this.calculateGridSize(); + this.iterator.gridSize = this.tileGrid; + + for (let tile of this.tiles) { + tile.move(); + tile.resize(); } + }, + + createTile: function() { + let tile = new Tile(this, this.iterator.current); - for (var i = this._offsetIndex; i < this.tiles.length && this.tiles[i].active; ++i) { - var tile = this.tiles[i]; - tile.active = false; - tile.element.style[tile.visibleCSS[0]] = tile.visibleCSS[1]; + if (this.iterator.isDone()) + this.expandGrid(); + + this.iterator.next(); + return tile; + }, + + complexity: function() { + return this.activeLength; + }, + + activeTiles: function() { + return this.tiles.slice(0, this.activeLength); + }, + + inactiveTiles: function(end) { + return this.tiles.slice(this.activeLength, end); + }, + + reusableTune: function(count) { + if (count == 0) + return; + + if (count < 0) { + this.activeLength = Math.max(this.activeLength + count, 0); + for (var i = this.activeLength; i < this.tiles.length; ++i) + this.tiles[i].hide(); + return; } + + let inactiveTiles = this.inactiveTiles(this.activeLength + count); + for (let tile of inactiveTiles) + tile.show(); + + for (let i = inactiveTiles.length; i < count; ++i) + this.tiles.push(this.createTile()); + + this.activeLength += count; + }, + + reusableAnimate: function(timestamp) { + for (let tile of this.activeTiles()) + tile.animate(timestamp); + }, + + tune: function(count) { + this.reusableTune(count); + + let centerSpiralCount = this.tileGrid.height * this.tileGrid.height; + let sidePanelCount = this.tileGrid.area() - centerSpiralCount; + let activeSidePanelCount = Math.max(this.activeLength - centerSpiralCount, 0); + this.distanceFactor = 1.5 * (1 - 0.5 * activeSidePanelCount / sidePanelCount) / Math.sqrt(this.activeLength); + }, + + animate: function(timestamp) { + let progress = timestamp % 10000 / 10000; + let bounceProgress = Math.sin(2 * Math.abs(0.5 - progress)); + this.l = Utilities.lerp(bounceProgress, 20, 50); + this.hslPrefix = "hsla(" + Utilities.lerp(progress, 0, 360) + ",100%,"; + + this.reusableAnimate(timestamp); } });