Last active
October 1, 2018 03:54
-
-
Save Sgeo/c1d8bb4693d7edc0861d53a0b5cb0894 to your computer and use it in GitHub Desktop.
vice32.js .. modifications off VICE 3.2 branch.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//"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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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