This commit is contained in:
Michael Bebenita 2014-01-31 11:57:53 -08:00
Родитель af2a9e04d8 97f87a6366
Коммит 707328b5ad
12 изменённых файлов: 1500 добавлений и 1380 удалений

Просмотреть файл

@ -125,6 +125,7 @@ limitations under the License.
<script> console.time("Load SWF Dependencies"); </script>
<!-- Load SWF Dependencies -->
<script src="../../src/swf/config.js"></script>
<script src="../../src/swf/options.js"></script>
<script src="../../src/flash/util.js"></script>
<script src="../../src/swf/swf.js"></script>
<script src="../../src/swf/inflate.js"></script>
@ -139,7 +140,8 @@ limitations under the License.
<script src="../../src/swf/text.js"></script>
<script src="../../src/swf/mp3worker.js"></script>
<script src="../../src/swf/embed.js"></script>
<script src="../../src/swf/renderer.js"></script>
<script src="../../src/swf/_renderer.js"></script>
<script src="../../src/swf/Renderer.js"></script>
<script> console.timeEnd("Load SWF Dependencies"); </script>
<script> console.time("Load SWF Parser"); </script>

Просмотреть файл

@ -61,9 +61,8 @@ function updateAVM2State() {
enableRegisterAllocator.value = state.allocator;
traceExecution.value = state.trace ? 2 : 0;
traceRenderer.value = state.trace ? 2 : 0;
disableRenderVisitor.value = state.render ? false : true;
disableMouseVisitor.value = state.mouse ? false : true;
showQuadTree.value = state.qtree ? true : false;
disableRendering.value = state.render ? false : true;
disableMouse.value = state.mouse ? false : true;
turboMode.value = state.turbo ? true : false;
//showRedrawRegions.value = state.redraw ? true : false;
//renderAsWireframe.value = state.wireframe ? true : false;

Просмотреть файл

@ -49,58 +49,6 @@ var BitmapDefinition = (function () {
return {
// (bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false)
__class__: "flash.display.Bitmap",
draw: function(ctx, ratio, colorTransform) {
if (!this._bitmapData) {
return;
}
var scaledImage;
ctx.save();
if (this._pixelSnapping === 'auto' || this._pixelSnapping === 'always') {
var transform = this._getConcatenatedTransform(null, true);
var EPSILON = 0.001;
var aInt = Math.abs(Math.round(transform.a));
var dInt = Math.abs(Math.round(transform.d));
var snapPixels;
if (aInt >= 1 && aInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
dInt >= 1 && dInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
Math.abs(Math.abs(transform.a) / aInt - 1) <= EPSILON &&
Math.abs(Math.abs(transform.d) / dInt - 1) <= EPSILON &&
Math.abs(transform.b) <= EPSILON && Math.abs(transform.c) <= EPSILON) {
if (aInt === 1 && dInt === 1) {
snapPixels = true;
} else {
var sizeKey = aInt + 'x' + dInt;
if (this._snapImageCache.size !== sizeKey) {
this._snapImageCache.size = sizeKey;
this._snapImageCache.hits = 0;
this._snapImageCache.image = null;
}
if (++this._snapImageCache.hits === CACHE_SNAP_DRAW_AFTER) {
this._cacheSnapImage(sizeKey, aInt, dInt);
}
scaledImage = this._snapImageCache.image;
snapPixels = !!scaledImage;
}
} else {
snapPixels = false;
}
if (snapPixels) {
ctx.setTransform(transform.a < 0 ? -1 : 1, 0,
0, transform.d < 0 ? -1 : 1,
(transform.tx/20)|0, (transform.ty/20)|0);
}
// TODO this._pixelSnapping === 'always'; does it even make sense in other cases?
}
colorTransform.setAlpha(ctx, true);
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
this._smoothing;
ctx.drawImage(scaledImage || this._bitmapData._getDrawable(), 0, 0);
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = false;
ctx.restore();
traceRenderer.value && frameWriter.writeLn("Bitmap.draw() snapping: " + this._pixelSnapping +
", dimensions: " + this._bitmapData._drawable.width + " x " + this._bitmapData._drawable.height);
},
_drawableChanged: function () {
this._invalidate();
this._snapImageCache.image = null;

Просмотреть файл

@ -32,10 +32,6 @@ var LoaderDefinition = (function () {
var WORKERS_ENABLED = true;
var LOADER_PATH = $RELEASE ? 'shumway-worker.js' : 'swf/resourceloader.js';
var head = document.head;
head.insertBefore(document.createElement('style'), head.firstChild);
var style = document.styleSheets[0];
var def = {
__class__: 'flash.display.Loader',
@ -446,13 +442,13 @@ var LoaderDefinition = (function () {
},
_commitImage : function (imageInfo) {
var loader = this;
var imgPromiseResolve;
var imgPromise = this._lastPromise = new Promise(function (resolve) {
imgPromiseResolve = resolve;
});
var img = new Image();
imageInfo.props.img = img;
img.onload = function() {
//var imgPromiseResolve;
//var imgPromise = this._lastPromise = new Promise(function (resolve) {
// imgPromiseResolve = resolve;
//});
//var img = new Image();
//imageInfo.props.img = img;
//img.onload = function() {
var Bitmap = avm2.systemDomain.getClass("flash.display.Bitmap");
var BitmapData = avm2.systemDomain.getClass("flash.display.BitmapData");
@ -472,15 +468,19 @@ var LoaderDefinition = (function () {
loader._invalidateBounds();
loader._content = image;
imgPromiseResolve(imageInfo);
// imgPromiseResolve(imageInfo);
var loaderInfo = loader._contentLoaderInfo;
loaderInfo._width = image.width;
loaderInfo._height = image.height;
loaderInfo._dispatchEvent("init");
};
img.src = URL.createObjectURL(imageInfo.data);
delete imageInfo.data;
//};
//img.src = URL.createObjectURL(imageInfo.data);
//delete imageInfo.data;
loader._stage._renderer.defineRenderable(loader._stage._renderer.nextId++,
symbol.type,
symbol);
},
_commitSymbol: function (symbol) {
var dictionary = this._dictionary;
@ -560,34 +560,6 @@ var LoaderDefinition = (function () {
props.buttonActions = symbol.buttonActions;
break;
case 'font':
var charset = fromCharCode.apply(null, symbol.codes);
if (charset) {
style.insertRule(
'@font-face{' +
'font-family:"' + symbol.uniqueName + '";' +
'src:url(data:font/opentype;base64,' + btoa(symbol.data) + ')' +
'}',
style.cssRules.length
);
// HACK non-Gecko browsers need time to load fonts
if (!/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.test(window.navigator.userAgent)) {
var testDiv = document.createElement('div');
testDiv.setAttribute('style', 'position: absolute; top: 0; right: 0;' +
'visibility: hidden; z-index: -500;' +
'font-family:"' + symbol.uniqueName + '";');
testDiv.textContent = 'font test';
document.body.appendChild(testDiv);
var fontPromise = new Promise(function (resolve) {
setTimeout(function () {
resolve();
document.body.removeChild(testDiv);
}, 200);
});
promiseQueue.push(fontPromise);
}
}
className = 'flash.text.Font';
props.name = symbol.name;
props.uniqueName = symbol.uniqueName;
@ -598,45 +570,13 @@ var LoaderDefinition = (function () {
this._registerFont(className, props);
break;
case 'image':
var img = new Image();
var imgPromiseResolve;
var imgPromise = new Promise(function (resolve) {
imgPromiseResolve = resolve;
});
img.onload = function () {
if (symbol.mask) {
// Write the symbol image into new canvas and apply
// the symbol mask.
var maskCanvas = document.createElement('canvas');
maskCanvas.width = symbol.width;
maskCanvas.height = symbol.height;
var maskContext = maskCanvas.getContext('2d');
maskContext.drawImage(img, 0, 0);
var maskImageData = maskContext.getImageData(0, 0, symbol.width, symbol.height);
var maskImageDataBytes = maskImageData.data;
var symbolMaskBytes = symbol.mask;
var length = maskImageData.width * maskImageData.height;
for (var i = 0, j = 3; i < length; i++, j += 4) {
maskImageDataBytes[j] = symbolMaskBytes[i];
}
maskContext.putImageData(maskImageData, 0, 0);
// Use the result canvas as symbol image
props.img = maskCanvas;
}
imgPromiseResolve();
};
img.src = URL.createObjectURL(symbol.data);
promiseQueue.push(imgPromise);
className = 'flash.display.Bitmap';
props.img = img;
props.width = symbol.width;
props.height = symbol.height;
break;
case 'label':
var drawFn = new Function('c,r,ct', symbol.data);
className = 'flash.text.StaticText';
props.bbox = symbol.bbox;
props.draw = drawFn;
break;
case 'text':
props.bbox = symbol.bbox;
@ -753,6 +693,10 @@ var LoaderDefinition = (function () {
};
dictionaryResolved[symbol.id] = symbolInfo;
symbolPromiseResolve(symbolInfo);
props.loader._stage._renderer.defineRenderable(symbol.id,
symbol.type,
symbol);
});
},
_registerFont: function (className, props) {

Просмотреть файл

@ -16,10 +16,6 @@
* limitations under the License.
*/
/* global finishShapePath */
var ShapeCache = { };
var ShapeDefinition = (function () {
var def = {
__class__: 'flash.display.Shape',
@ -30,44 +26,9 @@ var ShapeDefinition = (function () {
var s = this.symbol;
if (s && s.paths) {
graphics._paths = s.paths;
// TODO: this really should be done only once, but I don't know how I
// can know when all the required data has been loaded.
for (var i = 0; i < s.paths.length; i++) {
s.paths[i] = finishShapePath(s.paths[i], s.dictionaryResolved);
}
graphics.bbox = s.bbox;
graphics.strokeBbox = s.strokeBbox;
if (this._stage && this._stage._quality === 'low' && !graphics._bitmap)
graphics._cacheAsBitmap(this._bbox);
this.ratio = s.ratio || 0;
var renderable = ShapeCache[s.symbolId];
var bounds = graphics._getBounds(true);
var rect = new Shumway.Geometry.Rectangle(bounds.xMin / 20,
bounds.yMin / 20,
(bounds.xMax - bounds.xMin) / 20,
(bounds.yMax - bounds.yMin) / 20);
if (!renderable) {
renderable = {
getBounds: function () {
return rect;
},
properties: { },
render: function (ctx) {
ctx.save();
ctx.translate(-rect.x, -rect.y);
graphics.draw(ctx, false, 0, new RenderingColorTransform());
ctx.restore();
}
};
ShapeCache[s.symbolId] = renderable;
}
this._layer = new Shumway.Layers.Shape(renderable);
this._layer.origin = new Shumway.Geometry.Point(rect.x, rect.y);
}
}
};

Просмотреть файл

@ -48,6 +48,8 @@ var StageDefinition = (function () {
this._stageVideos = [];
this._concatenatedTransform.invalid = false;
this._renderer = new Renderer();
},
_setup: function setup(ctx, options) {
@ -74,7 +76,15 @@ var StageDefinition = (function () {
displayObject._dispatchEvent('addedToStage');
if (displayObject._layer) {
if (displayObject.symbol) {
if (!displayObject._layer) {
var renderable = this._renderer.getRenderable(displayObject.symbol.symbolId);
var layer = new Shumway.Layers.Shape(renderable);
layer.origin = new Shumway.Geometry.Point(renderable.rect.x,
renderable.rect.y);
displayObject._layer = layer;
}
displayObject._parent._layer.addChild(displayObject._layer);
}
},
@ -148,6 +158,7 @@ var StageDefinition = (function () {
m.d,
m.tx / 20,
m.ty / 20);
node._layer.alpha = node._alpha;
}
}
}
@ -186,7 +197,7 @@ var StageDefinition = (function () {
clear: true,
imageSmoothing: true,
snap: false,
alpha: false,
alpha: true
};
webGLContext = new WebGLContext(canvas, sceneOptions);
@ -255,10 +266,12 @@ var StageDefinition = (function () {
that._processInvalidations();
timelineLeave("INVALIDATE");
if (sceneOptions.webGL) {
timelineEnter("WebGL");
webGLStageRenderer.render(stage, sceneOptions);
timelineLeave("WebGL");
if (!disableRendering.value) {
if (sceneOptions.webGL) {
timelineEnter("WebGL");
webGLStageRenderer.render(stage, sceneOptions);
timelineLeave("WebGL");
}
}
if (sceneOptions.canvas2D) {
timelineEnter("Canvas2D");
@ -267,18 +280,20 @@ var StageDefinition = (function () {
}
}
if (that._mouseMoved) {
that._mouseMoved = false;
if (!disableMouse.value) {
if (that._mouseMoved) {
that._mouseMoved = false;
if (that._mouseOver) {
timelineEnter("MOUSE");
that._handleMouse();
timelineLeave("MOUSE");
if (that._mouseOver) {
timelineEnter("MOUSE");
that._handleMouse();
timelineLeave("MOUSE");
canvas.style.cursor = that._cursor;
canvas.style.cursor = that._cursor;
}
} else {
that._handleMouseButtons();
}
} else {
that._handleMouseButtons();
}
timelineLeave("FRAME");

Просмотреть файл

@ -16,47 +16,11 @@
* limitations under the License.
*/
var StaticTextCache = { };
var StaticTextDefinition = (function () {
var def = {
__class__: 'flash.text.StaticText',
initialize: function () {
var s = this.symbol;
if (s) {
this.draw = s.draw;
var renderable = StaticTextCache[s.symbolId];
var bounds = this.getBounds(null);
var rect = new Shumway.Geometry.Rectangle(bounds.xMin / 20,
bounds.yMin / 20,
(bounds.xMax - bounds.xMin) / 20,
(bounds.yMax - bounds.yMin) / 20);
if (!renderable) {
renderable = {
source: this,
getBounds: function () {
return rect;
},
properties: { },
render: function (ctx) {
ctx.save();
ctx.translate(-rect.x, -rect.y);
this.source.draw(ctx, 0, new RenderingColorTransform());
ctx.restore();
}
};
StaticTextCache[s.symbolId] = renderable;
}
this._layer = new Shumway.Layers.Shape(renderable);
this._layer.origin = new Shumway.Geometry.Point(rect.x, rect.y);
}
},
initialize: function () { },
get text() {
return this._text;

Разница между файлами не показана из-за своего большого размера Загрузить разницу

621
src/swf/Renderer.js Normal file
Просмотреть файл

@ -0,0 +1,621 @@
/*
* Copyright 2013 Mozilla Foundation
*
* 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.
*/
var timeline;
var hudTimeline;
function timelineEnter(name) {
timeline && timeline.enter(name);
hudTimeline && hudTimeline.enter(name);
}
function timelineLeave(name) {
timeline && timeline.leave(name);
hudTimeline && hudTimeline.leave(name);
}
function timelineWrapBroadcastMessage(domain, message) {
timelineEnter(message);
domain.broadcastMessage(message);
timelineLeave(message);
}
function initializeHUD(stage, parentCanvas) {
var canvas = document.createElement('canvas');
var canvasContainer = document.createElement('div');
canvasContainer.appendChild(canvas);
canvasContainer.style.position = "absolute";
canvasContainer.style.top = "0px";
canvasContainer.style.left = "0px";
canvasContainer.style.width = "100%";
canvasContainer.style.height = "150px";
canvasContainer.style.backgroundColor = "rgba(0, 0, 0, 0.4)";
canvasContainer.style.pointerEvents = "none";
parentCanvas.parentElement.appendChild(canvasContainer);
hudTimeline = new Timeline(canvas);
hudTimeline.setFrameRate(stage._frameRate);
hudTimeline.refreshEvery(10);
}
var BlendModeNameMap = {
"normal": 'normal',
"multiply": 'multiply',
"screen": 'screen',
"lighten": 'lighten',
"darken": 'darken',
"difference": 'difference',
"overlay": 'overlay',
"hardlight": 'hard-light'
};
function getBlendModeName(blendMode) {
// TODO:
// These Flash blend modes have no canvas equivalent:
// - blendModeClass.SUBTRACT
// - blendModeClass.INVERT
// - blendModeClass.SHADER
// - blendModeClass.ADD
// These blend modes are actually Porter-Duff compositing operators.
// The backdrop is the nearest parent with blendMode set to LAYER.
// When there is no LAYER parent, they are ignored (treated as NORMAL).
// - blendModeClass.ALPHA (destination-in)
// - blendModeClass.ERASE (destination-out)
// - blendModeClass.LAYER [defines backdrop]
return BlendModeNameMap[blendMode] || 'normal';
}
var head = document.head;
head.insertBefore(document.createElement('style'), head.firstChild);
var style = document.styleSheets[0];
// Used for creating gradients and patterns
var factoryCtx = !inWorker ?
document.createElement('canvas').getContext('2d') :
null;
function Renderer() {
this._renderables = { };
}
Renderer.prototype.nextId = 0xffff;
Renderer.prototype.defineRenderable = function defineRenderable(id, type, symbol) {
var renderable;
switch (type) {
case 'shape':
renderable = new RenderableShape(symbol, this);
break;
case 'gradient':
renderable = new RenderableGradient(symbol, this);
break;
case 'pattern':
renderable = new RenderablePattern(symbol, this);
break;
case 'bitmap':
renderable = new RenderableBitmap(symbol, this);
break;
case 'font':
renderable = new RenderableFont(symbol, this);
break;
case 'text':
case 'label':
renderable = new RenderableText(symbol, this);
break;
}
this._renderables[id] = renderable;
};
Renderer.prototype.getRenderable = function getRenderable(id) {
return this._renderables[id];
};
Renderer.prototype.undefineRenderable = function undefineRenderable(id) {
var renderable = this._renderables[id];
delete this._renderables[id];
return renderable;
};
function RenderableShape(symbol, renderer) {
this.commands = symbol.commands;
this.data = symbol.data;
this.properties = { renderer: renderer };
var bbox = symbol.strokeBbox || symbol.bbox;
this.rect = new Shumway.Geometry.Rectangle(bbox.xMin / 20,
bbox.yMin / 20,
(bbox.xMax - bbox.xMin) / 20,
(bbox.yMax - bbox.yMin) / 20);
var paths = symbol.paths;
for (var i = 0; i < paths.length; i++) {
paths[i] = finishShapePath(paths[i], renderer);
}
this.paths = paths;
}
RenderableShape.prototype.getBounds = function getBounds() {
return this.rect;
};
RenderableShape.prototype.render = function render(ctx) {
ctx.save();
ctx.translate(-this.rect.x, -this.rect.y);
var paths = this.paths;
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
if (!path.fillStyle) {
continue;
}
ctx.beginPath();
var commands = path.commands;
var data = path.data;
var morphData = path.morphData;
var formOpen = false;
var formOpenX = 0;
var formOpenY = 0;
if (!path.isMorph) {
for (var j = 0, k = 0; j < commands.length; j++) {
switch (commands[j]) {
case SHAPE_MOVE_TO:
formOpen = true;
formOpenX = data[k++]/20;
formOpenY = data[k++]/20;
ctx.moveTo(formOpenX, formOpenY);
break;
case SHAPE_WIDE_MOVE_TO:
ctx.moveTo(data[k++]/20, data[k++]/20);
k += 2;
break;
case SHAPE_LINE_TO:
ctx.lineTo(data[k++]/20, data[k++]/20);
break;
case SHAPE_WIDE_LINE_TO:
ctx.lineTo(data[k++]/20, data[k++]/20);
k += 2;
break;
case SHAPE_CURVE_TO:
ctx.quadraticCurveTo(data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20);
break;
case SHAPE_CUBIC_CURVE_TO:
ctx.bezierCurveTo(data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20);
break;
case SHAPE_CIRCLE:
if (formOpen) {
ctx.lineTo(formOpenX, formOpenY);
formOpen = false;
}
ctx.moveTo((data[k] + data[k+2])/20, data[k+1]/20);
ctx.arc(data[k++]/20, data[k++]/20, data[k++]/20, 0, Math.PI * 2,
false);
break;
case SHAPE_ELLIPSE:
if (formOpen) {
ctx.lineTo(formOpenX, formOpenY);
formOpen = false;
}
var x = data[k++];
var y = data[k++];
var rX = data[k++];
var rY = data[k++];
var radius;
if (rX !== rY) {
ctx.save();
var ellipseScale;
if (rX > rY) {
ellipseScale = rX / rY;
radius = rY;
x /= ellipseScale;
ctx.scale(ellipseScale, 1);
} else {
ellipseScale = rY / rX;
radius = rX;
y /= ellipseScale;
ctx.scale(1, ellipseScale);
}
}
ctx.moveTo((x + radius)/20, y/20);
ctx.arc(x/20, y/20, radius/20, 0, Math.PI * 2, false);
if (rX !== rY) {
ctx.restore();
}
break;
default:
// Sometimes, the very last command isn't properly set. Ignore it.
if (commands[j] === 0 && j === commands.length -1) {
break;
}
console.warn("Unknown drawing command encountered: " +
commands[j]);
}
}
} else {
for (var j = 0, k = 0; j < commands.length; j++) {
switch (commands[j]) {
case SHAPE_MOVE_TO:
ctx.moveTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
case SHAPE_LINE_TO:
ctx.lineTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
case SHAPE_CURVE_TO:
ctx.quadraticCurveTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
default:
console.warn("Drawing command not supported for morph " +
"shapes: " + commands[j]);
}
}
}
// TODO: enable in-path line-style changes
if (formOpen) {
ctx.lineTo(formOpenX, formOpenY);
}
var fillStyle = path.fillStyle;
if (fillStyle) {
if (isNaN(fillStyle.style)) {
ctx.fillStyle = fillStyle.style;
} else {
ctx.fillStyle = this.properties.renderer.getRenderable(fillStyle.style).fillStyle;
}
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
fillStyle.smooth;
var m = fillStyle.transform;
ctx.save();
if (m) {
ctx.transform(m.a, m.b, m.c, m.d, m.e/20, m.f/20);
}
ctx.fill();
ctx.restore();
}
var lineStyle = path.lineStyle;
// TODO: All widths except for `undefined` and `NaN` draw something
if (lineStyle) {
ctx.strokeStyle = lineStyle.style;
ctx.save();
// Flash's lines are always at least 1px/20twips
ctx.lineWidth = Math.max(lineStyle.width/20, 1);
ctx.lineCap = lineStyle.lineCap;
ctx.lineJoin = lineStyle.lineJoin;
ctx.miterLimit = lineStyle.miterLimit;
ctx.stroke();
ctx.restore();
}
ctx.closePath();
}
ctx.restore();
};
function RenderableGradient(symbol, renderer) {
this.rect = new Shumway.Geometry.Rectangle(0, 0, 0, 0);
this.properties = { };
var gradient;
if (symbol.type === GRAPHICS_FILL_LINEAR_GRADIENT) {
gradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
} else {
gradient = factoryCtx.createRadialGradient((symbol.focalPoint | 0) / 20,
0, 0, 0, 0, 1);
}
var records = symbol.records;
for (var i = 0; i < records.length; i++) {
var record = records[i];
var colorStr = rgbaObjToStr(record.color);
gradient.addColorStop(record.ratio / 255, colorStr);
}
this.fillStyle = gradient;
}
RenderableGradient.prototype.getBounds = function getBounds() {
return this.rect;
};
RenderableGradient.prototype.render = function render(ctx) {
// TODO
};
function RenderablePattern(symbol, renderer) {
var bitmap = renderer.getRenderable(style.bitmapId);
if (!bitmap) {
this.fillStyle = 'green';
return;
}
var rect = bitmap.rect;
this.rect = new Shumway.Geometry.Rectangle(rect.x, rect.y, rect.w, rect.h);
var repeat = (symbol.type === GRAPHICS_FILL_REPEATING_BITMAP) ||
(symbol.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP);
this.fillStyle = factoryCtx.createPattern(bitmap.img,
repeat ? 'repeat' : 'no-repeat');
}
RenderablePattern.prototype.getBounds = function getBounds() {
return this.rect;
};
RenderablePattern.prototype.render = function render(ctx) {
// TODO
};
function RenderableBitmap(symbol, renderer) {
this.properties = { };
this.rect = new Shumway.Geometry.Rectangle(symbol.width / 20,
symbol.height / 20);
var img = new Image();
//var imgPromiseResolve;
//var imgPromise = new Promise(function (resolve) {
// imgPromiseResolve = resolve;
//});
img.onload = function () {
if (symbol.mask) {
// Write the image into new canvas and apply the mask.
var maskCanvas = document.createElement('canvas');
maskCanvas.width = symbol.width;
maskCanvas.height = symbol.height;
var maskContext = maskCanvas.getContext('2d');
maskContext.drawImage(img, 0, 0);
var maskImageData = maskContext.getImageData(0, 0, symbol.width, symbol.height);
var maskImageDataBytes = maskImageData.data;
var symbolMaskBytes = symbol.mask;
var length = maskImageData.width * maskImageData.height;
for (var i = 0, j = 3; i < length; i++, j += 4) {
maskImageDataBytes[j] = symbolMaskBytes[i];
}
maskContext.putImageData(maskImageData, 0, 0);
// Use the result canvas as renderable image
props.img = maskCanvas;
}
//imgPromiseResolve();
};
img.src = URL.createObjectURL(symbol.data);
//promiseQueue.push(imgPromise);
this.img = img;
}
RenderableBitmap.prototype.getBounds = function getBounds() {
return this.rect;
};
RenderableBitmap.prototype.render = function render(ctx) {
// if (!this._bitmapData) {
// return;
// }
// var scaledImage;
// ctx.save();
// if (this._pixelSnapping === 'auto' || this._pixelSnapping === 'always') {
// var transform = this._getConcatenatedTransform(null, true);
// var EPSILON = 0.001;
// var aInt = Math.abs(Math.round(transform.a));
// var dInt = Math.abs(Math.round(transform.d));
// var snapPixels;
// if (aInt >= 1 && aInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
// dInt >= 1 && dInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
// Math.abs(Math.abs(transform.a) / aInt - 1) <= EPSILON &&
// Math.abs(Math.abs(transform.d) / dInt - 1) <= EPSILON &&
// Math.abs(transform.b) <= EPSILON && Math.abs(transform.c) <= EPSILON) {
// if (aInt === 1 && dInt === 1) {
// snapPixels = true;
// } else {
// var sizeKey = aInt + 'x' + dInt;
// if (this._snapImageCache.size !== sizeKey) {
// this._snapImageCache.size = sizeKey;
// this._snapImageCache.hits = 0;
// this._snapImageCache.image = null;
// }
// if (++this._snapImageCache.hits === CACHE_SNAP_DRAW_AFTER) {
// this._cacheSnapImage(sizeKey, aInt, dInt);
// }
// scaledImage = this._snapImageCache.image;
// snapPixels = !!scaledImage;
// }
// } else {
// snapPixels = false;
// }
// if (snapPixels) {
// ctx.setTransform(transform.a < 0 ? -1 : 1, 0,
// 0, transform.d < 0 ? -1 : 1,
// (transform.tx/20)|0, (transform.ty/20)|0);
// }
// // TODO this._pixelSnapping === 'always'; does it even make sense in other cases?
// }
//
// colorTransform.setAlpha(ctx, true);
// ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
// this._smoothing;
// ctx.drawImage(scaledImage || this._bitmapData._getDrawable(), 0, 0);
// ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = false;
// ctx.restore();
// traceRenderer.value && frameWriter.writeLn("Bitmap.draw() snapping: " + this._pixelSnapping +
// ", dimensions: " + this._bitmapData._drawable.width + " x " + this._bitmapData._drawable.height);
ctx.drawImage(this.img, 0, 0);
};
function RenderableFont(symbol, renderer) {
var charset = fromCharCode.apply(null, symbol.codes);
if (charset) {
style.insertRule(
'@font-face{' +
'font-family:"' + symbol.uniqueName + '";' +
'src:url(data:font/opentype;base64,' + btoa(symbol.data) + ')' +
'}',
style.cssRules.length
);
// HACK non-Gecko browsers need time to load fonts
//if (!/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.test(window.navigator.userAgent)) {
// var testDiv = document.createElement('div');
// testDiv.setAttribute('style', 'position: absolute; top: 0; right: 0;' +
// 'visibility: hidden; z-index: -500;' +
// 'font-family:"' + symbol.uniqueName + '";');
// testDiv.textContent = 'font test';
// document.body.appendChild(testDiv);
// var fontPromise = new Promise(function (resolve) {
// setTimeout(function () {
// resolve();
// document.body.removeChild(testDiv);
// }, 200);
// });
// promiseQueue.push(fontPromise);
//}
}
}
RenderableFont.prototype.getBounds = function getBounds() {
// TODO
};
RenderableFont.prototype.render = function render(ctx) {
// TODO
};
function RenderableText(symbol, renderer) {
this.properties = { };
this.rect = new Shumway.Geometry.Rectangle(0, 0, 0, 0);
if (symbol.data) {
this.render = new Function('c', symbol.data);
}
}
RenderableText.prototype.getBounds = function getBounds() {
return this.rect;
};
RenderableText.prototype.render = function render(ctx) {
// this.ensureDimensions();
// var bounds = this._bbox;
// var width = bounds.xMax / 20;
// var height = bounds.yMax / 20;
// if (width <= 0 || height <= 0) {
// return;
// }
//
// ctx.save();
// ctx.beginPath();
// ctx.rect(0, 0, width + 1, height + 1);
// ctx.clip();
// if (this._background) {
// ctx.fillStyle = this._backgroundColorStr;
// ctx.fill();
// }
// if (this._border) {
// ctx.strokeStyle = this._borderColorStr;
// ctx.lineCap = "square";
// ctx.lineWidth = 1;
// ctx.strokeRect(0.5, 0.5, width|0, height|0);
// }
// ctx.closePath();
//
// if (this._lines.length === 0) {
// ctx.restore();
// return;
// }
//
// ctx.translate(2, 2);
// ctx.save();
// var runs = this._content.textruns;
// var offsetY = this._lines[this._scrollV - 1].y;
// for (var i = 0; i < runs.length; i++) {
// var run = runs[i];
// if (run.type === 'f') {
// ctx.restore();
// ctx.font = run.format.str;
// // TODO: only apply color and alpha if it actually changed
// ctx.fillStyle = run.format.color;
// ctx.save();
// } else {
// assert(run.type === 't', 'Invalid run type: ' + run.type);
// if (run.y < offsetY) {
// continue;
// }
// ctx.fillText(run.text, run.x - this._drawingOffsetH, run.y - offsetY);
// }
// }
// ctx.restore();
// ctx.restore();
//}
};
function initStyle(style, renderer) {
if (style.type === undefined) {
return;
}
if (style.type === GRAPHICS_FILL_SOLID) {
// Solid fill styles are fully processed in shape.js's processStyle
return;
}
var id = renderer.nextId++;
switch (style.type) {
case GRAPHICS_FILL_LINEAR_GRADIENT:
case GRAPHICS_FILL_RADIAL_GRADIENT:
case GRAPHICS_FILL_FOCAL_RADIAL_GRADIENT:
renderer.defineRenderable(id, 'gradient', style);
break;
case GRAPHICS_FILL_REPEATING_BITMAP:
case GRAPHICS_FILL_CLIPPED_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
renderer.defineRenderable(id, 'pattern', style);
break;
default:
fail('invalid fill style', 'shape');
}
style.style = id;
}
/**
* For shapes parsed in a worker thread, we have to finish their
* paths after receiving the data in the main thread.
*
* This entails creating proper instances for all the contained data types.
*/
function finishShapePath(path, renderer) {
assert(!inWorker);
if (path.fullyInitialized) {
return path;
}
if (!(path instanceof ShapePath)) {
var untypedPath = path;
path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
// See the comment in the ShapePath ctor for why we're recreating the
// typed arrays here.
path.commands = new Uint8Array(untypedPath.buffers[0]);
path.data = new Int32Array(untypedPath.buffers[1]);
if (untypedPath.isMorph) {
path.morphData = new Int32Array(untypedPath.buffers[2]);
}
path.buffers = null;
}
path.fillStyle && initStyle(path.fillStyle, renderer);
path.lineStyle && initStyle(path.lineStyle, renderer);
path.fullyInitialized = true;
return path;
}

Просмотреть файл

@ -15,43 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*global rgbaObjToStr, FirefoxCom, Timer, FrameCounter, metrics, coreOptions, OptionSet, Option, appendToFrameTerminal, frameWriter, randomStyle, Timeline*/
var rendererOptions = coreOptions.register(new OptionSet("Renderer Options"));
var traceRenderer = rendererOptions.register(new Option("tr", "traceRenderer", "number", 0, "trace renderer execution"));
var disableRenderVisitor = rendererOptions.register(new Option("drv", "disableRenderVisitor", "boolean", false, "disable render visitor"));
var disableMouseVisitor = rendererOptions.register(new Option("dmv", "disableMouseVisitor", "boolean", false, "disable mouse visitor"));
var showRedrawRegions = rendererOptions.register(new Option("rr", "showRedrawRegions", "boolean", false, "show redraw regions"));
var renderAsWireframe = rendererOptions.register(new Option("raw", "renderAsWireframe", "boolean", false, "render as wireframe"));
var showQuadTree = rendererOptions.register(new Option("qt", "showQuadTree", "boolean", false, "show quad tree"));
var turboMode = rendererOptions.register(new Option("", "turbo", "boolean", false, "turbo mode"));
var forceHidpi = rendererOptions.register(new Option("", "forceHidpi", "boolean", false, "force hidpi"));
var skipFrameDraw = rendererOptions.register(new Option("", "skipFrameDraw", "boolean", true, "skip frame when not on time"));
var hud = rendererOptions.register(new Option("", "hud", "boolean", false, "show hud mode"));
var enableConstructChildren = rendererOptions.register(new Option("", "constructChildren", "boolean", true, "Construct Children"));
var enableEnterFrame = rendererOptions.register(new Option("", "enterFrame", "boolean", true, "Enter Frame"));
var enableAdvanceFrame = rendererOptions.register(new Option("", "advanceFrame", "boolean", true, "Advance Frame"));
var stageOptions = coreOptions.register(new OptionSet("Stage Renderer Options"));
var perspectiveCamera = stageOptions.register(new Option("", "pc", "boolean", false, "Use perspective camera."));
var perspectiveCameraFOV = stageOptions.register(new Option("", "pcFOV", "number", 60, "Perspective Camera FOV."));
var perspectiveCameraDistance = stageOptions.register(new Option("", "pcDistance", "number", 1, "Perspective Camera Distance."));
var perspectiveCameraAngle = stageOptions.register(new Option("", "pcAngle", "number", 0, "Perspective Camera Angle."));
var perspectiveCameraAngleRotate = stageOptions.register(new Option("", "pcRotate", "boolean", false, "Rotate Use perspective camera."));
var perspectiveCameraSpacing = stageOptions.register(new Option("", "pcSpacing", "number", 0.1, "Element Spacing."));
var perspectiveCameraSpacingInflate = stageOptions.register(new Option("", "pcInflate", "boolean", false, "Rotate Use perspective camera."));
var drawTiles = stageOptions.register(new Option("", "drawTiles", "boolean", false, "Draw tiles."));
var drawTextures = stageOptions.register(new Option("", "drawTextures", "boolean", false, "Draw textures."));
if (typeof FirefoxCom !== 'undefined') {
turboMode.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.turboMode', def: false});
hud.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.hud', def: false});
forceHidpi.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.force_hidpi', def: false});
}
/*global rgbaObjToStr, Timer, FrameCounter, metrics, coreOptions, OptionSet, Option, appendToFrameTerminal, frameWriter, randomStyle, Timeline*/
var CanvasCache = {
cache: [],
@ -103,36 +67,6 @@ function visitContainer(container, visitor, context) {
visitor.childrenEnd(container);
}
var BlendModeNameMap = {
"normal": 'normal',
"multiply": 'multiply',
"screen": 'screen',
"lighten": 'lighten',
"darken": 'darken',
"difference": 'difference',
"overlay": 'overlay',
"hardlight": 'hard-light'
};
function getBlendModeName(blendMode) {
// TODO:
// These Flash blend modes have no canvas equivalent:
// - blendModeClass.SUBTRACT
// - blendModeClass.INVERT
// - blendModeClass.SHADER
// - blendModeClass.ADD
// These blend modes are actually Porter-Duff compositing operators.
// The backdrop is the nearest parent with blendMode set to LAYER.
// When there is no LAYER parent, they are ignored (treated as NORMAL).
// - blendModeClass.ALPHA (destination-in)
// - blendModeClass.ERASE (destination-out)
// - blendModeClass.LAYER [defines backdrop]
return BlendModeNameMap[blendMode] || 'normal';
}
function RenderVisitor(root, ctx, invalidPath, refreshStage) {
this.root = root;
this.ctx = ctx;
@ -520,14 +454,7 @@ function RenderingContext(refreshStage, invalidPath) {
function renderDisplayObject(child, ctx, context) {
var m = child._currentTransform;
if (m) {
if (m.a * m.d == m.b * m.c) {
// Workaround for bug 844184 -- the object is invisible
ctx.closePath();
ctx.rect(0, 0, 0, 0);
ctx.clip();
} else {
ctx.transform(m.a, m.b, m.c, m.d, m.tx/20, m.ty/20);
}
ctx.transform(m.a, m.b, m.c, m.d, m.tx/20, m.ty/20);
}
if (!renderAsWireframe.value) {
@ -630,40 +557,48 @@ function sampleEnd() {
}
}
var timeline;
var hudTimeline;
function createRenderDummyBalls(ctx, stage) {
var dummyBalls;
var radius = 10;
var speed = 1;
var m = stage._concatenatedTransform;
var scaleX = m.a, scaleY = m.d;
dummyBalls = [];
for (var i = 0; i < 10; i++) {
dummyBalls.push({
position: {
x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX),
y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY)
},
velocity: {x: speed * (Math.random() - 0.5), y: speed * (Math.random() - 0.5)}
});
}
ctx.fillStyle = "black";
ctx.lineWidth = 2;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
function timelineEnter(name) {
timeline && timeline.enter(name);
hudTimeline && hudTimeline.enter(name);
}
function timelineLeave(name) {
timeline && timeline.leave(name);
hudTimeline && hudTimeline.leave(name);
}
function timelineWrapBroadcastMessage(domain, message) {
timelineEnter(message);
domain.broadcastMessage(message);
timelineLeave(message);
}
function initializeHUD(stage, parentCanvas) {
var canvas = document.createElement('canvas');
var canvasContainer = document.createElement('div');
canvasContainer.appendChild(canvas);
canvasContainer.style.position = "absolute";
canvasContainer.style.top = "0px";
canvasContainer.style.left = "0px";
canvasContainer.style.width = "100%";
canvasContainer.style.height = "150px";
canvasContainer.style.backgroundColor = "rgba(0, 0, 0, 0.4)";
// canvasContainer.style.pointerEvents = canvas.style.pointerEvents = "none";
parentCanvas.parentElement.appendChild(canvasContainer);
hudTimeline = new Timeline(canvas);
hudTimeline.setFrameRate(stage._frameRate);
hudTimeline.refreshEvery(10);
return function renderDummyBalls() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle = "green";
dummyBalls.forEach(function (ball) {
var position = ball.position;
var velocity = ball.velocity;
ctx.beginPath();
ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true);
ctx.stroke();
var x = (position.x + velocity.x);
var y = (position.y + velocity.y);
if (x < radius || x > ctx.canvas.width / scaleX - radius) {
velocity.x *= -1;
}
if (y < radius || y > ctx.canvas.height / scaleY - radius) {
velocity.y *= -1;
}
position.x += velocity.x;
position.y += velocity.y;
});
};
}
function renderStage(stage, ctx, events) {
@ -732,9 +667,8 @@ function renderStage(stage, ctx, events) {
updateRenderTransform();
var frameTime = 0;
var maxDelay = 1000 / stage._frameRate;
var nextRenderAt = performance.now();
var frameScheduler = new FrameScheduler();
stage._frameScheduler = frameScheduler;
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
@ -743,52 +677,7 @@ function renderStage(stage, ctx, events) {
window.msRequestAnimationFrame ||
window.setTimeout;
var renderDummyBalls;
var dummyBalls;
if (typeof FirefoxCom !== 'undefined' &&
FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.dummyMode', def: false})) {
var radius = 10;
var speed = 1;
var m = stage._concatenatedTransform;
var scaleX = m.a, scaleY = m.d;
dummyBalls = [];
for (var i = 0; i < 10; i++) {
dummyBalls.push({
position: {
x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX),
y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY)
},
velocity: {x: speed * (Math.random() - 0.5), y: speed * (Math.random() - 0.5)}
});
}
ctx.fillStyle = "black";
ctx.lineWidth = 2;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
renderDummyBalls = function () {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle = "green";
dummyBalls.forEach(function (ball) {
var position = ball.position;
var velocity = ball.velocity;
ctx.beginPath();
ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true);
ctx.stroke();
var x = (position.x + velocity.x);
var y = (position.y + velocity.y);
if (x < radius || x > ctx.canvas.width / scaleX - radius) {
velocity.x *= -1;
}
if (y < radius || y > ctx.canvas.height / scaleY - radius) {
velocity.y *= -1;
}
position.x += velocity.x;
position.y += velocity.y;
});
};
}
var renderDummyBalls = dummyAnimation.value && createRenderDummyBalls(ctx, stage);
console.timeEnd("Initialize Renderer");
console.timeEnd("Total");
@ -797,11 +686,9 @@ function renderStage(stage, ctx, events) {
var frameCount = 0;
var frameFPSAverage = new metrics.Average(120);
function drawFrame(renderFrame, frameRequested) {
if (!skipFrameDraw.value) {
frameRequested = true; // e.g. for testing we need to draw all frames
}
var frameRequested = true;
function drawFrame(renderFrame, repaint) {
sampleStart();
var refreshStage = false;
@ -849,9 +736,20 @@ function renderStage(stage, ctx, events) {
domain.broadcastMessage("render", "render");
}
if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) &&
frameRequested) {
var drawEnabled = isCanvasVisible(ctx.canvas) &&
(refreshStage || renderFrame) &&
(frameRequested || repaint || !skipFrameDraw.value);
// checking if we need to skip painting, however not doing it in repaint
// mode or during testing
if (drawEnabled && !repaint && skipFrameDraw.value &&
frameScheduler.shallSkipDraw) {
drawEnabled = false;
frameScheduler.skipDraw();
traceRenderer.value && appendToFrameTerminal("Skip Frame Draw", "red");
}
if (drawEnabled) {
frameScheduler.startDraw();
var invalidPath = null;
traceRenderer.value && frameWriter.enter("> Invalidation");
@ -878,6 +776,7 @@ function renderStage(stage, ctx, events) {
invalidPath.draw(ctx);
ctx.stroke();
}
frameScheduler.endDraw();
}
if (mouseMoved && !disableMouseVisitor.value) {
@ -910,10 +809,7 @@ function renderStage(stage, ctx, events) {
sampleEnd();
}
var frameRequested = true;
var skipNextFrameDraw = false;
(function draw() {
var now = performance.now();
var renderFrame = true;
if (events.onBeforeFrame) {
var e = { cancel: false };
@ -921,35 +817,25 @@ function renderStage(stage, ctx, events) {
renderFrame = !e.cancel;
}
frameTime = now;
if (renderFrame && renderDummyBalls) {
renderDummyBalls();
if (renderDummyBalls) {
if (renderFrame) {
renderDummyBalls();
events.onAfterFrame && events.onAfterFrame();
}
setTimeout(draw);
return;
}
drawFrame(renderFrame, frameRequested && !skipNextFrameDraw);
frameScheduler.startFrame(stage._frameRate);
drawFrame(renderFrame, false);
frameScheduler.endFrame();
frameRequested = false;
maxDelay = 1000 / stage._frameRate;
if (!turboMode.value) {
nextRenderAt += maxDelay;
var wasLate = false;
while (nextRenderAt < now) {
wasLate = true;
nextRenderAt += maxDelay;
}
if (wasLate && !skipNextFrameDraw) {
// skips painting of the very next frame if we are not keeping up
skipNextFrameDraw = true;
traceRenderer.value && appendToFrameTerminal("Skip Frame Draw", "red");
} else {
// .. but giving it a chance to draw sometime
skipNextFrameDraw = false;
}
} else {
nextRenderAt = now;
if (!frameScheduler.isOnTime) {
traceRenderer.value && appendToFrameTerminal("Frame Is Late", "red");
}
if (renderFrame && events.onAfterFrame) {
events.onAfterFrame();
}
@ -961,7 +847,7 @@ function renderStage(stage, ctx, events) {
return;
}
setTimeout(draw, Math.max(0, nextRenderAt - performance.now()));
setTimeout(draw, turboMode.value ? 0 : frameScheduler.nextFrameIn);
})();
(function frame() {
@ -969,11 +855,107 @@ function renderStage(stage, ctx, events) {
return;
}
if (stage._invalid || stage._mouseMoved) {
frameRequested = true;
if ((stage._invalid || stage._mouseMoved) && !renderDummyBalls) {
drawFrame(false, true);
}
frameRequested = true;
requestAnimationFrame(frame);
})();
}
var FrameScheduler = (function () {
var STATS_TO_REMEMBER = 50;
var MAX_DRAWS_TO_SKIP = 2;
var INTERVAL_PADDING_MS = 4;
var SPEED_ADJUST_RATE = 0.9;
function FrameScheduler() {
this._drawStats = [];
this._drawStatsSum = 0;
this._drawStarted = 0;
this._drawsSkipped = 0;
this._expectedNextFrameAt = performance.now();
this._onTime = true;
this._trackDelta = false;
this._delta = 0;
this._onTimeDelta = 0;
}
FrameScheduler.prototype = {
get shallSkipDraw() {
if (this._drawsSkipped >= MAX_DRAWS_TO_SKIP) {
return false;
}
var averageDraw = this._drawStats.length < STATS_TO_REMEMBER ? 0 :
this._drawStatsSum / this._drawStats.length;
var estimatedDrawEnd = performance.now() + averageDraw;
return estimatedDrawEnd + INTERVAL_PADDING_MS > this._expectedNextFrameAt;
},
get nextFrameIn() {
return Math.max(0, this._expectedNextFrameAt - performance.now());
},
get isOnTime() {
return this._onTime;
},
startFrame: function (frameRate) {
var interval = 1000 / frameRate;
var adjustedInterval = interval;
var delta = this._onTimeDelta + this._delta;
if (delta !== 0) {
if (delta < 0) {
adjustedInterval *= SPEED_ADJUST_RATE;
} else if (delta > 0) {
adjustedInterval /= SPEED_ADJUST_RATE;
}
this._onTimeDelta += (interval - adjustedInterval);
}
this._expectedNextFrameAt += adjustedInterval;
this._onTime = true;
},
endFrame: function () {
var estimatedNextFrameStart = performance.now() + INTERVAL_PADDING_MS;
if (estimatedNextFrameStart > this._expectedNextFrameAt) {
if (this._trackDelta) {
this._onTimeDelta += (this._expectedNextFrameAt - estimatedNextFrameStart);
console.log(this._onTimeDelta);
}
this._expectedNextFrameAt = estimatedNextFrameStart;
this._onTime = false;
}
},
startDraw: function () {
this._drawsSkipped = 0;
this._drawStarted = performance.now();
},
endDraw: function () {
var drawTime = performance.now() - this._drawStarted;
this._drawStats.push(drawTime);
this._drawStatsSum += drawTime;
while (this._drawStats.length > STATS_TO_REMEMBER) {
this._drawStatsSum -= this._drawStats.shift();
}
},
skipDraw: function () {
this._drawsSkipped++;
},
setDelta: function (value) {
if (!this._trackDelta) {
return;
}
this._delta = value;
},
startTrackDelta: function () {
this._trackDelta = true;
},
endTrackDelta: function () {
if (!this._trackDelta) {
return;
}
this._trackDelta = false;
this._delta = 0;
this._onTimeDelta = 0;
}
};
return FrameScheduler;
})();

43
src/swf/options.js Normal file
Просмотреть файл

@ -0,0 +1,43 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/*
* Copyright 2013 Mozilla Foundation
*
* 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.
*/
var rendererOptions = coreOptions.register(new OptionSet("Renderer Options"));
var traceRenderer = rendererOptions.register(new Option("tr", "traceRenderer", "number", 0, "trace renderer execution"));
var disableRendering = rendererOptions.register(new Option("drv", "disableRendering", "boolean", false, "disable rendering"));
var disableMouse = rendererOptions.register(new Option("dmv", "disableMouse", "boolean", false, "disable mouse handling"));
//var showRedrawRegions = rendererOptions.register(new Option("rr", "showRedrawRegions", "boolean", false, "show redraw regions"));
//var renderAsWireframe = rendererOptions.register(new Option("raw", "renderAsWireframe", "boolean", false, "render as wireframe"));
var turboMode = rendererOptions.register(new Option("", "turbo", "boolean", false, "turbo mode"));
var forceHidpi = rendererOptions.register(new Option("", "forceHidpi", "boolean", false, "force hidpi"));
var skipFrameDraw = rendererOptions.register(new Option("", "skipFrameDraw", "boolean", true, "skip frame when not on time"));
var hud = rendererOptions.register(new Option("", "hud", "boolean", false, "show hud mode"));
var dummyAnimation = rendererOptions.register(new Option("", "dummy", "boolean", false, "show test balls animation"));
var enableConstructChildren = rendererOptions.register(new Option("", "constructChildren", "boolean", true, "Construct Children"));
var enableEnterFrame = rendererOptions.register(new Option("", "enterFrame", "boolean", true, "Enter Frame"));
var enableAdvanceFrame = rendererOptions.register(new Option("", "advanceFrame", "boolean", true, "Advance Frame"));
var stageOptions = coreOptions.register(new OptionSet("Stage Renderer Options"));
var perspectiveCamera = stageOptions.register(new Option("", "pc", "boolean", false, "Use perspective camera."));
var perspectiveCameraFOV = stageOptions.register(new Option("", "pcFOV", "number", 60, "Perspective Camera FOV."));
var perspectiveCameraDistance = stageOptions.register(new Option("", "pcDistance", "number", 1, "Perspective Camera Distance."));
var perspectiveCameraAngle = stageOptions.register(new Option("", "pcAngle", "number", 0, "Perspective Camera Angle."));
var perspectiveCameraAngleRotate = stageOptions.register(new Option("", "pcRotate", "boolean", false, "Rotate Use perspective camera."));
var perspectiveCameraSpacing = stageOptions.register(new Option("", "pcSpacing", "number", 0.1, "Element Spacing."));
var perspectiveCameraSpacingInflate = stageOptions.register(new Option("", "pcInflate", "boolean", false, "Rotate Use perspective camera."));

Просмотреть файл

@ -697,156 +697,6 @@ ShapePath.prototype = {
this.commands.push(SHAPE_ELLIPSE);
this.data.push(x, y, radiusX, radiusY);
},
draw: function(ctx, clip, ratio, colorTransform) {
if (clip && !this.fillStyle) {
return;
}
ctx.beginPath();
var commands = this.commands;
var data = this.data;
var morphData = this.morphData;
var formOpen = false;
var formOpenX = 0;
var formOpenY = 0;
if (!this.isMorph) {
for (var j = 0, k = 0; j < commands.length; j++) {
switch (commands[j]) {
case SHAPE_MOVE_TO:
formOpen = true;
formOpenX = data[k++]/20;
formOpenY = data[k++]/20;
ctx.moveTo(formOpenX, formOpenY);
break;
case SHAPE_WIDE_MOVE_TO:
ctx.moveTo(data[k++]/20, data[k++]/20);
k += 2;
break;
case SHAPE_LINE_TO:
ctx.lineTo(data[k++]/20, data[k++]/20);
break;
case SHAPE_WIDE_LINE_TO:
ctx.lineTo(data[k++]/20, data[k++]/20);
k += 2;
break;
case SHAPE_CURVE_TO:
ctx.quadraticCurveTo(data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20);
break;
case SHAPE_CUBIC_CURVE_TO:
ctx.bezierCurveTo(data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20,
data[k++]/20, data[k++]/20);
break;
case SHAPE_CIRCLE:
if (formOpen) {
ctx.lineTo(formOpenX, formOpenY);
formOpen = false;
}
ctx.moveTo((data[k] + data[k+2])/20, data[k+1]/20);
ctx.arc(data[k++]/20, data[k++]/20, data[k++]/20, 0, Math.PI * 2,
false);
break;
case SHAPE_ELLIPSE:
if (formOpen) {
ctx.lineTo(formOpenX, formOpenY);
formOpen = false;
}
var x = data[k++];
var y = data[k++];
var rX = data[k++];
var rY = data[k++];
var radius;
if (rX !== rY) {
ctx.save();
var ellipseScale;
if (rX > rY) {
ellipseScale = rX / rY;
radius = rY;
x /= ellipseScale;
ctx.scale(ellipseScale, 1);
} else {
ellipseScale = rY / rX;
radius = rX;
y /= ellipseScale;
ctx.scale(1, ellipseScale);
}
}
ctx.moveTo((x + radius)/20, y/20);
ctx.arc(x/20, y/20, radius/20, 0, Math.PI * 2, false);
if (rX !== rY) {
ctx.restore();
}
break;
default:
// Sometimes, the very last command isn't properly set. Ignore it.
if (commands[j] === 0 && j === commands.length -1) {
break;
}
console.warn("Unknown drawing command encountered: " +
commands[j]);
}
}
} else {
for (var j = 0, k = 0; j < commands.length; j++) {
switch (commands[j]) {
case SHAPE_MOVE_TO:
ctx.moveTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
case SHAPE_LINE_TO:
ctx.lineTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
case SHAPE_CURVE_TO:
ctx.quadraticCurveTo(morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio),
morph(data[k]/20, morphData[k++]/20, ratio));
break;
default:
console.warn("Drawing command not supported for morph " +
"shapes: " + commands[j]);
}
}
}
// TODO: enable in-path line-style changes
// if (formOpen) {
// ctx.lineTo(formOpenX, formOpenY);
// }
if (!clip) {
var fillStyle = this.fillStyle;
if (fillStyle) {
colorTransform.setFillStyle(ctx, fillStyle.style);
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
fillStyle.smooth;
var m = fillStyle.transform;
ctx.save();
colorTransform.setAlpha(ctx);
if (m) {
ctx.transform(m.a, m.b, m.c, m.d, m.e/20, m.f/20);
}
ctx.fill();
ctx.restore();
}
var lineStyle = this.lineStyle;
// TODO: All widths except for `undefined` and `NaN` draw something
if (lineStyle) {
colorTransform.setStrokeStyle(ctx, lineStyle.style);
ctx.save();
colorTransform.setAlpha(ctx);
// Flash's lines are always at least 1px/20twips
ctx.lineWidth = Math.max(lineStyle.width/20, 1);
ctx.lineCap = lineStyle.lineCap;
ctx.lineJoin = lineStyle.lineJoin;
ctx.miterLimit = lineStyle.miterLimit;
ctx.stroke();
ctx.restore();
}
} else {
ctx.fill();
}
ctx.closePath();
},
isPointInPath: function(x, y) {
if (!(this.fillStyle || this.lineStyle)) {
return false;
@ -1667,156 +1517,4 @@ function morph(start, end, ratio) {
return start + (end - start) * ratio;
}
/**
* For shapes parsed in a worker thread, we have to finish their
* paths after receiving the data in the main thread.
*
* This entails creating proper instances for all the contained data types.
*/
function finishShapePath(path, dictionaryResolved) {
assert(!inWorker);
if (path.fullyInitialized) {
return path;
}
if (!(path instanceof ShapePath)) {
var untypedPath = path;
path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
// See the comment in the ShapePath ctor for why we're recreating the
// typed arrays here.
path.commands = new Uint8Array(untypedPath.buffers[0]);
path.data = new Int32Array(untypedPath.buffers[1]);
if (untypedPath.isMorph) {
path.morphData = new Int32Array(untypedPath.buffers[2]);
}
path.buffers = null;
}
path.fillStyle && initStyle(path.fillStyle, dictionaryResolved);
path.lineStyle && initStyle(path.lineStyle, dictionaryResolved);
path.fullyInitialized = true;
return path;
}
var inWorker = (typeof window) === 'undefined';
// Used for creating gradients and patterns
var factoryCtx = !inWorker ?
document.createElement('canvas').getContext('2d') :
null;
/**
* @param {Array} colorStops
* @returns {Function}
*/
function buildLinearGradientFactory(colorStops) {
var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
for (var i = 0; i < colorStops.length; i++) {
defaultGradient.addColorStop(colorStops[i].ratio, colorStops[i].color);
}
var fn = function createLinearGradient(ctx, colorTransform) {
var gradient = ctx.createLinearGradient(-1, 0, 1, 0);
for (var i = 0; i < colorStops.length; i++) {
colorTransform.addGradientColorStop(gradient, colorStops[i].ratio, colorStops[i].color);
}
return gradient;
};
fn.defaultFillStyle = defaultGradient;
return fn;
}
/**
* @param {number} focalPoint
* @param {Array} colorStops
* @returns {Function}
*/
function buildRadialGradientFactory(focalPoint, colorStops) {
var defaultGradient = factoryCtx.createRadialGradient(focalPoint, 0, 0, 0, 0, 1);
for (var i = 0; i < colorStops.length; i++) {
defaultGradient.addColorStop(colorStops[i].ratio, colorStops[i].color);
}
var fn = function createRadialGradient(ctx, colorTransform) {
var gradient = ctx.createRadialGradient(focalPoint, 0, 0, 0, 0, 1);
for (var i = 0; i < colorStops.length; i++) {
colorTransform.addGradientColorStop(gradient, colorStops[i].ratio, colorStops[i].color);
}
return gradient;
};
fn.defaultFillStyle = defaultGradient;
return fn;
}
/**
* @param {Object} img
* @param {String} repeat
*/
function buildBitmapPatternFactory(img, repeat) {
var defaultPattern = factoryCtx.createPattern(img, repeat);
var cachedTransform, cachedTransformKey;
var fn = function createBitmapPattern(ctx, colorTransform) {
if (!colorTransform.mode) {
return defaultPattern;
}
var key = colorTransform.getTransformFingerprint();
if (key === cachedTransformKey) {
return cachedTransform;
}
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
colorTransform.setAlpha(ctx, true);
ctx.drawImage(img, 0, 0);
cachedTransform = ctx.createPattern(canvas, repeat);
cachedTransformKey = key;
return cachedTransform;
};
fn.defaultFillStyle = defaultPattern;
return fn;
}
function initStyle(style, dictionaryResolved) {
if (style.type === undefined) {
return;
}
switch (style.type) {
case GRAPHICS_FILL_SOLID:
// Solid fill styles are fully processed in shape.js's processStyle
break;
case GRAPHICS_FILL_LINEAR_GRADIENT:
case GRAPHICS_FILL_RADIAL_GRADIENT:
case GRAPHICS_FILL_FOCAL_RADIAL_GRADIENT:
var records = style.records, colorStops = [];
for (var j = 0, n = records.length; j < n; j++) {
var record = records[j];
var colorStr = rgbaObjToStr(record.color);
colorStops.push({ratio: record.ratio / 255, color: colorStr});
}
var gradientConstructor;
var isLinear = style.type === GRAPHICS_FILL_LINEAR_GRADIENT;
if (isLinear) {
gradientConstructor = buildLinearGradientFactory(colorStops);
} else {
gradientConstructor = buildRadialGradientFactory((style.focalPoint|0)/20, colorStops);
}
style.style = gradientConstructor;
break;
case GRAPHICS_FILL_REPEATING_BITMAP:
case GRAPHICS_FILL_CLIPPED_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
var bitmap = dictionaryResolved[style.bitmapId];
var repeat = (style.type === GRAPHICS_FILL_REPEATING_BITMAP) ||
(style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP);
style.style = buildBitmapPatternFactory(bitmap.props.img,
repeat ? "repeat" : "no-repeat");
break;
default:
fail('invalid fill style', 'shape');
}
}