diff --git a/cocos2dx/platform/emscripten/CCEGLView.cpp b/cocos2dx/platform/emscripten/CCEGLView.cpp index 05b98df02b..bd6e3c122c 100644 --- a/cocos2dx/platform/emscripten/CCEGLView.cpp +++ b/cocos2dx/platform/emscripten/CCEGLView.cpp @@ -139,9 +139,6 @@ CCEGLView::CCEGLView() glutMouseFunc(&mouseCB); glutMotionFunc(&motionCB); glutPassiveMotionFunc(&motionCB); - - // Setup a JS namespace for all cocos2dx helper functions. - emscripten_run_script("Module.cocos2dx = { classes: {}, objects: {} };"); } CCEGLView::~CCEGLView() diff --git a/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.cpp b/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.cpp index 43a0601eab..2fdd04e2d6 100644 --- a/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.cpp +++ b/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.cpp @@ -59,11 +59,6 @@ void CCTextureCacheEmscripten_addImageAsyncCallBack(CCTextureCacheEmscripten *tc */ CCTextureCacheEmscripten::CCTextureCacheEmscripten() { - const char *js = -# include "CCTextureCacheEmscripten.js" - ; - emscripten_run_script(js); - // 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 // them. @@ -84,7 +79,6 @@ CCTextureCacheEmscripten::CCTextureCacheEmscripten() CCTextureCacheEmscripten::~CCTextureCacheEmscripten() { cocos2dx_shutdownAsyncImageLoader(); - emscripten_run_script("Module.cocos2dx.AsyncImageLoader = null;"); } /** diff --git a/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.js b/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.js index f588f7c15e..abbec0d97e 100644 --- a/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.js +++ b/cocos2dx/platform/emscripten/CCTextureCacheEmscripten.js @@ -22,261 +22,268 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ -MULTILINE( - // Class declarations. - // AsyncOperationQueue -- simple worker queue. Note that all functions - // passed in should effectively be "static", and have all requisite - // information contained in their args object. - Module.cocos2dx.classes.AsyncOperationQueue = function() - { - this.ops = []; - this.timeoutId = null; - this.sliceBudget = 8; - this.sliceInterval = 2 * this.sliceBudget; - }; - - /** - * 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; - }; +// 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. - /** - * "Private" and "static" method to copy an input image to a given - * rectangle in the output image. Used by @loadImage. - */ - Module.cocos2dx.classes.AsyncImageLoader.prototype._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. - */ - 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) +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() { - that.canvas.width = args.w; - that.canvas.height = args.h; - }; - that.operationQueue.enqueue(setupCanvas, { w: w, h: h }); + this.ops = []; + this.timeoutId = null; - 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) - { - for(var j = 0; j < h; j += that.regionSize) + /** + * 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) { - var args = { - i: i, j: j, - w: w, h: h, - img: img, - outImgData: outImgData + 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(); }; - 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 * 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 * destructor method. */ - _cocos2dx_shutdownAsyncImageLoader = function() + cocos2dx_shutdownAsyncImageLoader: function() { - Module.cocos2dx.objects.asyncImageLoader.shutdown(); - Module.cocos2dx.objects.asyncImageLoader = null; - }; + cocos2dx.objects.asyncImageLoader.shutdown(); + cocos2dx.objects.asyncImageLoader = null; + }, + /** * Load a new image asynchronously. */ - _cocos2dx_asyncImageLoader_LoadImage = function(path, asyncData) + cocos2dx_asyncImageLoader_LoadImage: function(path, asyncData) { var opArgs = { path: Pointer_stringify(path), @@ -284,8 +291,12 @@ MULTILINE( }; 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); + diff --git a/cocos2dx/proj.emscripten/cocos2dx.mk b/cocos2dx/proj.emscripten/cocos2dx.mk index 24cfdf8f88..01c9d78d3f 100644 --- a/cocos2dx/proj.emscripten/cocos2dx.mk +++ b/cocos2dx/proj.emscripten/cocos2dx.mk @@ -13,12 +13,21 @@ OBJ_DIR ?= obj EMSCRIPTEN_ROOT := $(realpath $(COCOS_ROOT)/external/emscripten) 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 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__ -CXXFLAGS += -MMD -Wall -fPIC -Qunused-arguments -Wno-overloaded-virtual -Qunused-variable -s TOTAL_MEMORY=268435456 -s VERBOSE=1 -U__native_client__ -ARFLAGS = cr + +# XXX: Not entirely sure why main, malloc and free need to be explicitly listed +# here, but after adding a --js-library library, these symbols seem to get +# 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 BIN_DIR = bin @@ -51,8 +60,8 @@ else # 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, # rendering them inaccessible from JS code. -CCFLAGS += -O2 --jcache -s GL_UNSAFE_OPTS=0 -s ASM_JS=0 --closure 0 -CXXFLAGS += -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=1 DEFINES += -DNDEBUG -DCP_USE_DOUBLES=0 OBJ_DIR := $(OBJ_DIR)/release LIB_DIR := $(LIB_DIR)/release