Get async image loading building with -O2 and asm.js

This commit is contained in:
James Gregory 2013-06-17 17:10:41 -07:00
parent 872be8e964
commit 5095b03f9c
4 changed files with 261 additions and 250 deletions

View File

@ -139,9 +139,6 @@ CCEGLView::CCEGLView()
glutMouseFunc(&mouseCB); glutMouseFunc(&mouseCB);
glutMotionFunc(&motionCB); glutMotionFunc(&motionCB);
glutPassiveMotionFunc(&motionCB); glutPassiveMotionFunc(&motionCB);
// Setup a JS namespace for all cocos2dx helper functions.
emscripten_run_script("Module.cocos2dx = { classes: {}, objects: {} };");
} }
CCEGLView::~CCEGLView() CCEGLView::~CCEGLView()

View File

@ -59,11 +59,6 @@ void CCTextureCacheEmscripten_addImageAsyncCallBack(CCTextureCacheEmscripten *tc
*/ */
CCTextureCacheEmscripten::CCTextureCacheEmscripten() CCTextureCacheEmscripten::CCTextureCacheEmscripten()
{ {
const char *js =
# include "CCTextureCacheEmscripten.js"
;
emscripten_run_script(js);
// Add dummy references to these functions so that the compiler will emit // Add dummy references to these functions so that the compiler will emit
// code for them prior to this point (which is before when we will call // code for them prior to this point (which is before when we will call
// them. // them.
@ -84,7 +79,6 @@ CCTextureCacheEmscripten::CCTextureCacheEmscripten()
CCTextureCacheEmscripten::~CCTextureCacheEmscripten() CCTextureCacheEmscripten::~CCTextureCacheEmscripten()
{ {
cocos2dx_shutdownAsyncImageLoader(); cocos2dx_shutdownAsyncImageLoader();
emscripten_run_script("Module.cocos2dx.AsyncImageLoader = null;");
} }
/** /**

View File

@ -22,261 +22,268 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
****************************************************************************/ ****************************************************************************/
MULTILINE(
// Class declarations.
// AsyncOperationQueue -- simple worker queue. Note that all functions // Non-namespaced facade over underlying objects. See above methods for
// passed in should effectively be "static", and have all requisite // documentation. These methods serve two purposes:
// information contained in their args object. //
Module.cocos2dx.classes.AsyncOperationQueue = function() // 1. convert types to/from JS equivalents.
{ // 2. expose a "C-like" interface, such that the symbols are easy to find
this.ops = []; // for calling code (Emscripten provides no easy way to access nested
this.timeoutId = null; // dictionaries).
this.sliceBudget = 8; //
this.sliceInterval = 2 * this.sliceBudget; // Note that the leading '_' is because Emscripten prefixes all function
}; // calls like this. Since this code is being injected at runtime,
// Emscripten's compiler doesn't have a chance to add it for us.
/**
* If there is a valid scheduled queue consumer, cancel it. Won't cancel
* currently executing operations (as this is not multi-threaded), but will
* prevent future execution from happening until it is re-scheduled.
*/
Module.cocos2dx.classes.AsyncOperationQueue.prototype.unschedule = function(interval)
{
if(typeof this.timeoutId == "number")
{
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
};
/**
* Schedule the queue-processor to run @interval ms in the future.
*/
Module.cocos2dx.classes.AsyncOperationQueue.prototype.schedule = function(interval)
{
this.unschedule();
var that = this;
var o = function() {
that.run();
};
this.timeoutId = setTimeout(o, interval);
};
/**
* Enqueue a funtcion @fn with arguments @args to run asynchronously at
* some point in the future, then return control to the caller. Operations
* are guaranteed to be run in the order in which they are enqueued, but no
* guarantee is given about when or in what context they will be executed.
*/
Module.cocos2dx.classes.AsyncOperationQueue.prototype.enqueue = function(fn, args)
{
var op = {
fn: fn,
args: args
};
this.ops.push(op);
this.schedule(this.sliceInterval);
};
/**
* Start running the consumer "thread". Will consume operations from the
* queue until such time as either the queue is empty, or it has exceeded
* its time budget. If the latter case (budget exceeded), it will
* reschedule itself to run again later and finish draining the queue.
*
* Rescheduling logic still in flux, but the objective is to try to
* schedule it to run at some point after the next frame has been
* requested, whilst still maintaining maximum bandwidth.
*/
Module.cocos2dx.classes.AsyncOperationQueue.prototype.run = function()
{
if(this.ops.length === 0)
{
return;
}
var start = +new Date();
var end = +new Date();
while(this.ops.length && ((end - start) < this.sliceBudget))
{
var op = this.ops.shift();
op.fn(op.args);
end = +new Date();
}
if(this.ops.length)
{
this.schedule(this.sliceInterval);
}
};
/**
* Unschedule any remaining operations in the queue.
*/
Module.cocos2dx.classes.AsyncOperationQueue.prototype.shutdown = function()
{
this.unschedule();
this.ops = [];
};
// AsyncImageLoader
/**
* Construct a new AsyncImageLoader object. @cxxTextureCache should be a
* pointer to a valid CCTextureCacheEmscripten object in C++.
* @deps__ignored is ignored, but is accepted so that we can communicate to
* the compiler what functions we need to ensure are ready by the time this
* class gets instantiated.
*/
Module.cocos2dx.classes.AsyncImageLoader = function(cxxTextureCache)
{
this.cxxTextureCache = cxxTextureCache;
this.operationQueue = new Module.cocos2dx.classes.AsyncOperationQueue();
// Since there will only ever be one image being processed at a time,
// we pre-initialize a canvas object and use it for all operations so
// as to save doing this for every image, incurring (probably) both
// compute work and memory fragmentation.
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.regionSize = 128;
};
/** var LibraryCocosHelper = {
* "Private" and "static" method to copy an input image to a given $cocos2dx__deps: [ '_CCTextureCacheEmscripten_preMultiplyImageRegion' ],
* rectangle in the output image. Used by @loadImage. $cocos2dx: {
*/ objects: {},
Module.cocos2dx.classes.AsyncImageLoader.prototype._blitAndPremultipyRegion = function(args) classes: {
{ // AsyncOperationQueue -- simple worker queue. Note that all functions
// Calculate width and height for this region such that we // passed in should effectively be "static", and have all requisite
// do not run off the edge of the image. // information contained in their args object.
var rw = Math.min(args.i + this.regionSize, args.w) - args.i; AsyncOperationQueue: function()
var rh = Math.min(args.j + this.regionSize, args.h) - args.j;
this.ctx.drawImage(args.img, args.i, args.j, rw, rh, args.i, args.j, rw, rh);
var imgData = this.ctx.getImageData(args.i, args.j, rw, rh);
var inImgData = _malloc(rw * rh * 4);
Module.HEAP8.set(imgData.data, inImgData);
// Call into C++ code in CCTextureCacheEmscripten.cpp to do the actual
// copy and pre-multiply.
_CCTextureCacheEmscripten_preMultiplyImageRegion(inImgData, rw, rh, args.outImgData, args.w, args.h, args.i, args.j);
_free(inImgData);
};
/**
* Enqueue the image at @path (interpreted as a URL) to be asynchronously
* loaded, then call the cocos2dx callback specified in @asyncData. Image
* fetch happens asynchronously using browser asset management code, and
* the work of copying pixel data to a buffer (for use by cocos2dx) is
* spread out over several frames such that disruptions to frame rate are
* minimized.
*/
Module.cocos2dx.classes.AsyncImageLoader.prototype.loadImage = function(path, asyncData)
{
var img = new Image();
var that = this;
img.onload = function()
{
var w = img.width;
var h = img.height;
// Setup the canvas in the queue also so that it happens in order
// with the operations that depend on the canvas having been
// configured to this resolution. We can get away with this since
// the queue guarantees that operations are executed in the order
// in which they are enqueued.
var setupCanvas = function(args)
{ {
that.canvas.width = args.w; this.ops = [];
that.canvas.height = args.h; this.timeoutId = null;
};
that.operationQueue.enqueue(setupCanvas, { w: w, h: h });
var outImgData = _malloc(w * h * 4); // Target using 2/3 of available CPU for texture loading.
this.sliceBudget = 8;
this.sliceInterval = 4;
for(var i = 0; i < w; i += that.regionSize) /**
{ * If there is a valid scheduled queue consumer, cancel it. Won't cancel
for(var j = 0; j < h; j += that.regionSize) * currently executing operations (as this is not multi-threaded), but will
* prevent future execution from happening until it is re-scheduled.
*/
this.unschedule = function(interval)
{ {
var args = { if(typeof this.timeoutId == "number")
i: i, j: j, {
w: w, h: h, clearTimeout(this.timeoutId);
img: img, this.timeoutId = null;
outImgData: outImgData }
};
/**
* Schedule the queue-processor to run @interval ms in the future.
*/
this.schedule = function(interval)
{
this.unschedule();
var that = this;
var o = function() {
that.run();
}; };
that.operationQueue.enqueue(function(a) { that._blitAndPremultipyRegion(a); }, args);
} this.timeoutId = setTimeout(o, interval);
};
/**
* Enqueue a funtcion @fn with arguments @args to run asynchronously at
* some point in the future, then return control to the caller. Operations
* are guaranteed to be run in the order in which they are enqueued, but no
* guarantee is given about when or in what context they will be executed.
*/
this.enqueue = function(fn, args)
{
var op = {
fn: fn,
args: args
};
this.ops.push(op);
this.schedule(this.sliceInterval);
};
/**
* Start running the consumer "thread". Will consume operations from the
* queue until such time as either the queue is empty, or it has exceeded
* its time budget. If the latter case (budget exceeded), it will
* reschedule itself to run again later and finish draining the queue.
*
* Rescheduling logic still in flux, but the objective is to try to
* schedule it to run at some point after the next frame has been
* requested, whilst still maintaining maximum bandwidth.
*/
this.run = function()
{
if(this.ops.length === 0)
{
return;
}
var start = +new Date();
var end = +new Date();
while(this.ops.length && ((end - start) < this.sliceBudget))
{
var op = this.ops.shift();
op.fn(op.args);
end = +new Date();
}
if(this.ops.length)
{
this.schedule(this.sliceInterval);
}
};
/**
* Unschedule any remaining operations in the queue.
*/
this.shutdown = function()
{
this.unschedule();
this.ops = [];
};
},
/**
* Construct a new AsyncImageLoader object. @cxxTextureCache should be a
* pointer to a valid CCTextureCacheEmscripten object in C++.
*/
AsyncImageLoader: function(cxxTextureCache)
{
this.cxxTextureCache = cxxTextureCache;
this.operationQueue = new cocos2dx.classes.AsyncOperationQueue();
// Since there will only ever be one image being processed at a time,
// we pre-initialize a canvas object and use it for all operations so
// as to save doing this for every image, incurring (probably) both
// compute work and memory fragmentation.
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.regionSize = 128;
/**
* "Private" and "static" method to copy an input image to a given
* rectangle in the output image. Used by @loadImage.
*/
this._blitAndPremultipyRegion = function(args)
{
// Calculate width and height for this region such that we
// do not run off the edge of the image.
var rw = Math.min(args.i + this.regionSize, args.w) - args.i;
var rh = Math.min(args.j + this.regionSize, args.h) - args.j;
this.ctx.drawImage(args.img, args.i, args.j, rw, rh, args.i, args.j, rw, rh);
var imgData = this.ctx.getImageData(args.i, args.j, rw, rh);
var inImgData = _malloc(rw * rh * 4);
Module.HEAP8.set(imgData.data, inImgData);
// Call into C++ code in CCTextureCacheEmscripten.cpp to do the actual
// copy and pre-multiply.
_CCTextureCacheEmscripten_preMultiplyImageRegion(inImgData, rw, rh, args.outImgData, args.w, args.h, args.i, args.j);
_free(inImgData);
};
/**
* Enqueue the image at @path (interpreted as a URL) to be asynchronously
* loaded, then call the cocos2dx callback specified in @asyncData. Image
* fetch happens asynchronously using browser asset management code, and
* the work of copying pixel data to a buffer (for use by cocos2dx) is
* spread out over several frames such that disruptions to frame rate are
* minimized.
*/
this.loadImage = function(path, asyncData)
{
var img = new Image();
var that = this;
img.onload = function()
{
var w = img.width;
var h = img.height;
// Setup the canvas in the queue also so that it happens in order
// with the operations that depend on the canvas having been
// configured to this resolution. We can get away with this since
// the queue guarantees that operations are executed in the order
// in which they are enqueued.
var setupCanvas = function(args)
{
that.canvas.width = args.w;
that.canvas.height = args.h;
};
that.operationQueue.enqueue(setupCanvas, { w: w, h: h });
var outImgData = _malloc(w * h * 4);
for(var i = 0; i < w; i += that.regionSize)
{
for(var j = 0; j < h; j += that.regionSize)
{
var args = {
i: i, j: j,
w: w, h: h,
img: img,
outImgData: outImgData
};
that.operationQueue.enqueue(function(a) { that._blitAndPremultipyRegion(a); }, args);
}
}
var fireCallback = function(args) {
_CCTextureCacheEmscripten_addImageAsyncCallBack(that.cxxTextureCache, args.asyncData, args.imgData, args.w, args.h);
};
var opArgs = {
asyncData: asyncData,
imgData: outImgData,
w: w, h: h
};
that.operationQueue.enqueue(fireCallback, opArgs);
};
img.onerror = function()
{
console.log("Error loading '" + path + "'");
};
img.src = path;
};
/**
* Shutdown this image loader object. Used by destructor in cocos2dx.
*/
this.shutdown = function()
{
this.operationQueue.shutdown();
};
} }
}
var fireCallback = function(args) { },
_CCTextureCacheEmscripten_addImageAsyncCallBack(that.cxxTextureCache, args.asyncData, args.imgData, args.w, args.h);
};
var opArgs = {
asyncData: asyncData,
imgData: outImgData,
w: w, h: h
};
that.operationQueue.enqueue(fireCallback, opArgs);
};
img.onerror = function()
{
console.log("Error loading '" + path + "'");
};
img.src = path;
};
/**
* Shutdown this image loader object. Used by destructor in cocos2dx.
*/
Module.cocos2dx.classes.AsyncImageLoader.prototype.shutdown = function()
{
this.operationQueue.shutdown();
};
// Non-namespaced facade over underlying objects. See above methods for
// documentation. These methods serve two purposes:
//
// 1. convert types to/from JS equivalents.
// 2. expose a "C-like" interface, such that the symbols are easy to find
// for calling code (Emscripten provides no easy way to access nested
// dictionaries).
//
// Note that the leading '_' is because Emscripten prefixes all function
// calls like this. Since this code is being injected at runtime,
// Emscripten's compiler doesn't have a chance to add it for us.
/* /*
* Construct a new AsyncImageLoader object. Held as a singleton, referred * Construct a new AsyncImageLoader object. Held as a singleton, referred
* to in other wrapper methods here. * to in other wrapper methods here.
* @deps__ignored is ignored, but is accepted so that we can communicate to
* the compiler what functions we need to ensure are ready by the time this
* class gets instantiated.
*/ */
_cocos2dx_newAsyncImageLoader = function(cxxTextureCache, deps__ignored) cocos2dx_newAsyncImageLoader: function(cxxTextureCache, deps__ignored)
{ {
Module.cocos2dx.objects.asyncImageLoader = new Module.cocos2dx.classes.AsyncImageLoader(cxxTextureCache); cocos2dx.objects.asyncImageLoader = new cocos2dx.classes.AsyncImageLoader(cxxTextureCache);
}; },
/** /**
* Shutdown the current image loader object. Used by the cocos2dx * Shutdown the current image loader object. Used by the cocos2dx
* destructor method. * destructor method.
*/ */
_cocos2dx_shutdownAsyncImageLoader = function() cocos2dx_shutdownAsyncImageLoader: function()
{ {
Module.cocos2dx.objects.asyncImageLoader.shutdown(); cocos2dx.objects.asyncImageLoader.shutdown();
Module.cocos2dx.objects.asyncImageLoader = null; cocos2dx.objects.asyncImageLoader = null;
}; },
/** /**
* Load a new image asynchronously. * Load a new image asynchronously.
*/ */
_cocos2dx_asyncImageLoader_LoadImage = function(path, asyncData) cocos2dx_asyncImageLoader_LoadImage: function(path, asyncData)
{ {
var opArgs = { var opArgs = {
path: Pointer_stringify(path), path: Pointer_stringify(path),
@ -284,8 +291,12 @@ MULTILINE(
}; };
var op = function(args) var op = function(args)
{ {
Module.cocos2dx.objects.asyncImageLoader.loadImage(args.path, args.data); cocos2dx.objects.asyncImageLoader.loadImage(args.path, args.data);
}; };
Module.cocos2dx.objects.asyncImageLoader.operationQueue.enqueue(op, opArgs); cocos2dx.objects.asyncImageLoader.operationQueue.enqueue(op, opArgs);
}; }
) };
autoAddDeps(LibraryCocosHelper, '$cocos2dx');
mergeInto(LibraryManager.library, LibraryCocosHelper);

View File

@ -13,12 +13,21 @@ OBJ_DIR ?= obj
EMSCRIPTEN_ROOT := $(realpath $(COCOS_ROOT)/external/emscripten) EMSCRIPTEN_ROOT := $(realpath $(COCOS_ROOT)/external/emscripten)
PACKAGER := $(EMSCRIPTEN_ROOT)/tools/file_packager.py PACKAGER := $(EMSCRIPTEN_ROOT)/tools/file_packager.py
AR := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/emar
ARFLAGS = cr
CC := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/emcc CC := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/emcc
CXX := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/em++ CXX := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/em++
AR := EMSCRIPTEN=$(EMSCRIPTEN_ROOT) $(COCOS_ROOT)/external/emscripten/emar
CCFLAGS += -MMD -Wall -fPIC -Qunused-arguments -Wno-overloaded-virtual -Qunused-variable -s TOTAL_MEMORY=268435456 -s VERBOSE=1 -U__native_client__ # XXX: Not entirely sure why main, malloc and free need to be explicitly listed
CXXFLAGS += -MMD -Wall -fPIC -Qunused-arguments -Wno-overloaded-virtual -Qunused-variable -s TOTAL_MEMORY=268435456 -s VERBOSE=1 -U__native_client__ # here, but after adding a --js-library library, these symbols seem to get
ARFLAGS = cr # stripped unless enumerated here.
EXPORTED_FLAGS := -s EXPORTED_FUNCTIONS="['_CCTextureCacheEmscripten_addImageAsyncCallBack','_CCTextureCacheEmscripten_preMultiplyImageRegion','_malloc','_free','_main']"
JSLIBS := --js-library $(COCOS_SRC)/platform/emscripten/CCTextureCacheEmscripten.js
CCFLAGS += -MMD -Wall -fPIC -Qunused-arguments -Wno-overloaded-virtual -Qunused-variable -s TOTAL_MEMORY=268435456 -s VERBOSE=1 -U__native_client__ $(EXPORTED_FLAGS) $(JSLIBS)
CXXFLAGS += -MMD -Wall -fPIC -Qunused-arguments -Wno-overloaded-virtual -Qunused-variable -s TOTAL_MEMORY=268435456 -s VERBOSE=1 -U__native_client__ $(EXPORTED_FLAGS) $(JSLIBS)
LIB_DIR = $(COCOS_SRC)/lib/emscripten LIB_DIR = $(COCOS_SRC)/lib/emscripten
BIN_DIR = bin BIN_DIR = bin
@ -51,8 +60,8 @@ else
# Async image loading code incompatible with asm.js for now. Disable until # Async image loading code incompatible with asm.js for now. Disable until
# we've had time to investigate. --closure 0 so that symbols don't get mangled, # we've had time to investigate. --closure 0 so that symbols don't get mangled,
# rendering them inaccessible from JS code. # rendering them inaccessible from JS code.
CCFLAGS += -O2 --jcache -s GL_UNSAFE_OPTS=0 -s ASM_JS=0 --closure 0 CCFLAGS += -O2 --jcache -s GL_UNSAFE_OPTS=0 -s ASM_JS=1
CXXFLAGS += -O2 --jcache -s GL_UNSAFE_OPTS=0 -s ASM_JS=0 --closure 0 CXXFLAGS += -O2 --jcache -s GL_UNSAFE_OPTS=0 -s ASM_JS=1
DEFINES += -DNDEBUG -DCP_USE_DOUBLES=0 DEFINES += -DNDEBUG -DCP_USE_DOUBLES=0
OBJ_DIR := $(OBJ_DIR)/release OBJ_DIR := $(OBJ_DIR)/release
LIB_DIR := $(LIB_DIR)/release LIB_DIR := $(LIB_DIR)/release