2013-06-13 05:46:32 +08:00
|
|
|
/****************************************************************************
|
|
|
|
Copyright (c) 2013 Zynga Inc.
|
|
|
|
|
|
|
|
http://www.cocos2d-x.org
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
|
|
|
|
var LibraryCocosHelper = {
|
|
|
|
$cocos2dx__deps: [ '_CCTextureCacheEmscripten_preMultiplyImageRegion' ],
|
|
|
|
$cocos2dx: {
|
|
|
|
objects: {},
|
|
|
|
classes: {
|
|
|
|
// AsyncOperationQueue -- simple worker queue. Note that all functions
|
|
|
|
// passed in should effectively be "static", and have all requisite
|
|
|
|
// information contained in their args object.
|
|
|
|
AsyncOperationQueue: function()
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
this.ops = [];
|
|
|
|
this.timeoutId = null;
|
|
|
|
|
|
|
|
// Target using 2/3 of available CPU for texture loading.
|
|
|
|
this.sliceBudget = 8;
|
|
|
|
this.sliceInterval = 4;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
this.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.
|
|
|
|
*/
|
|
|
|
this.schedule = function(interval)
|
|
|
|
{
|
|
|
|
this.unschedule();
|
|
|
|
|
|
|
|
var that = this;
|
|
|
|
var o = function() {
|
|
|
|
that.run();
|
|
|
|
};
|
2013-06-13 05:46:32 +08:00
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
this.timeoutId = setTimeout(o, interval);
|
|
|
|
};
|
2013-06-13 05:46:32 +08:00
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
/**
|
|
|
|
* 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)
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
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)
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
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);
|
2013-06-13 05:46:32 +08:00
|
|
|
};
|
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
img.onerror = function()
|
|
|
|
{
|
|
|
|
console.log("Error loading '" + path + "'");
|
|
|
|
};
|
2013-06-13 05:46:32 +08:00
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
img.src = path;
|
|
|
|
};
|
2013-06-13 05:46:32 +08:00
|
|
|
|
2013-06-18 08:10:41 +08:00
|
|
|
/**
|
|
|
|
* Shutdown this image loader object. Used by destructor in cocos2dx.
|
|
|
|
*/
|
|
|
|
this.shutdown = function()
|
|
|
|
{
|
|
|
|
this.operationQueue.shutdown();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2013-06-13 05:46:32 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Construct a new AsyncImageLoader object. Held as a singleton, referred
|
|
|
|
* to in other wrapper methods here.
|
2013-06-18 08:10:41 +08:00
|
|
|
* @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.
|
2013-06-13 05:46:32 +08:00
|
|
|
*/
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx_newAsyncImageLoader: function(cxxTextureCache, deps__ignored)
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx.objects.asyncImageLoader = new cocos2dx.classes.AsyncImageLoader(cxxTextureCache);
|
|
|
|
},
|
2013-06-13 05:46:32 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Shutdown the current image loader object. Used by the cocos2dx
|
|
|
|
* destructor method.
|
|
|
|
*/
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx_shutdownAsyncImageLoader: function()
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx.objects.asyncImageLoader.shutdown();
|
|
|
|
cocos2dx.objects.asyncImageLoader = null;
|
|
|
|
},
|
|
|
|
|
2013-06-13 05:46:32 +08:00
|
|
|
/**
|
|
|
|
* Load a new image asynchronously.
|
|
|
|
*/
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx_asyncImageLoader_LoadImage: function(path, asyncData)
|
2013-06-13 05:46:32 +08:00
|
|
|
{
|
|
|
|
var opArgs = {
|
|
|
|
path: Pointer_stringify(path),
|
|
|
|
data: asyncData
|
|
|
|
};
|
|
|
|
var op = function(args)
|
|
|
|
{
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx.objects.asyncImageLoader.loadImage(args.path, args.data);
|
2013-06-13 05:46:32 +08:00
|
|
|
};
|
2013-06-18 08:10:41 +08:00
|
|
|
cocos2dx.objects.asyncImageLoader.operationQueue.enqueue(op, opArgs);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
autoAddDeps(LibraryCocosHelper, '$cocos2dx');
|
|
|
|
mergeInto(LibraryManager.library, LibraryCocosHelper);
|
|
|
|
|