diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/jsifier.js | 4 | ||||
-rw-r--r-- | src/library.js | 330 | ||||
-rw-r--r-- | src/library_browser.js | 6 | ||||
-rw-r--r-- | src/library_gl.js | 70 | ||||
-rw-r--r-- | src/library_sdl.js | 236 | ||||
-rw-r--r-- | src/parseTools.js | 4 |
6 files changed, 543 insertions, 107 deletions
diff --git a/src/jsifier.js b/src/jsifier.js index 86931f3e..82b78d0a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1479,8 +1479,8 @@ function JSify(data, functionsOnly, givenFunctions) { return inline.apply(null, args); // Warning: inlining does not prevent recalculation of the arguments. They should be simple identifiers } - if (ASM_JS) { - // remove unneeded arguments, which the asm sig can show us. this lets us alias memset with llvm.memset, we just + if (ASM_JS && ident.indexOf('llvm_') >= 0) { + // remove unneeded arguments in llvm intrinsic functions, which the asm sig can show us. this lets us alias memset with llvm.memset, we just // drop the final 2 args so things validate properly in asm var libsig = LibraryManager.library[simpleIdent + '__sig']; if (libsig) { diff --git a/src/library.js b/src/library.js index bf969fd7..24e35178 100644 --- a/src/library.js +++ b/src/library.js @@ -588,8 +588,11 @@ LibraryManager.library = { // Create the I/O devices. var devFolder = FS.createFolder('/', 'dev', true, true); var stdin = FS.createDevice(devFolder, 'stdin', input); + stdin.isTerminal = !stdinOverridden; var stdout = FS.createDevice(devFolder, 'stdout', null, output); + stdout.isTerminal = !stdoutOverridden; var stderr = FS.createDevice(devFolder, 'stderr', null, error); + stderr.isTerminal = !stderrOverridden; FS.createDevice(devFolder, 'tty', input, output); FS.createDevice(devFolder, 'null', function(){}, function(){}); @@ -601,7 +604,6 @@ LibraryManager.library = { isRead: true, isWrite: false, isAppend: false, - isTerminal: !stdinOverridden, error: false, eof: false, ungotten: [] @@ -613,7 +615,6 @@ LibraryManager.library = { isRead: false, isWrite: true, isAppend: false, - isTerminal: !stdoutOverridden, error: false, eof: false, ungotten: [] @@ -625,7 +626,6 @@ LibraryManager.library = { isRead: false, isWrite: true, isAppend: false, - isTerminal: !stderrOverridden, error: false, eof: false, ungotten: [] @@ -737,7 +737,8 @@ LibraryManager.library = { // int closedir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/closedir.html if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - return ___setErrNo(ERRNO_CODES.EBADF); + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } else { _free(FS.streams[dirp].currentEntry); FS.streams[dirp] = null; @@ -749,7 +750,8 @@ LibraryManager.library = { // long int telldir(DIR *dirp); // http://pubs.opengroup.org/onlinepubs/007908799/xsh/telldir.html if (!FS.streams[dirp] || !FS.streams[dirp].object.isFolder) { - return ___setErrNo(ERRNO_CODES.EBADF); + ___setErrNo(ERRNO_CODES.EBADF); + return -1; } else { return FS.streams[dirp].position; } @@ -865,10 +867,6 @@ LibraryManager.library = { } var file = FS.findObject(Pointer_stringify(path)); if (file === null) return -1; - if (!file.write) { - ___setErrNo(ERRNO_CODES.EPERM); - return -1; - } file.timestamp = time; return 0; }, @@ -1213,7 +1211,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.EEXIST); return -1; } - if ((isWrite || isCreate || isTruncate) && target.isFolder) { + if ((isWrite || isTruncate) && target.isFolder) { ___setErrNo(ERRNO_CODES.EISDIR); return -1; } @@ -1381,7 +1379,7 @@ LibraryManager.library = { posix_fallocate: function(fd, offset, len) { // int posix_fallocate(int fd, off_t offset, off_t len); // http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html - if (!FS.streams[fd] || FS.streams[fd].link || + if (!FS.streams[fd] || !FS.streams[fd].isWrite || FS.streams[fd].link || FS.streams[fd].isFolder || FS.streams[fd].isDevice) { ___setErrNo(ERRNO_CODES.EBADF); return -1; @@ -1688,13 +1686,16 @@ LibraryManager.library = { isatty: function(fildes) { // int isatty(int fildes); // http://pubs.opengroup.org/onlinepubs/000095399/functions/isatty.html - if (!FS.streams[fildes]) { + var stream = FS.streams[fildes]; + if (!stream) { ___setErrNo(ERRNO_CODES.EBADF); return 0; } - if (FS.streams[fildes].isTerminal) return 1; - ___setErrNo(ERRNO_CODES.ENOTTY); - return 0; + if (!stream.object.isTerminal) { + ___setErrNo(ERRNO_CODES.ENOTTY); + return 0; + } + return 1; }, lchown__deps: ['chown'], lchown: function(path, owner, group) { @@ -1916,30 +1917,21 @@ LibraryManager.library = { if (!_ttyname.ret) _ttyname.ret = _malloc(256); return _ttyname_r(fildes, _ttyname.ret, 256) ? 0 : _ttyname.ret; }, - ttyname_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], + ttyname_r__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'isatty'], ttyname_r: function(fildes, name, namesize) { // int ttyname_r(int fildes, char *name, size_t namesize); // http://pubs.opengroup.org/onlinepubs/000095399/functions/ttyname.html var stream = FS.streams[fildes]; + var ttyname = '/dev/tty'; if (!stream) { return ___setErrNo(ERRNO_CODES.EBADF); - } else { - var object = stream.object; - if (!object.isDevice || !object.input || !object.output) { - return ___setErrNo(ERRNO_CODES.ENOTTY); - } else { - var ret = stream.path; - if (namesize < ret.length + 1) { - return ___setErrNo(ERRNO_CODES.ERANGE); - } else { - for (var i = 0; i < ret.length; i++) { - {{{ makeSetValue('name', 'i', 'ret.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'i', '0', 'i8') }}} - return 0; - } - } + } else if (!_isatty(fildes)) { + return ___setErrNo(ERRNO_CODES.ENOTTY); + } else if (namesize < ttyname.length + 1) { + return ___setErrNo(ERRNO_CODES.ERANGE); } + writeStringToMemory(ttyname, name); + return 0; }, symlink__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], symlink: function(path1, path2) { @@ -6223,10 +6215,282 @@ LibraryManager.library = { }, strftime_l: 'strftime', // no locale support yet + strptime__deps: ['__tm_struct_layout'], strptime: function(buf, format, tm) { // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html - // TODO: Implement. + var pattern = Pointer_stringify(format); + + // escape special characters + // TODO: not sure we really need to escape all of these in JS regexps + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i=0, ii=SPECIAL_CHARS.length; i<ii; ++i) { + pattern = pattern.replace(new RegExp('\\'+SPECIAL_CHARS[i], 'g'), '\\'+SPECIAL_CHARS[i]); + } + + // reduce number of matchers + var EQUIVALENT_MATCHERS = { + '%A': '%a', + '%B': '%b', + '%c': '%x\\s+%X', + '%D': '%m\\/%d\\/%y', + '%e': '%d', + '%h': '%b', + '%R': '%H\\:%M', + '%r': '%I\\:%M\\:%S\\s%p', + '%T': '%H\\:%M\\:%S', + '%x': '%m\\/%d\\/(?:%y|%Y)', + '%X': '%H\\:%M\\:%S' + }; + for (var matcher in EQUIVALENT_MATCHERS) { + pattern = pattern.replace(matcher, EQUIVALENT_MATCHERS[matcher]); + } + + // TODO: take care of locale + + var DATE_PATTERNS = { + /* weeday name */ '%a': '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + /* month name */ '%b': '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + /* century */ '%C': '\\d\\d', + /* day of month */ '%d': '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + /* hour (24hr) */ '%H': '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + /* hour (12hr) */ '%I': '\\d(?!\\d)|0\\d|10|11|12', + /* day of year */ '%j': '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + /* month */ '%m': '0[1-9]|[1-9](?!\\d)|10|11|12', + /* minutes */ '%M': '0\\d|\\d(?!\\d)|[1-5]\\d', + /* whitespace */ '%n': '\\s', + /* AM/PM */ '%p': 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + /* seconds */ '%S': '0\\d|\\d(?!\\d)|[1-5]\\d|60', + /* week number */ '%U': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* week number */ '%W': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* weekday number */ '%w': '[0-6]', + /* 2-digit year */ '%y': '\\d\\d', + /* 4-digit year */ '%Y': '\\d\\d\\d\\d', + /* % */ '%%': '%', + /* whitespace */ '%t': '\\s', + }; + + var MONTH_NUMBERS = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; + var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var DAY_NUMBERS_SUN_FIRST = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; + var DAY_NUMBERS_MON_FIRST = {MON: 0, TUE: 1, WED: 2, THU: 3, FRI: 4, SAT: 5, SUN: 6}; + + var isLeapYear = function(year) { + return year%4===0 && (year%100!==0 || year%400===0); + }; + + var arraySum = function(array, index) { + var sum = 0; + for (var i=0; i<=index; sum += array[i++]); + return sum; + }; + + var addDays = function(date, days) { + while(days>0) { + var leap = isLeapYear(date.getFullYear()); + var currentMonth = date.getMonth(); + var daysInCurrentMonth = (leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[currentMonth]; + + if (days>daysInCurrentMonth-date.getDate()) { + // we spill over to next month + days -= (daysInCurrentMonth-date.getDate()+1); + date.setDate(1); + if (currentMonth<11) { + date.setMonth(currentMonth+1) + } else { + date.setMonth(0); + date.setFullYear(date.getFullYear()+1); + } + } else { + // we stay in current month + date.setDate(date.getDate()+days); + return date; + } + } + return date; + }; + + for (var datePattern in DATE_PATTERNS) { + pattern = pattern.replace(datePattern, '('+datePattern+DATE_PATTERNS[datePattern]+')'); + } + + // take care of capturing groups + var capture = []; + for (var i=pattern.indexOf('%'); i>=0; i=pattern.indexOf('%')) { + capture.push(pattern[i+1]); + pattern = pattern.replace(new RegExp('\\%'+pattern[i+1], 'g'), ''); + } + + var matches = new RegExp('^'+pattern).exec(Pointer_stringify(buf)) + // Module['print'](Pointer_stringify(buf)+ ' is matched by '+((new RegExp('^'+pattern)).source)+' into: '+JSON.stringify(matches)); + + var initDate = function() { + var fixup = function(value, min, max) { + return (typeof value !== 'number' || isNaN(value)) ? min : (value>=min ? (value<=max ? value: max): min); + }; + + return { + year: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_year', 'i32') }}} + 1900 , 1970, 9999), + month: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mon', 'i32') }}}, 0, 11), + day: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_mday', 'i32') }}}, 1, 31), + hour: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_hour', 'i32') }}}, 0, 23), + min: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_min', 'i32') }}}, 0, 59), + sec: fixup({{{ makeGetValue('tm', '___tm_struct_layout.tm_sec', 'i32') }}}, 0, 59) + }; + }; + + if (matches) { + var date = initDate(); + var value; + + var getMatch = function(symbol) { + var pos = capture.indexOf(symbol); + // check if symbol appears in regexp + if (pos >= 0) { + // return matched value or null (falsy!) for non-matches + return matches[pos+1]; + } + return; + } + + // seconds + if ((value=getMatch('S'))) { + date.sec = parseInt(value); + } + + // minutes + if ((value=getMatch('M'))) { + date.min = parseInt(value); + } + + // hours + if ((value=getMatch('H'))) { + // 24h clock + date.hour = parseInt(value); + } else if ((value = getMatch('I'))) { + // AM/PM clock + var hour = parseInt(value); + if ((value=getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + + // year + if ((value=getMatch('Y'))) { + // parse from four-digit year + date.year = parseInt(value); + } else if ((value=getMatch('y'))) { + // parse from two-digit year... + var year = parseInt(value); + if ((value=getMatch('C'))) { + // ...and century + year += parseInt(value)*100; + } else { + // ...and rule-of-thumb + year += year<69 ? 2000 : 1900; + } + date.year = year; + } + + // month + if ((value=getMatch('m'))) { + // parse from month number + date.month = parseInt(value)-1; + } else if ((value=getMatch('b'))) { + // parse from month name + date.month = MONTH_NUMBERS[value.substring(0,3).toUpperCase()] || 0; + // TODO: derive month from day in year+year, week number+day of week+year + } + + // day + if ((value=getMatch('d'))) { + // get day of month directly + date.day = parseInt(value); + } else if ((value=getMatch('j'))) { + // get day of month from day of year ... + var day = parseInt(value); + var leapYear = isLeapYear(date.year); + for (var month=0; month<12; ++month) { + var daysUntilMonth = arraySum(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, month-1); + if (day<=daysUntilMonth+(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[month]) { + date.day = day-daysUntilMonth; + } + } + } else if ((value=getMatch('a'))) { + // get day of month from weekday ... + var weekDay = value.substring(0,3).toUpperCase(); + if ((value=getMatch('U'))) { + // ... and week number (Sunday being first day of week) + // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Sunday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = parseInt(value); + + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 0) { + // Jan 1st is a Sunday, and, hence in the 1st CW + endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1)); + } else { + // Jan 1st is not a Sunday, and, hence still in the 0th CW + endDate = addDays(janFirst, 7-janFirst.getDay()+weekDayNumber+7*(weekNumber-1)); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } else if ((value=getMatch('W'))) { + // ... and week number (Monday being first day of week) + // Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Monday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay]; + var weekNumber = parseInt(value); + + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay()===1) { + // Jan 1st is a Monday, and, hence in the 1st CW + endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1)); + } else { + // Jan 1st is not a Monday, and, hence still in the 0th CW + endDate = addDays(janFirst, 7-janFirst.getDay()+1+weekDayNumber+7*(weekNumber-1)); + } + + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } + } + + /* + tm_sec int seconds after the minute 0-61* + tm_min int minutes after the hour 0-59 + tm_hour int hours since midnight 0-23 + tm_mday int day of the month 1-31 + tm_mon int months since January 0-11 + tm_year int years since 1900 + tm_wday int days since Sunday 0-6 + tm_yday int days since January 1 0-365 + tm_isdst int Daylight Saving Time flag + */ + + var fullDate = new Date(date.year, date.month, date.day, date.hour, date.min, date.sec, 0); + {{{ makeSetValue('tm', '___tm_struct_layout.tm_sec', 'fullDate.getSeconds()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_min', 'fullDate.getMinutes()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_hour', 'fullDate.getHours()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_mday', 'fullDate.getDate()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_mon', 'fullDate.getMonth()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_year', 'fullDate.getFullYear()-1900', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_wday', 'fullDate.getDay()', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_yday', 'arraySum(isLeapYear(fullDate.getFullYear()) ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, fullDate.getMonth()-1)+fullDate.getDate()-1', 'i32') }}} + {{{ makeSetValue('tm', '___tm_struct_layout.tm_isdst', '0', 'i32') }}} + + // we need to convert the matched sequence into an integer array to take care of UTF-8 characters > 0x7F + // TODO: not sure that intArrayFromString handles all unicode characters correctly + return buf+intArrayFromString(matches[0]).length-1; + } + return 0; }, strptime_l: 'strptime', // no locale support yet diff --git a/src/library_browser.js b/src/library_browser.js index d9fd3ee5..7f79b2bd 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -84,13 +84,17 @@ mergeInto(LibraryManager.library, { var imagePlugin = {}; imagePlugin['canHandle'] = function(name) { - return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/.exec(name); + return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name); }; imagePlugin['handle'] = function(byteArray, name, onload, onerror) { var b = null; if (Browser.hasBlobConstructor) { try { b = new Blob([byteArray], { type: getMimetype(name) }); + if (b.size !== byteArray.length) { // Safari bug #118630 + // Safari's Blob can only take an ArrayBuffer + b = new Blob([(new Uint8Array(byteArray)).buffer], { type: getMimetype(name) }); + } } catch(e) { Runtime.warnOnce('Blob constructor present but fails: ' + e + '; falling back to blob builder'); } diff --git a/src/library_gl.js b/src/library_gl.js index e59492cf..959773bc 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -122,14 +122,6 @@ var LibraryGL = { } }, - // Linear lookup in one of the tables (buffers, programs, etc.). TODO: consider using a weakmap to make this faster, if it matters - scan: function(table, object) { - for (var item in table) { - if (table[item] == object) return item; - } - return 0; - }, - // Find a token in a shader source string findToken: function(source, token) { function isIdentChar(ch) { @@ -402,15 +394,15 @@ var LibraryGL = { {{{ makeSetValue('p', 'i*4', 'result[i]', 'i32') }}}; } } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'i32') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } @@ -445,15 +437,15 @@ var LibraryGL = { {{{ makeSetValue('p', 'i*4', 'result[i]', 'float') }}}; } } else if (result instanceof WebGLBuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.buffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLProgram) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.programs, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLFramebuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.framebuffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLRenderbuffer) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.renderbuffers, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else if (result instanceof WebGLTexture) { - {{{ makeSetValue('p', '0', 'GL.scan(GL.textures, result)', 'float') }}}; + {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else { throw 'Unknown object returned from WebGL getParameter'; } @@ -508,7 +500,9 @@ var LibraryGL = { glGenTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.textures); - GL.textures[id] = Module.ctx.createTexture(); + var texture = Module.ctx.createTexture(); + texture.name = id; + GL.textures[id] = texture; {{{ makeSetValue('textures', 'i*4', 'id', 'i32') }}}; } }, @@ -517,7 +511,9 @@ var LibraryGL = { glDeleteTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('textures', 'i*4', 'i32') }}}; - Module.ctx.deleteTexture(GL.textures[id]); + var texture = GL.textures[id]; + Module.ctx.deleteTexture(texture); + texture.name = 0; GL.textures[id] = null; } }, @@ -622,7 +618,9 @@ var LibraryGL = { glGenBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.buffers); - GL.buffers[id] = Module.ctx.createBuffer(); + var buffer = Module.ctx.createBuffer(); + buffer.name = id; + GL.buffers[id] = buffer; {{{ makeSetValue('buffers', 'i*4', 'id', 'i32') }}}; } }, @@ -631,7 +629,9 @@ var LibraryGL = { glDeleteBuffers: function(n, buffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('buffers', 'i*4', 'i32') }}}; - Module.ctx.deleteBuffer(GL.buffers[id]); + var buffer = GL.buffers[id]; + Module.ctx.deleteBuffer(buffer); + buffer.name = 0; GL.buffers[id] = null; if (id == GL.currArrayBuffer) GL.currArrayBuffer = 0; @@ -665,7 +665,9 @@ var LibraryGL = { glGenRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = GL.getNewId(GL.renderbuffers); - GL.renderbuffers[id] = Module.ctx.createRenderbuffer(); + var renderbuffer = Module.ctx.createRenderbuffer(); + renderbuffer.name = id; + GL.renderbuffers[id] = renderbuffer; {{{ makeSetValue('renderbuffers', 'i*4', 'id', 'i32') }}}; } }, @@ -674,8 +676,10 @@ var LibraryGL = { glDeleteRenderbuffers: function(n, renderbuffers) { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('renderbuffers', 'i*4', 'i32') }}}; - Module.ctx.deleteRenderbuffer(GL.renderbuffers[id]); - GL.renderbuffers[id]; + var renderbuffer = GL.renderbuffers[id]; + Module.ctx.deleteRenderbuffer(renderbuffer); + renderbuffer.name = 0; + GL.renderbuffers[id] = null; } }, @@ -1139,13 +1143,17 @@ var LibraryGL = { glCreateProgram__sig: 'i', glCreateProgram: function() { var id = GL.getNewId(GL.programs); - GL.programs[id] = Module.ctx.createProgram(); + var program = Module.ctx.createProgram(); + program.name = id; + GL.programs[id] = program; return id; }, glDeleteProgram__sig: 'vi', glDeleteProgram: function(program) { - Module.ctx.deleteProgram(GL.programs[program]); + var program = GL.programs[program]; + Module.ctx.deleteProgram(program); + program.name = 0; GL.programs[program] = null; GL.uniformTable[program] = null; }, @@ -1221,7 +1229,9 @@ var LibraryGL = { glGenFramebuffers: function(n, ids) { for (var i = 0; i < n; ++i) { var id = GL.getNewId(GL.framebuffers); - GL.framebuffers[id] = Module.ctx.createFramebuffer(); + var framebuffer = Module.ctx.createFramebuffer(); + framebuffer.name = id; + GL.framebuffers[id] = framebuffer; {{{ makeSetValue('ids', 'i*4', 'id', 'i32') }}}; } }, @@ -1230,7 +1240,9 @@ var LibraryGL = { glDeleteFramebuffers: function(n, framebuffers) { for (var i = 0; i < n; ++i) { var id = {{{ makeGetValue('framebuffers', 'i*4', 'i32') }}}; - Module.ctx.deleteFramebuffer(GL.framebuffers[id]); + var framebuffer = GL.framebuffers[id]; + Module.ctx.deleteFramebuffer(framebuffer); + framebuffer.name = 0; GL.framebuffers[id] = null; } }, diff --git a/src/library_sdl.js b/src/library_sdl.js index 176a2fae..b7d73862 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -21,6 +21,8 @@ var LibrarySDL = { version: null, surfaces: {}, + // A pool of freed canvas elements. Reusing them avoids GC pauses. + canvasPool: [], events: [], fonts: [null], @@ -246,7 +248,7 @@ var LibrarySDL = { }, translateRGBAToCSSRGBA: function(r, g, b, a) { - return 'rgba(' + r + ',' + g + ',' + b + ',' + (a/255) + ')'; + return 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')'; }, translateRGBAToColor: function(r, g, b, a) { @@ -288,7 +290,11 @@ var LibrarySDL = { SDL.GL = SDL.GL || useWebGL; var canvas; if (!usePageCanvas) { - canvas = document.createElement('canvas'); + if (SDL.canvasPool.length > 0) { + canvas = SDL.canvasPool.pop(); + } else { + canvas = document.createElement('canvas'); + } canvas.width = width; canvas.height = height; } else { @@ -355,14 +361,56 @@ var LibrarySDL = { }, freeSurface: function(surf) { - _free(SDL.surfaces[surf].buffer); - _free(SDL.surfaces[surf].pixelFormat); + var info = SDL.surfaces[surf]; + if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas); + _free(info.buffer); + _free(info.pixelFormat); _free(surf); SDL.surfaces[surf] = null; }, + touchX:0, touchY: 0, + receiveEvent: function(event) { switch(event.type) { + case 'touchstart': + event.preventDefault(); + var touch = event.touches[0]; + touchX = touch.pageX; + touchY = touch.pageY; + var event = { + type: 'mousedown', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.DOMButtons[0] = 1; + SDL.events.push(event); + break; + case 'touchmove': + event.preventDefault(); + var touch = event.touches[0]; + touchX = touch.pageX; + touchY = touch.pageY; + event = { + type: 'mousemove', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.events.push(event); + break; + case 'touchend': + event.preventDefault(); + event = { + type: 'mouseup', + button: 0, + pageX: touchX, + pageY: touchY + }; + SDL.DOMButtons[0] = 0; + SDL.events.push(event); + break; case 'mousemove': if (Browser.pointerLock) { // workaround for firefox bug 750111 @@ -1022,6 +1070,7 @@ var LibrarySDL = { surfData.ctx.fillStyle = SDL.translateColorToCSSRGBA(color); surfData.ctx.fillRect(r.x, r.y, r.w, r.h); surfData.ctx.restore(); + return 0; }, SDL_BlitSurface__deps: ['SDL_UpperBlit'], @@ -1031,11 +1080,19 @@ var LibrarySDL = { zoomSurface: function(src, x, y, smooth) { var srcData = SDL.surfaces[src]; - var w = srcData.width*x; - var h = srcData.height*y; - var ret = SDL.makeSurface(w, h, srcData.flags, false, 'zoomSurface'); + var w = srcData.width * x; + var h = srcData.height * y; + var ret = SDL.makeSurface(Math.abs(w), Math.abs(h), srcData.flags, false, 'zoomSurface'); var dstData = SDL.surfaces[ret]; - dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + if (x >= 0 && y >= 0) dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + else { + dstData.ctx.save(); + dstData.ctx.scale(x < 0 ? -1 : 1, y < 0 ? -1 : 1); + dstData.ctx.drawImage(srcData.canvas, w < 0 ? w : 0, h < 0 ? h : 0, Math.abs(w), Math.abs(h)); + // XXX I think this should work according to the spec, but currently + // fails on FF: dstData.ctx.drawImage(srcData.canvas, 0, 0, w, h); + dstData.ctx.restore(); + } return ret; }, @@ -1056,6 +1113,14 @@ var LibrarySDL = { SDL.surfaces[surf].alpha = alpha; }, + SDL_SetColorKey: function(surf, flag, key) { + // SetColorKey assigns one color to be rendered as transparent. I don't + // think the canvas API allows for anything like this, and iterating through + // each pixel to replace that color seems prohibitively expensive. + Runtime.warnOnce('SDL_SetColorKey is a no-op for performance reasons'); + return 0; + }, + SDL_GetTicks: function() { return Math.floor(Date.now() - SDL.startTime); }, @@ -1472,15 +1537,20 @@ var LibrarySDL = { }, Mix_HaltChannel: function(channel) { - var info = SDL.channels[channel]; - if (info.audio) { - info.audio.pause(); - info.audio = null; - } else { - Module.printErr('No Audio for channel: ' + channel); + function halt(channel) { + var info = SDL.channels[channel]; + if (info.audio) { + info.audio.pause(); + info.audio = null; + } + if (SDL.channelFinished) { + Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + } } - if (SDL.channelFinished) { - Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + if (channel != -1) { + halt(channel); + } else { + for (var i = 0; i < SDL.channels.length; ++i) halt(i); } return 0; }, @@ -1667,6 +1737,7 @@ var LibrarySDL = { }, TTF_RenderText_Blended: 'TTF_RenderText_Solid', // XXX ignore blending vs. solid TTF_RenderText_Shaded: 'TTF_RenderText_Solid', // XXX ignore blending vs. solid + TTF_RenderUTF8_Solid: 'TTF_RenderText_Solid', TTF_SizeText: function(font, text, w, h) { var fontData = SDL.fonts[font]; @@ -1691,35 +1762,114 @@ var LibrarySDL = { // SDL gfx + $SDL_gfx: { + drawRectangle: function(surf, x1, y1, x2, y2, action, cssColor) { + x1 = x1 << 16 >> 16; + y1 = y1 << 16 >> 16; + x2 = x2 << 16 >> 16; + y2 = y2 << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + // TODO: if ctx does not change, leave as is, and also do not re-set xStyle etc. + var x = x1 < x2 ? x1 : x2; + var y = y1 < y2 ? y1 : y2; + var w = Math.abs(x2 - x1); + var h = Math.abs(y2 - y1); + surfData.ctx.save(); + surfData.ctx[action + 'Style'] = cssColor; + surfData.ctx[action + 'Rect'](x, y, w, h); + surfData.ctx.restore(); + }, + drawLine: function(surf, x1, y1, x2, y2, cssColor) { + x1 = x1 << 16 >> 16; + y1 = y1 << 16 >> 16; + x2 = x2 << 16 >> 16; + y2 = y2 << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + surfData.ctx.save(); + surfData.ctx.strokeStyle = cssColor; + surfData.ctx.beginPath(); + surfData.ctx.moveTo(x1, y1); + surfData.ctx.lineTo(x2, y2); + surfData.ctx.stroke(); + surfData.ctx.restore(); + }, + // See http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + drawEllipse: function(surf, x, y, rx, ry, action, cssColor) { + x = x << 16 >> 16; + y = y << 16 >> 16; + rx = rx << 16 >> 16; + ry = ry << 16 >> 16; + var surfData = SDL.surfaces[surf]; + assert(!surfData.locked); // but we could unlock and re-lock if we must.. + + surfData.ctx.save(); + surfData.ctx.beginPath(); + surfData.ctx.translate(x, y); + surfData.ctx.scale(rx, ry); + surfData.ctx.arc(0, 0, 1, 0, 2 * Math.PI); + surfData.ctx.restore(); + + surfData.ctx.save(); + surfData.ctx[action + 'Style'] = cssColor; + surfData.ctx[action](); + surfData.ctx.restore(); + }, + // the gfx library uses something different from the rest of SDL... + translateColorToCSSRGBA: function(rgba) { + return 'rgba(' + (rgba>>>24) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba&0xff) + ')'; + } + }, + + boxColor__deps: ['$SDL_gfx'], + boxColor: function(surf, x1, y1, x2, y2, color) { + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'fill', SDL_gfx.translateColorToCSSRGBA(color)); + }, + + boxRGBA__deps: ['$SDL_gfx'], boxRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) { - var surfData = SDL.surfaces[surf]; - assert(!surfData.locked); // but we could unlock and re-lock if we must.. - // TODO: if ctx does not change, leave as is, and also do not re-set xStyle etc. - surfData.ctx.save(); - surfData.ctx.fillStyle = SDL.translateRGBAToCSSRGBA(r, g, b, a); - surfData.ctx.fillRect(x1, y1, x2-x1, y2-y1); - surfData.ctx.restore(); + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'fill', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, + + rectangleColor__deps: ['$SDL_gfx'], + rectangleColor: function(surf, x1, y1, x2, y2, color) { + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'stroke', SDL_gfx.translateColorToCSSRGBA(color)); }, + rectangleRGBA__deps: ['$SDL_gfx'], rectangleRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) { - var surfData = SDL.surfaces[surf]; - assert(!surfData.locked); // but we could unlock and re-lock if we must.. - surfData.ctx.save(); - surfData.ctx.strokeStyle = SDL.translateRGBAToCSSRGBA(r, g, b, a); - surfData.ctx.strokeRect(x1, y1, x2-x1, y2-y1); - surfData.ctx.restore(); + return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'stroke', SDL.translateRGBAToCSSRGBA(r, g, b, a)); + }, |