diff options
Diffstat (limited to 'src/library_sdl.js')
-rw-r--r-- | src/library_sdl.js | 1209 |
1 files changed, 1005 insertions, 204 deletions
diff --git a/src/library_sdl.js b/src/library_sdl.js index cd86fb93..73848502 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -1,124 +1,187 @@ //"use strict"; -// To use emscripten's SDL library here, you need to define -// Module.canvas. -// -// More specifically, our SDL implementation will look for -// Module.canvas. You should fill it using something like -// -// function onLoad() { -// // Pass canvas and context to the generated code -// Module.canvas = document.getElementById('canvas'); -// } -// -// Note that this must be called during onload, since you will -// only be able to access the canvas element in the page after -// it loads. You will likely also want to disable running by -// default, with something like -// -// var Module = { -// noInitialRun: true -// }; -// -// which is defined BEFORE you load the compiled code. - -// The test_emcc test in the tests/runner.py will test this -// in its last phase, where it generates HTML. You can see -// a concrete example there. The HTML source is in src/shell.html. -// Here is a more comprehensive example: - -/* -<html> - <head> - <title>Demo</title> - <script type='text/javascript'> - var Module = { - noInitialRun: true - }; +// See browser tests for examples (tests/runner.py, search for sdl_). Run with +// python tests/runner.py browser - // implement print - var print = function(text) { - var element = document.getElementById('output') - element.innerHTML = text.replace('\n', '<br>', 'g') + element.innerHTML; - } - </script> - <script src='doom.ccsimple.js' type='text/javascript'></script> - <script type='text/javascript'> - function onLoad() { - // Pass canvas and context to the generated code, and do the actual run() here - Module.canvas = document.getElementById('canvas'); - Module.run(); - } - </script> - <body onload='onLoad()' style='background-color: black; color: white'> - <center> - <canvas id='canvas' width='320' height='200'></canvas> - </center> - <div id='output'></div> - </body> -</html> -*/ - -// Other stuff to take into account: -// -// * Make sure alpha values are proper in your input. If they are all 0, everything will be transparent! -// -// * Your code should not write a 32-bit value and expect that to set an RGBA pixel. -// The reason is that that data will be read as 8-bit values, and according to the -// load-store consistency assumption, it should be written that way (see docs/paper.pdf). -// Instead, do something like *ptr++ = R; *ptr++ = G; *ptr++ = B; -// -// * A normal C++ main loop with SDL_Delay will not work in JavaScript - there is no way -// to wait for a short time without locking up the web page entirely. The simplest -// solution here is to have a singleIteration() function which is a single loop -// iteration, and from JS to do something like setInterval(_singleIteration, 1/30) -// -// * SDL_Quit does nothing. - -mergeInto(LibraryManager.library, { - //$SDL__deps: ['$Browser'], +var LibrarySDL = { + $SDL__deps: ['$FS', '$Browser'], $SDL: { defaults: { width: 320, height: 200, - copyScreenOnLock: false + copyOnLock: true }, + version: null, + surfaces: {}, events: [], + fonts: [null], + + audios: [null], + music: { + audio: null, + volume: 1.0 + }, + mixerFrequency: 22050, + mixerFormat: 0x8010, // AUDIO_S16LSB + mixerNumChannels: 2, + mixerChunkSize: 1024, + + keyboardState: null, + shiftKey: false, + ctrlKey: false, + altKey: false, + + startTime: null, + mouseX: 0, + mouseY: 0, + buttonState: 0, + DOMButtons: [0, 0, 0], + + DOMEventToSDLEvent: {}, + + keyCodes: { // DOM code ==> SDL code. See https://developer.mozilla.org/en/Document_Object_Model_%28DOM%29/KeyboardEvent and SDL_keycode.h + 38: 1106, // up arrow + 40: 1105, // down arrow + 37: 1104, // left arrow + 39: 1103, // right arrow + + 33: 1099, // pagedup + 34: 1102, // pagedown - keyCodes: { - 38: 273, // up arrow - 40: 274, // down arrow - 37: 276, // left arrow - 39: 275, // right arrow 17: 305, // control (right, or left) 18: 308, // alt - 109: 45, // minus - 16: 304 // shift + 173: 45, // minus + 16: 304, // shift + + 96: 88 | 1<<10, // keypad 0 + 97: 89 | 1<<10, // keypad 1 + 98: 90 | 1<<10, // keypad 2 + 99: 91 | 1<<10, // keypad 3 + 100: 92 | 1<<10, // keypad 4 + 101: 93 | 1<<10, // keypad 5 + 102: 94 | 1<<10, // keypad 6 + 103: 95 | 1<<10, // keypad 7 + 104: 96 | 1<<10, // keypad 8 + 105: 97 | 1<<10, // keypad 9 + + 112: 58 | 1<<10, // F1 + 113: 59 | 1<<10, // F2 + 114: 60 | 1<<10, // F3 + 115: 61 | 1<<10, // F4 + 116: 62 | 1<<10, // F5 + 117: 63 | 1<<10, // F6 + 118: 64 | 1<<10, // F7 + 119: 65 | 1<<10, // F8 + 120: 66 | 1<<10, // F9 + 121: 67 | 1<<10, // F10 + 122: 68 | 1<<10, // F11 + 123: 69 | 1<<10, // F12 + + 188: 44, // comma + 190: 46, // period + 191: 47, // slash (/) + 192: 96, // backtick/backquote (`) + }, + + scanCodes: { // SDL keycode ==> SDL scancode. See SDL_scancode.h + 97: 4, // A + 98: 5, + 99: 6, + 100: 7, + 101: 8, + 102: 9, + 103: 10, + 104: 11, + 105: 12, + 106: 13, + 107: 14, + 108: 15, + 109: 16, + 110: 17, + 111: 18, + 112: 19, + 113: 20, + 114: 21, + 115: 22, + 116: 23, + 117: 24, + 118: 25, + 119: 26, + 120: 27, + 121: 28, + 122: 29, // Z + 44: 54, // comma + 46: 55, // period + 47: 56, // slash + 49: 30, // 1 + 50: 31, + 51: 32, + 52: 33, + 53: 34, + 54: 35, + 55: 36, + 56: 37, + 57: 38, // 9 + 48: 39, // 0 + 13: 40, // return + 9: 43, // tab + 27: 41, // escape + 32: 44, // space + 92: 49, // backslash + 305: 224, // ctrl + 308: 226, // alt }, structs: { Rect: Runtime.generateStructInfo([ - ['i16', 'x'], ['i16', 'y'], ['i16', 'w'], ['i16', 'h'], + ['i32', 'x'], ['i32', 'y'], ['i32', 'w'], ['i32', 'h'], ]), PixelFormat: Runtime.generateStructInfo([ + ['i32', 'format'], ['void*', 'palette'], ['i8', 'BitsPerPixel'], ['i8', 'BytesPerPixel'], + ['i8', 'padding1'], ['i8', 'padding2'], + ['i32', 'Rmask'], ['i32', 'Gmask'], ['i32', 'Bmask'], ['i32', 'Amask'], ['i8', 'Rloss'], ['i8', 'Gloss'], ['i8', 'Bloss'], ['i8', 'Aloss'], - ['i8', 'Rshift'], ['i8', 'Gshift'], ['i8', 'Bshift'], ['i8', 'Ashift'], - ['i32', 'Rmask'], ['i32', 'Gmask'], ['i32', 'Bmask'], ['i32', 'Amask'] // Docs say i8, ./include/SDL_video.h says i32... + ['i8', 'Rshift'], ['i8', 'Gshift'], ['i8', 'Bshift'], ['i8', 'Ashift'] ]), KeyboardEvent: Runtime.generateStructInfo([ - ['i8', 'type'], - ['i8', 'which'], + ['i32', 'type'], + ['i32', 'windowID'], ['i8', 'state'], + ['i8', 'repeat'], + ['i8', 'padding2'], + ['i8', 'padding3'], ['i32', 'keysym'] ]), keysym: Runtime.generateStructInfo([ - ['i8', 'scancode'], + ['i32', 'scancode'], ['i32', 'sym'], - ['i32', 'mod'], - ['i16', 'unicode'] + ['i16', 'mod'], + ['i32', 'unicode'] + ]), + MouseMotionEvent: Runtime.generateStructInfo([ + ['i32', 'type'], + ['i32', 'windowID'], + ['i8', 'state'], + ['i8', 'padding1'], + ['i8', 'padding2'], + ['i8', 'padding3'], + ['i32', 'x'], + ['i32', 'y'], + ['i32', 'xrel'], + ['i32', 'yrel'] + ]), + MouseButtonEvent: Runtime.generateStructInfo([ + ['i32', 'type'], + ['i32', 'windowID'], + ['i8', 'button'], + ['i8', 'state'], + ['i8', 'padding1'], + ['i8', 'padding2'], + ['i32', 'x'], + ['i32', 'y'] ]), AudioSpec: Runtime.generateStructInfo([ ['i32', 'freq'], @@ -129,68 +192,143 @@ mergeInto(LibraryManager.library, { ['i32', 'size'], ['void*', 'callback'], ['void*', 'userdata'] + ]), + version: Runtime.generateStructInfo([ + ['i8', 'major'], + ['i8', 'minor'], + ['i8', 'patch'] ]) }, - makeSurface: function(width, height, flags) { + loadRect: function(rect) { + return { + x: {{{ makeGetValue('rect + SDL.structs.Rect.x', '0', 'i32') }}}, + y: {{{ makeGetValue('rect + SDL.structs.Rect.y', '0', 'i32') }}}, + w: {{{ makeGetValue('rect + SDL.structs.Rect.w', '0', 'i32') }}}, + h: {{{ makeGetValue('rect + SDL.structs.Rect.h', '0', 'i32') }}} + }; + }, + + // Load SDL color into a CSS-style color specification + loadColorToCSSRGB: function(color) { + var rgba = {{{ makeGetValue('color', '0', 'i32') }}}; + return 'rgb(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ')'; + }, + loadColorToCSSRGBA: function(color) { + var rgba = {{{ makeGetValue('color', '0', 'i32') }}}; + return 'rgba(' + (rgba&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba >> 16)&255) + ',' + (((rgba >> 24)&255)/255) + ')'; + }, + + translateColorToCSSRGBA: function(rgba) { + return 'rgba(' + ((rgba >> 24)&255) + ',' + ((rgba >> 16)&255) + ',' + ((rgba >> 8)&255) + ',' + ((rgba&255)/255) + ')'; + }, + + translateRGBAToCSSRGBA: function(r, g, b, a) { + return 'rgba(' + r + ',' + g + ',' + b + ',' + (a/255) + ')'; + }, + + translateRGBAToColor: function(r, g, b, a) { + return (r << 24) + (g << 16) + (b << 8) + a; + }, + + makeSurface: function(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) { + flags = flags || 0; var surf = _malloc(14*Runtime.QUANTUM_SIZE); // SDL_Surface has 14 fields of quantum size - var buffer = _malloc(width*height*4); + var buffer = _malloc(width*height*4); // TODO: only allocate when locked the first time var pixelFormat = _malloc(18*Runtime.QUANTUM_SIZE); flags |= 1; // SDL_HWSURFACE - this tells SDL_MUSTLOCK that this needs to be locked + //surface with SDL_HWPALETTE flag is 8bpp surface (1 byte) + var is_SDL_HWPALETTE = flags & 0x00200000; + var bpp = is_SDL_HWPALETTE ? 1 : 4; + {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}} // SDL_Surface.flags {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*1', '0', 'pixelFormat', 'void*') }}} // SDL_Surface.format TODO {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*2', '0', 'width', 'i32') }}} // SDL_Surface.w {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*3', '0', 'height', 'i32') }}} // SDL_Surface.h - {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*4', '0', 'width*4', 'i16') }}} // SDL_Surface.pitch, assuming RGBA for now, + {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*4', '0', 'width * bpp', 'i32') }}} // SDL_Surface.pitch, assuming RGBA or indexed for now, // since that is what ImageData gives us in browsers {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*5', '0', 'buffer', 'void*') }}} // SDL_Surface.pixels {{{ makeSetValue('surf+Runtime.QUANTUM_SIZE*6', '0', '0', 'i32*') }}} // SDL_Surface.offset + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.format', '0', '-2042224636', 'i32') }}} // SDL_PIXELFORMAT_RGBA8888 {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.palette', '0', '0', 'i32') }}} // TODO - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BitsPerPixel', '0', '32', 'i8') }}} // TODO - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BytesPerPixel', '0', '4', 'i8') }}} // TODO + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BitsPerPixel', '0', 'bpp * 8', 'i8') }}} + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.BytesPerPixel', '0', 'bpp', 'i8') }}} - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Rmask', '0', '0xff', 'i32') }}} - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Gmask', '0', '0xff', 'i32') }}} - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Bmask', '0', '0xff', 'i32') }}} - {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Amask', '0', '0xff', 'i32') }}} + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Rmask', '0', 'rmask || 0x000000ff', 'i32') }}} + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Gmask', '0', 'gmask || 0x0000ff00', 'i32') }}} + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Bmask', '0', 'bmask || 0x00ff0000', 'i32') }}} + {{{ makeSetValue('pixelFormat + SDL.structs.PixelFormat.Amask', '0', 'amask || 0xff000000', 'i32') }}} // Decide if we want to use WebGL or not var useWebGL = (flags & 0x04000000) != 0; // SDL_OPENGL - + var canvas; + if (!usePageCanvas) { + canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + } else { + canvas = Module['canvas']; + } + var ctx = Browser.createContext(canvas, useWebGL, usePageCanvas); SDL.surfaces[surf] = { width: width, height: height, - canvas: Module['canvas'], - ctx: SDL.createContext(useWebGL), + canvas: canvas, + ctx: ctx, surf: surf, buffer: buffer, pixelFormat: pixelFormat, - alpha: 255 + alpha: 255, + flags: flags, + locked: 0, + usePageCanvas: usePageCanvas, + source: source, + + isFlagSet: function (flag) { + return flags & flag; + } }; + return surf; }, - createContext: function(useWebGL) { -#if !USE_TYPED_ARRAYS - if (useWebGL) { - Module.print('(USE_TYPED_ARRAYS needs to be enabled for WebGL)'); - return null; + // Copy data from the C++-accessible storage to the canvas backing + // for surface with HWPALETTE flag(8bpp depth) + copyIndexedColorData: function(surfData, rX, rY, rW, rH) { + // HWPALETTE works with palette + // setted by SDL_SetColors + if (!surfData.colors) { + return; } -#endif - try { - var ctx = Module.canvas.getContext(useWebGL ? 'experimental-webgl' : '2d'); - if (!ctx) throw 'Could not create canvas :('; - if (useWebGL) { - // Set the background of the WebGL canvas to black, because SDL gives us a - // window which has a black background by default. - Module.canvas.style.backgroundColor = "black"; + + var fullWidth = Module['canvas'].width; + var fullHeight = Module['canvas'].height; + + var startX = rX || 0; + var startY = rY || 0; + var endX = (rW || (fullWidth - startX)) + startX; + var endY = (rH || (fullHeight - startY)) + startY; + + var buffer = surfData.buffer; + var data = surfData.image.data; + var colors = surfData.colors; + + for (var y = startY; y < endY; ++y) { + var indexBase = y * fullWidth; + var colorBase = indexBase * 4; + for (var x = startX; x < endX; ++x) { + // HWPALETTE have only 256 colors (not rgba) + var index = {{{ makeGetValue('buffer + indexBase + x', '0', 'i8', null, true) }}}; + var color = colors[index] || [Math.floor(Math.random()*255),Math.floor(Math.random()*255),Math.floor(Math.random()*255)]; // XXX + var colorOffset = colorBase + x * 4; + + data[colorOffset ] = color[0]; + data[colorOffset +1] = color[1]; + data[colorOffset +2] = color[2]; + //unused: data[colorOffset +3] = color[3]; } - return Module.ctx = ctx; - } catch (e) { - Module.print('(canvas not available)'); - return null; } }, @@ -198,61 +336,233 @@ mergeInto(LibraryManager.library, { _free(SDL.surfaces[surf].buffer); _free(SDL.surfaces[surf].pixelFormat); _free(surf); - delete SDL.surfaces[surf]; + SDL.surfaces[surf] = null; }, receiveEvent: function(event) { switch(event.type) { - case 'keydown': case 'keyup': - //print('zz receive Event: ' + event.keyCode); + case 'mousemove': + // workaround for firefox bug 750111 + event['movementX'] = event['mozMovementX']; + event['movementY'] = event['mozMovementY']; + // fall through + case 'keydown': case 'keyup': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': + if (event.type == 'DOMMouseScroll') { + event = { + type: 'mousedown', + button: event.detail > 0 ? 4 : 3, + pageX: event.pageX, + pageY: event.pageY + }; + } else if (event.type == 'mousedown') { + SDL.DOMButtons[event.button] = 1; + } else if (event.type == 'mouseup') { + if (!SDL.DOMButtons[event.button]) return false; // ignore extra ups, can happen if we leave the canvas while pressing down, then return, + // since we add a mouseup in that case + SDL.DOMButtons[event.button] = 0; + } + SDL.events.push(event); + if (SDL.events.length >= 10000) { + Module.printErr('SDL event queue full, dropping earliest event'); + SDL.events.shift(); + } + if ((event.keyCode >= 37 && event.keyCode <= 40) || // arrow keys + event.keyCode == 32 || // space + event.keyCode == 33 || event.keyCode == 34) { // page up/down + event.preventDefault(); + } + break; + case 'mouseout': + // Un-press all pressed mouse buttons, because we might miss the release outside of the canvas + for (var i = 0; i < 3; i++) { + if (SDL.DOMButtons[i]) { + SDL.events.push({ + type: 'mouseup', + button: i, + pageX: event.pageX, + pageY: event.pageY + }); + SDL.DOMButtons[i] = 0; + } + } + break; } - //event.preventDefault(); return false; }, - + makeCEvent: function(event, ptr) { if (typeof event === 'number') { // This is a pointer to a native C event that was SDL_PushEvent'ed - _memcpy(ptr, event, SDL.structs.KeyboardEvent.__size__); + _memcpy(ptr, event, SDL.structs.KeyboardEvent.__size__); // XXX return; } switch(event.type) { - case 'keydown': case 'keyup': + case 'keydown': case 'keyup': { var down = event.type === 'keydown'; - var key = SDL.keyCodes[event.keyCode] || event.keyCode; + //Module.print('Received key event: ' + event.keyCode); + var key = event.keyCode; if (key >= 65 && key <= 90) { - key = String.fromCharCode(key).toLowerCase().charCodeAt(0); + key += 32; // make lowercase for SDL + } else { + key = SDL.keyCodes[event.keyCode] || event.keyCode; } - //print('zz passing over Event: ' + key); - {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'down ? 2 : 3', 'i8') }}} - {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.which', '1', 'i8') }}} + var scan; + if (key >= 1024) { + scan = key - 1024; + } else { + scan = SDL.scanCodes[key] || key; + } + {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}} + //{{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.which', '1', 'i32') }}} {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.state', 'down ? 1 : 0', 'i8') }}} + {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.repeat', '0', 'i8') }}} // TODO - {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.scancode', 'key', 'i8') }}} + {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.scancode', 'scan', 'i32') }}} {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.sym', 'key', 'i32') }}} {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.mod', '0', 'i32') }}} - //{{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.unicode', 'key', 'i32') }}} + {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.keysym + SDL.structs.keysym.unicode', 'key', 'i32') }}} + + {{{ makeSetValue('SDL.keyboardState', 'SDL.keyCodes[event.keyCode] || event.keyCode', 'event.type == "keydown"', 'i8') }}}; + SDL.shiftKey = event.shiftKey; + SDL.ctrlKey = event.ctrlKey; + SDL.altKey = event.altKey; + + break; + } + case 'mousedown': case 'mouseup': + if (event.type == 'mousedown') { + // SDL_BUTTON(x) is defined as (1 << ((x)-1)). SDL buttons are 1-3, + // and DOM buttons are 0-2, so this means that the below formula is + // correct. + SDL.buttonState |= 1 << event.button; + } else if (event.type == 'mouseup') { + SDL.buttonState = 0; + } + // fall through + case 'mousemove': { + if (Browser.pointerLock) { + // When the pointer is locked, calculate the coordinates + // based on the movement of the mouse. + var movementX = Browser.getMovementX(event); + var movementY = Browser.getMovementY(event); + var x = SDL.mouseX + movementX; + var y = SDL.mouseY + movementY; + } else { + // Otherwise, calculate the movement based on the changes + // in the coordinates. + var x = event.pageX - Module["canvas"].offsetLeft; + var y = event.pageY - Module["canvas"].offsetTop; + var movementX = x - SDL.mouseX; + var movementY = y - SDL.mouseY; + } + if (event.type != 'mousemove') { + var down = event.type === 'mousedown'; + {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.button', 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3 + {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.state', 'down ? 1 : 0', 'i8') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.x', 'x', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseButtonEvent.y', 'y', 'i32') }}}; + } else { + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.state', 'SDL.buttonState', 'i8') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.x', 'x', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.y', 'y', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.xrel', 'movementX', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.MouseMotionEvent.yrel', 'movementY', 'i32') }}}; + } + SDL.mouseX = x; + SDL.mouseY = y; break; - case 'keypress': break // TODO - default: - throw 'Unhandled SDL event: ' + event.type; + } + default: throw 'Unhandled SDL event: ' + event.type; + } + }, + + estimateTextWidth: function(fontData, text) { + var h = fontData.size; + var fontString = h + 'px sans-serif'; + // TODO: use temp context, not screen's, to avoid affecting its performance? + var tempCtx = SDL.surfaces[SDL.screen].ctx; + tempCtx.save(); + tempCtx.font = fontString; + var ret = tempCtx.measureText(text).width | 0; + tempCtx.restore(); + return ret; + }, + + // Sound + + allocateChannels: function(num) { // called from Mix_AllocateChannels and init + if (SDL.numChannels && SDL.numChannels >= num) return; + SDL.numChannels = num; + SDL.channels = []; + for (var i = 0; i < num; i++) { + SDL.channels[i] = { + audio: null, + volume: 1.0 + }; + } + }, + + setGetVolume: function(info, volume) { + if (!info) return 0; + var ret = info.volume * 128; // MIX_MAX_VOLUME + if (volume != -1) { + info.volume = volume / 128; + if (info.audio) info.audio.volume = info.volume; + } + return ret; + }, + + // Debugging + + debugSurface: function(surfData) { + console.log('dumping surface ' + [surfData.surf, surfData.source, surfData.width, surfData.height]); + var image = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height); + var data = image.data; + var num = Math.min(surfData.width, surfData.height); + for (var i = 0; i < num; i++) { + console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]); } } }, - SDL_Init__deps: ['$SDL'], + SDL_Linked_Version: function() { + if (SDL.version === null) { + SDL.version = _malloc(SDL.structs.version.__size__); + {{{ makeSetValue('SDL.version + SDL.structs.version.major', '0', '1', 'i8') }}} + {{{ makeSetValue('SDL.version + SDL.structs.version.minor', '0', '3', 'i8') }}} + {{{ makeSetValue('SDL.version + SDL.structs.version.patch', '0', '0', 'i8') }}} + } + return SDL.version; + }, + SDL_Init: function(what) { SDL.startTime = Date.now(); - ['keydown', 'keyup', 'keypress'].forEach(function(event) { + ['keydown', 'keyup'].forEach(function(event) { addEventListener(event, SDL.receiveEvent, true); }); + SDL.keyboardState = _malloc(0x10000); + _memset(SDL.keyboardState, 0, 0x10000); + // Initialize this structure carefully for closure + SDL.DOMEventToSDLEvent['keydown'] = 0x300 /* SDL_KEYDOWN */; + SDL.DOMEventToSDLEvent['keyup'] = 0x301 /* SDL_KEYUP */; + SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */; + SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */; + SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; return 0; // success }, - SDL_WasInit: function() { return 0 }, // TODO + SDL_WasInit__deps: ['SDL_Init'], + SDL_WasInit: function() { + if (SDL.startTime === null) { + _SDL_Init(); + } + return 1; + }, SDL_GetVideoInfo: function() { // %struct.SDL_VideoInfo = type { i32, i32, %struct.SDL_PixelFormat*, i32, i32 } - 5 fields of quantum size @@ -269,61 +579,146 @@ mergeInto(LibraryManager.library, { return -1; // -1 == all modes are ok. TODO }, - SDL_GL_SetAttribute: function(attr, value) { - // TODO + SDL_VideoModeOK: function(width, height, depth, flags) { + // SDL_VideoModeOK returns 0 if the requested mode is not supported under any bit depth, or returns the + // bits-per-pixel of the closest available mode with the given width, height and requested surface flags + return depth; // all modes are ok. + }, + + SDL_VideoDriverName: function(buf, max_size) { + if (SDL.startTime === null) { + return 0; //return NULL + } + //driverName - emscripten_sdl_driver + var driverName = [101, 109, 115, 99, 114, 105, 112, 116, 101, + 110, 95, 115, 100, 108, 95, 100, 114, 105, 118, 101, 114]; + + var index = 0; + var size = driverName.length; + + if (max_size <= size) { + size = max_size - 1; //-1 cause null-terminator + } + + while (index < size) { + var value = driverName[index]; + {{{ makeSetValue('buf', 'index', 'value', 'i8') }}}; + index++; + } + + {{{ makeSetValue('buf', 'index', '0', 'i8') }}}; + return buf; }, SDL_SetVideoMode: function(width, height, depth, flags) { + ['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mouseout'].forEach(function(event) { + Module['canvas'].addEventListener(event, SDL.receiveEvent, true); + }); Module['canvas'].width = width; Module['canvas'].height = height; - return SDL.screen = SDL.makeSurface(width, height, flags); + return SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen'); + }, + + SDL_GetVideoSurface: function() { + return SDL.screen; + }, + + SDL_QuitSubSystem: function(flags) { + Module.print('SDL_QuitSubSystem called (and ignored)'); }, SDL_Quit: function() { + for (var i = 0; i < SDL.audios; i++) { + SDL.audios[i].pause(); + } Module.print('SDL_Quit called (and ignored)'); }, + // Copy data from the canvas backing to a C++-accessible storage SDL_LockSurface: function(surf) { var surfData = SDL.surfaces[surf]; + + surfData.locked++; + if (surfData.locked > 1) return 0; + if (!surfData.image) { surfData.image = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height); - var data = surfData.image.data; - var num = data.length; - for (var i = 0; i < num/4; i++) { - data[i*4+3] = 255; // opacity, as canvases blend alpha + if (surf == SDL.screen) { + var data = surfData.image.data; + var num = data.length; + for (var i = 0; i < num/4; i++) { + data[i*4+3] = 255; // opacity, as canvases blend alpha + } } } - if (SDL.defaults.copyScreenOnLock) { + if (SDL.defaults.copyOnLock) { // Copy pixel data to somewhere accessible to 'C/C++' + if (surfData.isFlagSet(0x00200000 /* SDL_HWPALETTE */)) { + // If this is neaded then + // we should compact the data from 32bpp to 8bpp index. + // I think best way to implement this is use + // additional colorMap hash (color->index). + // Something like this: + // + // var size = surfData.width * surfData.height; + // var data = ''; + // for (var i = 0; i<size; i++) { + // var color = SDL.translateRGBAToColor( + // surfData.image.data[i*4 ], + // surfData.image.data[i*4 +1], + // surfData.image.data[i*4 +2], + // 255); + // var index = surfData.colorMap[color]; + // {{{ makeSetValue('surfData.buffer', 'i', 'index', 'i8') }}}; + // } + throw 'CopyOnLock is not supported for SDL_LockSurface with SDL_HWPALETTE flag set' + new Error().stack; + } else { +#if USE_TYPED_ARRAYS == 2 + HEAPU8.set(surfData.image.data, surfData.buffer); +#else var num2 = surfData.image.data.length; for (var i = 0; i < num2; i++) { {{{ makeSetValue('surfData.buffer', 'i', 'surfData.image.data[i]', 'i8') }}}; } +#endif + } } + // Mark in C/C++-accessible SDL structure // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ... // So we have fields all of the same size, and 5 of them before us. // TODO: Use macros like in library.js {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}}; + + return 0; }, + // Copy data from the C++-accessible storage to the canvas backing SDL_UnlockSurface: function(surf) { var surfData = SDL.surfaces[surf]; + + surfData.locked--; + if (surfData.locked > 0) return; + // Copy pixel data to image - var num = surfData.image.data.length; - if (!surfData.colors) { + if (surfData.isFlagSet(0x00200000 /* SDL_HWPALETTE */)) { + SDL.copyIndexedColorData(surfData); + } else if (!surfData.colors) { + var num = surfData.image.data.length; var data = surfData.image.data; var buffer = surfData.buffer; #if USE_TYPED_ARRAYS == 2 assert(buffer % 4 == 0, 'Invalid buffer offset: ' + buffer); var src = buffer >> 2; var dst = 0; + var isScreen = surf == SDL.screen; while (dst < num) { + // TODO: access underlying data buffer and write in 32-bit chunks or more var val = HEAP32[src]; // This is optimized. Instead, we could do {{{ makeGetValue('buffer', 'dst', 'i32') }}}; - data[dst] = val & 0xff; + data[dst ] = val & 0xff; data[dst+1] = (val >> 8) & 0xff; data[dst+2] = (val >> 16) & 0xff; - data[dst+3] = 0xff; + data[dst+3] = isScreen ? 0xff : ((val >> 24) & 0xff); src++; dst += 4; } @@ -331,7 +726,7 @@ mergeInto(LibraryManager.library, { for (var i = 0; i < num; i++) { // We may need to correct signs here. Potentially you can hardcode a write of 255 to alpha, say, and // the compiler may decide to write -1 in the llvm bitcode... - data[i] = {{{ makeGetValue('buffer', 'i', 'i8') + (CORRECT_SIGNS ? '&0xff' : '') }}}; + data[i] = {{{ makeGetValue('buffer', 'i', 'i8', null, true) }}}; if (i % 4 == 3) data[i] = 0xff; } #endif @@ -345,7 +740,7 @@ mergeInto(LibraryManager.library, { var base = y*width*4; for (var x = 0; x < width; x++) { // See comment above about signs - var val = {{{ makeGetValue('s++', '0', 'i8') + (CORRECT_SIGNS ? '&0xff' : '') }}}; + var val = {{{ makeGetValue('s++', '0', 'i8', null, true) }}}; var color = colors[val] || [Math.floor(Math.random()*255),Math.floor(Math.random()*255),Math.floor(Math.random()*255)]; // XXX var start = base + x*4; data[start] = color[0]; @@ -361,15 +756,20 @@ mergeInto(LibraryManager.library, { }, SDL_Flip: function(surf) { - // We actually do this in Unlock... + // We actually do this in Unlock, since the screen surface has as its canvas + // backing the page canvas element }, SDL_UpdateRect: function(surf, x, y, w, h) { // We actually do the whole screen in Unlock... }, + SDL_UpdateRects: function(surf, numrects, rects) { + // We actually do the whole screen in Unlock... + }, + SDL_Delay: function(delay) { - Module.print('SDL_Delay called! - potential infinite loop'); + throw 'SDL_Delay called! Potential infinite loop, quitting. ' + new Error().stack; }, SDL_WM_SetCaption: function(title, icon) { @@ -381,6 +781,37 @@ mergeInto(LibraryManager.library, { // TODO }, + SDL_GetKeyboardState: function() { + return SDL.keyboardState; + }, + + SDL_GetKeyState__deps: ['SDL_GetKeyboardState'], + SDL_GetKeyState: function() { + return _SDL_GetKeyboardState(); + }, + + SDL_GetModState: function() { + // TODO: numlock, capslock, etc. + return (SDL.shiftKey ? 0x0001 & 0x0002 : 0) | // KMOD_LSHIFT & KMOD_RSHIFT + (SDL.ctrlKey ? 0x0040 & 0x0080 : 0) | // KMOD_LCTRL & KMOD_RCTRL + (SDL.altKey ? 0x0100 & 0x0200 : 0); // KMOD_LALT & KMOD_RALT + }, + + SDL_GetMouseState: function(x, y) { + if (x) {{{ makeSetValue('x', '0', 'SDL.mouseX', 'i32') }}}; + if (y) {{{ makeSetValue('y', '0', 'SDL.mouseY', 'i32') }}}; + return 0; + }, + + SDL_WarpMouse: function(x, y) { + return; // TODO: implement this in a non-buggy way. Need to keep relative mouse movements correct after calling this + SDL.events.push({ + type: 'mousemove', + pageX: x + Module['canvas'].offsetLeft, + pageY: y + Module['canvas'].offsetTop + }); + }, + SDL_ShowCursor: function(toggle) { // TODO }, @@ -390,55 +821,74 @@ mergeInto(LibraryManager.library, { }, SDL_CreateRGBSurface: function(flags, width, height, depth, rmask, gmask, bmask, amask) { - return SDL.makeSurface(width, height, flags); + return SDL.makeSurface(width, height, flags, false, 'CreateRGBSurface', rmask, gmask, bmask, amask); + }, + + SDL_DisplayFormatAlpha: function(surf) { + var oldData = SDL.surfaces[surf]; + var ret = SDL.makeSurface(oldData.width, oldData.height, oldData.flags, false, 'copy:' + oldData.source); + var newData = SDL.surfaces[ret]; + //newData.ctx.putImageData(oldData.ctx.getImageData(0, 0, oldData.width, oldData.height), 0, 0); + newData.ctx.drawImage(oldData.canvas, 0, 0); + return ret; }, SDL_FreeSurface: function(surf) { - SDL.freeSurface(surf); + if (surf) SDL.freeSurface(surf); }, SDL_UpperBlit: function(src, srcrect, dst, dstrect) { - assert(!srcrect && !dstrect); // TODO var srcData = SDL.surfaces[src]; var dstData = SDL.surfaces[dst]; - assert(srcData.width === dstData.width && srcData.height === dstData.height); - {{{ makeCopyValues('dstData.buffer', 'srcData.buffer', 'srcData.width*srcData.height*4', 'i8', null, 1 |