Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Changed async encoder to be frame based instead of timer based; this …
…fixes

a bug where serveral small queued images could get encoded all on the same
frame.
Also fixed targetFPS not being observed on queued encoders (properly this time).
  • Loading branch information
cameron314 committed Dec 17, 2013
1 parent 19c185a commit 8dfaf11
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 21 deletions.
39 changes: 19 additions & 20 deletions PNGEncoder2.hx
Expand Up @@ -42,7 +42,6 @@ import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.ProgressEvent;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.Lib;
import flash.Memory;
Expand Down Expand Up @@ -156,6 +155,7 @@ class PNGEncoder2 extends EventDispatcher
private static inline var CHUNK_START = DEFLATE_SCRATCH + DeflateStream.SCRATCH_MEMORY_SIZE;

private static inline var FRAME_AVG_SMOOTH_COUNT = 4; // Number of frames to calculate averages from. Must be power of 2
private static inline var FIRST_UPDATE_PIXELS = 20 * 1024; // Encode this many pixels right away on the first frame
private static inline var MIN_PIXELS_PER_UPDATE = 20 * 1024; // Always compress at least this many pixels per chunk
private static var data : ByteArray; // The select()ed working memory
private static var sprite : Sprite; // Used purely to listen to ENTER_FRAME events
Expand Down Expand Up @@ -187,7 +187,6 @@ class PNGEncoder2 extends EventDispatcher
private var lastFrameStart : Int; // Lib.getTimer() value. Used to calculate millisecond delta between two frames
private var step : Int; // Number of scanlines to process during the next update (in order to approximate targetFPS, but without wasting cycles)
private var done : Bool; // Whether there's any more scanlines to process or not
private var timer : Timer; // Used to trigger an update as often as possible

private var frameCount : Int; // Total number of frames that have elapsed so far during the encoding

Expand Down Expand Up @@ -562,6 +561,7 @@ class PNGEncoder2 extends EventDispatcher

public inline function new(image : BitmapData, dispatcher : IEventDispatcher)
{
targetFPS = 20; // Default, can be overridden
_new(image, dispatcher); // Constructors are slow -- delegate to function
}

Expand All @@ -570,11 +570,13 @@ class PNGEncoder2 extends EventDispatcher
fastNew(image, dispatcher);
}

//private static var __frame : Int;
//private static function staticEnterFrame(e : Event) { ++__frame; }

private inline function fastNew(image : BitmapData, dispatcher : IEventDispatcher)
{
img = image;
this.dispatcher = dispatcher;
targetFPS = 20; // Default, can be overridden

if (encoding) {
// Add to queue for later!
Expand Down Expand Up @@ -602,11 +604,13 @@ class PNGEncoder2 extends EventDispatcher
// duration of the encoding (even if the static level member changes)
deflateStream = DeflateStream.createEx(level, DEFLATE_SCRATCH, CHUNK_START, true);

// Get notified of new frames, and timer events
//if (!sprite.hasEventListener(Event.ENTER_FRAME)) {
// sprite.addEventListener(Event.ENTER_FRAME, staticEnterFrame);
//}
//trace("Async encoding began on frame " + __frame);

// Get notified of new frames
sprite.addEventListener(Event.ENTER_FRAME, onEnterFrame);
timer = new Timer(1); // Really means "update as often as possible"
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();

// We write data in chunks (one per update), starting with one
// chunk right now in order to gather some statistics up front
Expand All @@ -621,7 +625,7 @@ class PNGEncoder2 extends EventDispatcher
var startTime = Lib.getTimer();

// Write first ~20K pixels to see how fast it is
var height = Math.ceil(Math.min(20 * 1024 / img.width, img.height));
var height = Math.ceil(Math.min(FIRST_UPDATE_PIXELS / img.width, img.height));
writeIDATChunk(img, 0, height, deflateStream, png);

var endTime = Lib.getTimer();
Expand Down Expand Up @@ -653,7 +657,7 @@ class PNGEncoder2 extends EventDispatcher
private inline function updateMsPerLine(ms : Int, lines : Int)
{
if (lines != 0) {
if (ms == 0) {
if (ms <= 0) {
// Can occasionally happen because timer resolution on Windows is limited to 10ms
ms = 5; // Guess!
}
Expand Down Expand Up @@ -760,10 +764,6 @@ class PNGEncoder2 extends EventDispatcher
private function onEnterFrame(e : Event)
{
updateFrameInfo();
}

private function onTimer(e : TimerEvent)
{
update();
}

Expand All @@ -785,10 +785,10 @@ class PNGEncoder2 extends EventDispatcher

private inline function update()
{
// Need to check if we've finished or not since it's possible
// for the timer event to be dispatched before we stop it, but get
// processed after we've finished (e.g. if there's two timer events
// in the event queue and the first one finishes the work, the second
// Need to check if we've finished or not since it's possible (if we're
// attached to a timer) for a timer event to be dispatched before we stop
// it, but get processed after we've finished (e.g. if there's two timer
// events in the event queue and the first one finishes the work, the second
// will still cause this function to be entered since it was generated
// before we stopped the timer).
if (!done) {
Expand Down Expand Up @@ -845,9 +845,10 @@ class PNGEncoder2 extends EventDispatcher
}

if (done) {
//trace("Async encoding finished on frame " + __frame + " (targetFPS was " + targetFPS + ")");

// Clear some references to give the garbage collector an easier time
dispatcher = null; // This removes a circular reference, which might save a mark-and-sweep step
timer = null;
img = null;
deflateStream = null;
msPerFrame = null;
Expand All @@ -873,8 +874,6 @@ class PNGEncoder2 extends EventDispatcher
done = true;

sprite.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
timer.removeEventListener(TimerEvent.TIMER, onTimer);
timer.stop();

endEncoding(png);

Expand Down
22 changes: 21 additions & 1 deletion Test.hx
Expand Up @@ -15,8 +15,10 @@ import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flash.Vector;
import PNGEncoder2;
import DeflateStream;
//import com.remixtechnology.SWFProfiler;

class Test extends Sprite
{
Expand All @@ -26,6 +28,7 @@ class Test extends Sprite
private var saveFileRef : FileReference;
private var loader : Loader;
private var encoder : PNGEncoder2;
private var encoders : Vector<PNGEncoder2>;

public static function main()
{
Expand All @@ -36,6 +39,9 @@ class Test extends Sprite
{
super();

//testManyQueuedSmallImages();
//return;

//SWFProfiler.init();
//SWFProfiler.show();

Expand Down Expand Up @@ -142,7 +148,7 @@ class Test extends Sprite
trace("Async complete (" + (Lib.getTimer() - startTime) + "ms; " + Std.int(percent) + "%)");

/*var loader = new Loader();
loader.loadBytes(that.encoder.png);
loader.loadBytes(e.target.png);
that.addChild(loader);
loader.x = 250;
loader.y = 250;*/
Expand Down Expand Up @@ -197,6 +203,20 @@ class Test extends Sprite
}


private function testManyQueuedSmallImages()
{
var bmp = new BitmapData(80, 80, true, 0x00FFFFFF);
bmp.perlinNoise(80, 80, 2, 0xDEADBEEF, false, false);

encoders = new Vector<PNGEncoder2>();
for (i in 0 ... 14) {
var encoder = PNGEncoder2.encodeAsync(bmp);
encoder.targetFPS = 13;
encoders.push(encoder);
}
}


private static function testPNGEncoder(bmp : BitmapData, runs : Int) : Int
{
return time(makeCallback(PNGEncoder.encode, bmp), runs);
Expand Down

0 comments on commit 8dfaf11

Please sign in to comment.