Skip to content

Instantly share code, notes, and snippets.

@Sgeo
Last active October 1, 2018 03:54
Show Gist options
  • Save Sgeo/c1d8bb4693d7edc0861d53a0b5cb0894 to your computer and use it in GitHub Desktop.
Save Sgeo/c1d8bb4693d7edc0861d53a0b5cb0894 to your computer and use it in GitHub Desktop.
vice32.js .. modifications off VICE 3.2 branch.
echo compiling js...
emcc -O2 -o ../js/x64.js -s DOUBLE_MODE=0 -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1 -s EMTERPRETIFY_WHITELIST="[ \
'_sdl_ui_trap_top', \
'_sdl_ui_menu_display', \
'_attach_disk_callback', \
'_sdl_ui_file_selection_dialog', \
'_sdl_ui_menu_poll_input', \
'_custom_volume_callback', \
'_sdl_ui_slider_input_dialog', \
'_autostart_callback', \
'_SDL_WaitEvent', \
'_pause_trap_top', \
'_sdl_ui_readline', \
'_sdl_ui_readline_input',
'_sdl_ui_text_input_dialog', \
'_sdl_ui_menu_string_helper', \
'_string_RsDevice1_callback', \
'_string_RsDevice2_callback', \
'_string_RsDevice3_callback', \
'_string_RsDevice4_callback', \
'_monitor_callback', \
'_monitor_startup', \
'_uimon_in', \
'_uimon_get_in', \
'_custom_ui_keyset_callback', \
'_custom_keyset_callback', \
'_sdl_ui_poll_event', \
'_dummy_entry_must_be_last' \
]" -s PRECISE_I64_MATH=0 -s WARN_ON_UNDEFINED_SYMBOLS=1 -s TOTAL_MEMORY=33554432 -s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=1 -s WASM=1 -s SINGLE_FILE=1 --js-library ../library-vice.js \
-s EXPORTED_FUNCTIONS="[ \
'_autostart_autodetect', \
'_cmdline_options_string', \
'_file_system_attach_disk', \
'_file_system_detach_disk', \
'_file_system_get_disk_name', \
'_joystick_set_value_and', \
'_joystick_set_value_or', \
'_keyboard_key_pressed', \
'_keyboard_key_released', \
'_machine_trigger_reset', \
'_main', \
'_set_playback_enabled' \
]" \
--embed-file "data/C64@/C64" --embed-file "data/DRIVES@/DRIVES" --embed-file "data/fonts@/fonts" \
src/*.o \
src/arch/sdl/*.o \
src/c64/*.o \
src/c64/cart/*.o \
src/core/*.o \
src/diskimage/*.o \
src/drive/*.o \
src/drive/iec/*.o \
src/drive/iec/c64exp/*.o \
src/drive/iec/plus4exp/*.o \
src/drive/iec128dcr/*.o \
src/drive/iecieee/*.o \
src/drive/ieee/*.o \
src/drive/tcbm/*.o \
src/fileio/*.o \
src/fsdevice/*.o \
src/gfxoutputdrv/*.o \
src/iecbus/*.o \
src/imagecontents/*.o \
src/joyport/*.o \
src/lib/p64/*.o \
src/mididrv/*.o \
src/monitor/*.o \
src/parallel/*.o \
src/printerdrv/*.o \
src/raster/*.o \
src/resid/*.o \
src/rs232drv/*.o \
src/rtc/*.o \
src/samplerdrv/*.o \
src/serial/*.o \
src/sid/*.o \
src/sounddrv/*.o \
src/tape/*.o \
src/tapeport/*.o \
src/userport/*.o \
src/vdrive/*.o \
src/vicii/*.o \
src/video/*.o
export ar_check=no
./configure --enable-sdlui --with-sdlsound --without-png --without-uithreads
make
emconfigure ./configure --enable-sdlui --with-sdlsound --without-png --without-uithreads
emmake make
emmake make clean
emmake make x64
cd ..
./build-x64
//"use strict";
// See browser tests for examples (tests/runner.py, search for sdl_). Run with
// python tests/runner.py browser
// Notes:
// SDL_VIDEORESIZE: This is sent when the canvas is resized. Note that the user
// cannot manually do so, so this is only sent when the
// program manually resizes it (emscripten_set_canvas_size
// or otherwise).
var LibrarySDL = {
$SDL__deps: [
#if NO_FILESYSTEM == 0
'$FS',
#endif
'$PATH', '$Browser', 'SDL_GetTicks', 'SDL_LockSurface',
],
$SDL: {
defaults: {
width: 320,
height: 200,
// If true, SDL_LockSurface will copy the contents of each surface back to the Emscripten HEAP so that C code can access it. If false,
// the surface contents are captured only back to JS code.
copyOnLock: true,
// If true, SDL_LockSurface will discard the contents of each surface when SDL_LockSurface() is called. This greatly improves performance
// of SDL_LockSurface(). If discardOnLock is true, copyOnLock is ignored.
discardOnLock: false,
// If true, emulate compatibility with desktop SDL by ignoring alpha on the screen frontbuffer canvas. Setting this to false will improve
// performance considerably and enables alpha-blending on the frontbuffer, so be sure to properly write 0xFF alpha for opaque pixels
// if you set this to false!
opaqueFrontBuffer: true
},
version: null,
surfaces: {},
// A pool of freed canvas elements. Reusing them avoids GC pauses.
canvasPool: [],
events: [],
fonts: [null],
// The currently preloaded audio elements ready to be played
audios: [null],
rwops: [null],
// The currently playing audio element. There's only one music track.
music: {
audio: null,
volume: 1.0
},
mixerFrequency: 22050,
mixerFormat: {{{ cDefine('AUDIO_S16LSB') }}}, //0x8010, // AUDIO_S16LSB
mixerNumChannels: 2,
mixerChunkSize: 1024,
channelMinimumNumber: 0,
GL: false, // Set to true if we call SDL_SetVideoMode with SDL_OPENGL, and if so, we do not create 2D canvases&contexts for blitting
// Note that images loaded before SDL_SetVideoMode will not get this optimization
// all possible GL attributes, with their default value
glAttributes: {
0: 3, /* SDL_GL_RED_SIZE */
1: 3, /* SDL_GL_GREEN_SIZE */
2: 2, /* SDL_GL_BLUE_SIZE */
3: 0, /* SDL_GL_ALPHA_SIZE */
4: 0, /* SDL_GL_BUFFER_SIZE */
5: 1, /* SDL_GL_DOUBLEBUFFER */
6: 16, /* SDL_GL_DEPTH_SIZE */
7: 0, /* SDL_GL_STENCIL_SIZE */
8: 0, /* SDL_GL_ACCUM_RED_SIZE */
9: 0, /* SDL_GL_ACCUM_GREEN_SIZE */
10: 0, /* SDL_GL_ACCUM_BLUE_SIZE */
11: 0, /* SDL_GL_ACCUM_ALPHA_SIZE */
12: 0, /* SDL_GL_STEREO */
13: 0, /* SDL_GL_MULTISAMPLEBUFFERS */
14: 0, /* SDL_GL_MULTISAMPLESAMPLES */
15: 1, /* SDL_GL_ACCELERATED_VISUAL */
16: 0, /* SDL_GL_RETAINED_BACKING */
17: 0, /* SDL_GL_CONTEXT_MAJOR_VERSION */
18: 0 /* SDL_GL_CONTEXT_MINOR_VERSION */
},
keyboardState: null,
keyboardMap: {},
canRequestFullscreen: false,
isRequestingFullscreen: false,
textInput: false,
startTime: null,
initFlags: 0, // The flags passed to SDL_Init
buttonState: 0,
modState: 0,
DOMButtons: [0, 0, 0],
DOMEventToSDLEvent: {},
TOUCH_DEFAULT_ID: 0, // Our default deviceID for touch events (we get nothing from the browser)
eventHandler: null,
eventHandlerContext: null,
eventHandlerTemp: 0,
keyCodes: { // References: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values and https://github.com/hoffa/nSDL/blob/master/include/SDL_keysym.h
// Array indicates multiple-location key: [standard, left, right, numpad]
// SDL, despite defining SDLK_EXCLAIM, doesn't seem to actually use that.
"Alt": [0, 308, 307],
"CapsLock": 301,
"Control": [0, 306, 305],
"Shift": [0, 304, 303],
"Enter": 13,
"Tab": 9,
" ": 32,
"ArrowDown": [274, 0, 0, 258],
"ArrowLeft": [276, 0, 0, 260],
"ArrowRight": [275, 0, 0, 262],
"ArrowUp": [273, 0, 0, 264],
"End": [279, 0, 0, 257],
"Home": [278, 0, 0, 263],
"PageDown": [280, 0, 0, 259],
"PageUp": [281, 0, 0, 265],
"Backspace": 8,
"Clear": [12, 0, 0, 261],
"Delete": 127,
"Insert": [227, 0, 0, 256],
"Escape": 27,
"Pause": 19,
"~": 96,
"!": 49,
"@": 50,
"#": 51,
"$": 52,
"%": 53,
"^": 54,
"&": 55,
"*": 56,
"(": 57,
")": 48,
"_": 45,
"+": 61,
":": 59,
"<": 44,
">": 46,
"?": 47,
"{": 91,
"}": 93,
"|": 92,
"F1": 282,
"F2": 283,
"F3": 284,
"F4": 285,
"F5": 286,
"F6": 287,
"F7": 288,
"F8": 289,
"F9": 290,
"F10": 291,
"F11": 292,
"F12": 293,
"F13": 294,
"F14": 295,
"F15": 296,
"0": [48, 0, 0, 256], // Numeric keypad support
"1": [49, 0, 0, 257],
"2": [50, 0, 0, 258],
"3": [51, 0, 0, 259],
"4": [52, 0, 0, 260],
"5": [53, 0, 0, 261],
"6": [54, 0, 0, 262],
"7": [55, 0, 0, 263],
"8": [56, 0, 0, 264],
"9": [57, 0, 0, 265],
},
scanCodes: { // SDL keycode ==> SDL scancode. See SDL_scancode.h
8: 42, // backspace
9: 43, // tab
13: 40, // return
27: 41, // escape
32: 44, // space
35: 204, // hash
39: 53, // grave
44: 54, // comma
46: 55, // period
47: 56, // slash
48: 39, // 0
49: 30, // 1
50: 31, // 2
51: 32, // 3
52: 33, // 4
53: 34, // 5
54: 35, // 6
55: 36, // 7
56: 37, // 8
57: 38, // 9
58: 203, // colon
59: 51, // semicolon
61: 46, // equals
91: 47, // left bracket
92: 49, // backslash
93: 48, // right bracket
96: 52, // apostrophe
97: 4, // A
98: 5, // B
99: 6, // C
100: 7, // D
101: 8, // E
102: 9, // F
103: 10, // G
104: 11, // H
105: 12, // I
106: 13, // J
107: 14, // K
108: 15, // L
109: 16, // M
110: 17, // N
111: 18, // O
112: 19, // P
113: 20, // Q
114: 21, // R
115: 22, // S
116: 23, // T
117: 24, // U
118: 25, // V
119: 26, // W
120: 27, // X
121: 28, // Y
122: 29, // Z
127: 76, // delete
305: 224, // ctrl
308: 226, // alt
316: 70, // print screen
},
keyNames: {0: "unknown key",
8: "Backspace",
9: "Tab",
12: "Clear",
13: "Enter",
19: "Pause",
27: "Escape",
32: "Space",
44: "<",
45: "_",
46: ">",
47: "?",
48: ")",
49: "!",
50: "@",
51: "#",
52: "$",
53: "%",
54: "^",
55: "&",
56: "*",
57: "(",
59: ":",
61: "+",
91: "{",
92: "|",
93: "}",
96: "~",
127: "Delete",
227: "Insert",
256: "Insert",
257: "End",
258: "ArrowDown",
259: "PageDown",
260: "ArrowLeft",
261: "Clear",
262: "ArrowRight",
263: "Home",
264: "ArrowUp",
265: "PageUp",
273: "ArrowUp",
274: "ArrowDown",
275: "ArrowRight",
276: "ArrowLeft",
278: "Home",
279: "End",
280: "PageDown",
281: "PageUp",
282: "F1",
283: "F2",
284: "F3",
285: "F4",
286: "F5",
287: "F6",
288: "F7",
289: "F8",
290: "F9",
291: "F10",
292: "F11",
293: "F12",
294: "F13",
295: "F14",
296: "F15",
301: "CapsLock",
303: "Shift",
304: "Shift",
305: "Control",
306: "Control",
307: "Alt",
308: "Alt"},
loadRect: function(rect) {
return {
x: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.x, '0', 'i32') }}},
y: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.y, '0', 'i32') }}},
w: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.w, '0', 'i32') }}},
h: {{{ makeGetValue('rect + ' + C_STRUCTS.SDL_Rect.h, '0', 'i32') }}}
};
},
updateRect: function(rect, r) {
{{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.x, 'r.x', 'i32') }}};
{{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.y, 'r.y', 'i32') }}};
{{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.w, 'r.w', 'i32') }}};
{{{ makeSetValue('rect', C_STRUCTS.SDL_Rect.h, 'r.h', 'i32') }}};
},
intersectionOfRects: function(first, second) {
var leftX = Math.max(first.x, second.x);
var leftY = Math.max(first.y, second.y);
var rightX = Math.min(first.x + first.w, second.x + second.w);
var rightY = Math.min(first.y + first.h, second.y + second.h);
return {
x: leftX,
y: leftY,
w: Math.max(leftX, rightX) - leftX,
h: Math.max(leftY, rightY) - leftY
}
},
checkPixelFormat: function(fmt) {
#if ASSERTIONS
// Canvas screens are always RGBA.
var format = {{{ makeGetValue('fmt', C_STRUCTS.SDL_PixelFormat.format, 'i32') }}};
if (format != {{{ cDefine('SDL_PIXELFORMAT_RGBA8888') }}}) {
warnOnce('Unsupported pixel format!');
}
#endif
},
// 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&0xff) + ',' + (rgba>>8 & 0xff) + ',' + (rgba>>16 & 0xff) + ',' + (rgba>>>24)/0xff + ')';
},
translateRGBAToCSSRGBA: function(r, g, b, a) {
return 'rgba(' + (r&0xff) + ',' + (g&0xff) + ',' + (b&0xff) + ',' + (a&0xff)/255 + ')';
},
translateRGBAToColor: function(r, g, b, a) {
return r | g << 8 | b << 16 | a << 24;
},
makeSurface: function(width, height, flags, usePageCanvas, source, rmask, gmask, bmask, amask) {
flags = flags || 0;
var is_SDL_HWSURFACE = flags & 0x00000001;
var is_SDL_HWPALETTE = flags & 0x00200000;
var is_SDL_OPENGL = flags & 0x04000000;
var surf = _malloc({{{ C_STRUCTS.SDL_Surface.__size__ }}});
var pixelFormat = _malloc({{{ C_STRUCTS.SDL_PixelFormat.__size__ }}});
//surface with SDL_HWPALETTE flag is 8bpp surface (1 byte)
var bpp = is_SDL_HWPALETTE ? 1 : 4;
var buffer = 0;
// preemptively initialize this for software surfaces,
// otherwise it will be lazily initialized inside of SDL_LockSurface
if (!is_SDL_HWSURFACE && !is_SDL_OPENGL) {
buffer = _malloc(width * height * 4);
}
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.flags, 'flags', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.format, 'pixelFormat', 'void*') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.w, 'width', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.h, 'height', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pitch, 'width * bpp', 'i32') }}}; // assuming RGBA or indexed for now,
// since that is what ImageData gives us in browsers
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'buffer', 'void*') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.x, '0', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.y, '0', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.w, 'Module["canvas"].width', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.clip_rect+C_STRUCTS.SDL_Rect.h, 'Module["canvas"].height', 'i32') }}};
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.refcount, '1', 'i32') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.format, cDefine('SDL_PIXELFORMAT_RGBA8888'), 'i32') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.palette, '0', 'i32') }}};// TODO
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BitsPerPixel, 'bpp * 8', 'i8') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.BytesPerPixel, 'bpp', 'i8') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Rmask, 'rmask || 0x000000ff', 'i32') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Gmask, 'gmask || 0x0000ff00', 'i32') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Bmask, 'bmask || 0x00ff0000', 'i32') }}};
{{{ makeSetValue('pixelFormat', C_STRUCTS.SDL_PixelFormat.Amask, 'amask || 0xff000000', 'i32') }}};
// Decide if we want to use WebGL or not
SDL.GL = SDL.GL || is_SDL_OPENGL;
var canvas;
if (!usePageCanvas) {
if (SDL.canvasPool.length > 0) {
canvas = SDL.canvasPool.pop();
} else {
canvas = document.createElement('canvas');
}
canvas.width = width;
canvas.height = height;
} else {
canvas = Module['canvas'];
}
var webGLContextAttributes = {
antialias: ((SDL.glAttributes[13 /*SDL_GL_MULTISAMPLEBUFFERS*/] != 0) && (SDL.glAttributes[14 /*SDL_GL_MULTISAMPLESAMPLES*/] > 1)),
depth: (SDL.glAttributes[6 /*SDL_GL_DEPTH_SIZE*/] > 0),
stencil: (SDL.glAttributes[7 /*SDL_GL_STENCIL_SIZE*/] > 0),
alpha: (SDL.glAttributes[3 /*SDL_GL_ALPHA_SIZE*/] > 0)
};
var ctx = Browser.createContext(canvas, is_SDL_OPENGL, usePageCanvas, webGLContextAttributes);
SDL.surfaces[surf] = {
width: width,
height: height,
canvas: canvas,
ctx: ctx,
surf: surf,
buffer: buffer,
pixelFormat: pixelFormat,
alpha: 255,
flags: flags,
locked: 0,
usePageCanvas: usePageCanvas,
source: source,
isFlagSet: function(flag) {
return flags & flag;
}
};
return surf;
},
// 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;
}
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;
if (!surfData.image.data32) {
surfData.image.data32 = new Uint32Array(surfData.image.data.buffer);
}
var data32 = surfData.image.data32;
var colors32 = surfData.colors32;
for (var y = startY; y < endY; ++y) {
var base = y * fullWidth;
for (var x = startX; x < endX; ++x) {
data32[base + x] = colors32[{{{ makeGetValue('buffer + base + x', '0', 'i8', null, true) }}}];
}
}
},
freeSurface: function(surf) {
var refcountPointer = surf + {{{ C_STRUCTS.SDL_Surface.refcount }}};
var refcount = {{{ makeGetValue('refcountPointer', '0', 'i32') }}};
if (refcount > 1) {
{{{ makeSetValue('refcountPointer', '0', 'refcount - 1', 'i32') }}};
return;
}
var info = SDL.surfaces[surf];
if (!info.usePageCanvas && info.canvas) SDL.canvasPool.push(info.canvas);
if (info.buffer) _free(info.buffer);
_free(info.pixelFormat);
_free(surf);
SDL.surfaces[surf] = null;
if (surf === SDL.screen) {
SDL.screen = null;
}
},
blitSurface: function(src, srcrect, dst, dstrect, scale) {
var srcData = SDL.surfaces[src];
var dstData = SDL.surfaces[dst];
var sr, dr;
if (srcrect) {
sr = SDL.loadRect(srcrect);
} else {
sr = { x: 0, y: 0, w: srcData.width, h: srcData.height };
}
if (dstrect) {
dr = SDL.loadRect(dstrect);
} else {
dr = { x: 0, y: 0, w: srcData.width, h: srcData.height };
}
if (dstData.clipRect) {
var widthScale = (!scale || sr.w === 0) ? 1 : sr.w / dr.w;
var heightScale = (!scale || sr.h === 0) ? 1 : sr.h / dr.h;
dr = SDL.intersectionOfRects(dstData.clipRect, dr);
sr.w = dr.w * widthScale;
sr.h = dr.h * heightScale;
if (dstrect) {
SDL.updateRect(dstrect, dr);
}
}
var blitw, blith;
if (scale) {
blitw = dr.w; blith = dr.h;
} else {
blitw = sr.w; blith = sr.h;
}
if (sr.w === 0 || sr.h === 0 || blitw === 0 || blith === 0) {
return 0;
}
var oldAlpha = dstData.ctx.globalAlpha;
dstData.ctx.globalAlpha = srcData.alpha/255;
dstData.ctx.drawImage(srcData.canvas, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, blitw, blith);
dstData.ctx.globalAlpha = oldAlpha;
if (dst != SDL.screen) {
// XXX As in IMG_Load, for compatibility we write out |pixels|
warnOnce('WARNING: copying canvas data to memory for compatibility');
_SDL_LockSurface(dst);
dstData.locked--; // The surface is not actually locked in this hack
}
return 0;
},
// the browser sends out touchstart events with the whole group of touches
// even if we received a previous touchstart for a specific touch identifier.
// You can test this by pressing one finger to the screen, then another. You'll
// receive two touchstart events, the first with a touches count of 1 the second
// with a touches count of two.
// SDL sends out a new touchstart event for only each newly started touch so to
// emulate this, we keep track of previously started touches.
downFingers: {},
savedKeydown: null,
receiveEvent: function(event) {
function unpressAllPressedKeys() {
// Un-press all pressed keys: TODO
for (var code in SDL.keyboardMap) {
SDL.events.push({
type: 'keyup',
keyCode: SDL.keyboardMap[code]
});
}
};
switch(event.type) {
case 'touchstart': case 'touchmove': {
event.preventDefault();
var touches = [];
// Clear out any touchstart events that we've already processed
if (event.type === 'touchstart') {
for (var i = 0; i < event.touches.length; i++) {
var touch = event.touches[i];
if (SDL.downFingers[touch.identifier] != true) {
SDL.downFingers[touch.identifier] = true;
touches.push(touch);
}
}
} else {
touches = event.touches;
}
var firstTouch = touches[0];
if (firstTouch) {
if (event.type == 'touchstart') {
SDL.DOMButtons[0] = 1;
}
var mouseEventType;
switch(event.type) {
case 'touchstart': mouseEventType = 'mousedown'; break;
case 'touchmove': mouseEventType = 'mousemove'; break;
}
var mouseEvent = {
type: mouseEventType,
button: 0,
pageX: firstTouch.clientX,
pageY: firstTouch.clientY
};
SDL.events.push(mouseEvent);
}
for (var i = 0; i < touches.length; i++) {
var touch = touches[i];
SDL.events.push({
type: event.type,
touch: touch
});
};
break;
}
case 'touchend': {
event.preventDefault();
// Remove the entry in the SDL.downFingers hash
// because the finger is no longer down.
for(var i = 0; i < event.changedTouches.length; i++) {
var touch = event.changedTouches[i];
if (SDL.downFingers[touch.identifier] === true) {
delete SDL.downFingers[touch.identifier];
}
}
var mouseEvent = {
type: 'mouseup',
button: 0,
pageX: event.changedTouches[0].clientX,
pageY: event.changedTouches[0].clientY
};
SDL.DOMButtons[0] = 0;
SDL.events.push(mouseEvent);
for (var i = 0; i < event.changedTouches.length; i++) {
var touch = event.changedTouches[i];
SDL.events.push({
type: 'touchend',
touch: touch
});
};
break;
}
case 'DOMMouseScroll': case 'mousewheel': case 'wheel':
var delta = -Browser.getMouseWheelDelta(event); // Flip the wheel direction to translate from browser wheel direction (+:down) to SDL direction (+:up)
delta = (delta == 0) ? 0 : (delta > 0 ? Math.max(delta, 1) : Math.min(delta, -1)); // Quantize to integer so that minimum scroll is at least +/- 1.
// Simulate old-style SDL events representing mouse wheel input as buttons
var button = delta > 0 ? 3 /*SDL_BUTTON_WHEELUP-1*/ : 4 /*SDL_BUTTON_WHEELDOWN-1*/; // Subtract one since JS->C marshalling is defined to add one back.
SDL.events.push({ type: 'mousedown', button: button, pageX: event.pageX, pageY: event.pageY });
SDL.events.push({ type: 'mouseup', button: button, pageX: event.pageX, pageY: event.pageY });
// Pass a delta motion event.
SDL.events.push({ type: 'wheel', deltaX: 0, deltaY: delta });
event.preventDefault(); // If we don't prevent this, then 'wheel' event will be sent again by the browser as 'DOMMouseScroll' and we will receive this same event the second time.
break;
case 'mousemove':
if (SDL.DOMButtons[0] === 1) {
SDL.events.push({
type: 'touchmove',
touch: {
identifier: 0,
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
pageX: event.pageX,
pageY: event.pageY
}
});
}
if (Browser.pointerLock) {
// workaround for firefox bug 750111
if ('mozMovementX' in event) {
event['movementX'] = event['mozMovementX'];
event['movementY'] = event['mozMovementY'];
}
// workaround for Firefox bug 782777
if (event['movementX'] == 0 && event['movementY'] == 0) {
// ignore a mousemove event if it doesn't contain any movement info
// (without pointer lock, we infer movement from pageX/pageY, so this check is unnecessary)
event.preventDefault();
return;
}
}
// fall through
case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup':
// If we preventDefault on keydown events, the subsequent keypress events
// won't fire. However, it's fine (and in some cases necessary) to
// preventDefault for keys that don't generate a character. Otherwise,
// preventDefault is the right thing to do in general.
if (event.type !== 'keydown' || (!SDL.unicode && !SDL.textInput) || (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */)) {
event.preventDefault();
}
if (event.type == 'mousedown') {
SDL.DOMButtons[event.button] = 1;
SDL.events.push({
type: 'touchstart',
touch: {
identifier: 0,
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
pageX: event.pageX,
pageY: event.pageY
}
});
} else if (event.type == 'mouseup') {
// ignore extra ups, can happen if we leave the canvas while pressing down, then return,
// since we add a mouseup in that case
if (!SDL.DOMButtons[event.button]) {
return;
}
SDL.events.push({
type: 'touchend',
touch: {
identifier: 0,
deviceID: {{{ cDefine('SDL_TOUCH_MOUSEID') }}},
pageX: event.pageX,
pageY: event.pageY
}
});
SDL.DOMButtons[event.button] = 0;
}
// We can only request fullscreen as the result of user input.
// Due to this limitation, we toggle a boolean on keydown which
// SDL_WM_ToggleFullScreen will check and subsequently set another
// flag indicating for us to request fullscreen on the following
// keyup. This isn't perfect, but it enables SDL_WM_ToggleFullScreen
// to work as the result of a keypress (which is an extremely
// common use case).
if (event.type === 'keydown' || event.type === 'mousedown') {
SDL.canRequestFullscreen = true;
} else if (event.type === 'keyup' || event.type === 'mouseup') {
if (SDL.isRequestingFullscreen) {
Module['requestFullscreen'](/*lockPointer=*/true, /*resizeCanvas=*/true);
SDL.isRequestingFullscreen = false;
}
SDL.canRequestFullscreen = false;
}
// SDL expects a unicode character to be passed to its keydown events.
// Unfortunately, the browser APIs only provide a charCode property on
// keypress events, so we must backfill in keydown events with their
// subsequent keypress event's charCode.
if (event.type === 'keypress' && SDL.savedKeydown) {
// charCode is read-only
SDL.savedKeydown.keypressCharCode = event.charCode;
SDL.savedKeydown = null;
} else if (event.type === 'keydown') {
SDL.savedKeydown = event;
}
// Don't push keypress events unless SDL_StartTextInput has been called.
if (event.type !== 'keypress' || SDL.textInput) {
SDL.events.push(event);
}
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;
}
}
event.preventDefault();
break;
case 'focus':
SDL.events.push(event);
event.preventDefault();
break;
case 'blur':
SDL.events.push(event);
unpressAllPressedKeys();
event.preventDefault();
break;
case 'visibilitychange':
SDL.events.push({
type: 'visibilitychange',
visible: !document.hidden
});
unpressAllPressedKeys();
event.preventDefault();
break;
case 'unload':
if (Browser.mainLoop.runner) {
SDL.events.push(event);
// Force-run a main event loop, since otherwise this event will never be caught!
Browser.mainLoop.runner();
}
return;
case 'resize':
SDL.events.push(event);
// manually triggered resize event doesn't have a preventDefault member
if (event.preventDefault) {
event.preventDefault();
}
break;
}
if (SDL.events.length >= 10000) {
err('SDL event queue full, dropping events');
SDL.events = SDL.events.slice(0, 10000);
}
// If we have a handler installed, this will push the events to the app
// instead of the app polling for them.
SDL.flushEventsToHandler();
return;
},
lookupKeyCodeForEvent: function(event) {
var code = 0;
var key = event.key;
if (!key) { return 0; }
code = SDL.keyCodes[key] || key.charCodeAt(0);
if (code >= 65 && code <= 90) {
code += 32; // make lowercase for SDL
} else if(code == 34) {
code = 39; // Emscripten doesn't like valid Javascript syntax for " as a key in an object and turns it into something invalid. This is a hack.
}
if (Array.isArray(code)) {
code = code[event.location];
}
return code;
},
handleEvent: function(event) {
if (event.handled) return;
event.handled = true;
switch (event.type) {
case 'touchstart': case 'touchend': case 'touchmove': {
Browser.calculateMouseEvent(event);
break;
}
case 'keydown': case 'keyup': {
var down = event.type === 'keydown';
var code = SDL.lookupKeyCodeForEvent(event);
{{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}};
// TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED... TODO: DISTINGUISH LEFT FROM RIGHT
SDL.modState = (event.ctrlKey ? 0x0040 : 0) | // KMOD_LCTRL
(event.shiftKey ? 0x0001 : 0) | // KMOD_LSHIFT
(event.altKey ? 0x0100 : 0); // KMOD_LALT
if (down) {
SDL.keyboardMap[code] = event.keyCode; // save the DOM input, which we can use to unpress it during blur
} else {
delete SDL.keyboardMap[code];
}
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 &= ~(1 << event.button);
}
// fall through
case 'mousemove': {
Browser.calculateMouseEvent(event);
break;
}
}
},
flushEventsToHandler: function() {
if (!SDL.eventHandler) return;
while (SDL.pollEvent(SDL.eventHandlerTemp)) {
Module['dynCall_iii'](SDL.eventHandler, SDL.eventHandlerContext, SDL.eventHandlerTemp);
}
},
pollEvent: function(ptr) {
if (SDL.initFlags & 0x200 && SDL.joystickEventState) {
// If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured
// to automatically query for events, query for joystick events.
SDL.queryJoysticks();
}
if (ptr) {
while (SDL.events.length > 0) {
if (SDL.makeCEvent(SDL.events.shift(), ptr) !== false) return 1;
}
return 0;
} else {
// XXX: somewhat risky in that we do not check if the event is real or not (makeCEvent returns false) if no pointer supplied
return SDL.events.length > 0;
}
},
// returns false if the event was determined to be irrelevant
makeCEvent: function(event, ptr) {
if (typeof event === 'number') {
// This is a pointer to a copy of a native C event that was SDL_PushEvent'ed
_memcpy(ptr, event, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}});
_free(event); // the copy is no longer needed
return;
}
SDL.handleEvent(event);
switch (event.type) {
case 'keydown': case 'keyup': {
var down = event.type === 'keydown';
//out('Received key event: ' + event.keyCode);
var key = SDL.lookupKeyCodeForEvent(event);
var scan;
if (key >= 1024) {
scan = key - 1024;
} else {
scan = SDL.scanCodes[key] || key;
}
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.state, 'down ? 1 : 0', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.repeat, '0', 'i8') }}}; // TODO
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.scancode, 'scan', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.sym, 'key', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.mod, 'SDL.modState', 'i16') }}};
// some non-character keys (e.g. backspace and tab) won't have keypressCharCode set, fill in with the keyCode.
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.keysym + C_STRUCTS.SDL_Keysym.unicode, 'event.keypressCharCode || key', 'i32') }}};
break;
}
case 'keypress': {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
// Not filling in windowID for now
var cStr = intArrayFromString(String.fromCharCode(event.charCode));
for (var i = 0; i < cStr.length; ++i) {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TextInputEvent.text + ' + i', 'cStr[i]', 'i8') }}};
}
break;
}
case 'mousedown': case 'mouseup': case 'mousemove': {
if (event.type != 'mousemove') {
var down = event.type === 'mousedown';
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.timestamp, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.windowID, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.which, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.button, 'event.button+1', 'i8') }}}; // DOM buttons are 0-2, SDL 1-3
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.state, 'down ? 1 : 0', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.x, 'Browser.mouseX', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseButtonEvent.y, 'Browser.mouseY', 'i32') }}};
} else {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.timestamp, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.windowID, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.which, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.state, 'SDL.buttonState', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.x, 'Browser.mouseX', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.y, 'Browser.mouseY', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.xrel, 'Browser.mouseMovementX', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseMotionEvent.yrel, 'Browser.mouseMovementY', 'i32') }}};
}
break;
}
case 'wheel': {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}};
break;
}
case 'touchstart': case 'touchend': case 'touchmove': {
var touch = event.touch;
if (!Browser.touches[touch.identifier]) break;
var w = Module['canvas'].width;
var h = Module['canvas'].height;
var x = Browser.touches[touch.identifier].x / w;
var y = Browser.touches[touch.identifier].y / h;
var lx = Browser.lastTouches[touch.identifier].x / w;
var ly = Browser.lastTouches[touch.identifier].y / h;
var dx = x - lx;
var dy = y - ly;
if (touch['deviceID'] === undefined) touch.deviceID = SDL.TOUCH_DEFAULT_ID;
if (dx === 0 && dy === 0 && event.type === 'touchmove') return false; // don't send these if nothing happened
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.timestamp, '_SDL_GetTicks()', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.touchId, 'touch.deviceID', 'i64') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.fingerId, 'touch.identifier', 'i64') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.x, 'x', 'float') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.y, 'y', 'float') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dx, 'dx', 'float') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.dy, 'dy', 'float') }}};
if (touch.force !== undefined) {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'touch.force', 'float') }}};
} else { // No pressure data, send a digital 0/1 pressure.
{{{ makeSetValue('ptr', C_STRUCTS.SDL_TouchFingerEvent.pressure, 'event.type == "touchend" ? 0 : 1', 'float') }}};
}
break;
}
case 'unload': {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
break;
}
case 'resize': {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_KeyboardEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.w, 'event.w', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}};
break;
}
case 'joystick_button_up': case 'joystick_button_down': {
var state = event.type === 'joystick_button_up' ? 0 : 1;
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}};
break;
}
case 'joystick_axis_motion': {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}};
break;
}
case 'focus': {
var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */;
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}};
break;
}
case 'blur': {
var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */;
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}};
break;
}
case 'visibilitychange': {
var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */;
var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */;
var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN;
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}};
break;
}
default: throw 'Unhandled SDL event: ' + event.type;
}
},
makeFontString: function(height, fontName) {
if (fontName.charAt(0) != "'" && fontName.charAt(0) != '"') {
// https://developer.mozilla.org/ru/docs/Web/CSS/font-family
// Font family names containing whitespace should be quoted.
// BTW, quote all font names is easier than searching spaces
fontName = '"' + fontName + '"';
}
return height + 'px ' + fontName + ', serif';
},
estimateTextWidth: function(fontData, text) {
var h = fontData.size;
var fontString = SDL.makeFontString(h, fontData.name);
var tempCtx = SDL.ttfContext;
#if ASSERTIONS
assert(tempCtx, 'TTF_Init must have been called');
#endif
tempCtx.save();
tempCtx.font = fontString;
var ret = tempCtx.measureText(text).width | 0;
tempCtx.restore();
return ret;
},
// Sound
// Channels are a SDL abstraction for allowing multiple sound tracks to be
// played at the same time. We don't need to actually implement the mixing
// since the browser engine handles that for us. Therefore, in JS we just
// maintain a list of channels and return IDs for them to the SDL consumer.
allocateChannels: function(num) { // called from Mix_AllocateChannels and init
if (SDL.numChannels && SDL.numChannels >= num && num != 0) 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 = Math.min(Math.max(volume, 0), 128) / 128;
if (info.audio) {
try {
info.audio.volume = info.volume; // For <audio> element
if (info.audio.webAudioGainNode) info.audio.webAudioGainNode['gain']['value'] = info.volume; // For WebAudio playback
} catch(e) {
err('setGetVolume failed to set audio volume: ' + e);
}
}
}
return ret;
},
setPannerPosition: function(info, x, y, z) {
if (!info) return;
if (info.audio) {
if (info.audio.webAudioPannerNode) {
info.audio.webAudioPannerNode['setPosition'](x, y, z);
}
}
},
// Plays out an SDL audio resource that was loaded with the Mix_Load APIs, when using Web Audio..
playWebAudio: function(audio) {
if (!audio) return;
if (audio.webAudioNode) return; // This instance is already playing, don't start again.
if (!SDL.webAudioAvailable()) return;
try {
var webAudio = audio.resource.webAudio;
audio.paused = false;
if (!webAudio.decodedBuffer) {
if (webAudio.onDecodeComplete === undefined) abort("Cannot play back audio object that was not loaded");
webAudio.onDecodeComplete.push(function() { if (!audio.paused) SDL.playWebAudio(audio); });
return;
}
audio.webAudioNode = SDL.audioContext['createBufferSource']();
audio.webAudioNode['buffer'] = webAudio.decodedBuffer;
audio.webAudioNode['loop'] = audio.loop;
audio.webAudioNode['onended'] = function() { audio['onended'](); } // For <media> element compatibility, route the onended signal to the instance.
audio.webAudioPannerNode = SDL.audioContext['createPanner']();
// avoid Chrome bug
// If posz = 0, the sound will come from only the right.
// By posz = -0.5 (slightly ahead), the sound will come from right and left correctly.
audio.webAudioPannerNode["setPosition"](0, 0, -.5);
audio.webAudioPannerNode['panningModel'] = 'equalpower';
// Add an intermediate gain node to control volume.
audio.webAudioGainNode = SDL.audioContext['createGain']();
audio.webAudioGainNode['gain']['value'] = audio.volume;
audio.webAudioNode['connect'](audio.webAudioPannerNode);
audio.webAudioPannerNode['connect'](audio.webAudioGainNode);
audio.webAudioGainNode['connect'](SDL.audioContext['destination']);
audio.webAudioNode['start'](0, audio.currentPosition);
audio.startTime = SDL.audioContext['currentTime'] - audio.currentPosition;
} catch(e) {
err('playWebAudio failed: ' + e);
}
},
// Pauses an SDL audio resource that was played with Web Audio.
pauseWebAudio: function(audio) {
if (!audio) return;
if (audio.webAudioNode) {
try {
// Remember where we left off, so that if/when we resume, we can restart the playback at a proper place.
audio.currentPosition = (SDL.audioContext['currentTime'] - audio.startTime) % audio.resource.webAudio.decodedBuffer.duration;
// Important: When we reach here, the audio playback is stopped by the user. But when calling .stop() below, the Web Audio
// graph will send the onended signal, but we don't want to process that, since pausing should not clear/destroy the audio
// channel.
audio.webAudioNode['onended'] = undefined;
audio.webAudioNode.stop(0); // 0 is a default parameter, but WebKit is confused by it #3861
audio.webAudioNode = undefined;
} catch(e) {
err('pauseWebAudio failed: ' + e);
}
}
audio.paused = true;
},
openAudioContext: function() {
// Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page,
// since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'.
if (!SDL.audioContext) {
if (typeof(AudioContext) !== 'undefined') SDL.audioContext = new AudioContext();
else if (typeof(webkitAudioContext) !== 'undefined') SDL.audioContext = new webkitAudioContext();
}
},
webAudioAvailable: function() { return !!SDL.audioContext; },
fillWebAudioBufferFromHeap: function(heapPtr, sizeSamplesPerChannel, dstAudioBuffer) {
// The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit, 16-bit or float as
// supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data,
// so perform a buffer conversion for the data.
var numChannels = SDL.audio.channels;
for(var c = 0; c < numChannels; ++c) {
var channelData = dstAudioBuffer['getChannelData'](c);
if (channelData.length != sizeSamplesPerChannel) {
throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + sizeSamplesPerChannel + ' samples!';
}
if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
for(var j = 0; j < sizeSamplesPerChannel; ++j) {
channelData[j] = ({{{ makeGetValue('heapPtr', '(j*numChannels + c)*2', 'i16', 0, 0) }}}) / 0x8000;
}
} else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
for(var j = 0; j < sizeSamplesPerChannel; ++j) {
var v = ({{{ makeGetValue('heapPtr', 'j*numChannels + c', 'i8', 0, 0) }}});
channelData[j] = ((v >= 0) ? v-128 : v+128) /128;
}
} else if (SDL.audio.format == 0x8120 /*AUDIO_F32*/) {
for(var j = 0; j < sizeSamplesPerChannel; ++j) {
channelData[j] = ({{{ makeGetValue('heapPtr', '(j*numChannels + c)*4', 'float', 0, 0) }}});
}
} else {
throw 'Invalid SDL audio format ' + SDL.audio.format + '!';
}
}
},
// 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]]);
}
},
// Joystick helper methods and state
joystickEventState: 1, // SDL_ENABLE
lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred.
// Maps Joystick names to pointers. Allows us to avoid reallocating memory for
// joystick names each time this function is called.
joystickNamePool: {},
recordJoystickState: function(joystick, state) {
// Standardize button state.
var buttons = new Array(state.buttons.length);
for (var i = 0; i < state.buttons.length; i++) {
buttons[i] = SDL.getJoystickButtonState(state.buttons[i]);
}
SDL.lastJoystickState[joystick] = {
buttons: buttons,
axes: state.axes.slice(0),
timestamp: state.timestamp,
index: state.index,
id: state.id
};
},
// Retrieves the button state of the given gamepad button.
// Abstracts away implementation differences.
// Returns 'true' if pressed, 'false' otherwise.
getJoystickButtonState: function(button) {
if (typeof button === 'object') {
// Current gamepad API editor's draft (Firefox Nightly)
// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton
return button['pressed'];
} else {
// Current gamepad API working draft (Firefox / Chrome Stable)
// http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface
return button > 0;
}
},
// Queries for and inserts controller events into the SDL queue.
queryJoysticks: function() {
for (var joystick in SDL.lastJoystickState) {
var state = SDL.getGamepad(joystick - 1);
var prevState = SDL.lastJoystickState[joystick];
// If joystick was removed, state returns null.
if (typeof state === 'undefined') return;
if (state === null) return;
// Check only if the timestamp has differed.
// NOTE: Timestamp is not available in Firefox.
// NOTE: Timestamp is currently not properly set for the GearVR controller
// on Samsung Internet: it is always zero.
if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp || !state.timestamp) {
var i;
for (i = 0; i < state.buttons.length; i++) {
var buttonState = SDL.getJoystickButtonState(state.buttons[i]);
// NOTE: The previous state already has a boolean representation of
// its button, so no need to standardize its button state here.
if (buttonState !== prevState.buttons[i]) {
// Insert button-press event.
SDL.events.push({
type: buttonState ? 'joystick_button_down' : 'joystick_button_up',
joystick: joystick,
index: joystick - 1,
button: i
});
}
}
for (i = 0; i < state.axes.length; i++) {
if (state.axes[i] !== prevState.axes[i]) {
// Insert axes-change event.
SDL.events.push({
type: 'joystick_axis_motion',
joystick: joystick,
index: joystick - 1,
axis: i,
value: state.axes[i]
});
}
}
SDL.recordJoystickState(joystick, state);
}
}
},
// Converts the double-based browser axis value [-1, 1] into SDL's 16-bit
// value [-32768, 32767]
joystickAxisValueConversion: function(value) {
// Make sure value is properly clamped
value = Math.min(1, Math.max(value, -1));
// Ensures that 0 is 0, 1 is 32767, and -1 is 32768.
return Math.ceil(((value+1) * 32767.5) - 32768);
},
getGamepads: function() {
var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads;
if (fcn !== undefined) {
// The function must be applied on the navigator object.
return fcn.apply(navigator);
} else {
return [];
}
},
// Helper function: Returns the gamepad if available, or null if not.
getGamepad: function(deviceIndex) {
var gamepads = SDL.getGamepads();
if (gamepads.length > deviceIndex && deviceIndex >= 0) {
return gamepads[deviceIndex];
}
return null;
},
},
SDL_Linked_Version__proxy: 'sync',
SDL_Linked_Version__sig: 'i',
SDL_Linked_Version: function() {
if (SDL.version === null) {
SDL.version = _malloc({{{ C_STRUCTS.SDL_version.__size__ }}});
{{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.major, '0', '1', 'i8') }}};
{{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.minor, '0', '3', 'i8') }}};
{{{ makeSetValue('SDL.version + ' + C_STRUCTS.SDL_version.patch, '0', '0', 'i8') }}};
}
return SDL.version;
},
SDL_Init__proxy: 'sync',
SDL_Init__sig: 'ii',
SDL_Init: function(initFlags) {
SDL.startTime = Date.now();
SDL.initFlags = initFlags;
// capture all key events. we just keep down and up, but also capture press to prevent default actions
if (!Module['doNotCaptureKeyboard']) {
var keyboardListeningElement = Module['keyboardListeningElement'] || document;
keyboardListeningElement.addEventListener("keydown", SDL.receiveEvent);
keyboardListeningElement.addEventListener("keyup", SDL.receiveEvent);
keyboardListeningElement.addEventListener("keypress", SDL.receiveEvent);
window.addEventListener("focus", SDL.receiveEvent);
window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
window.addEventListener("unload", SDL.receiveEvent);
SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs
_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['keypress'] = 0x303 /* SDL_TEXTINPUT */;
SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */;
SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */;
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
SDL.DOMEventToSDLEvent['wheel'] = 0x403 /* SDL_MOUSEWHEEL */;
SDL.DOMEventToSDLEvent['touchstart'] = 0x700 /* SDL_FINGERDOWN */;
SDL.DOMEventToSDLEvent['touchend'] = 0x701 /* SDL_FINGERUP */;
SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */;
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
SDL.DOMEventToSDLEvent['visibilitychange'] = 0x200 /* SDL_WINDOWEVENT */;
SDL.DOMEventToSDLEvent['focus'] = 0x200 /* SDL_WINDOWEVENT */;
SDL.DOMEventToSDLEvent['blur'] = 0x200 /* SDL_WINDOWEVENT */;
// These are not technically DOM events; the HTML gamepad API is poll-based.
// However, we define them here, as the rest of the SDL code assumes that
// all SDL events originate as DOM events.
SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */;
SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */;
SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */;
return 0; // success
},
SDL_WasInit__deps: ['SDL_Init'],
SDL_WasInit__proxy: 'sync',
SDL_WasInit__sig: 'i',
SDL_WasInit: function() {
if (SDL.startTime === null) {
_SDL_Init();
}
return 1;
},
SDL_GetVideoInfo__proxy: 'sync',
SDL_GetVideoInfo__sig: 'i',
SDL_GetVideoInfo: function() {
// %struct.SDL_VideoInfo = type { i32, i32, %struct.SDL_PixelFormat*, i32, i32 } - 5 fields of quantum size
var ret = _malloc(5 * {{{ Runtime.QUANTUM_SIZE }}});
{{{ makeSetValue('ret+' + (Runtime.QUANTUM_SIZE*0), '0', '0', 'i32') }}}; // TODO
{{{ makeSetValue('ret+' + (Runtime.QUANTUM_SIZE*1), '0', '0', 'i32') }}}; // TODO
{{{ makeSetValue('ret+' + (Runtime.QUANTUM_SIZE*2), '0', '0', 'void*') }}};
{{{ makeSetValue('ret+' + (Runtime.QUANTUM_SIZE*3), '0', 'Module["canvas"].width', 'i32') }}};
{{{ makeSetValue('ret+' + (Runtime.QUANTUM_SIZE*4), '0', 'Module["canvas"].height', 'i32') }}};
return ret;
},
SDL_ListModes: function(format, flags) {
return -1; // -1 == all modes are ok. 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_AudioDriverName__deps: ['SDL_VideoDriverName'],
SDL_AudioDriverName: function(buf, max_size) {
return _SDL_VideoDriverName(buf, max_size);
},
SDL_VideoDriverName__proxy: 'sync',
SDL_VideoDriverName__sig: 'iii',
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__deps: ['$GL'],
SDL_SetVideoMode__proxy: 'sync',
SDL_SetVideoMode__sig: 'iiiii',
SDL_SetVideoMode: function(width, height, depth, flags) {
['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'wheel', 'mouseout'].forEach(function(event) {
Module['canvas'].addEventListener(event, SDL.receiveEvent, true);
});
var canvas = Module['canvas'];
// (0,0) means 'use fullscreen' in native; in Emscripten, use the current canvas size.
if (width == 0 && height == 0) {
width = canvas.width;
height = canvas.height;
}
if (!SDL.addedResizeListener) {
SDL.addedResizeListener = true;
Browser.resizeListeners.push(function(w, h) {
if (!SDL.settingVideoMode) {
SDL.receiveEvent({
type: 'resize',
w: w,
h: h
});
}
});
}
if (width !== canvas.width || height !== canvas.height) {
SDL.settingVideoMode = true; // SetVideoMode itself should not trigger resize events
Browser.setCanvasSize(width, height);
SDL.settingVideoMode = false;
}
// Free the old surface first if there is one
if (SDL.screen) {
SDL.freeSurface(SDL.screen);
assert(!SDL.screen);
}
if (SDL.GL) flags = flags | 0x04000000; // SDL_OPENGL - if we are using GL, then later calls to SetVideoMode may not mention GL, but we do need it. Once in GL mode, we never leave it.
SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen');
return SDL.screen;
},
SDL_GetVideoSurface__proxy: 'sync',
SDL_GetVideoSurface__sig: 'i',
SDL_GetVideoSurface: function() {
return SDL.screen;
},
SDL_AudioQuit__proxy: 'sync',
SDL_AudioQuit__sig: 'v',
SDL_AudioQuit: function() {
for (var i = 0; i < SDL.numChannels; ++i) {
if (SDL.channels[i].audio) {
SDL.channels[i].audio.pause();
SDL.channels[i].audio = undefined;
}
}
if (SDL.music.audio) SDL.music.audio.pause();
SDL.music.audio = undefined;
},
SDL_VideoQuit: function() {
out('SDL_VideoQuit called (and ignored)');
},
SDL_QuitSubSystem: function(flags) {
out('SDL_QuitSubSystem called (and ignored)');
},
SDL_Quit__deps: ['SDL_AudioQuit'],
SDL_Quit: function() {
_SDL_AudioQuit();
out('SDL_Quit called (and ignored)');
},
// Copy data from the canvas backing to a C++-accessible storage
SDL_LockSurface__proxy: 'sync',
SDL_LockSurface__sig: 'ii',
SDL_LockSurface: function(surf) {
var surfData = SDL.surfaces[surf];
surfData.locked++;
if (surfData.locked > 1) return 0;
if (!surfData.buffer) {
surfData.buffer = _malloc(surfData.width * surfData.height * 4);
{{{ makeSetValue('surf', C_STRUCTS.SDL_Surface.pixels, 'surfData.buffer', 'void*') }}};
}
// 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', C_STRUCTS.SDL_Surface.pixels, 'surfData.buffer', 'void*') }}};
if (surf == SDL.screen && Module.screenIsReadOnly && surfData.image) return 0;
if (SDL.defaults.discardOnLock) {
if (!surfData.image) {
surfData.image = surfData.ctx.createImageData(surfData.width, surfData.height);
}
if (!SDL.defaults.opaqueFrontBuffer) return;
} else {
surfData.image = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height);
}
// Emulate desktop behavior and kill alpha values on the locked surface. (very costly!) Set SDL.defaults.opaqueFrontBuffer = false
// if you don't want this.
if (surf == SDL.screen && SDL.defaults.opaqueFrontBuffer) {
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.copyOnLock && !SDL.defaults.discardOnLock) {
// 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 {
HEAPU8.set(surfData.image.data, surfData.buffer);
}
}
return 0;
},
// Copy data from the C++-accessible storage to the canvas backing
SDL_UnlockSurface__proxy: 'sync',
SDL_UnlockSurface__sig: 'vi',
SDL_UnlockSurface: function(surf) {
assert(!SDL.GL); // in GL mode we do not keep around 2D canvases and contexts
var surfData = SDL.surfaces[surf];
if (!surfData.locked || --surfData.locked > 0) {
return;
}
// Copy pixel data to image
if (surfData.isFlagSet(0x00200000 /* SDL_HWPALETTE */)) {
SDL.copyIndexedColorData(surfData);
} else if (!surfData.colors) {
var data = surfData.image.data;
var buffer = surfData.buffer;
assert(buffer % 4 == 0, 'Invalid buffer offset: ' + buffer);
var src = buffer >> 2;
var dst = 0;
var isScreen = surf == SDL.screen;
var num;
if (typeof CanvasPixelArray !== 'undefined' && data instanceof CanvasPixelArray) {
// IE10/IE11: ImageData objects are backed by the deprecated CanvasPixelArray,
// not UInt8ClampedArray. These don't have buffers, so we need to revert
// to copying a byte at a time. We do the undefined check because modern
// browsers do not define CanvasPixelArray anymore.
num = data.length;
while (dst < num) {
var val = HEAP32[src]; // This is optimized. Instead, we could do {{{ makeGetValue('buffer', 'dst', 'i32') }}};
data[dst ] = val & 0xff;
data[dst+1] = (val >> 8) & 0xff;
data[dst+2] = (val >> 16) & 0xff;
data[dst+3] = isScreen ? 0xff : ((val >> 24) & 0xff);
src++;
dst += 4;
}
} else {
var data32 = new Uint32Array(data.buffer);
if (isScreen && SDL.defaults.opaqueFrontBuffer) {
num = data32.length;
// logically we need to do
// while (dst < num) {
// data32[dst++] = HEAP32[src++] | 0xff000000
// }
// the following code is faster though, because
// .set() is almost free - easily 10x faster due to
// native memcpy efficiencies, and the remaining loop
// just stores, not load + store, so it is faster
data32.set(HEAP32.subarray(src, src + num));
var data8 = new Uint8Array(data.buffer);
var i = 3;
var j = i + 4*num;
if (num % 8 == 0) {
// unrolling gives big speedups
while (i < j) {
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
data8[i] = 0xff;
i = i + 4 | 0;
}
} else {
while (i < j) {
data8[i] = 0xff;
i = i + 4 | 0;
}
}
} else {
data32.set(HEAP32.subarray(src, src + data32.length));
}
}
} else {
var width = Module['canvas'].width;
var height = Module['canvas'].height;
var s = surfData.buffer;
var data = surfData.image.data;
var colors = surfData.colors; // TODO: optimize using colors32
for (var y = 0; y < height; y++) {
var base = y*width*4;
for (var x = 0; x < width; x++) {
// See comment above about signs
var val = {{{ makeGetValue('s++', '0', 'i8', null, true) }}} * 4;
var start = base + x*4;
data[start] = colors[val];
data[start+1] = colors[val+1];
data[start+2] = colors[val+2];
}
s += width*3;
}
}
// Copy to canvas
surfData.ctx.putImageData(surfData.image, 0, 0);
// Note that we save the image, so future writes are fast. But, memory is not yet released
},
SDL_Flip: function(surf) {
// 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...
},
#if EMTERPRETIFY_ASYNC == 0
SDL_Delay: function(delay) {
if (!ENVIRONMENT_IS_WORKER) abort('SDL_Delay called on the main thread! Potential infinite loop, quitting.');
// horrible busy-wait, but in a worker it at least does not block rendering
var now = Date.now();
while (Date.now() - now < delay) {}
},
#else
SDL_Delay__deps: ['emscripten_sleep'],
SDL_Delay: function(delay) {
_emscripten_sleep(delay);
},
#endif
SDL_WM_SetCaption__proxy: 'sync',
SDL_WM_SetCaption__sig: 'vii',
SDL_WM_SetCaption: function(title, icon) {
if (title && typeof Module['setWindowTitle'] !== 'undefined') {
Module['setWindowTitle'](Pointer_stringify(title));
}
icon = icon && Pointer_stringify(icon);
},
SDL_EnableKeyRepeat: function(delay, interval) {
// TODO
},
SDL_GetKeyboardState__proxy: 'sync',
SDL_GetKeyboardState__sig: 'ii',
SDL_GetKeyboardState: function(numKeys) {
if (numKeys) {
{{{ makeSetValue('numKeys', 0, 0x10000, 'i32') }}};
}
return SDL.keyboardState;
},
SDL_GetKeyState__deps: ['SDL_GetKeyboardState'],
SDL_GetKeyState: function() {
return _SDL_GetKeyboardState();
},
SDL_GetKeyName__proxy: 'sync',
SDL_GetKeyName__sig: 'ii',
SDL_GetKeyName: function(key) {
if (!SDL.keyNameMap) {
SDL.keyNameMap = {};
}
if (!SDL.keyNameMap[key]) {
var keyName = SDL.keyNames[key];
if(!keyName && 32 <= key && key <= 126) {
keyName = String.fromCharCode(key);
}
SDL.keyNameMap[key] = allocate(intArrayFromString(keyName || "unknown key"), 'i8', ALLOC_NORMAL);
}
return SDL.keyNameMap[key];
},
SDL_GetModState__proxy: 'sync',
SDL_GetModState__sig: 'i',
SDL_GetModState: function() {
return SDL.modState;
},
SDL_GetMouseState__proxy: 'sync',
SDL_GetMouseState__sig: 'iii',
SDL_GetMouseState: function(x, y) {
if (x) {{{ makeSetValue('x', '0', 'Browser.mouseX', 'i32') }}};
if (y) {{{ makeSetValue('y', '0', 'Browser.mouseY', 'i32') }}};
return SDL.buttonState;
},
SDL_WarpMouse__proxy: 'sync',
SDL_WarpMouse__sig: 'vii',
SDL_WarpMouse: function(x, y) {
return; // TODO: implement this in a non-buggy way. Need to keep relative mouse movements correct after calling this
/*
var rect = Module["canvas"].getBoundingClientRect();
SDL.events.push({
type: 'mousemove',
pageX: x + (window.scrollX + rect.left),
pageY: y + (window.scrollY + rect.top)
});
*/
},
SDL_ShowCursor__proxy: 'sync',
SDL_ShowCursor__sig: 'ii',
SDL_ShowCursor: function(toggle) {
switch (toggle) {
case 0: // SDL_DISABLE
if (Browser.isFullscreen) { // only try to lock the pointer when in full screen mode
Module['canvas'].requestPointerLock();
return 0;
} else { // else return SDL_ENABLE to indicate the failure
return 1;
}
break;
case 1: // SDL_ENABLE
Module['canvas'].exitPointerLock();
return 1;
break;
case -1: // SDL_QUERY
return !Browser.pointerLock;
break;
default:
console.log( "SDL_ShowCursor called with unknown toggle parameter value: " + toggle + "." );
break;
}
},
SDL_GetError__proxy: 'sync',
SDL_GetError__sig: 'i',
SDL_GetError: function() {
if (!SDL.errorMessage) {
SDL.errorMessage = allocate(intArrayFromString("unknown SDL-emscripten error"), 'i8', ALLOC_NORMAL);
}
return SDL.errorMessage;
},
SDL_SetError: function() {},
SDL_malloc: 'malloc',
SDL_free: 'free',
SDL_CreateRGBSurface__proxy: 'sync',
SDL_CreateRGBSurface__sig: 'iiiiiiiii',
SDL_CreateRGBSurface: function(flags, width, height, depth, rmask, gmask, bmask, amask) {
return SDL.makeSurface(width, height, flags, false, 'CreateRGBSurface', rmask, gmask, bmask, amask);
},
SDL_CreateRGBSurfaceFrom__proxy: 'sync',
SDL_CreateRGBSurfaceFrom__sig: 'iiiiiiiiii',
SDL_CreateRGBSurfaceFrom: function(pixels, width, height, depth, pitch, rmask, gmask, bmask, amask) {
var surf = SDL.makeSurface(width, height, 0, false, 'CreateRGBSurfaceFrom', rmask, gmask, bmask, amask);
if (depth !== 32) {
// TODO: Actually fill pixel data to created surface.
// TODO: Take into account depth and pitch parameters.
console.log('TODO: Partially unimplemented SDL_CreateRGBSurfaceFrom called!');
return surf;
}
var data = SDL.surfaces[surf];
var image = data.ctx.createImageData(width, height);
var pitchOfDst = width * 4;
for (var row = 0; row < height; ++row) {
var baseOfSrc = row * pitch;
var baseOfDst = row * pitchOfDst;
for (var col = 0; col < width * 4; ++col) {
image.data[baseOfDst + col] = {{{ makeGetValue('pixels', 'baseOfDst + col', 'i8', null, true) }}};
}
}
data.ctx.putImageData(image, 0, 0);
return surf;
},
SDL_ConvertSurface__proxy: 'sync',
SDL_ConvertSurface__sig: 'iiii',
SDL_ConvertSurface: function(surf, format, flags) {
if (format) {
SDL.checkPixelFormat(format);
}
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.globalCompositeOperation = "copy";
newData.ctx.drawImage(oldData.canvas, 0, 0);
newData.ctx.globalCompositeOperation = oldData.ctx.globalCompositeOperation;
return ret;
},
SDL_DisplayFormatAlpha__deps: ['SDL_ConvertSurface'],
SDL_DisplayFormatAlpha: function(surf) {
return _SDL_ConvertSurface(surf);
},
SDL_FreeSurface__proxy: 'sync',
SDL_FreeSurface__sig: 'vi',
SDL_FreeSurface: function(surf) {
if (surf) SDL.freeSurface(surf);
},
SDL_UpperBlit__proxy: 'sync',
SDL_UpperBlit__sig: 'iiiii',
SDL_UpperBlit: function(src, srcrect, dst, dstrect) {
return SDL.blitSurface(src, srcrect, dst, dstrect, false);
},
SDL_UpperBlitScaled__proxy: 'sync',
SDL_UpperBlitScaled__sig: 'iiiii',
SDL_UpperBlitScaled: function(src, srcrect, dst, dstrect) {
return SDL.blitSurface(src, srcrect, dst, dstrect, true);
},
SDL_LowerBlit: 'SDL_UpperBlit',
SDL_LowerBlitScaled: 'SDL_UpperBlitScaled',
SDL_GetClipRect__proxy: 'sync',
SDL_GetClipRect__sig: 'vii',
SDL_GetClipRect: function(surf, rect) {
assert(rect);
var surfData = SDL.surfaces[surf];
var r = surfData.clipRect || { x: 0, y: 0, w: surfData.width, h: surfData.height };
SDL.updateRect(rect, r);
},
SDL_SetClipRect__proxy: 'sync',
SDL_SetClipRect__sig: 'vii',
SDL_SetClipRect: function(surf, rect) {
var surfData = SDL.surfaces[surf];
if (rect) {
surfData.clipRect = SDL.intersectionOfRects({ x: 0, y: 0, w: surfData.width, h: surfData.height }, SDL.loadRect(rect));
} else {
delete surfData.clipRect;
}
},
SDL_FillRect__proxy: 'sync',
SDL_FillRect__sig: 'iiii',
SDL_FillRect: function(surf, rect, color) {
var surfData = SDL.surfaces[surf];
assert(!surfData.locked); // but we could unlock and re-lock if we must..
if (surfData.isFlagSet(0x00200000 /* SDL_HWPALETTE */)) {
//in SDL_HWPALETTE color is index (0..255)
//so we should translate 1 byte value to
//32 bit canvas
color = surfData.colors32[color];
}
var r = rect ? SDL.loadRect(rect) : { x: 0, y: 0, w: surfData.width, h: surfData.height };
if (surfData.clipRect) {
r = SDL.intersectionOfRects(surfData.clipRect, r);
if (rect) {
SDL.updateRect(rect, r);
}
}
surfData.ctx.save();
surfData.ctx.fillStyle = SDL.translateColorToCSSRGBA(color);
surfData.ctx.fillRect(r.x, r.y, r.w, r.h);
surfData.ctx.restore();
return 0;
},
SDL_BlitSurface__proxy: 'sync',
SDL_BlitSurface__sig: 'iiiii',
SDL_BlitSurface: function(src, srcrect, dst, dstrect) {
return SDL.blitSurface(src, srcrect, dst, dstrect, false);
},
SDL_BlitScaled__proxy: 'sync',
SDL_BlitScaled__sig: 'iiiii',
SDL_BlitScaled: function(src, srcrect, dst, dstrect) {
return SDL.blitSurface(src, srcrect, dst, dstrect, true);
},
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(Math.abs(w), Math.abs(h), srcData.flags, false, 'zoomSurface');
var dstData = SDL.surfaces[ret];
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;
},
rotozoomSurface__deps: ['zoomSurface'],
rotozoomSurface: function(src, angle, zoom, smooth) {
if (angle % 360 === 0) {
return _zoomSurface(src, zoom, zoom, smooth);
}
var srcData = SDL.surfaces[src];
var w = srcData.width * zoom;
var h = srcData.height * zoom;
var diagonal = Math.ceil(Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2)));
var ret = SDL.makeSurface(diagonal, diagonal, srcData.flags, false, 'rotozoomSurface');
var dstData = SDL.surfaces[ret];
dstData.ctx.translate(diagonal / 2, diagonal / 2);
dstData.ctx.rotate(-angle * Math.PI / 180);
dstData.ctx.drawImage(srcData.canvas, -w / 2, -h / 2, w, h);
return ret;
},
SDL_SetAlpha__proxy: 'sync',
SDL_SetAlpha__sig: 'iiii',
SDL_SetAlpha: function(surf, flag, alpha) {
var surfData = SDL.surfaces[surf];
surfData.alpha = alpha;
if (!(flag & 0x00010000)) { // !SDL_SRCALPHA
surfData.alpha = 255;
}
},
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.
warnOnce('SDL_SetColorKey is a no-op for performance reasons');
return 0;
},
SDL_GetTicks__proxy: 'sync',
SDL_GetTicks__sig: 'i',
SDL_GetTicks: function() {
return (Date.now() - SDL.startTime)|0;
},
SDL_PollEvent__proxy: 'sync',
SDL_PollEvent__sig: 'ii',
SDL_PollEvent: function(ptr) {
return SDL.pollEvent(ptr);
},
SDL_PushEvent__proxy: 'sync',
SDL_PushEvent__sig: 'ii',
SDL_PushEvent: function(ptr) {
var copy = _malloc({{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}});
_memcpy(copy, ptr, {{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}});
SDL.events.push(copy);
return 0;
},
SDL_PeepEvents__proxy: 'sync',
SDL_PeepEvents__sig: 'iiiiii',
SDL_PeepEvents: function(events, requestedEventCount, action, from, to) {
switch(action) {
case 2: { // SDL_GETEVENT
// We only handle 1 event right now
assert(requestedEventCount == 1);
var index = 0;
var retrievedEventCount = 0;
// this should look through the entire queue until it has filled up the events
// array
while (index < SDL.events.length && retrievedEventCount < requestedEventCount) {
var event = SDL.events[index];
var type = SDL.DOMEventToSDLEvent[event.type];
if (from <= type && type <= to) {
if (SDL.makeCEvent(event, events) === false) {
index++;
} else {
SDL.events.splice(index, 1);
retrievedEventCount++;
}
} else {
index++;
}
}
return retrievedEventCount;
}
default: throw 'SDL_PeepEvents does not yet support that action: ' + action;
}
},
SDL_PumpEvents__proxy: 'sync',
SDL_PumpEvents__sig: 'v',
SDL_PumpEvents: function(){
SDL.events.forEach(function(event) {
SDL.handleEvent(event);
});
},
// An Emscripten-specific extension to SDL: Some browser APIs require that they are called from within an event handler function.
// Allow recording a callback that will be called for each received event.
emscripten_SDL_SetEventHandler__proxy: 'sync',
emscripten_SDL_SetEventHandler__sig: 'vii',
emscripten_SDL_SetEventHandler: function(handler, userdata) {
SDL.eventHandler = handler;
SDL.eventHandlerContext = userdata;
// All SDLEvents take the same amount of memory
if (!SDL.eventHandlerTemp) SDL.eventHandlerTemp = _malloc({{{ C_STRUCTS.SDL_KeyboardEvent.__size__ }}});
},
SDL_SetColors__proxy: 'sync',
SDL_SetColors__sig: 'iiiii',
SDL_SetColors: function(surf, colors, firstColor, nColors) {
var surfData = SDL.surfaces[surf];
// we should create colors array
// only once cause client code
// often wants to change portion
// of palette not all palette.
if (!surfData.colors) {
var buffer = new ArrayBuffer(256 * 4); // RGBA, A is unused, but faster this way
surfData.colors = new Uint8Array(buffer);
surfData.colors32 = new Uint32Array(buffer);
}
for (var i = 0; i < nColors; ++i) {
var index = (firstColor + i) * 4;
surfData.colors[index] = {{{ makeGetValue('colors', 'i*4', 'i8', null, true) }}};
surfData.colors[index + 1] = {{{ makeGetValue('colors', 'i*4 + 1', 'i8', null, true) }}};
surfData.colors[index + 2] = {{{ makeGetValue('colors', 'i*4 + 2', 'i8', null, true) }}};
surfData.colors[index + 3] = 255; // opaque
}
return 1;
},
SDL_SetPalette__deps: ['SDL_SetColors'],
SDL_SetPalette: function(surf, flags, colors, firstColor, nColors) {
return _SDL_SetColors(surf, colors, firstColor, nColors);
},
SDL_MapRGB__proxy: 'sync',
SDL_MapRGB__sig: 'iiiii',
SDL_MapRGB: function(fmt, r, g, b) {
SDL.checkPixelFormat(fmt);
// We assume the machine is little-endian.
return r&0xff|(g&0xff)<<8|(b&0xff)<<16|0xff000000;
},
SDL_MapRGBA__proxy: 'sync',
SDL_MapRGBA__sig: 'iiiiii',
SDL_MapRGBA: function(fmt, r, g, b, a) {
SDL.checkPixelFormat(fmt);
// We assume the machine is little-endian.
return r&0xff|(g&0xff)<<8|(b&0xff)<<16|(a&0xff)<<24;
},
SDL_GetRGB__proxy: 'sync',
SDL_GetRGB__sig: 'viiiii',
SDL_GetRGB: function(pixel, fmt, r, g, b) {
SDL.checkPixelFormat(fmt);
// We assume the machine is little-endian.
if (r) {
{{{ makeSetValue('r', '0', 'pixel&0xff', 'i8') }}};
}
if (g) {
{{{ makeSetValue('g', '0', '(pixel>>8)&0xff', 'i8') }}};
}
if (b) {
{{{ makeSetValue('b', '0', '(pixel>>16)&0xff', 'i8') }}};
}
},
SDL_GetRGBA__proxy: 'sync',
SDL_GetRGBA__sig: 'viiiiii',
SDL_GetRGBA: function(pixel, fmt, r, g, b, a) {
SDL.checkPixelFormat(fmt);
// We assume the machine is little-endian.
if (r) {
{{{ makeSetValue('r', '0', 'pixel&0xff', 'i8') }}};
}
if (g) {
{{{ makeSetValue('g', '0', '(pixel>>8)&0xff', 'i8') }}};
}
if (b) {
{{{ makeSetValue('b', '0', '(pixel>>16)&0xff', 'i8') }}};
}
if (a) {
{{{ makeSetValue('a', '0', '(pixel>>24)&0xff', 'i8') }}};
}
},
SDL_GetAppState__proxy: 'sync',
SDL_GetAppState__sig: 'i',
SDL_GetAppState: function() {
var state = 0;
if (Browser.pointerLock) {
state |= 0x01; // SDL_APPMOUSEFOCUS
}
if (document.hasFocus()) {
state |= 0x02; // SDL_APPINPUTFOCUS
}
state |= 0x04; // SDL_APPACTIVE
return state;
},
SDL_WM_GrabInput: function() {},
SDL_WM_ToggleFullScreen__proxy: 'sync',
SDL_WM_ToggleFullScreen__sig: 'ii',
SDL_WM_ToggleFullScreen: function(surf) {
if (Browser.isFullscreen) {
Module['canvas'].exitFullscreen();
return 1;
} else {
if (!SDL.canRequestFullscreen) {
return 0;
}
SDL.isRequestingFullscreen = true;
return 1;
}
},
// SDL_Image
IMG_Init: function(flags) {
return flags; // We support JPG, PNG, TIF because browsers do
},
IMG_Load_RW__deps: ['SDL_LockSurface', 'SDL_FreeRW'],
IMG_Load_RW__proxy: 'sync',
IMG_Load_RW__sig: 'iii',
IMG_Load_RW: function(rwopsID, freeSrc) {
try {
// stb_image integration support
var cleanup = function() {
if (rwops && freeSrc) _SDL_FreeRW(rwopsID);
}
var addCleanup = function(func) {
var old = cleanup;
cleanup = function added_cleanup() {
old();
func();
}
}
var callStbImage = function(func, params) {
var x = Module['_malloc']({{{ QUANTUM_SIZE }}});
var y = Module['_malloc']({{{ QUANTUM_SIZE }}});
var comp = Module['_malloc']({{{ QUANTUM_SIZE }}});
addCleanup(function() {
Module['_free'](x);
Module['_free'](y);
Module['_free'](comp);
if (data) Module['_stbi_image_free'](data);
});
var data = Module['_' + func].apply(null, params.concat([x, y, comp, 0]));
if (!data) return null;
return {
rawData: true,
data: data,
width: {{{ makeGetValue('x', 0, 'i32') }}},
height: {{{ makeGetValue('y', 0, 'i32') }}},
size: {{{ makeGetValue('x', 0, 'i32') }}} * {{{ makeGetValue('y', 0, 'i32') }}} * {{{ makeGetValue('comp', 0, 'i32') }}},
bpp: {{{ makeGetValue('comp', 0, 'i32') }}}
};
}
var rwops = SDL.rwops[rwopsID];
if (rwops === undefined) {
return 0;
}
var filename = rwops.filename;
if (filename === undefined) {
#if STB_IMAGE
var raw = callStbImage('stbi_load_from_memory', [rwops.bytes, rwops.count]);
if (!raw) return 0;
#else
warnOnce('Only file names that have been preloaded are supported for IMG_Load_RW. Consider using STB_IMAGE=1 if you want synchronous image decoding (see settings.js), or package files with --use-preload-plugins');
return 0;
#endif
}
if (!raw) {
filename = PATH.resolve(filename);
var raw = Module["preloadedImages"][filename];
if (!raw) {
if (raw === null) err('Trying to reuse preloaded image, but freePreloadedMediaOnUse is set!');
#if STB_IMAGE
var lengthBytes = lengthBytesUTF8(filename)+1;
var name = Module['_malloc'](lengthBytes);
stringToUTF8(filename, name, lengthBytes);
addCleanup(function() {
Module['_free'](name);
});
var raw = callStbImage('stbi_load', [name]);
if (!raw) return 0;
#else
warnOnce('Cannot find preloaded image ' + filename);
warnOnce('Cannot find preloaded image ' + filename + '. Consider using STB_IMAGE=1 if you want synchronous image decoding (see settings.js), or package files with --use-preload-plugins');
return 0;
#endif
} else if (Module['freePreloadedMediaOnUse']) {
Module["preloadedImages"][filename] = null;
}
}
var surf = SDL.makeSurface(raw.width, raw.height, 0, false, 'load:' + filename);
var surfData = SDL.surfaces[surf];
surfData.ctx.globalCompositeOperation = "copy";
if (!raw.rawData) {
surfData.ctx.drawImage(raw, 0, 0, raw.width, raw.height, 0, 0, raw.width, raw.height);
} else {
var imageData = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height);
if (raw.bpp == 4) {
// rgba
imageData.data.set({{{ makeHEAPView('U8', 'raw.data', 'raw.data+raw.size') }}});
} else if (raw.bpp == 3) {
// rgb
var pixels = raw.size/3;
var data = imageData.data;
var sourcePtr = raw.data;
var destPtr = 0;
for (var i = 0; i < pixels; i++) {
data[destPtr++] = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
data[destPtr++] = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
data[destPtr++] = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
data[destPtr++] = 255;
}
} else if (raw.bpp == 2) {
// grayscale + alpha
var pixels = raw.size;
var data = imageData.data;
var sourcePtr = raw.data;
var destPtr = 0;
for (var i = 0; i < pixels; i++) {
var gray = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
var alpha = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
data[destPtr++] = gray;
data[destPtr++] = gray;
data[destPtr++] = gray;
data[destPtr++] = alpha;
}
} else if (raw.bpp == 1) {
// grayscale
var pixels = raw.size;
var data = imageData.data;
var sourcePtr = raw.data;
var destPtr = 0;
for (var i = 0; i < pixels; i++) {
var value = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}};
data[destPtr++] = value;
data[destPtr++] = value;
data[destPtr++] = value;
data[destPtr++] = 255;
}
} else {
err('cannot handle bpp ' + raw.bpp);
return 0;
}
surfData.ctx.putImageData(imageData, 0, 0);
}
surfData.ctx.globalCompositeOperation = "source-over";
// XXX SDL does not specify that loaded images must have available pixel data, in fact
// there are cases where you just want to blit them, so you just need the hardware
// accelerated version. However, code everywhere seems to assume that the pixels
// are in fact available, so we retrieve it here. This does add overhead though.
_SDL_LockSurface(surf);
surfData.locked--; // The surface is not actually locked in this hack
if (SDL.GL) {
// After getting the pixel data, we can free the canvas and context if we do not need to do 2D canvas blitting
surfData.canvas = surfData.ctx = null;
}
return surf;
} finally {
cleanup();
}
},
SDL_LoadBMP: 'IMG_Load',
SDL_LoadBMP_RW: 'IMG_Load_RW',
IMG_Load__deps: ['IMG_Load_RW', 'SDL_RWFromFile'],
IMG_Load__proxy: 'sync',
IMG_Load__sig: 'ii',
IMG_Load: function(filename){
var rwops = _SDL_RWFromFile(filename);
var result = _IMG_Load_RW(rwops, 1);
return result;
},
IMG_Quit: function() {
out('IMG_Quit called (and ignored)');
},
// SDL_Audio
#if EMTERPRETIFY_ASYNC
SDL_OpenAudio__deps: ['$EmterpreterAsync'],
#endif
SDL_OpenAudio__proxy: 'sync',
SDL_OpenAudio__sig: 'iii',
SDL_OpenAudio: function(desired, obtained) {
try {
SDL.audio = {
freq: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.freq, 'i32', 0, 1) }}},
format: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.format, 'i16', 0, 1) }}},
channels: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.channels, 'i8', 0, 1) }}},
samples: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.samples, 'i16', 0, 1) }}}, // Samples in the CB buffer per single sound channel.
callback: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.callback, 'void*', 0, 1) }}},
userdata: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.userdata, 'void*', 0, 1) }}},
paused: true,
timer: null
};
// The .silence field tells the constant sample value that corresponds to the safe un-skewed silence value for the wave data.
if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
SDL.audio.silence = 128; // Audio ranges in [0, 255], so silence is half-way in between.
} else if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
SDL.audio.silence = 0; // Signed data in range [-32768, 32767], silence is 0.
} else if (SDL.audio.format == 0x8120 /*AUDIO_F32*/) {
SDL.audio.silence = 0.0; // Float data in range [-1.0, 1.0], silence is 0.0
} else {
throw 'Invalid SDL audio format ' + SDL.audio.format + '!';
}
// Round the desired audio frequency up to the next 'common' frequency value.
// Web Audio API spec states 'An implementation must support sample-rates in at least the range 22050 to 96000.'
if (SDL.audio.freq <= 0) {
throw 'Unsupported sound frequency ' + SDL.audio.freq + '!';
} else if (SDL.audio.freq <= 22050) {
SDL.audio.freq = 22050; // Take it safe and clamp everything lower than 22kHz to that.
} else if (SDL.audio.freq <= 32000) {
SDL.audio.freq = 32000;
} else if (SDL.audio.freq <= 44100) {
SDL.audio.freq = 44100;
} else if (SDL.audio.freq <= 48000) {
SDL.audio.freq = 48000;
} else if (SDL.audio.freq <= 96000) {
SDL.audio.freq = 96000;
} else {
throw 'Unsupported sound frequency ' + SDL.audio.freq + '!';
}
if (SDL.audio.channels == 0) {
SDL.audio.channels = 1; // In SDL both 0 and 1 mean mono.
} else if (SDL.audio.channels < 0 || SDL.audio.channels > 32) {
throw 'Unsupported number of audio channels for SDL audio: ' + SDL.audio.channels + '!';
} else if (SDL.audio.channels != 1 && SDL.audio.channels != 2) { // Unsure what SDL audio spec supports. Web Audio spec supports up to 32 channels.
console.log('Warning: Using untested number of audio channels ' + SDL.audio.channels);
}
if (SDL.audio.samples < 128 || SDL.audio.samples > 524288 /* arbitrary cap */) {
throw 'Unsupported audio callback buffer size ' + SDL.audio.samples + '!';
} else if ((SDL.audio.samples & (SDL.audio.samples-1)) != 0) {
throw 'Audio callback buffer size ' + SDL.audio.samples + ' must be a power-of-two!';
}
var totalSamples = SDL.audio.samples*SDL.audio.channels;
if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) {
SDL.audio.bytesPerSample = 1;
} else if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) {
SDL.audio.bytesPerSample = 2;
} else if (SDL.audio.format == 0x8120 /*AUDIO_F32*/) {
SDL.audio.bytesPerSample = 4;
} else {
throw 'Invalid SDL audio format ' + SDL.audio.format + '!';
}
SDL.audio.bufferSize = totalSamples*SDL.audio.bytesPerSample;
SDL.audio.bufferDurationSecs = SDL.audio.bufferSize / SDL.audio.bytesPerSample / SDL.audio.channels / SDL.audio.freq; // Duration of a single queued buffer in seconds.
SDL.audio.bufferingDelay = 50 / 1000; // Audio samples are played with a constant delay of this many seconds to account for browser and jitter.
SDL.audio.buffer = _malloc(SDL.audio.bufferSize);
// To account for jittering in frametimes, always have multiple audio buffers queued up for the audio output device.
// This helps that we won't starve that easily if a frame takes long to complete.
SDL.audio.numSimultaneouslyQueuedBuffers = Module['SDL_numSimultaneouslyQueuedBuffers'] || 5;
// Pulls and queues new audio data if appropriate. This function gets "over-called" in both requestAnimationFrames and
// setTimeouts to ensure that we get the finest granularity possible and as many chances from the browser to fill
// new audio data. This is because setTimeouts alone have very poor granularity for audio streaming purposes, but also
// the application might not be using emscripten_set_main_loop to drive the main loop, so we cannot rely on that alone.
SDL.audio.queueNewAudioData = function SDL_queueNewAudioData() {
if (!SDL.audio) return;
for(var i = 0; i < SDL.audio.numSimultaneouslyQueuedBuffers; ++i) {
// Only queue new data if we don't have enough audio data already in queue. Otherwise skip this time slot
// and wait to queue more in the next time the callback is run.
var secsUntilNextPlayStart = SDL.audio.nextPlayTime - SDL.audioContext['currentTime'];
if (secsUntilNextPlayStart >= SDL.audio.bufferingDelay + SDL.audio.bufferDurationSecs*SDL.audio.numSimultaneouslyQueuedBuffers) return;
// Ask SDL audio data from the user code.
Module['dynCall_viii'](SDL.audio.callback, SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize);
// And queue it to be played after the currently playing audio stream.
SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize);
}
}
#if EMTERPRETIFY_ASYNC
var yieldCallback = function() {
if (SDL.audio && SDL.audio.queueNewAudioData) SDL.audio.queueNewAudioData();
};
SDL.audio.yieldCallback = yieldCallback;
EmterpreterAsync.yieldCallbacks.push(yieldCallback);
#endif
// Create a callback function that will be routinely called to ask more audio data from the user application.
SDL.audio.caller = function SDL_audioCaller() {
if (!SDL.audio) return;
--SDL.audio.numAudioTimersPending;
SDL.audio.queueNewAudioData();
// Queue this callback function to be called again later to pull more audio data.
var secsUntilNextPlayStart = SDL.audio.nextPlayTime - SDL.audioContext['currentTime'];
// Queue the next audio frame push to be performed half-way when the previously queued buffer has finished playing.
var preemptBufferFeedSecs = SDL.audio.bufferDurationSecs/2.0;
if (SDL.audio.numAudioTimersPending < SDL.audio.numSimultaneouslyQueuedBuffers) {
++SDL.audio.numAudioTimersPending;
SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*(secsUntilNextPlayStart-preemptBufferFeedSecs)));
// If we are risking starving, immediately queue an extra buffer.
if (SDL.audio.numAudioTimersPending < SDL.audio.numSimultaneouslyQueuedBuffers) {
++SDL.audio.numAudioTimersPending;
Browser.safeSetTimeout(SDL.audio.caller, 1.0);
}
}
};
SDL.audio.audioOutput = new Audio();
// Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page,
// since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'.
SDL.openAudioContext();
if (!SDL.audioContext) throw 'Web Audio API is not available!';
SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start.
// The pushAudio function with a new audio buffer whenever there is new audio data to schedule to be played back on the device.
SDL.audio.pushAudio=function(ptr,sizeBytes) {
try {
if (SDL.audio.paused) return;
var sizeSamples = sizeBytes / SDL.audio.bytesPerSample; // How many samples fit in the callback buffer?
var sizeSamplesPerChannel = sizeSamples / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer?
if (sizeSamplesPerChannel != SDL.audio.samples) {
throw 'Received mismatching audio buffer size!';
}
// Allocate new sound buffer to be played.
var source = SDL.audioContext['createBufferSource']();
var soundBuffer = SDL.audioContext['createBuffer'](SDL.audio.channels,sizeSamplesPerChannel,SDL.audio.freq);
source['connect'](SDL.audioContext['destination']);
SDL.fillWebAudioBufferFromHeap(ptr, sizeSamplesPerChannel, soundBuffer);
// Workaround https://bugzilla.mozilla.org/show_bug.cgi?id=883675 by setting the buffer only after filling. The order is important here!
source['buffer'] = soundBuffer;
// Schedule the generated sample buffer to be played out at the correct time right after the previously scheduled
// sample buffer has finished.
var curtime = SDL.audioContext['currentTime'];
#if ASSERTIONS
if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) {
console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.');
}
#endif
// Don't ever start buffer playbacks earlier from current time than a given constant 'SDL.audio.bufferingDelay', since a browser
// may not be able to mix that audio clip in immediately, and there may be subsequent jitter that might cause the stream to starve.
var playtime = Math.max(curtime + SDL.audio.bufferingDelay, SDL.audio.nextPlayTime);
if (typeof source['start'] !== 'undefined') {
source['start'](playtime); // New Web Audio API: sound sources are started with a .start() call.
} else if (typeof source['noteOn'] !== 'undefined') {
source['noteOn'](playtime); // Support old Web Audio API specification which had the .noteOn() API.
}
/*
// Uncomment to debug SDL buffer feed starves.
if (SDL.audio.curBufferEnd) {
var thisBufferStart = Math.round(playtime * SDL.audio.freq);
if (thisBufferStart != SDL.audio.curBufferEnd) console.log('SDL starved ' + (thisBufferStart - SDL.audio.curBufferEnd) + ' samples!');
}
SDL.audio.curBufferEnd = Math.round(playtime * SDL.audio.freq + sizeSamplesPerChannel);
*/
SDL.audio.nextPlayTime = playtime + SDL.audio.bufferDurationSecs;
} catch(e) {
console.log('Web Audio API error playing back audio: ' + e.toString());
}
}
if (obtained) {
// Report back the initialized audio parameters.
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.freq, 'SDL.audio.freq', 'i32') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.format, 'SDL.audio.format', 'i16') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.channels, 'SDL.audio.channels', 'i8') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.silence, 'SDL.audio.silence', 'i8') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.samples, 'SDL.audio.samples', 'i16') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.callback, 'SDL.audio.callback', '*') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.userdata, 'SDL.audio.userdata', '*') }}};
}
SDL.allocateChannels(32);
} catch(e) {
console.log('Initializing SDL audio threw an exception: "' + e.toString() + '"! Continuing without audio.');
SDL.audio = null;
SDL.allocateChannels(0);
if (obtained) {
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.freq, 0, 'i32') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.format, 0, 'i16') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.channels, 0, 'i8') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.silence, 0, 'i8') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.samples, 0, 'i16') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.callback, 0, '*') }}};
{{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.userdata, 0, '*') }}};
}
}
if (!SDL.audio) {
return -1;
}
return 0;
},
SDL_PauseAudio__proxy: 'sync',
SDL_PauseAudio__sig: 'vi',
SDL_PauseAudio: function(pauseOn) {
if (!SDL.audio) {
return;
}
if (pauseOn) {
if (SDL.audio.timer !== undefined) {
clearTimeout(SDL.audio.timer);
SDL.audio.numAudioTimersPending = 0;
SDL.audio.timer = undefined;
}
} else if (!SDL.audio.timer) {
// Start the audio playback timer callback loop.
SDL.audio.numAudioTimersPending = 1;
SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1);
}
SDL.audio.paused = pauseOn;
},
SDL_CloseAudio__deps: ['SDL_PauseAudio', 'free'],
SDL_CloseAudio__proxy: 'sync',
SDL_CloseAudio__sig: 'v',
SDL_CloseAudio: function() {
if (SDL.audio) {
#if EMTERPRETIFY_ASYNC
EmterpreterAsync.yieldCallbacks = EmterpreterAsync.yieldCallbacks.filter(function(callback) {
return callback !== SDL.audio.yieldCallback;
});
#endif
_SDL_PauseAudio(1);
_free(SDL.audio.buffer);
SDL.audio = null;
SDL.allocateChannels(0);
}
},
SDL_LockAudio: function() {},
SDL_UnlockAudio: function() {},
SDL_CreateMutex: function() { return 0 },
SDL_LockMutex: function() {},
SDL_UnlockMutex: function() {},
SDL_mutexP: function() { return 0 },
SDL_mutexV: function() { return 0 },
SDL_DestroyMutex: function() {},
SDL_CreateCond: function() { return 0 },
SDL_CondSignal: function() {},
SDL_CondWait: function() {},
SDL_DestroyCond: function() {},
SDL_StartTextInput__proxy: 'sync',
SDL_StartTextInput__sig: 'v',
SDL_StartTextInput: function() {
SDL.textInput = true;
},
SDL_StopTextInput__proxy: 'sync',
SDL_StopTextInput__sig: 'v',
SDL_StopTextInput: function() {
SDL.textInput = false;
},
// SDL Mixer
Mix_Init: function(flags) {
if (!flags) return 0;
return 8; /* MIX_INIT_OGG */
},
Mix_Quit: function(){},
Mix_OpenAudio__proxy: 'sync',
Mix_OpenAudio__sig: 'iiiii',
Mix_OpenAudio: function(frequency, format, channels, chunksize) {
SDL.openAudioContext();
SDL.allocateChannels(32);
// Just record the values for a later call to Mix_QuickLoad_RAW
SDL.mixerFrequency = frequency;
SDL.mixerFormat = format;
SDL.mixerNumChannels = channels;
SDL.mixerChunkSize = chunksize;
return 0;
},
Mix_CloseAudio: 'SDL_CloseAudio',
Mix_AllocateChannels__proxy: 'sync',
Mix_AllocateChannels__sig: 'ii',
Mix_AllocateChannels: function(num) {
SDL.allocateChannels(num);
return num;
},
Mix_ChannelFinished__proxy: 'sync',
Mix_ChannelFinished__sig: 'vi',
Mix_ChannelFinished: function(func) {
SDL.channelFinished = func;
},
Mix_Volume__proxy: 'sync',
Mix_Volume__sig: 'iii',
Mix_Volume: function(channel, volume) {
if (channel == -1) {
for (var i = 0; i < SDL.numChannels-1; i++) {
_Mix_Volume(i, volume);
}
return _Mix_Volume(SDL.numChannels-1, volume);
}
return SDL.setGetVolume(SDL.channels[channel], volume);
},
// Note: Mix_SetPanning requires WebAudio (file loaded from memory).
Mix_SetPanning__proxy: 'sync',
Mix_SetPanning__sig: 'iiii',
Mix_SetPanning: function(channel, left, right) {
// SDL API uses [0-255], while PannerNode has an (x, y, z) position.
// Normalizing.
left /= 255;
right /= 255;
// Set the z coordinate a little forward, otherwise there won't be any
// smooth transition between left and right.
SDL.setPannerPosition(SDL.channels[channel], right - left, 0, 0.1);
return 1;
},
Mix_LoadWAV_RW__proxy: 'sync',
Mix_LoadWAV_RW__sig: 'iii',
Mix_LoadWAV_RW: function(rwopsID, freesrc) {
var rwops = SDL.rwops[rwopsID];
#if USE_SDL == 2
if (rwops === undefined) {
var type = {{{ makeGetValue('rwopsID + ' + 20 /*type*/, '0', 'i32') }}};
if (type === 2/*SDL_RWOPS_STDFILE*/) {
var fp = {{{ makeGetValue('rwopsID + ' + 28 /*hidden.stdio.fp*/, '0', 'i32') }}};
var fd = Module['_fileno'](fp);
var stream = FS.getStream(fd);
if (stream) {
rwops = { filename: stream.path };
}
}
else if (type === 4/*SDL_RWOPS_MEMORY*/ || type === 5/*SDL_RWOPS_MEMORY_RO*/) {
var base = {{{ makeGetValue('rwopsID + ' + 24 /*hidden.mem.base*/, '0', 'i32') }}};
var stop = {{{ makeGetValue('rwopsID + ' + 32 /*hidden.mem.stop*/, '0', 'i32') }}};
rwops = { bytes: base, count: stop - base };
}
}
#endif
if (rwops === undefined)
return 0;
var filename = '';
var audio;
var webAudio;
var bytes;
if (rwops.filename !== undefined) {
filename = PATH.resolve(rwops.filename);
var raw = Module["preloadedAudios"][filename];
if (!raw) {
if (raw === null) err('Trying to reuse preloaded audio, but freePreloadedMediaOnUse is set!');
if (!Module.noAudioDecoding) warnOnce('Cannot find preloaded audio ' + filename);
// see if we can read the file-contents from the in-memory FS
try {
bytes = FS.readFile(filename);
} catch (e) {
err('Couldn\'t find file for: ' + filename);
return 0;
}
}
if (Module['freePreloadedMediaOnUse']) {
Module["preloadedAudios"][filename] = null;
}
audio = raw;
}
else if (rwops.bytes !== undefined) {
// For Web Audio context buffer decoding, we must make a clone of the audio data, but for <media> element,
// a view to existing data is sufficient.
if (SDL.webAudioAvailable()) bytes = HEAPU8.buffer.slice(rwops.bytes, rwops.bytes + rwops.count);
else bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count);
}
else {
return 0;
}
var arrayBuffer = bytes ? bytes.buffer || bytes : bytes;
// To allow user code to work around browser bugs with audio playback on <audio> elements an Web Audio, enable
// the user code to hook in a callback to decide on a file basis whether each file should use Web Audio or <audio> for decoding and playback.
// In particular, see https://bugzilla.mozilla.org/show_bug.cgi?id=654787 and ?id=1012801 for tradeoffs.
var canPlayWithWebAudio = Module['SDL_canPlayWithWebAudio'] === undefined || Module['SDL_canPlayWithWebAudio'](filename, arrayBuffer);
if (bytes !== undefined && SDL.webAudioAvailable() && canPlayWithWebAudio) {
audio = undefined;
webAudio = {};
// The audio decoding process is asynchronous, which gives trouble if user code plays the audio data back immediately
// after loading. Therefore prepare an array of callback handlers to run when this audio decoding is complete, which
// will then start the playback (with some delay).
webAudio.onDecodeComplete = []; // While this member array exists, decoding hasn't finished yet.
function onDecodeComplete(data) {
webAudio.decodedBuffer = data;
// Call all handlers that were waiting for this decode to finish, and clear the handler list.
webAudio.onDecodeComplete.forEach(function(e) { e(); });
webAudio.onDecodeComplete = undefined; // Don't allow more callback handlers since audio has finished decoding.
}
SDL.audioContext['decodeAudioData'](arrayBuffer, onDecodeComplete);
} else if (audio === undefined && bytes) {
// Here, we didn't find a preloaded audio but we either were passed a filepath for
// which we loaded bytes, or we were passed some bytes
var blob = new Blob([bytes], {type: rwops.mimetype});
var url = URL.createObjectURL(blob);
audio = new Audio();
audio.src = url;
audio.mozAudioChannelType = 'content'; // bugzilla 910340
}
var id = SDL.audios.length;
// Keep the loaded audio in the audio arrays, ready for playback
SDL.audios.push({
source: filename,
audio: audio, // Points to the <audio> element, if loaded
webAudio: webAudio // Points to a Web Audio -specific resource object, if loaded
});
return id;
},
Mix_LoadWAV__deps: ['Mix_LoadWAV_RW', 'SDL_RWFromFile', 'SDL_FreeRW'],
Mix_LoadWAV__proxy: 'sync',
Mix_LoadWAV__sig: 'ii',
Mix_LoadWAV: function(filename) {
var rwops = _SDL_RWFromFile(filename);
var result = _Mix_LoadWAV_RW(rwops);
_SDL_FreeRW(rwops);
return result;
},
Mix_QuickLoad_RAW__proxy: 'sync',
Mix_QuickLoad_RAW__sig: 'iii',
Mix_QuickLoad_RAW: function(mem, len) {
var audio;
var webAudio;
var numSamples = len >> 1; // len is the length in bytes, and the array contains 16-bit PCM values
var buffer = new Float32Array(numSamples);
for (var i = 0; i < numSamples; ++i) {
buffer[i] = ({{{ makeGetValue('mem', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?)
}
if (SDL.webAudioAvailable()) {
webAudio = {};
webAudio.decodedBuffer = buffer;
} else {
var audio = new Audio();
audio.mozAudioChannelType = 'content'; // bugzilla 910340
// Record the number of channels and frequency for later usage
audio.numChannels = SDL.mixerNumChannels;
audio.frequency = SDL.mixerFrequency;
// FIXME: doesn't make sense to keep the audio element in the buffer
}
var id = SDL.audios.length;
SDL.audios.push({
source: '',
audio: audio,
webAudio: webAudio,
buffer: buffer
});
return id;
},
Mix_FreeChunk__proxy: 'sync',
Mix_FreeChunk__sig: 'vi',
Mix_FreeChunk: function(id) {
SDL.audios[id] = null;
},
Mix_ReserveChannels__proxy: 'sync',
Mix_ReserveChannels__sig: 'ii',
Mix_ReserveChannels: function(num) {
SDL.channelMinimumNumber = num;
},
Mix_PlayChannel__proxy: 'sync',
Mix_PlayChannel__sig: 'iiii',
Mix_PlayChannel: function(channel, id, loops) {
// TODO: handle fixed amount of N loops. Currently loops either 0 or infinite times.
// Get the audio element associated with the ID
var info = SDL.audios[id];
if (!info) return -1;
if (!info.audio && !info.webAudio) return -1;
// If the user asks us to allocate a channel automatically, get the first
// free one.
if (channel == -1) {
for (var i = SDL.channelMinimumNumber; i < SDL.numChannels; i++) {
if (!SDL.channels[i].audio) {
channel = i;
break;
}
}
if (channel == -1) {
err('All ' + SDL.numChannels + ' channels in use!');
return -1;
}
}
var channelInfo = SDL.channels[channel];
var audio;
if (info.webAudio) {
// Create an instance of the WebAudio object.
audio = {};
audio.resource = info; // This new object is an instance that refers to this existing resource.
audio.paused = false;
audio.currentPosition = 0;
// Make our instance look similar to the instance of a <media> to make api simple.
audio.play = function() { SDL.playWebAudio(this); }
audio.pause = function() { SDL.pauseWebAudio(this); }
} else {
// We clone the audio node to utilize the preloaded audio buffer, since
// the browser has already preloaded the audio file.
audio = info.audio.cloneNode(true);
audio.numChannels = info.audio.numChannels;
audio.frequency = info.audio.frequency;
}
audio['onended'] = function SDL_audio_onended() { // TODO: cache these
if (channelInfo.audio == this) { channelInfo.audio.paused = true; channelInfo.audio = null; }
if (SDL.channelFinished) getFuncWrapper(SDL.channelFinished, 'vi')(channel);
}
channelInfo.audio = audio;
// TODO: handle N loops. Behavior matches Mix_PlayMusic
audio.loop = loops != 0;
audio.volume = channelInfo.volume;
audio.play();
return channel;
},
Mix_PlayChannelTimed: 'Mix_PlayChannel', // XXX ignore Timing
Mix_FadingChannel: function(channel) {
return 0; // MIX_NO_FADING, TODO
},
Mix_HaltChannel__proxy: 'sync',
Mix_HaltChannel__sig: 'ii',
Mix_HaltChannel: function(channel) {
function halt(channel) {
var info = SDL.channels[channel];
if (info.audio) {
info.audio.pause();
info.audio = null;
}
if (SDL.channelFinished) {
getFuncWrapper(SDL.channelFinished, 'vi')(channel);
}
}
if (channel != -1) {
halt(channel);
} else {
for (var i = 0; i < SDL.channels.length; ++i) halt(i);
}
return 0;
},
Mix_HookMusicFinished__deps: ['Mix_HaltMusic'],
Mix_HookMusicFinished__proxy: 'sync',
Mix_HookMusicFinished__sig: 'vi',
Mix_HookMusicFinished: function(func) {
SDL.hookMusicFinished = func;
if (SDL.music.audio) { // ensure the callback will be called, if a music is already playing
SDL.music.audio['onended'] = _Mix_HaltMusic;
}
},
Mix_VolumeMusic__proxy: 'sync',
Mix_VolumeMusic__sig: 'ii',
Mix_VolumeMusic: function(volume) {
return SDL.setGetVolume(SDL.music, volume);
},
Mix_LoadMUS_RW: 'Mix_LoadWAV_RW',
Mix_LoadMUS__deps: ['Mix_LoadMUS_RW', 'SDL_RWFromFile', 'SDL_FreeRW'],
Mix_LoadMUS__proxy: 'sync',
Mix_LoadMUS__sig: 'ii',
Mix_LoadMUS: function(filename) {
var rwops = _SDL_RWFromFile(filename);
var result = _Mix_LoadMUS_RW(rwops);
_SDL_FreeRW(rwops);
return result;
},
Mix_FreeMusic: 'Mix_FreeChunk',
Mix_PlayMusic__deps: ['Mix_HaltMusic'],
Mix_PlayMusic__proxy: 'sync',
Mix_PlayMusic__sig: 'iii',
Mix_PlayMusic: function(id, loops) {
// Pause old music if it exists.
if (SDL.music.audio) {
if (!SDL.music.audio.paused) err('Music is already playing. ' + SDL.music.source);
SDL.music.audio.pause();
}
var info = SDL.audios[id];
var audio;
if (info.webAudio) { // Play via Web Audio API
// Create an instance of the WebAudio object.
audio = {};
audio.resource = info; // This new webAudio object is an instance that refers to this existing resource.
audio.paused = false;
audio.currentPosition = 0;
audio.play = function() { SDL.playWebAudio(this); }
audio.pause = function() { SDL.pauseWebAudio(this); }
} else if (info.audio) { // Play via the <audio> element
audio = info.audio;
}
audio['onended'] = function() { if (SDL.music.audio == this) _Mix_HaltMusic(); } // will send callback
audio.loop = loops != 0; // TODO: handle N loops for finite N
audio.volume = SDL.music.volume;
SDL.music.audio = audio;
audio.play();
return 0;
},
Mix_PauseMusic__proxy: 'sync',
Mix_PauseMusic__sig: 'v',
Mix_PauseMusic: function() {
var audio = SDL.music.audio;
if (audio) audio.pause();
},
Mix_ResumeMusic__proxy: 'sync',
Mix_ResumeMusic__sig: 'v',
Mix_ResumeMusic: function() {
var audio = SDL.music.audio;
if (audio) audio.play();
},
Mix_HaltMusic__proxy: 'sync',
Mix_HaltMusic__sig: 'i',
Mix_HaltMusic: function() {
var audio = SDL.music.audio;
if (audio) {
audio.src = audio.src; // rewind <media> element
audio.currentPosition = 0; // rewind Web Audio graph playback.
audio.pause();
}
SDL.music.audio = null;
if (SDL.hookMusicFinished) {
Module['dynCall_v'](SDL.hookMusicFinished);
}
return 0;
},
Mix_FadeInMusicPos: 'Mix_PlayMusic', // XXX ignore fading in effect
Mix_FadeOutMusic: 'Mix_HaltMusic', // XXX ignore fading out effect
Mix_PlayingMusic__proxy: 'sync',
Mix_PlayingMusic__sig: 'i',
Mix_PlayingMusic: function() {
return (SDL.music.audio && !SDL.music.audio.paused) ? 1 : 0;
},
// http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_38.html#SEC38
// "Note: Does not check if the channel has been paused."
Mix_Playing__proxy: 'sync',
Mix_Playing__sig: 'ii',
Mix_Playing: function(channel) {
if (channel === -1) {
var count = 0;
for (var i = 0; i < SDL.channels.length; i++) {
count += _Mix_Playing(i);
}
return count;
}
var info = SDL.channels[channel];
if (info && info.audio && !info.audio.paused) {
return 1;
}
return 0;
},
Mix_Pause__proxy: 'sync',
Mix_Pause__sig: 'vi',
Mix_Pause: function(channel) {
if (channel === -1) {
for (var i = 0; i<SDL.channels.length;i++) {
_Mix_Pause(i);
}
return;
}
var info = SDL.channels[channel];
if (info && info.audio) {
info.audio.pause();
} else {
//err('Mix_Pause: no sound found for channel: ' + channel);
}
},
// http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_39.html#SEC39
Mix_Paused__proxy: 'sync',
Mix_Paused__sig: 'ii',
Mix_Paused: function(channel) {
if (channel === -1) {
var pausedCount = 0;
for (var i = 0; i<SDL.channels.length;i++) {
pausedCount += _Mix_Paused(i);
}
return pausedCount;
}
var info = SDL.channels[channel];
if (info && info.audio && info.audio.paused) {
return 1;
}
return 0;
},
Mix_PausedMusic__proxy: 'sync',
Mix_PausedMusic__sig: 'i',
Mix_PausedMusic: function() {
return (SDL.music.audio && SDL.music.audio.paused) ? 1 : 0;
},
// http://www.libsdl.org/projects/SDL_mixer/docs/SDL_mixer_33.html#SEC33
Mix_Resume__proxy: 'sync',
Mix_Resume__sig: 'vi',
Mix_Resume: function(channel) {
if (channel === -1) {
for (var i = 0; i<SDL.channels.length;i++) {
_Mix_Resume(i);
}
return;
}
var info = SDL.channels[channel];
if (info && info.audio) info.audio.play();
},
// SDL TTF
TTF_Init__proxy: 'sync',
TTF_Init__sig: 'i',
TTF_Init: function() {
var canvas = document.createElement('canvas');
SDL.ttfContext = canvas.getContext('2d');
return 0;
},
TTF_OpenFont__proxy: 'sync',
TTF_OpenFont__sig: 'iii',
TTF_OpenFont: function(filename, size) {
filename = PATH.normalize(Pointer_stringify(filename));
var id = SDL.fonts.length;
SDL.fonts.push({
name: filename, // but we don't actually do anything with it..
size: size
});
return id;
},
TTF_CloseFont__proxy: 'sync',
TTF_CloseFont__sig: 'vi',
TTF_CloseFont: function(font) {
SDL.fonts[font] = null;
},
TTF_RenderText_Solid__proxy: 'sync',
TTF_RenderText_Solid__sig: 'iiii',
TTF_RenderText_Solid: function(font, text, color) {
// XXX the font and color are ignored
text = Pointer_stringify(text) || ' '; // if given an empty string, still return a valid surface
var fontData = SDL.fonts[font];
var w = SDL.estimateTextWidth(fontData, text);
var h = fontData.size;
color = SDL.loadColorToCSSRGB(color); // XXX alpha breaks fonts?
var fontString = SDL.makeFontString(h, fontData.name);
var surf = SDL.makeSurface(w, h, 0, false, 'text:' + text); // bogus numbers..
var surfData = SDL.surfaces[surf];
surfData.ctx.save();
surfData.ctx.fillStyle = color;
surfData.ctx.font = fontString;
// use bottom alligment, because it works
// same in all browsers, more info here:
// https://bugzilla.mozilla.org/show_bug.cgi?id=737852
surfData.ctx.textBaseline = 'bottom';
surfData.ctx.fillText(text, 0, h|0);
surfData.ctx.restore();
return surf;
},
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_SizeUTF8: 'TTF_SizeText',
TTF_SizeText__proxy: 'sync',
TTF_SizeText__sig: 'iiiii',
TTF_SizeText: function(font, text, w, h) {
var fontData = SDL.fonts[font];
if (w) {
{{{ makeSetValue('w', '0', 'SDL.estimateTextWidth(fontData, Pointer_stringify(text))', 'i32') }}};
}
if (h) {
{{{ makeSetValue('h', '0', 'fontData.size', 'i32') }}};
}
return 0;
},
TTF_GlyphMetrics__proxy: 'sync',
TTF_GlyphMetrics__sig: 'iiiiiiii',
TTF_GlyphMetrics: function(font, ch, minx, maxx, miny, maxy, advance) {
var fontData = SDL.fonts[font];
var width = SDL.estimateTextWidth(fontData, String.fromCharCode(ch));
if (advance) {
{{{ makeSetValue('advance', '0', 'width', 'i32') }}};
}
if (minx) {
{{{ makeSetValue('minx', '0', '0', 'i32') }}};
}
if (maxx) {
{{{ makeSetValue('maxx', '0', 'width', 'i32') }}};
}
if (miny) {
{{{ makeSetValue('miny', '0', '0', 'i32') }}};
}
if (maxy) {
{{{ makeSetValue('maxy', '0', 'fontData.size', 'i32') }}};
}
},
TTF_FontAscent__proxy: 'sync',
TTF_FontAscent__sig: 'ii',
TTF_FontAscent: function(font) {
var fontData = SDL.fonts[font];
return (fontData.size*0.98)|0; // XXX
},
TTF_FontDescent__proxy: 'sync',
TTF_FontDescent__sig: 'ii',
TTF_FontDescent: function(font) {
var fontData = SDL.fonts[font];
return (fontData.size*0.02)|0; // XXX
},
TTF_FontHeight__proxy: 'sync',
TTF_FontHeight__sig: 'ii',
TTF_FontHeight: function(font) {
var fontData = SDL.fonts[font];
return fontData.size;
},
TTF_FontLineSkip: 'TTF_FontHeight', // XXX
TTF_Quit: function() {
out('TTF_Quit called (and ignored)');
},
// 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) {
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) {
return SDL_gfx.drawRectangle(surf, x1, y1, x2, y2, 'stroke', SDL.translateRGBAToCSSRGBA(r, g, b, a));
},
ellipseColor__deps: ['$SDL_gfx'],
ellipseColor: function(surf, x, y, rx, ry, color) {
return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'stroke', SDL_gfx.translateColorToCSSRGBA(color));
},
ellipseRGBA__deps: ['$SDL_gfx'],
ellipseRGBA: function(surf, x, y, rx, ry, r, g, b, a) {
return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'stroke', SDL.translateRGBAToCSSRGBA(r, g, b, a));
},
filledEllipseColor__deps: ['$SDL_gfx'],
filledEllipseColor: function(surf, x, y, rx, ry, color) {
return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'fill', SDL_gfx.translateColorToCSSRGBA(color));
},
filledEllipseRGBA__deps: ['$SDL_gfx'],
filledEllipseRGBA: function(surf, x, y, rx, ry, r, g, b, a) {
return SDL_gfx.drawEllipse(surf, x, y, rx, ry, 'fill', SDL.translateRGBAToCSSRGBA(r, g, b, a));
},
lineColor__deps: ['$SDL_gfx'],
lineColor: function(surf, x1, y1, x2, y2, color) {
return SDL_gfx.drawLine(surf, x1, y1, x2, y2, SDL_gfx.translateColorToCSSRGBA(color));
},
lineRGBA__deps: ['$SDL_gfx'],
lineRGBA: function(surf, x1, y1, x2, y2, r, g, b, a) {
return SDL_gfx.drawLine(surf, x1, y1, x2, y2, SDL.translateRGBAToCSSRGBA(r, g, b, a));
},
pixelRGBA__deps: ['boxRGBA'],
pixelRGBA: function(surf, x1, y1, r, g, b, a) {
// This cannot be fast, to render many pixels this way!
_boxRGBA(surf, x1, y1, x1, y1, r, g, b, a);
},
// GL
SDL_GL_SetAttribute__proxy: 'sync',
SDL_GL_SetAttribute__sig: 'iii',
SDL_GL_SetAttribute: function(attr, value) {
if (!(attr in SDL.glAttributes)) {
abort('Unknown SDL GL attribute (' + attr + '). Please check if your SDL version is supported.');
}
SDL.glAttributes[attr] = value;
},
SDL_GL_GetAttribute__proxy: 'sync',
SDL_GL_GetAttribute__sig: 'iii',
SDL_GL_GetAttribute: function(attr, value) {
if (!(attr in SDL.glAttributes)) {
abort('Unknown SDL GL attribute (' + attr + '). Please check if your SDL version is supported.');
}
if (value) {{{ makeSetValue('value', '0', 'SDL.glAttributes[attr]', 'i32') }}};
return 0;
},
SDL_GL_GetProcAddress__deps: ['emscripten_GetProcAddress'],
SDL_GL_GetProcAddress: function(name_) {
return _emscripten_GetProcAddress(name_);
},
SDL_GL_SwapBuffers__proxy: 'sync',
SDL_GL_SwapBuffers__sig: 'v',
SDL_GL_SwapBuffers: function() {
if (Browser.doSwapBuffers) Browser.doSwapBuffers(); // in workers, this is used to send out a buffered frame
},
// SDL 2
SDL_GL_ExtensionSupported__proxy: 'sync',
SDL_GL_ExtensionSupported__sig: 'ii',
SDL_GL_ExtensionSupported: function(extension) {
return Module.ctx.getExtension(extension) | 0;
},
SDL_DestroyWindow: function(window) {},
SDL_DestroyRenderer: function(renderer) {},
SDL_GetWindowFlags__proxy: 'sync',
SDL_GetWindowFlags__sig: 'iii',
SDL_GetWindowFlags: function(x, y) {
if (Browser.isFullscreen) {
return 1;
}
return 0;
},
SDL_GL_SwapWindow: function(window) {},
SDL_GL_MakeCurrent: function(window, context) {},
SDL_GL_DeleteContext: function(context) {},
SDL_GL_GetSwapInterval__proxy: 'sync',
SDL_GL_GetSwapInterval__sig: 'ii',
SDL_GL_GetSwapInterval: function(state) {
if (Browser.mainLoop.timingMode == 1/*EM_TIMING_RAF*/) return Browser.mainLoop.timingValue;
else return 0;
},
SDL_GL_SetSwapInterval__deps: ['emscripten_set_main_loop_timing'],
SDL_GL_SetSwapInterval: function(state) {
_emscripten_set_main_loop_timing(1/*EM_TIMING_RAF*/, state);
},
SDL_SetWindowTitle__proxy: 'sync',
SDL_SetWindowTitle__sig: 'vii',
SDL_SetWindowTitle: function(window, title) {
if (title) document.title = Pointer_stringify(title);
},
SDL_GetWindowSize__proxy: 'sync',
SDL_GetWindowSize__sig: 'viii',
SDL_GetWindowSize: function(window, width, height){
var w = Module['canvas'].width;
var h = Module['canvas'].height;
if (width) {{{ makeSetValue('width', '0', 'w', 'i32') }}};
if (height) {{{ makeSetValue('height', '0', 'h', 'i32') }}};
},
SDL_LogSetOutputFunction: function(callback, userdata) {},
SDL_SetWindowFullscreen__proxy: 'sync',
SDL_SetWindowFullscreen__sig: 'iii',
SDL_SetWindowFullscreen: function(window, fullscreen) {
if (Browser.isFullscreen) {
Module['canvas'].exitFullscreen();
return 1;
} else {
return 0;
}
},
SDL_GetWindowFlags: function() {},
SDL_ClearError: function() {},
SDL_getenv: 'getenv',
SDL_putenv: 'putenv',
// TODO
SDL_SetGamma: function(r, g, b) {
return -1;
},
SDL_SetGammaRamp: function(redTable, greenTable, blueTable) {
return -1;
},
// Joysticks
SDL_NumJoysticks__proxy: 'sync',
SDL_NumJoysticks__sig: 'i',
SDL_NumJoysticks: function() {
var count = 0;
var gamepads = SDL.getGamepads();
// The length is not the number of gamepads; check which ones are defined.
for (var i = 0; i < gamepads.length; i++) {
if (gamepads[i] !== undefined) count++;
}
return count;
},
SDL_JoystickName__proxy: 'sync',
SDL_JoystickName__sig: 'ii',
SDL_JoystickName: function(deviceIndex) {
var gamepad = SDL.getGamepad(deviceIndex);
if (gamepad) {
var name = gamepad.id;
if (SDL.joystickNamePool.hasOwnProperty(name)) {
return SDL.joystickNamePool[name];
}
return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL);
}
return 0;
},
SDL_JoystickOpen__proxy: 'sync',
SDL_JoystickOpen__sig: 'ii',
SDL_JoystickOpen: function(deviceIndex) {
var gamepad = SDL.getGamepad(deviceIndex);
if (gamepad) {
// Use this as a unique 'pointer' for this joystick.
var joystick = deviceIndex+1;
SDL.recordJoystickState(joystick, gamepad);
return joystick;
}
return 0;
},
SDL_JoystickOpened__proxy: 'sync',
SDL_JoystickOpened__sig: 'ii',
SDL_JoystickOpened: function(deviceIndex) {
return SDL.lastJoystickState.hasOwnProperty(deviceIndex+1) ? 1 : 0;
},
SDL_JoystickIndex: function(joystick) {
// joystick pointers are simply the deviceIndex+1.
return joystick - 1;
},
SDL_JoystickNumAxes__proxy: 'sync',
SDL_JoystickNumAxes__sig: 'ii',
SDL_JoystickNumAxes: function(joystick) {
var gamepad = SDL.getGamepad(joystick - 1);
if (gamepad) {
return gamepad.axes.length;
}
return 0;
},
SDL_JoystickNumBalls: function(joystick) { return 0; },
SDL_JoystickNumHats: function(joystick) { return 0; },
SDL_JoystickNumButtons__proxy: 'sync',
SDL_JoystickNumButtons__sig: 'ii',
SDL_JoystickNumButtons: function(joystick) {
var gamepad = SDL.getGamepad(joystick - 1);
if (gamepad) {
return gamepad.buttons.length;
}
return 0;
},
SDL_JoystickUpdate__proxy: 'sync',
SDL_JoystickUpdate__sig: 'v',
SDL_JoystickUpdate: function() {
SDL.queryJoysticks();
},
SDL_JoystickEventState__proxy: 'sync',
SDL_JoystickEventState__sig: 'ii',
SDL_JoystickEventState: function(state) {
if (state < 0) {
// SDL_QUERY: Return current state.
return SDL.joystickEventState;
}
return SDL.joystickEventState = state;
},
SDL_JoystickGetAxis__proxy: 'sync',
SDL_JoystickGetAxis__sig: 'iii',
SDL_JoystickGetAxis: function(joystick, axis) {
var gamepad = SDL.getGamepad(joystick - 1);
if (gamepad && gamepad.axes.length > axis) {
return SDL.joystickAxisValueConversion(gamepad.axes[axis]);
}
return 0;
},
SDL_JoystickGetHat: function(joystick, hat) { return 0; },
SDL_JoystickGetBall: function(joystick, ball, dxptr, dyptr) { return -1; },
SDL_JoystickGetButton__proxy: 'sync',
SDL_JoystickGetButton__sig: 'iii',
SDL_JoystickGetButton: function(joystick, button) {
var gamepad = SDL.getGamepad(joystick - 1);
if (gamepad && gamepad.buttons.length > button) {
return SDL.getJoystickButtonState(gamepad.buttons[button]) ? 1 : 0;
}
return 0;
},
SDL_JoystickClose__proxy: 'sync',
SDL_JoystickClose__sig: 'vi',
SDL_JoystickClose: function(joystick) {
delete SDL.lastJoystickState[joystick];
},
// Misc
SDL_InitSubSystem: function(flags) { return 0 },
SDL_RWFromConstMem__proxy: 'sync',
SDL_RWFromConstMem__sig: 'iii',
SDL_RWFromConstMem: function(mem, size) {
var id = SDL.rwops.length; // TODO: recycle ids when they are null
SDL.rwops.push({ bytes: mem, count: size });
return id;
},
SDL_RWFromMem: 'SDL_RWFromConstMem',
SDL_RWFromFile__proxy: 'sync',
SDL_RWFromFile__sig: 'iii',
SDL_RWFromFile: function(_name, mode) {
var id = SDL.rwops.length; // TODO: recycle ids when they are null
var name = Pointer_stringify(_name)
SDL.rwops.push({ filename: name, mimetype: Browser.getMimetype(name) });
return id;
},
SDL_FreeRW__proxy: 'sync',
SDL_FreeRW__sig: 'vi',
SDL_FreeRW: function(rwopsID) {
SDL.rwops[rwopsID] = null;
while (SDL.rwops.length > 0 && SDL.rwops[SDL.rwops.length-1] === null) {
SDL.rwops.pop();
}
},
SDL_GetNumAudioDrivers: function() { return 1 },
SDL_GetCurrentAudioDriver: function() {
return allocate(intArrayFromString('Emscripten Audio'), 'i8', ALLOC_NORMAL);
},
SDL_GetAudioDriver__deps: ['SDL_GetCurrentAudioDriver'],
SDL_GetAudioDriver: function(index) { return _SDL_GetCurrentAudioDriver() },
SDL_EnableUNICODE__proxy: 'sync',
SDL_EnableUNICODE__sig: 'ii',
SDL_EnableUNICODE: function(on) {
var ret = SDL.unicode || 0;
SDL.unicode = on;
return ret;
},
SDL_AddTimer__proxy: 'sync',
SDL_AddTimer__sig: 'iiii',
SDL_AddTimer: function(interval, callback, param) {
return window.setTimeout(function() {
Module['dynCall_iii'](callback, interval, param);
}, interval);
},
SDL_RemoveTimer__proxy: 'sync',
SDL_RemoveTimer__sig: 'ii',
SDL_RemoveTimer: function(id) {
window.clearTimeout(id);
return true;
},
// TODO:
SDL_CreateThread: function() {
throw 'SDL threads cannot be supported in the web platform because they assume shared state. See emscripten_create_worker etc. for a message-passing concurrency model that does let you run code in another thread.'
},
SDL_WaitThread: function() { throw 'SDL_WaitThread' },
SDL_GetThreadID: function() { throw 'SDL_GetThreadID' },
SDL_ThreadID: function() { return 0; },
SDL_AllocRW: function() { throw 'SDL_AllocRW: TODO' },
SDL_CondBroadcast: function() { throw 'SDL_CondBroadcast: TODO' },
SDL_CondWaitTimeout: function() { throw 'SDL_CondWaitTimeout: TODO' },
SDL_WM_IconifyWindow: function() { throw 'SDL_WM_IconifyWindow TODO' },
Mix_SetPostMix: function() { warnOnce('Mix_SetPostMix: TODO') },
Mix_VolumeChunk: function(chunk, volume) { throw 'Mix_VolumeChunk: TODO' },
Mix_SetPosition: function(channel, angle, distance) { throw 'Mix_SetPosition: TODO' },
Mix_QuerySpec: function() { throw 'Mix_QuerySpec: TODO' },
Mix_FadeInChannelTimed: function() { throw 'Mix_FadeInChannelTimed' },
Mix_FadeOutChannel: function() { throw 'Mix_FadeOutChannel' },
Mix_Linked_Version: function() { throw 'Mix_Linked_Version: TODO' },
SDL_SaveBMP_RW: function() { throw 'SDL_SaveBMP_RW: TODO' },
SDL_WM_SetIcon: function() { /* This function would set the application window icon surface, which doesn't apply for web canvases, so a no-op. */ },
SDL_HasRDTSC: function() { return 0; },
SDL_HasMMX: function() { return 0; },
SDL_HasMMXExt: function() { return 0; },
SDL_Has3DNow: function() { return 0; },
SDL_Has3DNowExt: function() { return 0; },
SDL_HasSSE: function() { return 0; },
SDL_HasSSE2: function() { return 0; },
SDL_HasAltiVec: function() { return 0; }
};
autoAddDeps(LibrarySDL, '$SDL');
mergeInto(LibraryManager.library, LibrarySDL);
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2012 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Sam Lantinga
slouken@libsdl.org
*/
#ifndef _SDL_keysym_h
#define _SDL_keysym_h
#include "SDL_stdinc.h"
#include "SDL_scancode.h"
typedef Sint32 SDL_Keycode;
/** What we really want is a mapping of every raw key on the keyboard.
* To support international keyboards, we use the range 0xA1 - 0xFF
* as international virtual keycodes. We'll follow in the footsteps of X11...
* @brief The names of the keys
*/
typedef enum {
/** @name ASCII mapped keysyms
* The keyboard syms have been cleverly chosen to map to ASCII
*/
/*@{*/
SDLK_UNKNOWN = 0,
SDLK_FIRST = 0,
SDLK_BACKSPACE = 8,
SDLK_TAB = 9,
SDLK_CLEAR = 12,
SDLK_RETURN = 13,
SDLK_PAUSE = 19,
SDLK_ESCAPE = 27,
SDLK_SPACE = 32,
SDLK_EXCLAIM = 33,
SDLK_QUOTEDBL = 34,
SDLK_HASH = 35,
SDLK_DOLLAR = 36,
SDLK_AMPERSAND = 38,
SDLK_QUOTE = 39,
SDLK_LEFTPAREN = 40,
SDLK_RIGHTPAREN = 41,
SDLK_ASTERISK = 42,
SDLK_PLUS = 43,
SDLK_COMMA = 44,
SDLK_MINUS = 45,
SDLK_PERIOD = 46,
SDLK_SLASH = 47,
SDLK_0 = 48,
SDLK_1 = 49,
SDLK_2 = 50,
SDLK_3 = 51,
SDLK_4 = 52,
SDLK_5 = 53,
SDLK_6 = 54,
SDLK_7 = 55,
SDLK_8 = 56,
SDLK_9 = 57,
SDLK_COLON = 58,
SDLK_SEMICOLON = 59,
SDLK_LESS = 60,
SDLK_EQUALS = 61,
SDLK_GREATER = 62,
SDLK_QUESTION = 63,
SDLK_AT = 64,
/*
Skip uppercase letters
*/
SDLK_LEFTBRACKET = 91,
SDLK_BACKSLASH = 92,
SDLK_RIGHTBRACKET = 93,
SDLK_CARET = 94,
SDLK_UNDERSCORE = 95,
SDLK_BACKQUOTE = 96,
SDLK_a = 97,
SDLK_b = 98,
SDLK_c = 99,
SDLK_d = 100,
SDLK_e = 101,
SDLK_f = 102,
SDLK_g = 103,
SDLK_h = 104,
SDLK_i = 105,
SDLK_j = 106,
SDLK_k = 107,
SDLK_l = 108,
SDLK_m = 109,
SDLK_n = 110,
SDLK_o = 111,
SDLK_p = 112,
SDLK_q = 113,
SDLK_r = 114,
SDLK_s = 115,
SDLK_t = 116,
SDLK_u = 117,
SDLK_v = 118,
SDLK_w = 119,
SDLK_x = 120,
SDLK_y = 121,
SDLK_z = 122,
SDLK_DELETE = 127,
/* End of ASCII mapped keysyms */
/*@}*/
/** @name International keyboard syms */
/*@{*/
SDLK_WORLD_0 = 160, /* 0xA0 */
SDLK_WORLD_1 = 161,
SDLK_WORLD_2 = 162,
SDLK_WORLD_3 = 163,
SDLK_WORLD_4 = 164,
SDLK_WORLD_5 = 165,
SDLK_WORLD_6 = 166,
SDLK_WORLD_7 = 167,
SDLK_WORLD_8 = 168,
SDLK_WORLD_9 = 169,
SDLK_WORLD_10 = 170,
SDLK_WORLD_11 = 171,
SDLK_WORLD_12 = 172,
SDLK_WORLD_13 = 173,
SDLK_WORLD_14 = 174,
SDLK_WORLD_15 = 175,
SDLK_WORLD_16 = 176,
SDLK_WORLD_17 = 177,
SDLK_WORLD_18 = 178,
SDLK_WORLD_19 = 179,
SDLK_WORLD_20 = 180,
SDLK_WORLD_21 = 181,
SDLK_WORLD_22 = 182,
SDLK_WORLD_23 = 183,
SDLK_WORLD_24 = 184,
SDLK_WORLD_25 = 185,
SDLK_WORLD_26 = 186,
SDLK_WORLD_27 = 187,
SDLK_WORLD_28 = 188,
SDLK_WORLD_29 = 189,
SDLK_WORLD_30 = 190,
SDLK_WORLD_31 = 191,
SDLK_WORLD_32 = 192,
SDLK_WORLD_33 = 193,
SDLK_WORLD_34 = 194,
SDLK_WORLD_35 = 195,
SDLK_WORLD_36 = 196,
SDLK_WORLD_37 = 197,
SDLK_WORLD_38 = 198,
SDLK_WORLD_39 = 199,
SDLK_WORLD_40 = 200,
SDLK_WORLD_41 = 201,
SDLK_WORLD_42 = 202,
SDLK_WORLD_43 = 203,
SDLK_WORLD_44 = 204,
SDLK_WORLD_45 = 205,
SDLK_WORLD_46 = 206,
SDLK_WORLD_47 = 207,
SDLK_WORLD_48 = 208,
SDLK_WORLD_49 = 209,
SDLK_WORLD_50 = 210,
SDLK_WORLD_51 = 211,
SDLK_WORLD_52 = 212,
SDLK_WORLD_53 = 213,
SDLK_WORLD_54 = 214,
SDLK_WORLD_55 = 215,
SDLK_WORLD_56 = 216,
SDLK_WORLD_57 = 217,
SDLK_WORLD_58 = 218,
SDLK_WORLD_59 = 219,
SDLK_WORLD_60 = 220,
SDLK_WORLD_61 = 221,
SDLK_WORLD_62 = 222,
SDLK_WORLD_63 = 223,
SDLK_WORLD_64 = 224,
SDLK_WORLD_65 = 225,
SDLK_WORLD_66 = 226,
SDLK_WORLD_67 = 227,
SDLK_WORLD_68 = 228,
SDLK_WORLD_69 = 229,
SDLK_WORLD_70 = 230,
SDLK_WORLD_71 = 231,
SDLK_WORLD_72 = 232,
SDLK_WORLD_73 = 233,
SDLK_WORLD_74 = 234,
SDLK_WORLD_75 = 235,
SDLK_WORLD_76 = 236,
SDLK_WORLD_77 = 237,
SDLK_WORLD_78 = 238,
SDLK_WORLD_79 = 239,
SDLK_WORLD_80 = 240,
SDLK_WORLD_81 = 241,
SDLK_WORLD_82 = 242,
SDLK_WORLD_83 = 243,
SDLK_WORLD_84 = 244,
SDLK_WORLD_85 = 245,
SDLK_WORLD_86 = 246,
SDLK_WORLD_87 = 247,
SDLK_WORLD_88 = 248,
SDLK_WORLD_89 = 249,
SDLK_WORLD_90 = 250,
SDLK_WORLD_91 = 251,
SDLK_WORLD_92 = 252,
SDLK_WORLD_93 = 253,
SDLK_WORLD_94 = 254,
SDLK_WORLD_95 = 255, /* 0xFF */
/*@}*/
/** @name Numeric keypad */
/*@{*/
SDLK_KP_0 = 256,
SDLK_KP_1 = 257,
SDLK_KP_2 = 258,
SDLK_KP_3 = 259,
SDLK_KP_4 = 260,
SDLK_KP_5 = 261,
SDLK_KP_6 = 262,
SDLK_KP_7 = 263,
SDLK_KP_8 = 264,
SDLK_KP_9 = 265,
SDLK_KP_PERIOD = 266,
SDLK_KP_DIVIDE = 267,
SDLK_KP_MULTIPLY = 268,
SDLK_KP_MINUS = 269,
SDLK_KP_PLUS = 270,
SDLK_KP_ENTER = 271,
SDLK_KP_EQUALS = 272,
/*@}*/
/** @name Arrows + Home/End pad */
/*@{*/
SDLK_UP = 273,
SDLK_DOWN = 274,
SDLK_RIGHT = 275,
SDLK_LEFT = 276,
SDLK_INSERT = 277,
SDLK_HOME = 278,
SDLK_END = 279,
SDLK_PAGEUP = 280,
SDLK_PAGEDOWN = 281,
/*@}*/
/** @name Function keys */
/*@{*/
SDLK_F1 = 282,
SDLK_F2 = 283,
SDLK_F3 = 284,
SDLK_F4 = 285,
SDLK_F5 = 286,
SDLK_F6 = 287,
SDLK_F7 = 288,
SDLK_F8 = 289,
SDLK_F9 = 290,
SDLK_F10 = 291,
SDLK_F11 = 292,
SDLK_F12 = 293,
SDLK_F13 = 294,
SDLK_F14 = 295,
SDLK_F15 = 296,
/*@}*/
/** @name Key state modifier keys */
/*@{*/
SDLK_NUMLOCK = 300,
SDLK_CAPSLOCK = 301,
SDLK_SCROLLOCK = 302,
SDLK_RSHIFT = 303,
SDLK_LSHIFT = 304,
SDLK_RCTRL = 305,
SDLK_LCTRL = 306,
SDLK_RALT = 307,
SDLK_LALT = 308,
SDLK_RGUI = 309,
SDLK_LGUI = 310,
SDLK_LSUPER = 311, /**< Left "Windows" key */
SDLK_RSUPER = 312, /**< Right "Windows" key */
SDLK_MODE = 313, /**< "Alt Gr" key */
SDLK_COMPOSE = 314, /**< Multi-key compose key */
/*@}*/
/** @name Miscellaneous function keys */
/*@{*/
SDLK_HELP = 315,
SDLK_PRINT = 316,
SDLK_SYSREQ = 317,
SDLK_BREAK = 318,
SDLK_MENU = 319,
SDLK_POWER = 320, /**< Power Macintosh power key */
SDLK_EURO = 321, /**< Some european keyboards */
SDLK_UNDO = 322, /**< Atari keyboard has Undo */
/*@}*/
/* Add any other keys here */
SDLK_LAST
} SDLKey;
/** Enumeration of valid key mods (possibly OR'd together) */
typedef enum {
KMOD_NONE = 0x0000,
KMOD_LSHIFT= 0x0001,
KMOD_RSHIFT= 0x0002,
KMOD_LCTRL = 0x0040,
KMOD_RCTRL = 0x0080,
KMOD_LALT = 0x0100,
KMOD_RALT = 0x0200,
KMOD_LGUI = 0x0400,
KMOD_RGUI = 0x0800,
KMOD_NUM = 0x1000,
KMOD_CAPS = 0x2000,
KMOD_MODE = 0x4000,
KMOD_RESERVED = 0x8000
} SDL_Keymod;
#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL)
#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT)
#define KMOD_ALT (KMOD_LALT|KMOD_RALT)
#define KMOD_GUI (KMOD_LGUI|KMOD_RGUI)
#endif /* _SDL_keysym_h */
Index: src/arch/sdl/Makefile.am
===================================================================
--- src/arch/sdl/Makefile.am (revision 35456)
+++ src/arch/sdl/Makefile.am (working copy)
@@ -46,7 +46,6 @@
archdep.c \
archdep.h \
blockdev.c \
- console.c \
console_unix.c \
coproc.c \
coproc.h \
Index: src/arch/sdl/emscripten/coroutine.h
===================================================================
--- src/arch/sdl/emscripten/coroutine.h (nonexistent)
+++ src/arch/sdl/emscripten/coroutine.h (working copy)
@@ -0,0 +1,187 @@
+/* coroutine.h
+ *
+ * Coroutine mechanics, implemented on top of standard ANSI C. See
+ * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for
+ * a full discussion of the theory behind this.
+ *
+ * To use these macros to define a coroutine, you need to write a
+ * function that looks something like this.
+ *
+ * [Simple version using static variables (scr macros)]
+ * int ascending (void) {
+ * static int i;
+ *
+ * scrBegin;
+ * for (i=0; i<10; i++) {
+ * scrReturn(i);
+ * }
+ * scrFinish(-1);
+ * }
+ *
+ * [Re-entrant version using an explicit context structure (ccr macros)]
+ * int ascending (ccrContParam) {
+ * ccrBeginContext;
+ * int i;
+ * ccrEndContext(foo);
+ *
+ * ccrBegin(foo);
+ * for (foo->i=0; foo->i<10; foo->i++) {
+ * ccrReturn(foo->i);
+ * }
+ * ccrFinish(-1);
+ * }
+ *
+ * In the static version, you need only surround the function body
+ * with `scrBegin' and `scrFinish', and then you can do `scrReturn'
+ * within the function and on the next call control will resume
+ * just after the scrReturn statement. Any local variables you need
+ * to be persistent across an `scrReturn' must be declared static.
+ *
+ * In the re-entrant version, you need to declare your persistent
+ * variables between `ccrBeginContext' and `ccrEndContext'. These
+ * will be members of a structure whose name you specify in the
+ * parameter to `ccrEndContext'.
+ *
+ * The re-entrant macros will malloc() the state structure on first
+ * call, and free() it when `ccrFinish' is reached. If you want to
+ * abort in the middle, you can use `ccrStop' to free the state
+ * structure immediately (equivalent to an explicit return() in a
+ * caller-type routine).
+ *
+ * A coroutine returning void type may call `ccrReturnV',
+ * `ccrFinishV' and `ccrStopV', or `scrReturnV', to avoid having to
+ * specify an empty parameter to the ordinary return macros.
+ *
+ * Ground rules:
+ * - never put `ccrReturn' or `scrReturn' within an explicit `switch'.
+ * - never put two `ccrReturn' or `scrReturn' statements on the same
+ * source line.
+ *
+ * The caller of a static coroutine calls it just as if it were an
+ * ordinary function:
+ *
+ * void main(void) {
+ * int i;
+ * do {
+ * i = ascending();
+ * printf("got number %d\n", i);
+ * } while (i != -1);
+ * }
+ *
+ * The caller of a re-entrant coroutine must provide a context
+ * variable:
+ *
+ * void main(void) {
+ * ccrContext z = 0;
+ * do {
+ * printf("got number %d\n", ascending (&z));
+ * } while (z);
+ * }
+ *
+ * Note that the context variable is set back to zero when the
+ * coroutine terminates (by crStop, or by control reaching
+ * crFinish). This can make the re-entrant coroutines more useful
+ * than the static ones, because you can tell when they have
+ * finished.
+ *
+ * If you need to dispose of a crContext when it is non-zero (that
+ * is, if you want to stop calling a coroutine without suffering a
+ * memory leak), the caller should call `ccrAbort(ctx)' where `ctx'
+ * is the context variable.
+ *
+ * This mechanism could have been better implemented using GNU C
+ * and its ability to store pointers to labels, but sadly this is
+ * not part of the ANSI C standard and so the mechanism is done by
+ * case statements instead. That's why you can't put a crReturn()
+ * inside a switch() statement.
+ */
+
+/*
+ * coroutine.h is copyright 1995,2000 Simon Tatham.
+ *
+ * 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 SIMON TATHAM 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.
+ *
+ * $Id$
+ */
+
+#ifndef COROUTINE_H
+#define COROUTINE_H
+
+#include <stdlib.h>
+
+/*
+ * `scr' macros for static coroutines.
+ */
+
+#define scrBegin static int scrLine = 0; switch(scrLine) { case 0:;
+#define scrFinish(z) } return (z)
+#define scrFinishV } return
+
+#define scrReturn(z) \
+ do {\
+ scrLine=__LINE__;\
+ return (z); case __LINE__:;\
+ } while (0)
+#define scrReturnV \
+ do {\
+ scrLine=__LINE__;\
+ return; case __LINE__:;\
+ } while (0)
+
+/*
+ * `ccr' macros for re-entrant coroutines.
+ */
+
+#define ccrContParam void **ccrParam
+
+#define ccrBeginContext struct ccrContextTag { int ccrLine
+#define ccrEndContext(x) } *x = (struct ccrContextTag *)*ccrParam
+
+#define ccrBegin(x) if(!x) {x= *ccrParam=malloc(sizeof(*x)); x->ccrLine=0;}\
+ if (x) switch(x->ccrLine) { case 0:;
+#define ccrFinish(z) } free(*ccrParam); *ccrParam=0; return (z)
+#define ccrFinishV } free(*ccrParam); *ccrParam=0; return
+
+#define ccrReturn(z) \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ return (z); case __LINE__:;\
+ } while (0)
+#define ccrReturnV \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ return; case __LINE__:;\
+ } while (0)
+
+#define ccrJumpFrom \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ case __LINE__:;\
+ } while (0)
+
+#define ccrStop(z) do{ free(*ccrParam); *ccrParam=0; return (z); }while(0)
+#define ccrStopV do{ free(*ccrParam); *ccrParam=0; return; }while(0)
+
+#define ccrContext void *
+#define ccrAbort(ctx) do { free (ctx); ctx = 0; } while (0)
+
+#endif /* COROUTINE_H */
Index: src/arch/sdl/emscripten/coroutine.h
===================================================================
--- src/arch/sdl/emscripten/coroutine.h (nonexistent)
+++ src/arch/sdl/emscripten/coroutine.h (working copy)
@@ -0,0 +1,187 @@
+/* coroutine.h
+ *
+ * Coroutine mechanics, implemented on top of standard ANSI C. See
+ * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for
+ * a full discussion of the theory behind this.
+ *
+ * To use these macros to define a coroutine, you need to write a
+ * function that looks something like this.
+ *
+ * [Simple version using static variables (scr macros)]
+ * int ascending (void) {
+ * static int i;
+ *
+ * scrBegin;
+ * for (i=0; i<10; i++) {
+ * scrReturn(i);
+ * }
+ * scrFinish(-1);
+ * }
+ *
+ * [Re-entrant version using an explicit context structure (ccr macros)]
+ * int ascending (ccrContParam) {
+ * ccrBeginContext;
+ * int i;
+ * ccrEndContext(foo);
+ *
+ * ccrBegin(foo);
+ * for (foo->i=0; foo->i<10; foo->i++) {
+ * ccrReturn(foo->i);
+ * }
+ * ccrFinish(-1);
+ * }
+ *
+ * In the static version, you need only surround the function body
+ * with `scrBegin' and `scrFinish', and then you can do `scrReturn'
+ * within the function and on the next call control will resume
+ * just after the scrReturn statement. Any local variables you need
+ * to be persistent across an `scrReturn' must be declared static.
+ *
+ * In the re-entrant version, you need to declare your persistent
+ * variables between `ccrBeginContext' and `ccrEndContext'. These
+ * will be members of a structure whose name you specify in the
+ * parameter to `ccrEndContext'.
+ *
+ * The re-entrant macros will malloc() the state structure on first
+ * call, and free() it when `ccrFinish' is reached. If you want to
+ * abort in the middle, you can use `ccrStop' to free the state
+ * structure immediately (equivalent to an explicit return() in a
+ * caller-type routine).
+ *
+ * A coroutine returning void type may call `ccrReturnV',
+ * `ccrFinishV' and `ccrStopV', or `scrReturnV', to avoid having to
+ * specify an empty parameter to the ordinary return macros.
+ *
+ * Ground rules:
+ * - never put `ccrReturn' or `scrReturn' within an explicit `switch'.
+ * - never put two `ccrReturn' or `scrReturn' statements on the same
+ * source line.
+ *
+ * The caller of a static coroutine calls it just as if it were an
+ * ordinary function:
+ *
+ * void main(void) {
+ * int i;
+ * do {
+ * i = ascending();
+ * printf("got number %d\n", i);
+ * } while (i != -1);
+ * }
+ *
+ * The caller of a re-entrant coroutine must provide a context
+ * variable:
+ *
+ * void main(void) {
+ * ccrContext z = 0;
+ * do {
+ * printf("got number %d\n", ascending (&z));
+ * } while (z);
+ * }
+ *
+ * Note that the context variable is set back to zero when the
+ * coroutine terminates (by crStop, or by control reaching
+ * crFinish). This can make the re-entrant coroutines more useful
+ * than the static ones, because you can tell when they have
+ * finished.
+ *
+ * If you need to dispose of a crContext when it is non-zero (that
+ * is, if you want to stop calling a coroutine without suffering a
+ * memory leak), the caller should call `ccrAbort(ctx)' where `ctx'
+ * is the context variable.
+ *
+ * This mechanism could have been better implemented using GNU C
+ * and its ability to store pointers to labels, but sadly this is
+ * not part of the ANSI C standard and so the mechanism is done by
+ * case statements instead. That's why you can't put a crReturn()
+ * inside a switch() statement.
+ */
+
+/*
+ * coroutine.h is copyright 1995,2000 Simon Tatham.
+ *
+ * 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 SIMON TATHAM 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.
+ *
+ * $Id$
+ */
+
+#ifndef COROUTINE_H
+#define COROUTINE_H
+
+#include <stdlib.h>
+
+/*
+ * `scr' macros for static coroutines.
+ */
+
+#define scrBegin static int scrLine = 0; switch(scrLine) { case 0:;
+#define scrFinish(z) } return (z)
+#define scrFinishV } return
+
+#define scrReturn(z) \
+ do {\
+ scrLine=__LINE__;\
+ return (z); case __LINE__:;\
+ } while (0)
+#define scrReturnV \
+ do {\
+ scrLine=__LINE__;\
+ return; case __LINE__:;\
+ } while (0)
+
+/*
+ * `ccr' macros for re-entrant coroutines.
+ */
+
+#define ccrContParam void **ccrParam
+
+#define ccrBeginContext struct ccrContextTag { int ccrLine
+#define ccrEndContext(x) } *x = (struct ccrContextTag *)*ccrParam
+
+#define ccrBegin(x) if(!x) {x= *ccrParam=malloc(sizeof(*x)); x->ccrLine=0;}\
+ if (x) switch(x->ccrLine) { case 0:;
+#define ccrFinish(z) } free(*ccrParam); *ccrParam=0; return (z)
+#define ccrFinishV } free(*ccrParam); *ccrParam=0; return
+
+#define ccrReturn(z) \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ return (z); case __LINE__:;\
+ } while (0)
+#define ccrReturnV \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ return; case __LINE__:;\
+ } while (0)
+
+#define ccrJumpFrom \
+ do {\
+ ((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
+ case __LINE__:;\
+ } while (0)
+
+#define ccrStop(z) do{ free(*ccrParam); *ccrParam=0; return (z); }while(0)
+#define ccrStopV do{ free(*ccrParam); *ccrParam=0; return; }while(0)
+
+#define ccrContext void *
+#define ccrAbort(ctx) do { free (ctx); ctx = 0; } while (0)
+
+#endif /* COROUTINE_H */
Index: src/arch/sdl/kbd.c
===================================================================
--- src/arch/sdl/kbd.c (revision 35456)
+++ src/arch/sdl/kbd.c (working copy)
@@ -136,7 +136,7 @@
SDL1 key handling, but a proper solution for
handling unicode will still need to be made.
*/
-#ifdef USE_SDLUI2
+#ifdef SDL2UI
typedef struct SDL2Key_s {
SDLKey SDL1x;
SDLKey SDL2x;
Index: src/arch/sdl/menu_help.c
===================================================================
--- src/arch/sdl/menu_help.c (revision 35456)
+++ src/arch/sdl/menu_help.c (working copy)
@@ -46,6 +46,8 @@
#include "svnversion.h"
#endif
+
+
#ifdef WINMIPS
static char *concat_all(char **text)
{
Index: src/arch/sdl/uimenu.c
===================================================================
--- src/arch/sdl/uimenu.c (revision 35456)
+++ src/arch/sdl/uimenu.c (working copy)
@@ -62,6 +62,16 @@
#include <stdlib.h>
#include <string.h>
+#include <emscripten.h>
+
+int SDL_WaitEvent(SDL_Event *event)
+{
+ while(!SDL_PollEvent(event)) {
+ emscripten_sleep(0);
+ }
+ return 1;
+}
+
int sdl_menu_state = 0;
void (*sdl_ui_set_menu_params)(int index, menu_draw_t *menu_draw);
@@ -548,8 +558,15 @@
return MENU_RETVAL_DEFAULT;
}
+static void sdl_ui_trap_top(void *data);
+
static void sdl_ui_trap(uint16_t addr, void *data)
{
+ emscripten_push_main_loop_blocker(sdl_ui_trap_top, data);
+}
+
+static void sdl_ui_trap_top(void *data)
+{
unsigned int width;
unsigned int height;
static char msg[0x40];
Index: src/arch/sdl/uipause.c
===================================================================
--- src/arch/sdl/uipause.c (revision 35456)
+++ src/arch/sdl/uipause.c (working copy)
@@ -38,12 +38,16 @@
#include "uimenu.h"
#include "vsync.h"
+#include <emscripten.h>
+
/* ----------------------------------------------------------------- */
/* ui.h */
static int is_paused = 0;
-static void pause_trap(uint16_t addr, void *data)
+
+
+static void pause_trap_top(void *data)
{
vsync_suspend_speed_eval();
while (is_paused) {
@@ -52,6 +56,11 @@
}
}
+static void pause_trap(uint16_t addr, void *data)
+{
+ emscripten_push_main_loop_blocker(pause_trap_top, data);
+}
+
void ui_pause_emulation(int flag)
{
if (flag) {
Index: src/arch/sdl/video.c
===================================================================
--- src/arch/sdl/video.c (revision 35456)
+++ src/arch/sdl/video.c (working copy)
@@ -790,7 +790,7 @@
if (canvas == sdl_active_canvas) {
#ifndef ANDROID_COMPILE
- SDL_EventState(SDL_VIDEORESIZE, SDL_IGNORE);
+// SDL_EventState(SDL_VIDEORESIZE, SDL_IGNORE);
#endif
#ifndef HAVE_HWSCALE
new_screen = SDL_SetVideoMode(actual_width, actual_height, sdl_bitdepth, flags);
@@ -836,9 +836,7 @@
}
}
#endif
-#ifndef ANDROID_COMPILE
- SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE);
-#endif
+// SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE);
} else {
#ifdef HAVE_HWSCALE
/* free the old hwscale screen when hwscaled screen is switched away */
@@ -1421,11 +1419,11 @@
}
#ifndef ANDROID_COMPILE
- SDL_EventState(SDL_VIDEORESIZE, SDL_IGNORE);
+// SDL_EventState(SDL_VIDEORESIZE, SDL_IGNORE);
#endif
sdl_active_canvas->hwscale_screen = SDL_SetVideoMode((int)w, (int)h, sdl_bitdepth, flags);
#ifndef ANDROID_COMPILE
- SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE);
+// SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE);
#endif
#ifdef SDL_DEBUG
Index: src/arch/sdl/vsyncarch.c
===================================================================
--- src/arch/sdl/vsyncarch.c (revision 35456)
+++ src/arch/sdl/vsyncarch.c (working copy)
@@ -44,6 +44,10 @@
#include "vice_sdl.h"
+#ifdef EMSCRIPTEN
+ #include <emscripten.h>
+#endif
+
/* ------------------------------------------------------------------------- */
/* SDL_Delay & GetTicks have 1ms resolution, while VICE needs 1us */
@@ -77,10 +81,24 @@
ui_display_speed((float)speed, (float)frame_rate, warp_enabled);
}
+#ifdef EMSCRIPTEN
+/* Wrap the resume main loop function to prevent a pointer cast making it safe for asm.js */
+void emscripten_resume_main_loop_wrapper(void *arg)
+{
+ emscripten_resume_main_loop();
+}
+#endif
+
+
/* Sleep a number of timer units. */
void vsyncarch_sleep(unsigned long delay)
{
- SDL_Delay(delay / VICE_SDL_TICKS_SCALE);
+#ifdef EMSCRIPTEN
+ emscripten_pause_main_loop();
+ emscripten_async_call(emscripten_resume_main_loop_wrapper, NULL, delay/VICE_SDL_TICKS_SCALE);
+#else
+ SDL_Delay(delay/VICE_SDL_TICKS_SCALE);
+#endif
}
void vsyncarch_presync(void)
Index: src/main.c
===================================================================
--- src/main.c (revision 35456)
+++ src/main.c (working copy)
@@ -75,6 +75,10 @@
#include "svnversion.h"
#endif
+#ifdef EMSCRIPTEN
+ #include <emscripten.h>
+#endif
+
#ifdef DEBUG_MAIN
#define DBG(x) printf x
#else
@@ -286,7 +290,11 @@
/* Let's go... */
log_message(LOG_DEFAULT, "Main CPU: starting at ($FFFC).");
+#ifdef EMSCRIPTEN
+ emscripten_set_main_loop(maincpu_mainloop, 0, 0);
+#else
maincpu_mainloop();
+#endif
log_error(LOG_DEFAULT, "perkele!");
Index: src/maincpu.c
===================================================================
--- src/maincpu.c (revision 35456)
+++ src/maincpu.c (working copy)
@@ -50,6 +50,11 @@
#include "traps.h"
#include "types.h"
+#ifdef EMSCRIPTEN
+ #include "arch/sdl/emscripten/coroutine.h"
+ #include "vsync.h"
+#endif
+
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
@@ -402,8 +407,8 @@
}
}
-void maincpu_mainloop(void)
-{
+
+
#ifndef C64DTV
/* Notice that using a struct for these would make it a lot slower (at
least, on gcc 2.7.2.x). */
@@ -453,13 +458,31 @@
int bank_start = 0;
int bank_limit = 0;
+void maincpu_mainloop(void)
+{
+#ifdef EMSCRIPTEN
+ scrBegin;
+#endif
+
o_bank_base = &bank_base;
o_bank_start = &bank_start;
o_bank_limit = &bank_limit;
- machine_trigger_reset(MACHINE_RESET_MODE_SOFT);
+
+ int machine_reset_triggered = 0;
+ if(!machine_reset_triggered) {
+ machine_trigger_reset(MACHINE_RESET_MODE_SOFT);
+ machine_reset_triggered = 1;
+ }
while (1) {
+#ifdef EMSCRIPTEN
+ /* Loop until next frame */
+ int startFrame = vsync_frame_counter;
+ while (vsync_frame_counter == startFrame) {
+#endif
+
+
#define CLK maincpu_clk
#define RMW_FLAG maincpu_rmw_flag
#define LAST_OPCODE_INFO last_opcode_info
@@ -520,7 +543,15 @@
debug.maincpu_traceflg = 1;
}
#endif
+#ifdef EMSCRIPTEN
+ }
+ scrReturnV;
+
+#endif
}
+#ifdef EMSCRIPTEN
+ scrFinishV;
+#endif
}
/* ------------------------------------------------------------------------- */
Index: src/opencbm.h
===================================================================
--- src/opencbm.h (revision 35456)
+++ src/opencbm.h (working copy)
@@ -123,6 +123,10 @@
typedef unsigned char __u_char;
#endif
+#ifdef EMSCRIPTEN
+typedef unsigned char __u_char;
+#endif
+
#if defined(__minix) || defined(OPENSERVER5_COMPILE) || defined(__QNXNTO__)
typedef unsigned char __u_char;
#endif
Index: src/sounddrv/soundsdl.c
===================================================================
--- src/sounddrv/soundsdl.c (revision 35456)
+++ src/sounddrv/soundsdl.c (working copy)
@@ -43,6 +43,8 @@
#include "loader.h"
#endif
+
+
static int16_t *sdl_buf = NULL;
static SDL_AudioSpec sdl_spec;
static volatile int sdl_inptr = 0;
Index: src/vice.h
===================================================================
--- src/vice.h (revision 35456)
+++ src/vice.h (working copy)
@@ -30,6 +30,7 @@
#ifndef VICE_VICE_H
#define VICE_VICE_H
+#include <string.h>
/* We use <config.h> instead of "config.h" so that a compilation using
-I. -I$srcdir will use ./config.h rather than $srcdir/config.h
Index: src/vice_sdl.h
===================================================================
--- src/vice_sdl.h (revision 35456)
+++ src/vice_sdl.h (working copy)
@@ -56,7 +56,7 @@
#else
# ifdef USE_SDL_PREFIX
# include <SDL/SDL.h>
-# include <SDL/SDL_keysym.h>
+# include <SDL/SDL_keycode.h>
# ifdef INCLUDE_SDL_SYSWM_H
# include <SDL/SDL_syswm.h>
# endif
@@ -68,7 +68,7 @@
# endif
# else
# include <SDL.h>
-# include <SDL_keysym.h>
+# include <SDL_keycode.h>
# ifdef INCLUDE_SDL_SYSWM_H
# include <SDL_syswm.h>
# endif
Index: configure.proto
===================================================================
--- configure.proto (revision 35456)
+++ configure.proto (working copy)
@@ -2847,89 +2847,6 @@
fi
dnl check for opengl libs.
- AC_MSG_CHECKING(whether we can use the GL library)
-
- old_LIBS="$LIBS"
- LIBS="$LIBS -lGL"
- if test x"$use_sdl2_prefix" = "xyes"; then
- AC_DEFINE(HAVE_HWSCALE,,[Enable arbitrary window scaling])
- HAVE_HWSCALE_SUPPORT="yes"
- LIBS="$old_LIBS"
- else
- if test x"$use_sdl_prefix" = "xyes"; then
- AC_TRY_LINK([#include <SDL/SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- else
- AC_TRY_LINK([#include <SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- fi
- fi
-
- if test x"$have_opengl_lib" != "xyes"; then
- AC_MSG_CHECKING(whether we can use the opengl32 library)
- old_LIBS="$LIBS"
- LIBS="$LIBS -lopengl32"
- if test x"$use_sdl2_prefix" = "xyes"; then
- AC_TRY_LINK([#include <SDL2/SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- else
- if test x"$use_sdl_prefix" = "xyes"; then
- AC_TRY_LINK([#include <SDL/SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- else
- AC_TRY_LINK([#include <SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- fi
- fi
- fi
-
- if test x"$have_opengl_lib" != "xyes" -a x"enable_sdlui2" != "xyes" -a x"$is_unix_macosx" = "xyes"; then
- AC_MSG_CHECKING(whether we can use the OpenGL Framework)
- old_LIBS="$LIBS"
- LIBS="$LIBS -framework OpenGL"
-
- if test x"$use_sdl_prefix" = "xyes"; then
- AC_TRY_LINK([#include <SDL/SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- else
- AC_TRY_LINK([#include <SDL_opengl.h>],
- [glViewport(1,2,3,4)],
- [AC_MSG_RESULT(yes);
- have_opengl_lib=yes],
- [AC_MSG_RESULT(no);
- LIBS="$old_LIBS"])
- fi
- fi
-
- if test x"$have_opengl_lib" = "xyes"; then
- AC_DEFINE(HAVE_HWSCALE,,[Enable arbitrary window scaling])
- HAVE_HWSCALE_SUPPORT="yes"
- fi
-
dnl check for the SDL_main.h header
if test x"$use_sdl2_prefix" = "xyes"; then
AC_CHECK_HEADERS(SDL2/SDL_main.h)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment