mirror of
https://github.com/libretro/PUAE.git
synced 2024-11-26 17:40:38 +00:00
PNaCl
This commit is contained in:
parent
1574c898ee
commit
a988ca0541
240
build_native_client.sh
Executable file
240
build_native_client.sh
Executable file
@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
|
||||
###########################################################################
|
||||
# Set up. Adjust these as necessary.
|
||||
###########################################################################
|
||||
|
||||
# Path to the NaCl SDK to use. Should be pepper_31 or later.
|
||||
export NACL_SDK_ROOT=${HOME}/nacl_sdk/pepper_31
|
||||
|
||||
# Check out naclports and point this to the folder with 'src/'.
|
||||
export NACL_PORTS_ROOT=${HOME}/work/naclports
|
||||
|
||||
# Staging dir on local web server.
|
||||
export WEB_SERVER_DESTINATION_DIR=${HOME}/work/puae/staging
|
||||
|
||||
# URL to access the staging dir via the web server.
|
||||
export WEB_SERVER="http://localhost:8080"
|
||||
|
||||
# The OS you're building this on.
|
||||
# Note: Building on Windows has not been tried.
|
||||
export OS=mac # Should be mac, linux, or win.
|
||||
|
||||
# Set the Chrome/Chromium to use.
|
||||
export CHROME_EXE='/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
|
||||
|
||||
# Any extra CPPFLAGS.
|
||||
export NACL_CPPFLAGS="-DDEBUG"
|
||||
export NACL_CFLAGS=""
|
||||
|
||||
# Enable build key (yes or no/empty).
|
||||
# This generates a random key that is used to apply naive XOR encryption
|
||||
# on all UAE data files (ROMs, ADFs, etc.) fetched from the web server.
|
||||
export ENABLE_BUILD_KEY=yes
|
||||
export DATA_FILES_TO_DEPLOY_DIR=${HOME}/work/puae/data_files_to_deploy
|
||||
|
||||
# Choose SDL or Pepper for graphics, sound, and input.
|
||||
# TODO(cstefansen): Fix NaCl SDL build.
|
||||
#export UAE_CONFIGURE_FLAGS_NACL="--enable-drvsnd \
|
||||
# --with-sdl --with-sdl-gfx --with-sdl-gl --with-sdl-sound"
|
||||
export UAE_CONFIGURE_FLAGS_NACL="--enable-drvsnd --with-pepper --enable-serial-port"
|
||||
# (Serial port emulation is needed for debugging AROS ROMs.)
|
||||
|
||||
|
||||
###########################################################################
|
||||
# You shouldn't need to change anything below this point.
|
||||
###########################################################################
|
||||
|
||||
export UAE_ROOT=`cd \`dirname $0\` && pwd`
|
||||
|
||||
|
||||
############################################################################
|
||||
# Do a clean build for PNaCl.
|
||||
############################################################################
|
||||
|
||||
if [ "$1" = "pnacl" ]; then
|
||||
export NACL_TOOLCHAIN_ROOT=${NACL_SDK_ROOT}/toolchain/${OS}_pnacl
|
||||
export CC=${NACL_TOOLCHAIN_ROOT}/bin/pnacl-clang++
|
||||
export CXX=$CC
|
||||
export RANLIB=${NACL_TOOLCHAIN_ROOT}/bin/pnacl-ranlib
|
||||
export AR=${NACL_TOOLCHAIN_ROOT}/bin/pnacl-ar
|
||||
export NACL_INCLUDE=${NACL_SDK_ROOT}/include
|
||||
export NACL_LIB=${NACL_SDK_ROOT}/lib/pnacl/Release
|
||||
|
||||
export CPPFLAGS="-I${NACL_INCLUDE} ${NACL_CPPFLAGS}"
|
||||
export CFLAGS="${NACL_CFLAGS} -O2 -g -Wno-unused-parameter -Wno-missing-field-initializers"
|
||||
export CXXFLAGS="-O2 -g -Wno-unused-parameter -Wno-missing-field-initializers"
|
||||
export LDFLAGS="-L${NACL_LIB}"
|
||||
|
||||
# Build zlib.
|
||||
cd ${NACL_PORTS_ROOT}/src/libraries/zlib
|
||||
if [ "$OS" = "mac" ]; then
|
||||
# TODO(cstefansen): Upstream this fix, so it builds on Mac without
|
||||
# patching.
|
||||
patch -p0 -N <<EOF
|
||||
index fe99ed0..2a45383 100755
|
||||
--- nacl-zlib.sh
|
||||
+++ nacl-zlib.sh
|
||||
@@ -91,7 +91,7 @@ PackageInstall() {
|
||||
ConfigureStep
|
||||
BuildStep
|
||||
ValidateStep
|
||||
- TestStep
|
||||
+# TestStep
|
||||
DefaultInstallStep
|
||||
if [ "${NACL_GLIBC}" = "1" ]; then
|
||||
ConfigureStep shared
|
||||
EOF
|
||||
fi # "$OS" = "mac"
|
||||
NACL_ARCH=pnacl NACL_GLIBC=0 ./nacl-zlib.sh
|
||||
|
||||
# Generate build key.
|
||||
cd ${UAE_ROOT}/src
|
||||
if [ "$ENABLE_BUILD_KEY" = "yes" ]; then
|
||||
time dd if=/dev/urandom of=build.key bs=1 count=4096
|
||||
xxd -i build.key build.key.c
|
||||
export CPPFLAGS="${CPPFLAGS} -DENABLE_BUILD_KEY"
|
||||
fi # ENABLE_BUILD_KEY
|
||||
|
||||
# Build PUAE.
|
||||
cd ${UAE_ROOT}
|
||||
make clean
|
||||
./bootstrap.sh
|
||||
./configure --host=le32-unknown-nacl ${UAE_CONFIGURE_FLAGS_NACL}
|
||||
make
|
||||
exit $?
|
||||
fi # "pnacl"
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Incremental
|
||||
###########################################################################
|
||||
|
||||
if [ "$1" = "incremental" ]; then
|
||||
make
|
||||
exit $?
|
||||
fi # incremental
|
||||
|
||||
|
||||
############################################################################
|
||||
# Build for desktop.
|
||||
# This is left in for convenience to aid debugging/profiling.
|
||||
############################################################################
|
||||
|
||||
if [ "$1" = "desktop" ]; then
|
||||
CPPFLAGS=""
|
||||
CFLAGS="-m32 -g"
|
||||
CXXFLAGS="-m32 -g"
|
||||
LDFLAGS=""
|
||||
cd ${UAE_ROOT}
|
||||
make clean
|
||||
./bootstrap.sh
|
||||
./configure --disable-ui --disable-jit \
|
||||
--with-sdl --with-sdl-gfx --without-sdl-gl --with-sdl-sound \
|
||||
--disable-autoconfig
|
||||
if [ "$?" -ne "0" ]; then
|
||||
echo "./configure failed for desktop UAE."
|
||||
exit 1
|
||||
fi
|
||||
make
|
||||
if [ "$?" -ne "0" ]; then
|
||||
echo "make failed for desktop UAE."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi # "desktop"
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Running/debugging
|
||||
###########################################################################
|
||||
|
||||
if [ "$1" = "run" ] || [ "$1" = "debug" ]; then
|
||||
# Encrypt if requested and then stage data files.
|
||||
cd ${DATA_FILES_TO_DEPLOY_DIR}
|
||||
if [ "$ENABLE_BUILD_KEY" = "yes" ]; then
|
||||
for f in *
|
||||
do
|
||||
${UAE_ROOT}/src/xor_encryption.py $f ${UAE_ROOT}/src/build.key
|
||||
mv $f.encrypted ${WEB_SERVER_DESTINATION_DIR}/$f
|
||||
done
|
||||
else
|
||||
cp * ${WEB_SERVER_DESTINATION_DIR}/
|
||||
fi # ENABLE_BUILD_KEY
|
||||
|
||||
# Copy stuff to web server.
|
||||
cd ${UAE_ROOT}
|
||||
echo "Copying files to web server directory."
|
||||
mkdir -p ${WEB_SERVER_DESTINATION_DIR}/img
|
||||
cp src/gui-html/img/amiga500_128x128.png \
|
||||
src/gui-html/img/amiga_pointer.png \
|
||||
src/gui-html/img/first_demos.png \
|
||||
src/gui-html/img/read_me.png \
|
||||
src/gui-html/img/loading.gif \
|
||||
${WEB_SERVER_DESTINATION_DIR}/img/
|
||||
cp src/gui-html/uae.html src/gui-html/uae_faq.html src/gui-html/uae.css \
|
||||
src/gui-html/uae.js src/gui-html/uae.nmf src/gui-html/default.uaerc \
|
||||
src/gui-html/img/favicon.ico \
|
||||
${WEB_SERVER_DESTINATION_DIR}/
|
||||
|
||||
# TODO(cstefansen): Put the pnacl-finalize step in a make rule instead.
|
||||
${NACL_SDK_ROOT}/toolchain/${OS}_pnacl/bin/pnacl-finalize src/uae \
|
||||
-o ${WEB_SERVER_DESTINATION_DIR}/uae.pexe
|
||||
|
||||
# Make sure the files are readable.
|
||||
chmod -R 0755 ${WEB_SERVER_DESTINATION_DIR}
|
||||
|
||||
# Have Chrome navigate to the app.
|
||||
CHROME_APP_LOCATION="$WEB_SERVER/uae.html"
|
||||
|
||||
# Chrome flags.
|
||||
# Add the flag --disable-gpu to simulate system without OpenGLES support.
|
||||
CHROME_FLAGS='--user-data-dir=../chrome-profile \
|
||||
--no-first-run'
|
||||
# Some other convenient command-line flags to use:
|
||||
# --show-fps-counter --incognito
|
||||
|
||||
# Clean the user profile. We don't want cached files.
|
||||
rm -fR ../chrome-profile
|
||||
|
||||
if [ "$1" = "debug" ]; then
|
||||
CHROME_FLAGS+=' --enable-nacl-debug'
|
||||
echo "*** Starting Chrome in NaCl debug mode. ***"
|
||||
echo "Start nacl-gdb and connect with 'target remote localhost 4010'."
|
||||
else
|
||||
${NACL_SDK_ROOT}/toolchain/${OS}_pnacl/bin/pnacl-strip \
|
||||
${WEB_SERVER_DESTINATION_DIR}/uae.pexe
|
||||
fi
|
||||
echo "${CHROME_EXE}" ${CHROME_FLAGS} ${CHROME_APP_LOCATION}
|
||||
"${CHROME_EXE}" ${CHROME_FLAGS} ${CHROME_APP_LOCATION}
|
||||
exit 0
|
||||
fi # "run" || "debug"
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Didn't find anything useful to do; print usage message.
|
||||
###########################################################################
|
||||
|
||||
cat <<EOF
|
||||
Usage: $0 (pnacl | desktop | incremental | run | debug)
|
||||
|
||||
How to use this script:
|
||||
|
||||
1. Adjust the variables in the beginning of the script.
|
||||
|
||||
2. To make a clean PNaCl build of UAE, execute
|
||||
./build_native_client.sh pnacl
|
||||
|
||||
3. Start the web server, e.g., by executing
|
||||
python -m SimpleHTTPServer 8080
|
||||
from the web server destination dir.
|
||||
|
||||
4. To stage the files and run Chrome do
|
||||
./build_native_client.sh run
|
||||
|
||||
5. Do incremental builds with
|
||||
./build_native_client.sh incremental
|
||||
|
||||
6. To run and make Chrome/PNaCl wait until a NaCl debugger is attached use
|
||||
./build_native_client.sh run
|
||||
EOF
|
9
src/gfx-pepper/Makefile.am
Normal file
9
src/gfx-pepper/Makefile.am
Normal file
@ -0,0 +1,9 @@
|
||||
AM_CPPFLAGS = @UAE_CPPFLAGS@
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/include -I$(top_builddir)/src -I$(top_srcdir)/src
|
||||
AM_CFLAGS = @UAE_CFLAGS@
|
||||
|
||||
noinst_LIBRARIES = libgfxdep.a
|
||||
|
||||
libgfxdep_a_SOURCES = gfx-pepper.c gfx-pepper-2d.c gfx-pepper-3d.c
|
||||
|
||||
noinst_HEADERS = gfx.h
|
194
src/gfx-pepper/gfx-pepper-2d.c
Normal file
194
src/gfx-pepper/gfx-pepper-2d.c
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper 2D graphics to be used for Native Client builds.
|
||||
*
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ppapi/c/ppb_core.h"
|
||||
#include "ppapi/c/ppb_graphics_2d.h"
|
||||
#include "ppapi/c/ppb_image_data.h"
|
||||
|
||||
/* guidep == gui-html is currently the only way to build with Pepper. */
|
||||
#include "guidep/ppapi.h"
|
||||
#include "options.h"
|
||||
#include "writelog.h"
|
||||
#include "xwin.h"
|
||||
|
||||
static PPB_Core *ppb_core_interface;
|
||||
static PPB_Graphics2D *ppb_g2d_interface;
|
||||
static PPB_Instance *ppb_instance_interface;
|
||||
static PPB_ImageData *ppb_image_data_interface;
|
||||
|
||||
static PP_ImageDataFormat preferredFormat;
|
||||
static PP_Instance pp_instance;
|
||||
static PP_Resource image_data;
|
||||
static PP_Resource graphics_context;
|
||||
|
||||
static int graphics_initialized = 0;
|
||||
|
||||
static const struct PP_Point origo = {0, 0};
|
||||
static struct PP_Point top = {0, 0};
|
||||
static struct PP_Rect src_rect;
|
||||
static struct PP_Size canvasSize; /* The canvas available to the Amiga. */
|
||||
static struct PP_Size screenSize; /* The actual resolution of the embed. */
|
||||
static uint32_t alpha_mask;
|
||||
|
||||
void screen_size_changed_2d(int32_t width, int32_t height) {
|
||||
if (!graphics_initialized ||
|
||||
(screenSize.width == width && screenSize.height == height) ||
|
||||
width < canvasSize.width || height < canvasSize.height) {
|
||||
return;
|
||||
}
|
||||
screenSize.width = width;
|
||||
screenSize.height = height;
|
||||
|
||||
/* Unbind and release old graphics context. */
|
||||
if (!ppb_instance_interface->BindGraphics(pp_instance, 0)) {
|
||||
write_log("Failed to unbind old context from instance.\n");
|
||||
return;
|
||||
}
|
||||
ppb_core_interface->ReleaseResource(graphics_context);
|
||||
|
||||
/* Create new graphics context. */
|
||||
graphics_context = ppb_g2d_interface->Create(
|
||||
pp_instance,
|
||||
&screenSize,
|
||||
PP_TRUE /* is_always_opaque */);
|
||||
if (!graphics_context) {
|
||||
write_log("Could not obtain a PPB_Graphics2D context.\n");
|
||||
return;
|
||||
}
|
||||
if (!ppb_instance_interface->BindGraphics(pp_instance, graphics_context)) {
|
||||
write_log("Failed to bind context to instance.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Give the screen around the emulator a dark gray background. */
|
||||
PP_Resource temp_image_data =
|
||||
ppb_image_data_interface->Create(pp_instance, preferredFormat,
|
||||
&screenSize,
|
||||
/* init_to_zero */ PP_FALSE);
|
||||
if (!temp_image_data) {
|
||||
write_log("Could not create image data.\n");
|
||||
return;
|
||||
}
|
||||
uint32_t* pixels = ppb_image_data_interface->Map(temp_image_data);
|
||||
uint32_t* end = pixels + screenSize.width * screenSize.height;
|
||||
for (uint32_t* p = pixels; p < end; ++p) {
|
||||
/* This assumes each color channel is 8-bits */
|
||||
*p = alpha_mask | 0x33333333 /* dark grey background */;
|
||||
}
|
||||
ppb_image_data_interface->Unmap(temp_image_data);
|
||||
ppb_g2d_interface->ReplaceContents(graphics_context, temp_image_data);
|
||||
ppb_g2d_interface->Flush(graphics_context, PP_BlockUntilComplete());
|
||||
ppb_core_interface->ReleaseResource(temp_image_data);
|
||||
|
||||
/* Set the top corner for the canvas on the new screen. */
|
||||
top.x = (screenSize.width - canvasSize.width) / 2;
|
||||
top.y = (screenSize.height - canvasSize.height) / 2;
|
||||
}
|
||||
|
||||
|
||||
STATIC_INLINE void pepper_graphics2d_flush_screen(
|
||||
struct vidbuf_description *gfxinfo,
|
||||
int first_line, int last_line) {
|
||||
/* We can't use Graphics2D->ReplaceContents here because the emulator
|
||||
* only draws partial updates in the buffer and expects the remaining lines
|
||||
* to be unchanged from the previous frame. ReplaceContents would thus
|
||||
* require a memcpy of the complete canvas for every frame, as the
|
||||
* buffer given by Chrome when using ReplaceContents followed by
|
||||
* Create and Map will generally not contain the previous frame,
|
||||
* but the one before that (or garbage).
|
||||
*/
|
||||
ppb_g2d_interface->PaintImageData(graphics_context, image_data,
|
||||
&top, &src_rect);
|
||||
ppb_g2d_interface->Flush(graphics_context, PP_BlockUntilComplete());
|
||||
/* TODO(cstefansen): Properly throttle 2D graphics to 50 Hz in PAL mode. */
|
||||
}
|
||||
|
||||
int graphics_2d_subinit(uint32_t *Rmask, uint32_t *Gmask, uint32_t *Bmask,
|
||||
uint32_t *Amask) {
|
||||
/* Pepper Graphics2D setup. */
|
||||
ppb_instance_interface = (PPB_Instance *)
|
||||
NaCl_GetInterface(PPB_INSTANCE_INTERFACE);
|
||||
if (!ppb_instance_interface) {
|
||||
write_log("Could not acquire PPB_Instance interface.\n");
|
||||
return 0;
|
||||
}
|
||||
ppb_g2d_interface = (PPB_Graphics2D *)
|
||||
NaCl_GetInterface(PPB_GRAPHICS_2D_INTERFACE);
|
||||
if (!ppb_g2d_interface) {
|
||||
write_log("Could not acquire PPB_Graphics2D interface.\n");
|
||||
return 0;
|
||||
}
|
||||
ppb_core_interface = (PPB_Core *) NaCl_GetInterface(PPB_CORE_INTERFACE);
|
||||
if (!ppb_core_interface) {
|
||||
write_log("Could not acquire PPB_Core interface.\n");
|
||||
return 0;
|
||||
}
|
||||
ppb_image_data_interface = (PPB_ImageData *)
|
||||
NaCl_GetInterface(PPB_IMAGEDATA_INTERFACE);
|
||||
if (!ppb_image_data_interface) {
|
||||
write_log("Could not acquire PPB_ImageData interface.\n");
|
||||
return 0;
|
||||
}
|
||||
pp_instance = NaCl_GetInstance();
|
||||
if (!pp_instance) {
|
||||
write_log("Could not find current Pepper instance.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
screenSize.width = canvasSize.width = gfxvidinfo.width;
|
||||
screenSize.height = canvasSize.height = gfxvidinfo.height;
|
||||
src_rect.point = origo;
|
||||
src_rect.size = canvasSize;
|
||||
graphics_context =
|
||||
ppb_g2d_interface->Create(pp_instance,
|
||||
&screenSize,
|
||||
PP_TRUE /* is_always_opaque */);
|
||||
if (!graphics_context) {
|
||||
write_log("Could not obtain a PPB_Graphics2D context.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!ppb_instance_interface->BindGraphics(pp_instance, graphics_context)) {
|
||||
write_log("Failed to bind context to instance.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
preferredFormat = ppb_image_data_interface->GetNativeImageDataFormat();
|
||||
switch (preferredFormat) {
|
||||
case PP_IMAGEDATAFORMAT_BGRA_PREMUL:
|
||||
*Rmask = 0x00FF0000, *Gmask = 0x0000FF00, *Bmask = 0x000000FF;
|
||||
*Amask = 0xFF000000;
|
||||
alpha_mask = *Amask;
|
||||
break;
|
||||
case PP_IMAGEDATAFORMAT_RGBA_PREMUL:
|
||||
*Rmask = 0x000000FF, *Gmask = 0x0000FF00, *Bmask = 0x00FF0000;
|
||||
*Amask = 0xFF000000;
|
||||
alpha_mask = *Amask;
|
||||
break;
|
||||
default:
|
||||
write_log("Unrecognized preferred image data format: %d.\n",
|
||||
preferredFormat);
|
||||
return 0;
|
||||
}
|
||||
image_data = ppb_image_data_interface->Create(pp_instance, preferredFormat,
|
||||
&canvasSize, /* init_to_zero = */ PP_FALSE);
|
||||
if (!image_data) {
|
||||
write_log("Could not create image data.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* UAE gfxvidinfo setup. */
|
||||
gfxvidinfo.pixbytes = 4; /* 32-bit graphics */
|
||||
gfxvidinfo.rowbytes = gfxvidinfo.width * gfxvidinfo.pixbytes;
|
||||
gfxvidinfo.bufmem = (uae_u8 *) ppb_image_data_interface->Map(image_data);
|
||||
gfxvidinfo.emergmem = 0;
|
||||
gfxvidinfo.flush_screen = pepper_graphics2d_flush_screen;
|
||||
|
||||
graphics_initialized = 1;
|
||||
return 1;
|
||||
}
|
257
src/gfx-pepper/gfx-pepper-3d.c
Normal file
257
src/gfx-pepper/gfx-pepper-3d.c
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper 3D graphics to be used for Native Client builds.
|
||||
*
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*
|
||||
*/
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "GLES2/gl2.h"
|
||||
#include "ppapi/c/ppb_graphics_3d.h"
|
||||
#include "ppapi/gles2/gl2ext_ppapi.h"
|
||||
|
||||
/* guidep == gui-html is currently the only way to build with Pepper. */
|
||||
#include "guidep/ppapi.h"
|
||||
#include "options.h"
|
||||
#include "writelog.h"
|
||||
#include "xwin.h"
|
||||
|
||||
static PP_Instance pp_instance;
|
||||
static PP_Resource graphics_context;
|
||||
static PPB_Graphics3D *ppb_g3d_interface;
|
||||
|
||||
/* Hard-coding for GL_UNSIGNED_SHORT_5_6_5 for now. */
|
||||
static const GLenum pixel_data_type = GL_UNSIGNED_SHORT_5_6_5;
|
||||
|
||||
static struct timeval tv_now, tv_prev;
|
||||
|
||||
int graphics_3d_subinit(uint32_t *Rmask, uint32_t *Gmask, uint32_t *Bmask,
|
||||
uint32_t *Amask);
|
||||
|
||||
/* This function get call to notify us that the resolution of the embed
|
||||
* has changed. In 3D mode Chrome automatically stretches the embed tag to take
|
||||
* up the whole screen, so there is nothing for us to do.
|
||||
*/
|
||||
void screen_size_changed_3d(int32_t width, int32_t height) {}
|
||||
|
||||
STATIC_INLINE void pepper_graphics3d_flush_screen(
|
||||
struct vidbuf_description *gfxinfo,
|
||||
int first_line, int last_line) {
|
||||
glTexSubImage2D (GL_TEXTURE_2D, 0 /* level */, 0, 0,
|
||||
gfxvidinfo.width, gfxvidinfo.height, GL_RGB,
|
||||
pixel_data_type, gfxinfo->bufmem);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
ppb_g3d_interface->SwapBuffers(graphics_context, PP_BlockUntilComplete());
|
||||
|
||||
/* TODO(cstefansen): Properly throttle 3D graphics to 50 Hz in PAL mode. */
|
||||
/* Currently, the frame rate is throttled by finish_sound_buffer
|
||||
* in sd-pepper/sound.c, but this is less than ideal, as it only
|
||||
* provides throttling when sound is playing. It does, however,
|
||||
* mean that scrolling is noticably smoother when no music is
|
||||
* playing because the emulator can go to 60 Hz. */
|
||||
}
|
||||
|
||||
static GLuint compileShader(GLenum type, const char *data) {
|
||||
const char *shaderStrings[1];
|
||||
shaderStrings[0] = data;
|
||||
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, shaderStrings, NULL );
|
||||
glCompileShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
static int round_up_to_power_of_2 (int value)
|
||||
{
|
||||
int result = 1;
|
||||
while (result < value)
|
||||
result *= 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int graphics_3d_subinit(uint32_t *Rmask, uint32_t *Gmask, uint32_t *Bmask,
|
||||
uint32_t *Amask) {
|
||||
/* Pepper Graphics3D setup. */
|
||||
PPB_Instance *ppb_instance_interface =
|
||||
(PPB_Instance *) NaCl_GetInterface(PPB_INSTANCE_INTERFACE);
|
||||
if (!ppb_instance_interface) {
|
||||
write_log("Could not acquire PPB_Instance interface.\n");
|
||||
return 0;
|
||||
}
|
||||
ppb_g3d_interface =
|
||||
(PPB_Graphics3D *) NaCl_GetInterface(PPB_GRAPHICS_3D_INTERFACE);
|
||||
if (!ppb_g3d_interface) {
|
||||
write_log("Could not acquire PPB_Graphics3D interface.\n");
|
||||
return 0;
|
||||
}
|
||||
pp_instance = NaCl_GetInstance();
|
||||
if (!pp_instance) {
|
||||
write_log("Could not find current Pepper instance.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t attribs[] = {
|
||||
PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
|
||||
PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
|
||||
PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8,
|
||||
PP_GRAPHICS3DATTRIB_SAMPLES, 0,
|
||||
PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
|
||||
PP_GRAPHICS3DATTRIB_WIDTH, gfxvidinfo.width,
|
||||
PP_GRAPHICS3DATTRIB_HEIGHT, gfxvidinfo.height,
|
||||
PP_GRAPHICS3DATTRIB_GPU_PREFERENCE,
|
||||
PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_PERFORMANCE,
|
||||
PP_GRAPHICS3DATTRIB_NONE
|
||||
};
|
||||
|
||||
graphics_context = ppb_g3d_interface->Create(
|
||||
pp_instance,
|
||||
0 /* share_context */,
|
||||
attribs);
|
||||
if (!graphics_context) {
|
||||
write_log("Could not obtain a PPB_Graphics3D context.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!ppb_instance_interface->BindGraphics(pp_instance, graphics_context)) {
|
||||
write_log("Failed to bind context to instance.\n");
|
||||
return 0;
|
||||
}
|
||||
glSetCurrentContextPPAPI(graphics_context);
|
||||
|
||||
/* UAE gfxvidinfo setup. */
|
||||
/* TODO(cstefansen): Implement gfx double-buffering if perf. mandates it. */
|
||||
gfxvidinfo.pixbytes = 2; /* 16-bit graphics */
|
||||
gfxvidinfo.rowbytes = gfxvidinfo.width * gfxvidinfo.pixbytes;
|
||||
gfxvidinfo.bufmem =
|
||||
(uae_u8 *) calloc(round_up_to_power_of_2(gfxvidinfo.rowbytes),
|
||||
round_up_to_power_of_2(gfxvidinfo.height));
|
||||
gfxvidinfo.flush_screen = pepper_graphics3d_flush_screen;
|
||||
*Rmask = 0x0000F800, *Gmask = 0x000007E0, *Bmask = 0x0000001F, *Amask = 0;
|
||||
|
||||
/* OpenGL ES setup. */
|
||||
glViewport(0, 0, gfxvidinfo.width, gfxvidinfo.height);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
/* Uniform index. */
|
||||
enum {
|
||||
UNIFORM_VIDEOFRAME,
|
||||
UNIFORM_INPUTCOLOR,
|
||||
UNIFORM_THRESHOLD,
|
||||
NUM_UNIFORMS
|
||||
};
|
||||
GLint uniforms[NUM_UNIFORMS];
|
||||
|
||||
/* Attribute index. */
|
||||
enum {
|
||||
ATTRIB_VERTEX,
|
||||
ATTRIB_TEXTUREPOSITON,
|
||||
NUM_ATTRIBUTES
|
||||
};
|
||||
|
||||
static GLuint g_programObj;
|
||||
static GLuint g_textureID;
|
||||
|
||||
char *g_VShaderData =
|
||||
"attribute vec4 position;"
|
||||
"attribute vec4 inputTextureCoordinate;"
|
||||
"varying vec2 textureCoordinate;"
|
||||
"void main()"
|
||||
"{"
|
||||
"gl_Position = position;"
|
||||
"textureCoordinate = inputTextureCoordinate.xy;"
|
||||
"}";
|
||||
|
||||
char *g_FShaderData =
|
||||
"varying highp vec2 textureCoordinate;"
|
||||
"uniform sampler2D videoFrame;"
|
||||
"void main()"
|
||||
"{"
|
||||
"gl_FragColor = texture2D(videoFrame, textureCoordinate);"
|
||||
"}";
|
||||
|
||||
GLuint g_vertexShader;
|
||||
GLuint g_fragmentShader;
|
||||
|
||||
g_vertexShader = compileShader(GL_VERTEX_SHADER, g_VShaderData);
|
||||
g_fragmentShader = compileShader(GL_FRAGMENT_SHADER, g_FShaderData);
|
||||
|
||||
g_programObj = glCreateProgram();
|
||||
glAttachShader(g_programObj, g_vertexShader);
|
||||
glAttachShader(g_programObj, g_fragmentShader);
|
||||
|
||||
glBindAttribLocation(g_programObj, ATTRIB_VERTEX, "position");
|
||||
glBindAttribLocation(g_programObj, ATTRIB_TEXTUREPOSITON,
|
||||
"inputTextureCoordinate");
|
||||
|
||||
glLinkProgram(g_programObj);
|
||||
|
||||
uniforms[UNIFORM_VIDEOFRAME] = glGetUniformLocation(g_programObj,
|
||||
"videoFrame");
|
||||
uniforms[UNIFORM_INPUTCOLOR] = glGetUniformLocation(g_programObj,
|
||||
"inputColor");
|
||||
uniforms[UNIFORM_THRESHOLD] = glGetUniformLocation(g_programObj,
|
||||
"threshold");
|
||||
|
||||
glGenTextures(1, &g_textureID);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, g_textureID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
GLint textureWidth = round_up_to_power_of_2(gfxvidinfo.width);
|
||||
GLint textureHeight = round_up_to_power_of_2(gfxvidinfo.height);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGB,
|
||||
textureWidth, textureHeight,
|
||||
0, GL_RGB, pixel_data_type, gfxvidinfo.bufmem);
|
||||
|
||||
static const GLfloat squareVertices[] = {
|
||||
-1.0f, -1.0,
|
||||
1.0f, -1.0,
|
||||
-1.0f, 1.0,
|
||||
1.0f, 1.0,
|
||||
};
|
||||
|
||||
GLfloat xFraction = ((GLfloat) gfxvidinfo.width) / (GLfloat) textureWidth;
|
||||
GLfloat yFraction = ((GLfloat) gfxvidinfo.height) / (GLfloat) textureHeight;
|
||||
static GLfloat textureVertices[] = {
|
||||
0.0f, 0.0f /* yFraction */,
|
||||
0.0f /* xFraction */, 0.0f /* yFraction */,
|
||||
0.0f, 0.0f,
|
||||
0.0f /* xFraction */, 0.0f,
|
||||
};
|
||||
textureVertices[1] = yFraction;
|
||||
textureVertices[2] = xFraction;
|
||||
textureVertices[3] = yFraction;
|
||||
textureVertices[6] = xFraction;
|
||||
|
||||
glUseProgram(g_programObj);
|
||||
glUniform1i(uniforms[UNIFORM_VIDEOFRAME], 0);
|
||||
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
|
||||
glEnableVertexAttribArray(ATTRIB_VERTEX);
|
||||
glVertexAttribPointer(ATTRIB_TEXTUREPOSITON, 2, GL_FLOAT, 0, 0,
|
||||
textureVertices);
|
||||
glEnableVertexAttribArray(ATTRIB_TEXTUREPOSITON);
|
||||
|
||||
DEBUG_LOG("Shaders compiled and program linked.\n");
|
||||
|
||||
GLenum glErrorStatus = glGetError();
|
||||
if (glErrorStatus) {
|
||||
write_log("GL error %d while initializing.\n", glErrorStatus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
ppb_g3d_interface->SwapBuffers(graphics_context, PP_BlockUntilComplete());
|
||||
|
||||
gettimeofday(&tv_prev, NULL);
|
||||
|
||||
return 1; /* Success! */
|
||||
}
|
||||
|
||||
|
633
src/gfx-pepper/gfx-pepper.c
Normal file
633
src/gfx-pepper/gfx-pepper.c
Normal file
@ -0,0 +1,633 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper graphics to be used for Native Client builds.
|
||||
*
|
||||
* Copyright 1997 Bernd Schmidt
|
||||
* Copyright 2003 Richard Drummond
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ppapi/c/pp_point.h"
|
||||
#include "ppapi/c/ppb_input_event.h"
|
||||
|
||||
#include "hotkeys.h"
|
||||
#include "inputdevice.h"
|
||||
#include "keyboard.h"
|
||||
#include "keybuf.h"
|
||||
#include "keymap/keymap.h"
|
||||
#include "options.h"
|
||||
#ifdef PICASSO96
|
||||
#include "picasso96.h"
|
||||
#endif /* PICASSO96 */
|
||||
#include "writelog.h"
|
||||
#include "xwin.h"
|
||||
|
||||
/* guidep == gui-html is currently the only way to build with Pepper. */
|
||||
#include "guidep/ppapi.h"
|
||||
|
||||
/* External function declarations. */
|
||||
/* From misc.c. */
|
||||
extern void my_kbd_handler (int keyboard, int scancode, int newstate);
|
||||
/* From drawing.h. */
|
||||
extern void reset_drawing();
|
||||
/* TODO(cstefansen): Fix drawing.h so it can be included in isolation without
|
||||
* tons of unsatisfied dependencies.
|
||||
*/
|
||||
|
||||
/* Forward declaration. */
|
||||
int push_event(PP_Resource event);
|
||||
|
||||
/* graphics_2d_subinit is defined in gfx-pepper-2d.c. */
|
||||
int graphics_2d_subinit(uint32_t *Rmask, uint32_t *Gmask, uint32_t *Bmask,
|
||||
uint32_t *Amask);
|
||||
void screen_size_changed_2d(int32_t width, int32_t height);
|
||||
|
||||
/* graphics_3d_subinit is defined in gfx-pepper-3d.c. */
|
||||
int graphics_3d_subinit(uint32_t *Rmask, uint32_t *Gmask, uint32_t *Bmask,
|
||||
uint32_t *Amask);
|
||||
void screen_size_changed_3d(int32_t width, int32_t height);
|
||||
|
||||
static int using_3d;
|
||||
static uint32_t Rmask, Gmask, Bmask, Amask;
|
||||
|
||||
void screen_size_changed(unsigned int width, unsigned int height) {
|
||||
if (using_3d) {
|
||||
screen_size_changed_3d(width, height);
|
||||
} else {
|
||||
screen_size_changed_2d(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_INLINE int pepper_lockscr(struct vidbuf_description *gfxinfo) {
|
||||
return 1;
|
||||
}
|
||||
STATIC_INLINE void pepper_unlockscr(struct vidbuf_description *gfxinfo) {}
|
||||
STATIC_INLINE void pepper_flush_line(struct vidbuf_description *gfxinfo,
|
||||
int line_no) {}
|
||||
|
||||
STATIC_INLINE void pepper_flush_block(struct vidbuf_description *gfxinfo,
|
||||
int first_line, int last_line) {}
|
||||
STATIC_INLINE void pepper_flush_clear_screen(
|
||||
struct vidbuf_description *gfxinfo) {}
|
||||
|
||||
int graphics_setup() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
STATIC_INLINE unsigned long bitsInMask (unsigned long mask)
|
||||
{
|
||||
/* count bits in mask */
|
||||
unsigned long n = 0;
|
||||
while (mask) {
|
||||
n += mask & 1;
|
||||
mask >>= 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
STATIC_INLINE unsigned long maskShift (unsigned long mask)
|
||||
{
|
||||
/* determine how far mask is shifted */
|
||||
unsigned long n = 0;
|
||||
while (!(mask & 1)) {
|
||||
n++;
|
||||
mask >>= 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static int init_colors (void)
|
||||
{
|
||||
if (gfxvidinfo.pixbytes > 1) {
|
||||
int red_bits = bitsInMask(Rmask);
|
||||
int green_bits = bitsInMask(Gmask);
|
||||
int blue_bits = bitsInMask(Bmask);
|
||||
int red_shift = maskShift(Rmask);
|
||||
int green_shift = maskShift(Gmask);
|
||||
int blue_shift = maskShift(Bmask);
|
||||
if (Amask == 0) {
|
||||
alloc_colors64k (red_bits, green_bits, blue_bits,
|
||||
red_shift, green_shift, blue_shift,
|
||||
0, 0, 0, 0);
|
||||
} else {
|
||||
int alpha_shift = maskShift(Amask);
|
||||
int alpha_bits = bitsInMask(Amask);
|
||||
alloc_colors64k (red_bits, green_bits, blue_bits,
|
||||
red_shift, green_shift, blue_shift,
|
||||
alpha_bits, alpha_shift, /* alpha = */ 0xffffffff, 0);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG ("init_colors: only 256 colors\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* A key thing here is to populate the struct gfxvidinfo; most of it is done
|
||||
* in the subinit functions.
|
||||
*
|
||||
* We try hardware accelerated graphics via Graphics3D, If Chrome doesn't
|
||||
* support it on the given adapter/driver/OS combination, we fall back to
|
||||
* Graphics2D, which is generally speaking slower.
|
||||
*/
|
||||
int graphics_init(void) {
|
||||
/* Set up the common parts of the gfxvidinfo struct here. The subinit
|
||||
* functions for 2D and 3D initialize the remaining values appropriately.
|
||||
*/
|
||||
/* TODO(cstefansen): Generalize resolution. For now we force 720x568. */
|
||||
gfxvidinfo.width = currprefs.gfx_size_fs.width = 720;
|
||||
gfxvidinfo.height = currprefs.gfx_size_fs.height = 568;
|
||||
gfxvidinfo.emergmem = 0;
|
||||
gfxvidinfo.linemem = 0;
|
||||
gfxvidinfo.maxblocklines = MAXBLOCKLINES_MAX;
|
||||
gfxvidinfo.lockscr = pepper_lockscr;
|
||||
gfxvidinfo.unlockscr = pepper_unlockscr;
|
||||
gfxvidinfo.flush_line = pepper_flush_line;
|
||||
gfxvidinfo.flush_block = pepper_flush_block;
|
||||
gfxvidinfo.flush_clear_screen = pepper_flush_clear_screen;
|
||||
|
||||
/* Try 3D graphics. */
|
||||
using_3d = 1;
|
||||
if (!graphics_3d_subinit(&Rmask, &Gmask, &Bmask, &Amask)) {
|
||||
DEBUG_LOG("Could not initialize hardware accelerated graphics "
|
||||
"(Graphics3D).\n");
|
||||
using_3d = 0;
|
||||
/* Try 2D graphics. */
|
||||
if (!graphics_2d_subinit(&Rmask, &Gmask, &Bmask, &Amask)) {
|
||||
DEBUG_LOG("Could not initialize 2D graphics (Graphics2D).\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset_drawing();
|
||||
init_colors();
|
||||
|
||||
DEBUG_LOG("Screen height : %d\n", gfxvidinfo.height);
|
||||
DEBUG_LOG("Screen width : %d\n", gfxvidinfo.width);
|
||||
DEBUG_LOG("Using Graphics3D?: %d\n", using_3d);
|
||||
DEBUG_LOG("Bytes per pixel : %d\n", gfxvidinfo.pixbytes);
|
||||
DEBUG_LOG("Bytes per line : %d\n", gfxvidinfo.rowbytes);
|
||||
DEBUG_LOG("Buffer address : %p\n", gfxvidinfo.bufmem);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void setmaintitle(void) {}
|
||||
|
||||
int gfx_parse_option(struct uae_prefs *p, const char *option,
|
||||
const char *value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gfx_default_options(struct uae_prefs *p) {}
|
||||
|
||||
int check_prefs_changed_gfx(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void graphics_leave(void) {}
|
||||
|
||||
int debuggable(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Input device functions.
|
||||
*/
|
||||
static PPB_InputEvent *ppb_input_event_interface;
|
||||
static PPB_KeyboardInputEvent *ppb_keyboard_event_interface;
|
||||
static PPB_MouseInputEvent *ppb_mouse_event_interface;
|
||||
|
||||
/*
|
||||
* Mouse input device functions.
|
||||
*/
|
||||
|
||||
#define MAX_BUTTONS 3
|
||||
#define MAX_AXES 3
|
||||
#define FIRST_AXIS 0
|
||||
#define FIRST_BUTTON MAX_AXES
|
||||
|
||||
static int init_mouse (void)
|
||||
{
|
||||
if (!ppb_input_event_interface) {
|
||||
ppb_input_event_interface =
|
||||
(PPB_InputEvent *) NaCl_GetInterface(PPB_INPUT_EVENT_INTERFACE);
|
||||
}
|
||||
ppb_mouse_event_interface =
|
||||
(PPB_MouseInputEvent *) NaCl_GetInterface(
|
||||
PPB_MOUSE_INPUT_EVENT_INTERFACE);
|
||||
|
||||
if (!ppb_input_event_interface) {
|
||||
DEBUG_LOG("Could not acquire PPB_InputEvent interface.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!ppb_mouse_event_interface) {
|
||||
DEBUG_LOG("Could not acquire PPB_MouseInputEvent interface.\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void close_mouse(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static int acquire_mouse(int num, int flags)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void unacquire_mouse (int num)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static void read_mouse(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int get_mouse_num (void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static TCHAR *get_mouse_friendlyname (int mouse)
|
||||
{
|
||||
return "Default mouse";
|
||||
}
|
||||
static TCHAR *get_mouse_uniquename (int mouse)
|
||||
{
|
||||
return "DEFMOUSE1";
|
||||
}
|
||||
|
||||
static int get_mouse_widget_num (int mouse)
|
||||
{
|
||||
return MAX_AXES + MAX_BUTTONS;
|
||||
}
|
||||
|
||||
static int get_mouse_widget_type (int mouse, int num, TCHAR *name,
|
||||
uae_u32 *code)
|
||||
{
|
||||
if (num >= MAX_AXES && num < MAX_AXES + MAX_BUTTONS) {
|
||||
if (name)
|
||||
sprintf (name, "Button %d", num + 1 + MAX_AXES);
|
||||
return IDEV_WIDGET_BUTTON;
|
||||
} else if (num < MAX_AXES) {
|
||||
if (name)
|
||||
sprintf (name, "Axis %d", num + 1);
|
||||
return IDEV_WIDGET_AXIS;
|
||||
}
|
||||
return IDEV_WIDGET_NONE;
|
||||
}
|
||||
|
||||
static int get_mouse_widget_first (int mouse, int type)
|
||||
{
|
||||
switch (type) {
|
||||
case IDEV_WIDGET_BUTTON:
|
||||
return FIRST_BUTTON;
|
||||
case IDEV_WIDGET_AXIS:
|
||||
return FIRST_AXIS;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_mouse_flags (int num)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct inputdevice_functions inputdevicefunc_mouse = {
|
||||
init_mouse,
|
||||
close_mouse,
|
||||
acquire_mouse,
|
||||
unacquire_mouse,
|
||||
read_mouse,
|
||||
get_mouse_num,
|
||||
get_mouse_friendlyname,
|
||||
get_mouse_uniquename,
|
||||
get_mouse_widget_num,
|
||||
get_mouse_widget_type,
|
||||
get_mouse_widget_first,
|
||||
get_mouse_flags
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Keyboard inputdevice functions
|
||||
*/
|
||||
|
||||
static int init_kb(void)
|
||||
{
|
||||
if (!ppb_input_event_interface) {
|
||||
ppb_input_event_interface =
|
||||
(PPB_InputEvent *) NaCl_GetInterface(PPB_INPUT_EVENT_INTERFACE);
|
||||
}
|
||||
ppb_keyboard_event_interface =
|
||||
(PPB_KeyboardInputEvent *) NaCl_GetInterface(
|
||||
PPB_KEYBOARD_INPUT_EVENT_INTERFACE);
|
||||
|
||||
if (!ppb_input_event_interface) {
|
||||
DEBUG_LOG("Could not acquire PPB_InputEvent interface.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!ppb_keyboard_event_interface) {
|
||||
DEBUG_LOG("Could not acquire PPB_KeyboardInputEvent interface.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
inputdevice_release_all_keys ();
|
||||
reset_hotkeys ();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void close_kb(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int acquire_kb(int num, int flags)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void unacquire_kb(int num)
|
||||
{
|
||||
}
|
||||
|
||||
static void read_kb(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int get_kb_num(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static TCHAR *get_kb_friendlyname(int kb)
|
||||
{
|
||||
return "Default keyboard";
|
||||
}
|
||||
static TCHAR *get_kb_uniquename(int kb)
|
||||
{
|
||||
return "DEFKEYB1";
|
||||
}
|
||||
|
||||
static int get_kb_widget_num(int kb)
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
|
||||
static int get_kb_widget_first(int kb, int type)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_kb_widget_type(int kb, int num, TCHAR *name, uae_u32 *code)
|
||||
{
|
||||
*code = num;
|
||||
return IDEV_WIDGET_KEY;
|
||||
}
|
||||
|
||||
static int get_kb_flags(int num)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct inputdevice_functions inputdevicefunc_keyboard =
|
||||
{
|
||||
init_kb,
|
||||
close_kb,
|
||||
acquire_kb,
|
||||
unacquire_kb,
|
||||
read_kb,
|
||||
get_kb_num,
|
||||
get_kb_friendlyname,
|
||||
get_kb_uniquename,
|
||||
get_kb_widget_num,
|
||||
get_kb_widget_type,
|
||||
get_kb_widget_first,
|
||||
get_kb_flags
|
||||
};
|
||||
|
||||
|
||||
/* TODO(cstefansen): Implement caps lock, num lock, and scroll lock. */
|
||||
int getcapslockstate (void) { return 0; }
|
||||
void setcapslockstate (int state) {}
|
||||
int target_checkcapslock (int scancode, int *state) { return 0; }
|
||||
|
||||
/* TODO(cstefansen): Update commented-out keys if they get added to DOM
|
||||
* KeyboardEvent Keycodes. Currently, the codes for alt, shift, and control
|
||||
* are the same for left and right. Similarly for =/enter and =/enter on the
|
||||
* numeric keypad.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent */
|
||||
|
||||
static int ppapi_keycode_to_dik(uint32_t key) {
|
||||
switch (key) {
|
||||
case 27: return DIK_ESCAPE;
|
||||
|
||||
case '1': return DIK_1;
|
||||
case '2': return DIK_2;
|
||||
case '3': return DIK_3;
|
||||
case '4': return DIK_4;
|
||||
case '5': return DIK_5;
|
||||
case '6': return DIK_6;
|
||||
case '7': return DIK_7;
|
||||
case '8': return DIK_8;
|
||||
case '9': return DIK_9;
|
||||
case '0': return DIK_0;
|
||||
case 8: return DIK_BACK;
|
||||
|
||||
case 9: return DIK_TAB;
|
||||
case 'Q': return DIK_Q;
|
||||
case 'W': return DIK_W;
|
||||
case 'E': return DIK_E;
|
||||
case 'R': return DIK_R;
|
||||
case 'T': return DIK_T;
|
||||
case 'Y': return DIK_Y;
|
||||
case 'U': return DIK_U;
|
||||
case 'I': return DIK_I;
|
||||
case 'O': return DIK_O;
|
||||
case 'P': return DIK_P;
|
||||
case 13: return DIK_RETURN;
|
||||
case 17: return DIK_LCONTROL;
|
||||
|
||||
case 'A': return DIK_A;
|
||||
case 'S': return DIK_S;
|
||||
case 'D': return DIK_D;
|
||||
case 'F': return DIK_F;
|
||||
case 'G': return DIK_G;
|
||||
case 'H': return DIK_H;
|
||||
case 'J': return DIK_J;
|
||||
case 'K': return DIK_K;
|
||||
case 'L': return DIK_L;
|
||||
case 16: return DIK_LSHIFT;
|
||||
|
||||
case 'Z': return DIK_Z;
|
||||
case 'X': return DIK_X;
|
||||
case 'C': return DIK_C;
|
||||
case 'V': return DIK_V;
|
||||
case 'B': return DIK_B;
|
||||
case 'N': return DIK_N;
|
||||
case 'M': return DIK_M;
|
||||
/* case 16: return DIK_RSHIFT; */
|
||||
case ' ': return DIK_SPACE;
|
||||
|
||||
case 112: return DIK_F1;
|
||||
case 113: return DIK_F2;
|
||||
case 114: return DIK_F3;
|
||||
case 115: return DIK_F4;
|
||||
case 116: return DIK_F5;
|
||||
case 117: return DIK_F6;
|
||||
case 118: return DIK_F7;
|
||||
case 119: return DIK_F8;
|
||||
case 120: return DIK_F9;
|
||||
case 121: return DIK_F10;
|
||||
case 122: return DIK_F11;
|
||||
case 123: return DIK_F12;
|
||||
case 124: return DIK_F13;
|
||||
case 125: return DIK_F14;
|
||||
case 126: return DIK_F15;
|
||||
case 144: return DIK_NUMLOCK;
|
||||
case 20: return DIK_CAPITAL;
|
||||
case 145: return DIK_SCROLL;
|
||||
|
||||
/* TODO(cstefansen): +, *, and / don't work on the numeric pad - why? */
|
||||
case 103: return DIK_NUMPAD7;
|
||||
case 104: return DIK_NUMPAD8;
|
||||
case 105: return DIK_NUMPAD9;
|
||||
case 109: return DIK_SUBTRACT;
|
||||
case 100: return DIK_NUMPAD4;
|
||||
case 101: return DIK_NUMPAD5;
|
||||
case 102: return DIK_NUMPAD6;
|
||||
case 107: return DIK_ADD;
|
||||
case 97: return DIK_NUMPAD1;
|
||||
case 98: return DIK_NUMPAD2;
|
||||
case 99: return DIK_NUMPAD3;
|
||||
case 96: return DIK_NUMPAD0;
|
||||
case 110: return DIK_DECIMAL;
|
||||
/* case 13: return DIK_NUMPADENTER; */
|
||||
case 111: return DIK_DIVIDE;
|
||||
case 106: return DIK_MULTIPLY;
|
||||
/* case 187: return DIK_NUMPADEQUALS; */
|
||||
|
||||
case 46: return DIK_DELETE;
|
||||
/* case 17: return DIK_RCONTROL; */
|
||||
case 18: return DIK_LMENU;
|
||||
/* case 18: return DIK_RMENU; */
|
||||
|
||||
case 45: return DIK_INSERT;
|
||||
case 36: return DIK_HOME;
|
||||
case 35: return DIK_END;
|
||||
|
||||
case 186: return DIK_SEMICOLON;
|
||||
case 187: return DIK_EQUALS;
|
||||
case 188: return DIK_COMMA;
|
||||
case 189: return DIK_MINUS;
|
||||
case 190: return DIK_PERIOD;
|
||||
case 191: return DIK_SLASH;
|
||||
case 192: return DIK_GRAVE;
|
||||
case 219: return DIK_LBRACKET;
|
||||
case 220: return DIK_BACKSLASH;
|
||||
case 221: return DIK_RBRACKET;
|
||||
case 222: return DIK_APOSTROPHE;
|
||||
case 226: return DIK_OEM_102;
|
||||
|
||||
case 38: return DIK_UP;
|
||||
case 33: return DIK_PRIOR;
|
||||
case 37: return DIK_LEFT;
|
||||
case 39: return DIK_RIGHT;
|
||||
case 40: return DIK_DOWN;
|
||||
case 34: return DIK_NEXT;
|
||||
|
||||
case 19: return DIK_PAUSE;
|
||||
case 91: return DIK_LWIN;
|
||||
case 93: return DIK_RWIN;
|
||||
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int input_get_default_mouse (struct uae_input_device *uid, int num, int port,
|
||||
int af) {
|
||||
/* Supports only one mouse */
|
||||
uid[0].eventid[ID_AXIS_OFFSET + 0][0] = INPUTEVENT_MOUSE1_HORIZ;
|
||||
uid[0].eventid[ID_AXIS_OFFSET + 1][0] = INPUTEVENT_MOUSE1_VERT;
|
||||
uid[0].eventid[ID_AXIS_OFFSET + 2][0] = INPUTEVENT_MOUSE1_WHEEL;
|
||||
uid[0].eventid[ID_BUTTON_OFFSET + 0][0] = INPUTEVENT_JOY1_FIRE_BUTTON;
|
||||
uid[0].eventid[ID_BUTTON_OFFSET + 1][0] = INPUTEVENT_JOY1_2ND_BUTTON;
|
||||
uid[0].eventid[ID_BUTTON_OFFSET + 2][0] = INPUTEVENT_JOY1_3RD_BUTTON;
|
||||
uid[0].enabled = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void toggle_fullscreen (int mode) {}
|
||||
void toggle_mousegrab (void) {}
|
||||
void screenshot (int mode, int doprepare) {}
|
||||
|
||||
/* Returns 0 if the queue is full, the event cannot be parsed, or the event
|
||||
* was not handled. */
|
||||
int push_event(PP_Resource event) {
|
||||
if (!ppb_input_event_interface || !ppb_mouse_event_interface ||
|
||||
!ppb_keyboard_event_interface) {
|
||||
DEBUG_LOG("Refusing to process pre-initialization input event.\n");
|
||||
return 0;
|
||||
}
|
||||
PP_InputEvent_Type type = ppb_input_event_interface->GetType(event);
|
||||
switch (type) {
|
||||
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
|
||||
case PP_INPUTEVENT_TYPE_MOUSEUP: {
|
||||
int buttonno = -1;
|
||||
switch (ppb_mouse_event_interface->GetButton(event)) {
|
||||
case PP_INPUTEVENT_MOUSEBUTTON_NONE: return 0;
|
||||
case PP_INPUTEVENT_MOUSEBUTTON_LEFT: buttonno = 0; break;
|
||||
case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: buttonno = 2; break;
|
||||
case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: buttonno = 1; break;
|
||||
}
|
||||
setmousebuttonstate(0, buttonno, type == PP_INPUTEVENT_TYPE_MOUSEDOWN ?
|
||||
1 : 0);
|
||||
break;
|
||||
}
|
||||
case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
|
||||
struct PP_Point delta = ppb_mouse_event_interface->GetMovement(event);
|
||||
setmousestate (0, 0, delta.x, 0);
|
||||
setmousestate (0, 1, delta.y, 0);
|
||||
break;
|
||||
}
|
||||
case PP_INPUTEVENT_TYPE_KEYDOWN:
|
||||
case PP_INPUTEVENT_TYPE_KEYUP: {
|
||||
int keycode = ppb_keyboard_event_interface->GetKeyCode(event);
|
||||
my_kbd_handler(0, ppapi_keycode_to_dik(keycode),
|
||||
type == PP_INPUTEVENT_TYPE_KEYDOWN ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void handle_events(void) {}
|
||||
|
||||
#ifdef PICASSO96
|
||||
int picasso_palette (void) { return 0; }
|
||||
void gfx_set_picasso_modeinfo (uae_u32 w, uae_u32 h, uae_u32 depth,
|
||||
RGBFTYPE rgbfmt) {}
|
||||
void gfx_set_picasso_colors (RGBFTYPE rgbfmt) {}
|
||||
void gfx_set_picasso_state (int on) {}
|
||||
int WIN32GFX_IsPicassoScreen (void) { return 0; }
|
||||
void DX_Invalidate (int first, int last) {}
|
||||
int DX_Fill (int dstx, int dsty, int width, int height, uae_u32 color,
|
||||
RGBFTYPE rgbtype) { return 0; }
|
||||
int DX_FillResolutions (uae_u16 *ppixel_format) { return 0; }
|
||||
void gfx_unlock_picasso (void) {}
|
||||
uae_u8 *gfx_lock_picasso (int fullupdate) {
|
||||
DEBUG_LOG("Function 'gfx_lock_picasso' not implemented.\n");
|
||||
return 0;
|
||||
}
|
||||
#endif /* PICASSO96 */
|
45
src/gfx-pepper/gfx.h
Normal file
45
src/gfx-pepper/gfx.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper graphics to be used in for Native Client builds.
|
||||
*
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*/
|
||||
|
||||
#ifndef UAE_GFX_PEPPER_GFX_H
|
||||
#define UAE_GFX_PEPPER_GFX_H
|
||||
|
||||
#define GFX_NAME "pepper"
|
||||
|
||||
struct uae_prefs; /* Defined in options.h. */
|
||||
|
||||
/* Call this function to have the Pepper graphics subsystem update itself
|
||||
* when the screen size changes. */
|
||||
void screen_size_changed(unsigned int width, unsigned int height);
|
||||
|
||||
/* The following functions must be provided by any graphics system and hence
|
||||
* also by Pepper.
|
||||
*/
|
||||
void setmaintitle(void);
|
||||
int gfx_parse_option(struct uae_prefs *p, const char *option, const char *value);
|
||||
void gfx_default_options(struct uae_prefs *p);
|
||||
|
||||
// TODO(cstefansen): Remove these defines. We don't support Picasso, but the
|
||||
// codebase has unguarded PICASSO96-specific code all over so it's hard to
|
||||
// compile without these.
|
||||
#define PICASSO96_SUPPORTED
|
||||
#define PICASSO96
|
||||
|
||||
#ifdef PICASSO96
|
||||
/* TODO(cstefansen): Cyclic inclusion issue: sysdeps.h includes this file. If
|
||||
* we include picasso96.h, it will not know TCHAR which is defined later in sysdeps.h.
|
||||
* Should be a simple matter of cleaning up sysdeps.h. */
|
||||
//#include "picasso96.h"
|
||||
//int DX_Fill(int dstx, int dsty, int width, int height, uae_u32 color,
|
||||
// RGBFTYPE rgbtype);
|
||||
// void DX_Invalidate(int first, int last);
|
||||
int WIN32GFX_IsPicassoScreen(void);
|
||||
int DX_FillResolutions (uae_u16 *ppixel_format);
|
||||
#endif /* PICASSO96 */
|
||||
|
||||
#endif /* UAE_GFX_PEPPER_GFX_H */
|
8
src/gui-html/Makefile.am
Normal file
8
src/gui-html/Makefile.am
Normal file
@ -0,0 +1,8 @@
|
||||
AM_CPPFLAGS = @UAE_CPPFLAGS@
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/include -I$(top_builddir)/src -I$(top_srcdir)/src
|
||||
AM_CFLAGS = @UAE_CFLAGS@
|
||||
|
||||
noinst_LIBRARIES = libguidep.a
|
||||
|
||||
libguidep_a_SOURCES = html_gui.c ppapi.cc
|
||||
|
87
src/gui-html/default.uaerc
Normal file
87
src/gui-html/default.uaerc
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
kickstart_rom_file=kick.rom
|
||||
_IGNORE_kickstart_rom_file=:AROS
|
||||
|
||||
__COMMENT__=Amiga500
|
||||
cpu_type=68000
|
||||
chipmem_size=1
|
||||
bogomem_size=2
|
||||
fastmem_size=0
|
||||
z3mem_size=0
|
||||
gfxcard_size=0
|
||||
chipset=ocs
|
||||
cpu_speed=max
|
||||
cpu_compatible=true
|
||||
cpu_cycle_exact=true
|
||||
collision_level=full
|
||||
|
||||
__COMMENT__=AMIGA1200_68020
|
||||
_cpu_type=68020/68881
|
||||
_cpu_model=68020
|
||||
_fpu_model=68881
|
||||
_chipmem_size=4
|
||||
_bogomem_size=0
|
||||
_fastmem_size=4
|
||||
_z3mem_size=0
|
||||
_gfxcard_size=0
|
||||
_cpu_speed=max
|
||||
_chipset=aga
|
||||
_cpu_compatible=true
|
||||
_cpu_cycle_exact=true
|
||||
_collision_level=full
|
||||
_blitter_cycle_exact=true
|
||||
_cycle_exact=true
|
||||
_rtg_nocustom=true
|
||||
_rtg_modes=0x212
|
||||
_cpu_24bit_addressing=false
|
||||
_chipset_compatible=A4000
|
||||
|
||||
|
||||
joyport0=mouse
|
||||
joyport0autofire=none
|
||||
joyport1=none
|
||||
__joyport1=kbd2
|
||||
joyport1autofire=none
|
||||
_NOTE_Keyboards_do_not_have_the_same_numbering_here_as_in_the_HTML=1
|
||||
_NOTE_See_keymap.c_and_html_gui.c=1
|
||||
sdl.map_raw_keys=true
|
||||
|
||||
show_leds=true
|
||||
|
||||
nr_floppies=2
|
||||
floppy0type=0
|
||||
floppy1type=0
|
||||
floppy2type=-1
|
||||
floppy3type=-1
|
||||
floppy0sound=1
|
||||
floppy1sound=1
|
||||
floppy_volume=75
|
||||
floppy_channel_mask=0x0
|
||||
|
||||
_IGNORE_NACL_DOESNT_CARE_sound_channels=stereo
|
||||
_IGNORE_NACL_DOESNT_CARE_sound_frequency=44100
|
||||
_IGNORE_NACL_DOESNT_CARE_sound_latency=100
|
||||
_IGNORE_sound_output=none
|
||||
_IGNORE_sound_output=exact
|
||||
_IGNORE_sound_output=interrupts
|
||||
sound_output=normal
|
||||
|
||||
sdl.use_gl=true
|
||||
gfx_framerate=1
|
||||
_IGNORE_ntsc=true
|
||||
_IGNORE_gfxcard_size=32
|
||||
gfx_vsync=true
|
||||
gfx_vsyncmode=normal
|
||||
gfx_refreshrate=50
|
||||
_IGNORE_gfx_vsync_picasso=true
|
||||
_IGNORE_gfx_vsyncmode_picasso=normal
|
||||
_IGNORE_gfx_lores=true
|
||||
_IGNORE_gfx_linemode=none
|
||||
_IGNORE_gfx_center_horizontal=true
|
||||
_IGNORE_gfx_center_vertical=true
|
||||
_IGNORE_gfx_width_windowed=720
|
||||
_IGNORE_gfx_height_windowed=568
|
||||
|
||||
_NOTE_fullscreen_is_needed_for_relative_mouse_mode_to_be_active=1
|
||||
gfx_fullscreen_amiga=true
|
||||
gfx_fullscreen_picasso=true
|
368
src/gui-html/html_gui.c
Normal file
368
src/gui-html/html_gui.c
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
* UAE - the Un*x Amiga Emulator
|
||||
*
|
||||
* HTML user interface via Pepper used under Native Client.
|
||||
*
|
||||
* Messages from the HTML UI are dispatched via PostMessage to the Native
|
||||
* Client module.
|
||||
*/
|
||||
|
||||
#include "gui.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "inputdevice.h"
|
||||
#include "options.h"
|
||||
#include "sounddep/sound.h"
|
||||
#include "sysdeps.h"
|
||||
#include "threaddep/thread.h"
|
||||
#include "uae.h"
|
||||
#include "writelog.h"
|
||||
|
||||
/* For mutual exclusion on pref settings. */
|
||||
static uae_sem_t gui_sem;
|
||||
/* For sending messages from the GUI to UAE. */
|
||||
static smp_comm_pipe from_gui_pipe;
|
||||
|
||||
static char *gui_romname = 0;
|
||||
static char *new_disk_string[4];
|
||||
static unsigned int gui_pause_uae = 0;
|
||||
|
||||
static int gui_initialized = 0;
|
||||
|
||||
int using_restricted_cloanto_rom = 1;
|
||||
|
||||
/*
|
||||
* Supported messages. Sent from the GUI to UAE via from_gui_pipe.
|
||||
*/
|
||||
enum uae_commands {
|
||||
UAECMD_START,
|
||||
UAECMD_STOP,
|
||||
UAECMD_QUIT,
|
||||
UAECMD_RESET,
|
||||
UAECMD_PAUSE,
|
||||
UAECMD_RESUME,
|
||||
UAECMD_DEBUG,
|
||||
UAECMD_SAVE_CONFIG,
|
||||
UAECMD_EJECTDISK,
|
||||
UAECMD_INSERTDISK,
|
||||
UAECMD_SELECT_ROM,
|
||||
UAECMD_SAVESTATE_LOAD,
|
||||
UAECMD_SAVESTATE_SAVE,
|
||||
UAECMD_RESIZE
|
||||
};
|
||||
|
||||
/* handle_message()
|
||||
*
|
||||
* This is called from the GUI when a GUI event happened. Specifically,
|
||||
* HandleMessage (PPP_Messaging) forwards dispatched UI action
|
||||
* messages posted from JavaScript to handle_message().
|
||||
*/
|
||||
int handle_message(const char* msg) {
|
||||
/* Grammar for messages from the UI:
|
||||
*
|
||||
* message ::= 'insert' drive fileURL
|
||||
* | 'rom' fileURL
|
||||
* | 'connect' port input
|
||||
* | 'eject' drive
|
||||
* | 'reset' | 'pause' | 'resume'
|
||||
* | 'resize' <width> <height>
|
||||
* device ::= 'kickstart' | drive
|
||||
* drive ::= 'df0' | 'df1'
|
||||
* port ::= 'port0' | 'port1'
|
||||
* input ::= 'mouse' | 'joy0' | 'joy1' | 'kbd0' | 'kbd1'
|
||||
* fileURL ::= <a URL of the form blob://>
|
||||
*/
|
||||
|
||||
DEBUG_LOG("%s\n", msg);
|
||||
if (!gui_initialized) {
|
||||
DEBUG_LOG("GUI message refused; not yet initialized.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* TODO(cstefansen): scan the string instead of these shenanigans. */
|
||||
|
||||
/* Copy to non-const buffer. */
|
||||
char buf[1024];
|
||||
(void) strncpy(buf, msg, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0'; /* Ensure NUL termination. */
|
||||
|
||||
/* Tokenize message up to 3 tokens (max given the grammar). */
|
||||
int i = 0;
|
||||
char *t[3], *token, *rest = NULL, *sep = " ";
|
||||
|
||||
for (token = strtok_r(buf, sep, &rest);
|
||||
token != NULL && i <= 3;
|
||||
token = strtok_r(NULL, sep, &rest), ++i) {
|
||||
t[i] = token;
|
||||
}
|
||||
|
||||
/* Pipe message to UAE main thread. */
|
||||
if (i == 1 && !strcmp(t[0], "reset")) {
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_RESET, 1);
|
||||
} else if (i == 1 && !strcmp(t[0], "pause")) {
|
||||
/* It would be cleaner to call pause_sound and resume_sound in
|
||||
* gui_handle_events below, i.e, on the emulator thread. However,
|
||||
* if we're pausing because the tab is no longer in the foreground,
|
||||
* no graphics flush calls will unblock and no graphics callbacks will
|
||||
* be delivered until the tab is back in front. This means that the
|
||||
* emulator is probably already stuck in some call and won't get to
|
||||
* our UI request to pause the sound. */
|
||||
/* TODO(cstefansen)People are reporting pausing/resuming problems; let's
|
||||
not do this until investigated. */
|
||||
/* pause_sound(); */
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_PAUSE, 1);
|
||||
} else if (i == 1 && !strcmp(t[0], "resume")) {
|
||||
/* resume_sound(); */
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_RESUME, 1);
|
||||
} else if (i == 2 && !strcmp(t[0], "eject")) {
|
||||
int drive_num;
|
||||
if (!strcmp(t[1], "df0")) {
|
||||
drive_num = 0;
|
||||
} else if (!strcmp(t[1], "df1")) {
|
||||
drive_num = 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_EJECTDISK, 0);
|
||||
write_comm_pipe_int(&from_gui_pipe, drive_num, 1);
|
||||
} else if (i == 3 && !strcmp(t[0], "resize")) {
|
||||
long width = strtol(t[1], NULL, 10);
|
||||
long height = strtol(t[2], NULL, 10);
|
||||
if (width > INT_MAX || height > INT_MAX || errno == ERANGE
|
||||
|| width <= 0 || height <= 0) {
|
||||
write_log("Could not parse width/height in message: %s\n", msg);
|
||||
return -1;
|
||||
}
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_RESIZE, 0);
|
||||
write_comm_pipe_int(&from_gui_pipe, (int) width, 0);
|
||||
write_comm_pipe_int(&from_gui_pipe, (int) height, 1);
|
||||
} else if (i == 3 && !strcmp(t[0], "insert")) {
|
||||
int drive_num;
|
||||
if (!strcmp(t[1], "df0")) {
|
||||
drive_num = 0;
|
||||
} else if (!strcmp(t[1], "df1")) {
|
||||
drive_num = 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
uae_sem_wait(&gui_sem);
|
||||
if (new_disk_string[drive_num] != 0)
|
||||
free (new_disk_string[drive_num]);
|
||||
new_disk_string[drive_num] = strdup(t[2]);
|
||||
uae_sem_post(&gui_sem);
|
||||
write_comm_pipe_int (&from_gui_pipe, UAECMD_INSERTDISK, 0);
|
||||
write_comm_pipe_int (&from_gui_pipe, drive_num, 1);
|
||||
} else if (i == 2 && !strcmp(t[0], "rom")) {
|
||||
uae_sem_wait(&gui_sem);
|
||||
if (gui_romname != 0)
|
||||
free (gui_romname);
|
||||
gui_romname = strdup(t[1]);
|
||||
uae_sem_post(&gui_sem);
|
||||
write_comm_pipe_int(&from_gui_pipe, UAECMD_SELECT_ROM, 1);
|
||||
} else if (i == 3 && !strcmp(t[0], "connect")) {
|
||||
int port_num;
|
||||
if (!strcmp(t[1], "port0")) {
|
||||
port_num = 0;
|
||||
} else if (!strcmp(t[1], "port1")) {
|
||||
port_num = 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int input_device =
|
||||
!strcmp(t[2], "mouse") ? JSEM_MICE :
|
||||
!strcmp(t[2], "joy0") ? JSEM_JOYS :
|
||||
!strcmp(t[2], "joy1") ? JSEM_JOYS + 1 :
|
||||
!strcmp(t[2], "kbd0") ? JSEM_KBDLAYOUT + 1 :
|
||||
!strcmp(t[2], "kbd1") ? JSEM_KBDLAYOUT + 2 :
|
||||
JSEM_END;
|
||||
|
||||
changed_prefs.jports[port_num].id = input_device;
|
||||
if (changed_prefs.jports[port_num].id !=
|
||||
currprefs.jports[port_num].id) {
|
||||
/* It's a little fishy that the typical way to update input
|
||||
* devices doesn't use the comm pipe.
|
||||
*/
|
||||
inputdevice_updateconfig (&changed_prefs);
|
||||
inputdevice_config_change();
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO(cstefansen): Factor out general descriptions like the following to
|
||||
* gui.h.
|
||||
*/
|
||||
/*
|
||||
* gui_init()
|
||||
*
|
||||
* This is called from the main UAE thread to tell the GUI to initialize.
|
||||
* To indicate failure to initialize, return -1.
|
||||
*/
|
||||
int gui_init (void)
|
||||
{
|
||||
init_comm_pipe (&from_gui_pipe, 8192 /* size */, 1 /* chunks */);
|
||||
uae_sem_init (&gui_sem, 0, 1);
|
||||
gui_initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* gui_update()
|
||||
*
|
||||
* This is called from the main UAE thread to tell the GUI to update itself
|
||||
* using the current state of currprefs. This function will block
|
||||
* until it receives a message from the GUI telling it that the update
|
||||
* is complete.
|
||||
*/
|
||||
int gui_update (void)
|
||||
{
|
||||
DEBUG_LOG("gui_update() unimplemented for HTML GUI.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* gui_exit()
|
||||
*
|
||||
* This called from the main UAE thread to tell the GUI to quit gracefully.
|
||||
*/
|
||||
void gui_exit (void) {}
|
||||
|
||||
/*
|
||||
* gui_led()
|
||||
*
|
||||
* Called from the main UAE thread to inform the GUI
|
||||
* of disk activity so that indicator LEDs may be refreshed.
|
||||
*/
|
||||
void gui_led (int num, int on) {}
|
||||
|
||||
/*
|
||||
* gui_handle_events()
|
||||
*
|
||||
* This is called from the main UAE thread to process events sent from
|
||||
* the GUI thread.
|
||||
*
|
||||
* If the UAE emulation proper is not running yet or is paused,
|
||||
* this loops continuously waiting for and responding to events
|
||||
* until the emulation is started or resumed, respectively. When
|
||||
* the emulation is running, this is called periodically from
|
||||
* the main UAE event loop.
|
||||
*/
|
||||
void gui_handle_events (void)
|
||||
{
|
||||
/* Read GUI command if any. */
|
||||
|
||||
/* Process it, e.g., call uae_reset(). */
|
||||
while (comm_pipe_has_data (&from_gui_pipe) || gui_pause_uae) {
|
||||
DEBUG_LOG("gui_handle_events: trying to read...\n");
|
||||
int cmd = read_comm_pipe_int_blocking (&from_gui_pipe);
|
||||
DEBUG_LOG("gui_handle_events: %i\n", cmd);
|
||||
|
||||
switch (cmd) {
|
||||
case UAECMD_EJECTDISK: {
|
||||
int n = read_comm_pipe_int_blocking (&from_gui_pipe);
|
||||
uae_sem_wait(&gui_sem);
|
||||
changed_prefs.floppyslots[n].df[0] = '\0';
|
||||
uae_sem_post(&gui_sem);
|
||||
continue;
|
||||
}
|
||||
case UAECMD_INSERTDISK: {
|
||||
int n = read_comm_pipe_int_blocking (&from_gui_pipe);
|
||||
if (using_restricted_cloanto_rom) {
|
||||
write_log("Loading other disks is not permitted under the "
|
||||
"license for the built-in Cloanto Kickstart "
|
||||
"ROM.\n");
|
||||
continue;
|
||||
}
|
||||
uae_sem_wait(&gui_sem);
|
||||
strncpy (changed_prefs.floppyslots[n].df, new_disk_string[n], 255);
|
||||
free (new_disk_string[n]);
|
||||
new_disk_string[n] = 0;
|
||||
changed_prefs.floppyslots[n].df[255] = '\0';
|
||||
uae_sem_post(&gui_sem);
|
||||
continue;
|
||||
}
|
||||
case UAECMD_RESET:
|
||||
uae_reset(1);
|
||||
break; // Stop GUI command processing until UAE is ready again.
|
||||
case UAECMD_PAUSE:
|
||||
gui_pause_uae = 1;
|
||||
continue;
|
||||
case UAECMD_RESUME:
|
||||
gui_pause_uae = 0;
|
||||
continue;
|
||||
case UAECMD_SELECT_ROM:
|
||||
uae_sem_wait(&gui_sem);
|
||||
strncpy(changed_prefs.romfile, gui_romname, 255);
|
||||
changed_prefs.romfile[255] = '\0';
|
||||
free(gui_romname);
|
||||
gui_romname = 0;
|
||||
|
||||
/* Switching to non-restricted ROM; rebooting. */
|
||||
using_restricted_cloanto_rom = 0;
|
||||
uae_reset(1);
|
||||
|
||||
uae_sem_post(&gui_sem);
|
||||
continue;
|
||||
case UAECMD_RESIZE: {
|
||||
int width = read_comm_pipe_int_blocking(&from_gui_pipe);
|
||||
int height = read_comm_pipe_int_blocking(&from_gui_pipe);
|
||||
screen_size_changed(width, height);
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
DEBUG_LOG("Unknown command %d received from GUI.\n", cmd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* gui_filename()
|
||||
*
|
||||
* This is called from the main UAE thread to inform
|
||||
* the GUI that a floppy disk has been inserted or ejected.
|
||||
*/
|
||||
void gui_filename (int num, const char *name) {}
|
||||
|
||||
/* gui_fps()
|
||||
*
|
||||
* This is called from the main UAE thread to provide the GUI with
|
||||
* the most recent FPS and idle numbers.
|
||||
*/
|
||||
void gui_fps (int fps, int idle) {
|
||||
gui_data.fps = fps;
|
||||
gui_data.idle = idle;
|
||||
}
|
||||
|
||||
/* gui_lock() */
|
||||
void gui_lock (void) {}
|
||||
|
||||
/* gui_unlock() */
|
||||
void gui_unlock (void) {}
|
||||
|
||||
/* gui_flicker_led()
|
||||
*
|
||||
* This is called from the main UAE thread to tell the GUI that a particular
|
||||
* drive LED should flicker to indicate I/O activity.
|
||||
*/
|
||||
void gui_flicker_led (int led, int unitnum, int status) {}
|
||||
|
||||
/* gui_disk_image_change() */
|
||||
void gui_disk_image_change (int unitnum, const TCHAR *name, bool writeprotected) {}
|
||||
|
||||
/* gui_display() */
|
||||
void gui_display (int shortcut) {}
|
||||
|
||||
/* gui_gameport_button_change() */
|
||||
void gui_gameport_button_change (int port, int button, int onoff) {}
|
||||
|
||||
/* gui_gameport_axis_change */
|
||||
void gui_gameport_axis_change (int port, int axis, int state, int max) {}
|
||||
|
||||
/* gui_message() */
|
||||
void gui_message (const char *format,...) {}
|
17
src/gui-html/img/LICENSE
Normal file
17
src/gui-html/img/LICENSE
Normal file
@ -0,0 +1,17 @@
|
||||
The images in this directory are under licenses as follows:
|
||||
|
||||
amiga500_128x128.png is Copyright Bill Bertram 2006, CC-BY-2.5
|
||||
(http://en.wikipedia.org/wiki/File:Amiga500_system1.jpg)
|
||||
|
||||
favicon.ico is a resized (to 16x16) and renamed version of
|
||||
Icon_Disk_256x256.png, which was made available by 'Devcore' under
|
||||
Creative Commons CC0 1.0 Universal Public Domain Dedication
|
||||
(http://commons.wikimedia.org/wiki/File:Icon_Disk_256x256.png)
|
||||
|
||||
loading.gif is "Generated gifs are free" (http://www.loadinfo.net/)
|
||||
|
||||
amiga_pointer.png screenshot by Christian Stefansen 2013 is CC-BY-3.0
|
||||
|
||||
first_demos.png screenshot by Christian Stefansen 2013 is CC-BY-3.0
|
||||
|
||||
read_me.png screenshot by Christian Stefansen 2013 is CC-BY-3.0
|
BIN
src/gui-html/img/amiga500_128x128.png
Normal file
BIN
src/gui-html/img/amiga500_128x128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
src/gui-html/img/amiga_pointer.png
Normal file
BIN
src/gui-html/img/amiga_pointer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
src/gui-html/img/favicon.ico
Normal file
BIN
src/gui-html/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
src/gui-html/img/first_demos.png
Normal file
BIN
src/gui-html/img/first_demos.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
src/gui-html/img/loading.gif
Normal file
BIN
src/gui-html/img/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
BIN
src/gui-html/img/read_me.png
Normal file
BIN
src/gui-html/img/read_me.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
362
src/gui-html/ppapi.cc
Normal file
362
src/gui-html/ppapi.cc
Normal file
@ -0,0 +1,362 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* PPAPI module and instance for running in Chrome.
|
||||
*
|
||||
* Copyright 2012, 2013 Christian Stefansen
|
||||
*/
|
||||
|
||||
#include "ppapi.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <istream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GLES2/gl2.h"
|
||||
|
||||
#include "ppapi/c/ppb_instance.h"
|
||||
#include "ppapi/c/ppb_message_loop.h"
|
||||
#include "ppapi/cpp/completion_callback.h"
|
||||
#include "ppapi/cpp/core.h"
|
||||
#include "ppapi/cpp/fullscreen.h"
|
||||
#include "ppapi/cpp/host_resolver.h"
|
||||
#include "ppapi/cpp/input_event.h"
|
||||
#include "ppapi/cpp/instance.h"
|
||||
#include "ppapi/cpp/message_loop.h"
|
||||
#include "ppapi/cpp/mouse_lock.h"
|
||||
#include "ppapi/cpp/url_loader.h"
|
||||
#include "ppapi/cpp/url_response_info.h"
|
||||
#include "ppapi/cpp/url_request_info.h"
|
||||
#include "ppapi/gles2/gl2ext_ppapi.h"
|
||||
#include "ppapi/utility/completion_callback_factory.h"
|
||||
|
||||
#ifdef ENABLE_BUILD_KEY
|
||||
#include "build.key.c"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SDL
|
||||
#include "SDL/SDL.h"
|
||||
#include "SDL/SDL_nacl.h"
|
||||
#endif /* USE_SDL */
|
||||
|
||||
// The main function of the emulator thread.
|
||||
extern "C" void real_main(int argc, const char **argv);
|
||||
|
||||
// We call this function to relay messages from the HTML UI to the messaging
|
||||
// pipe that the emulator reads.
|
||||
extern "C" int handle_message(const char* msg);
|
||||
|
||||
// We call this function to queue up input events. The emulator handles these
|
||||
// in handle_events().
|
||||
extern "C" int push_event(PP_Resource event);
|
||||
|
||||
class UAEInstance : public pp::Instance, public pp::MouseLock {
|
||||
private:
|
||||
static UAEInstance* the_instance_; // Ensure we only create one instance.
|
||||
pthread_t uae_main_thread_; // This thread will run real_main().
|
||||
bool first_changed_view_; // Ensure we initialize an instance only
|
||||
// once.
|
||||
int width_; int height_; // Dimension of the video screen.
|
||||
bool mouse_locked_; // Whether mouse is locked or not.
|
||||
pp::Fullscreen fullscreen_;
|
||||
pp::CompletionCallbackFactory<UAEInstance> cc_factory_;
|
||||
|
||||
// This function allows us to delay game start until all
|
||||
// resources are ready.
|
||||
void StartUAEInNewThread(int32_t dummy) {
|
||||
pthread_create(&uae_main_thread_, NULL, &UAEInstance::LaunchUAE, this);
|
||||
}
|
||||
|
||||
// Launches the actual game, e.g., by calling real_main().
|
||||
static void* LaunchUAE(void* data) {
|
||||
// Get access to instance object.
|
||||
UAEInstance* this_instance = reinterpret_cast<UAEInstance*>(data);
|
||||
pp::MessageLoop msg_loop = pp::MessageLoop(this_instance);
|
||||
if (PP_OK != msg_loop.AttachToCurrentThread()) {
|
||||
printf("Failed to attach a message loop to the UAE thread.\n");
|
||||
}
|
||||
|
||||
// Call the UAE 'real_main' defined in main.c
|
||||
const char *argv[] = { "", "-f", "default.uaerc" };
|
||||
int argc = 3;
|
||||
real_main(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DidLockMouse(int32_t result) {
|
||||
mouse_locked_ = result == PP_OK;
|
||||
if (result != PP_OK) {
|
||||
std::stringstream ss;
|
||||
ss << "Mouselock failed with failed with error number " << result;
|
||||
PostMessage(pp::Var(ss.str()));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit UAEInstance(PP_Instance instance)
|
||||
: pp::Instance(instance),
|
||||
pp::MouseLock(this),
|
||||
uae_main_thread_(NULL),
|
||||
first_changed_view_(true),
|
||||
width_(720), height_(568),
|
||||
mouse_locked_(false),
|
||||
cc_factory_(this),
|
||||
fullscreen_(this) {
|
||||
assert(the_instance_ == NULL);
|
||||
the_instance_ = this;
|
||||
}
|
||||
|
||||
virtual ~UAEInstance() {
|
||||
// Wait for game thread to finish.
|
||||
if (uae_main_thread_) { pthread_join(uae_main_thread_, NULL); }
|
||||
}
|
||||
|
||||
// This function is called with the HTML attributes of the embed tag,
|
||||
// which can be used in lieu of command line arguments.
|
||||
virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
|
||||
// UAE requires mouse and keyboard events; add more if necessary.
|
||||
RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE|
|
||||
PP_INPUTEVENT_CLASS_KEYBOARD);
|
||||
|
||||
printf("Received %d arguments from HTML:\n", argc);
|
||||
for (unsigned int i = 0; i < argc; i++) {
|
||||
printf(" [%d] %s=\"%s\"\n", i, argn[i], argv[i]);
|
||||
}
|
||||
// From here we could pass the arguments to the emulator, if it was
|
||||
// needed.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This crucial function forwards PPAPI events to the emulator.
|
||||
virtual bool HandleInputEvent(const pp::InputEvent& event) {
|
||||
switch (event.GetType()) {
|
||||
case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
|
||||
if (!mouse_locked_) {
|
||||
LockMouse(cc_factory_.NewCallback(
|
||||
&UAEInstance::DidLockMouse));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PP_INPUTEVENT_TYPE_KEYDOWN: {
|
||||
pp::KeyboardInputEvent key_event(event);
|
||||
uint32_t modifiers = event.GetModifiers();
|
||||
uint32_t keycode = key_event.GetKeyCode();
|
||||
if (keycode == 70 /* 'f' */ &&
|
||||
(modifiers & PP_INPUTEVENT_MODIFIER_SHIFTKEY) &&
|
||||
(modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY)) {
|
||||
printf("Fullscreen toggle.\n");
|
||||
if (fullscreen_.IsFullscreen()) {
|
||||
// Leaving full-screen mode also unlocks the mouse if it was
|
||||
// locked.
|
||||
if (!fullscreen_.SetFullscreen(false)) {
|
||||
printf("Could not leave fullscreen mode.\n");
|
||||
}
|
||||
} else {
|
||||
if (!fullscreen_.SetFullscreen(true)) {
|
||||
printf("Could not set fullscreen mode.\n");
|
||||
} else {
|
||||
printf("Fullscreen mode set.\n");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Ignore all other events when not in mouse lock mode.
|
||||
if (!mouse_locked_) return false;
|
||||
break;
|
||||
}
|
||||
/* TODO(cstefansen): remove ifdef; this should just call a 'push_event'
|
||||
* function in the current gfx-dep or something.
|
||||
*/
|
||||
#ifdef USE_SDL
|
||||
SDL_NACL_PushEvent(event);
|
||||
#else
|
||||
if (!push_event(event.pp_resource())) return false;
|
||||
#endif /* USE_SDL */
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void HandleMessage(const pp::Var &message) {
|
||||
if (!message.is_string()) {
|
||||
PostMessage("Warning: non-string message posted to NaCl module "
|
||||
"- ignoring.\n");
|
||||
return;
|
||||
}
|
||||
handle_message(message.AsString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// This function is called for various reasons, e.g. visibility and page
|
||||
// size changes. We ignore these calls except for the first
|
||||
// invocation, which we use to start the game.
|
||||
virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
|
||||
if (first_changed_view_) {
|
||||
first_changed_view_ = false;
|
||||
#ifdef USE_SDL
|
||||
// Make SDL aware of the Pepper instance.
|
||||
// TODO(cstefansen): do not ifdef; this should just call an 'init'
|
||||
// function in the current gfx-dep or something.
|
||||
SDL_NACL_SetInstance(pp_instance(), width_, height_);
|
||||
#endif
|
||||
StartUAEInNewThread(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send resize message to emulator.
|
||||
std::stringstream msg;
|
||||
msg << "resize " << position.width() << " " << position.height();
|
||||
HandleMessage(pp::Var(msg.str()));
|
||||
}
|
||||
|
||||
virtual void MouseLockLost() {
|
||||
if (mouse_locked_) {
|
||||
PostMessage(pp::Var(std::string("Mouse lock lost.\n")));
|
||||
mouse_locked_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
static UAEInstance* GetInstance() { return the_instance_; }
|
||||
|
||||
// To work with UAE this function returns the number of bytes read, or 0
|
||||
// if anything went wrong.
|
||||
size_t LoadUrl(const char* name, char** data) {
|
||||
// This function is using blocking calls, so it should not be used (and
|
||||
// wouldn't work) on the main thread.
|
||||
if (pp::Module::Get()->core()->IsMainThread()) {
|
||||
*data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::URLLoader loader(this);
|
||||
pp::URLRequestInfo request(this);
|
||||
request.SetURL(name);
|
||||
request.SetMethod("GET");
|
||||
request.SetFollowRedirects(true);
|
||||
request.SetAllowCredentials(true);
|
||||
request.SetAllowCrossOriginRequests(true);
|
||||
|
||||
int32_t result =
|
||||
loader.Open(request, pp::CompletionCallback::CompletionCallback());
|
||||
if (PP_OK != result) {
|
||||
printf("Could not open file %s. PP error %d.\n", name, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::URLResponseInfo response = loader.GetResponseInfo();
|
||||
int32_t status_code = response.GetStatusCode();
|
||||
|
||||
std::vector<char> dst;
|
||||
static const int32_t BUFFER_SIZE = 65536;
|
||||
char buf[BUFFER_SIZE];
|
||||
do {
|
||||
result = loader.ReadResponseBody(buf,
|
||||
BUFFER_SIZE,
|
||||
pp::CompletionCallback
|
||||
::CompletionCallback());
|
||||
if (result > 0) {
|
||||
std::vector<char>::size_type pos = dst.size();
|
||||
dst.resize(pos + result);
|
||||
memcpy(&(dst)[pos], buf, result);
|
||||
}
|
||||
} while (result > 0);
|
||||
|
||||
printf("%s: got HTTP status code %d and %d bytes while loading %s.\n",
|
||||
(status_code == 200 ? "Success" : "Failed"), status_code,
|
||||
dst.size(), name);
|
||||
|
||||
if (status_code == 200) {
|
||||
#ifdef ENABLE_BUILD_KEY
|
||||
// Detect naive XOR encryption and decrypt if needed.
|
||||
char encryption_marker[] = "AMIGA_RULEZ_XORENC";
|
||||
const int kEncryptionMarkerLen =
|
||||
sizeof(encryption_marker) - 1; /* No \0 terminator. */
|
||||
int adjusted_len = dst.size() - kEncryptionMarkerLen;
|
||||
|
||||
if (adjusted_len >= 0 &&
|
||||
!memcmp(encryption_marker, dst.data(), kEncryptionMarkerLen)) {
|
||||
*data = reinterpret_cast<char*>(malloc(sizeof(char) *
|
||||
adjusted_len));
|
||||
for(std::vector<char>::size_type i = 0;
|
||||
i < adjusted_len; ++i) {
|
||||
(*data)[i] = dst[i + kEncryptionMarkerLen] ^
|
||||
build_key[i % build_key_len];
|
||||
}
|
||||
return adjusted_len;
|
||||
}
|
||||
#endif // ENABLE_BUILD_KEY
|
||||
|
||||
*data = reinterpret_cast<char*>(malloc(sizeof(char) *
|
||||
dst.size()));
|
||||
std::copy(dst.begin(), dst.end(), *data);
|
||||
return dst.size();
|
||||
|
||||
} else {
|
||||
*data = NULL;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const void *GetInterface(const char *interface_name) {
|
||||
PPB_GetInterface get_interface_function =
|
||||
pp::Module::Get()->get_browser_interface();
|
||||
return get_interface_function(interface_name);
|
||||
}
|
||||
};
|
||||
|
||||
UAEInstance* UAEInstance::the_instance_ = NULL;
|
||||
|
||||
// Helper to allow calls from C to LoadUrl.
|
||||
size_t NaCl_LoadUrl(const char* name, char** data) {
|
||||
UAEInstance* instance = UAEInstance::GetInstance();
|
||||
assert(instance);
|
||||
return instance->LoadUrl(name, data);
|
||||
}
|
||||
|
||||
// Helper to allow C code to get PPB interfaces.
|
||||
const void *NaCl_GetInterface(const char *interface_name) {
|
||||
UAEInstance* instance = UAEInstance::GetInstance();
|
||||
assert(instance);
|
||||
return instance->GetInterface(interface_name);
|
||||
}
|
||||
|
||||
PP_Instance NaCl_GetInstance() {
|
||||
UAEInstance* instance = UAEInstance::GetInstance();
|
||||
assert(instance);
|
||||
return instance->pp_instance();
|
||||
}
|
||||
|
||||
class UAEModule : public pp::Module {
|
||||
public:
|
||||
UAEModule() : pp::Module() {}
|
||||
virtual ~UAEModule() {
|
||||
glTerminatePPAPI();
|
||||
}
|
||||
|
||||
virtual bool Init() {
|
||||
// Force stdout to be line-buffered (not the default in glibc).
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
return glInitializePPAPI(get_browser_interface()) == GL_TRUE;
|
||||
}
|
||||
|
||||
virtual pp::Instance* CreateInstance(PP_Instance instance) {
|
||||
return new UAEInstance(instance);
|
||||
}
|
||||
};
|
||||
|
||||
namespace pp {
|
||||
Module* CreateModule() {
|
||||
return new UAEModule();
|
||||
}
|
||||
} // namespace pp
|
28
src/gui-html/ppapi.h
Normal file
28
src/gui-html/ppapi.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* PPAPI module and instance for running in Chrome.
|
||||
*
|
||||
* Copyright 2012, 2013 Christian Stefansen
|
||||
*/
|
||||
|
||||
#ifndef SRC_GUI_HTML_PPAPI_H_
|
||||
#define SRC_GUI_HTML_PPAPI_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ppapi/c/ppb_instance.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
size_t NaCl_LoadUrl(const char* name, char** data);
|
||||
const void *NaCl_GetInterface(const char *interface_name);
|
||||
PP_Instance NaCl_GetInstance(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SRC_GUI_HTML_PPAPI_H_
|
130
src/gui-html/uae.css
Normal file
130
src/gui-html/uae.css
Normal file
@ -0,0 +1,130 @@
|
||||
html {
|
||||
background: #2c2c2c;
|
||||
color: #eee;
|
||||
font-family: Belleza, helvetica, arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
padding-left: 1.5cm;
|
||||
padding-right: 1.5cm;
|
||||
padding-bottom: 0.60cm;
|
||||
}
|
||||
body {
|
||||
width: 900px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#status_field, #status_field a, #status_field a:visited {
|
||||
color: red;
|
||||
}
|
||||
#one_column {
|
||||
width: 720px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#uae_container {
|
||||
position: relative;
|
||||
background: #000;
|
||||
width: 720px;
|
||||
height: 568px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
#instructions {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background: #3e3e3e;
|
||||
margin-top: 0px;
|
||||
margin-right: 0px;
|
||||
margin-left: 20px;
|
||||
width: 140px;
|
||||
height: 548px;
|
||||
float: right;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
#instructions img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
#instructions p b {
|
||||
color: dodgerblue;
|
||||
}
|
||||
#instructions hr {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
#overlay {
|
||||
position: absolute;
|
||||
width: 720px;
|
||||
height: 568px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
#cloanto-logo {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
#progress {
|
||||
display: inline-block;
|
||||
margin-top: 35%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#progress_text {
|
||||
color: #fff;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 24pt;
|
||||
margin-top: 0.5cm;
|
||||
margin-bottom: 0.15cm;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 16pt;
|
||||
margin-top: 0.15cm;
|
||||
margin-bottom: 0.15cm;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16pt;
|
||||
color: dodgerblue;
|
||||
margin-top: 1cm;
|
||||
margin-bottom: -0.15cm;
|
||||
}
|
||||
h4 {
|
||||
font-size: 12pt;
|
||||
margin-top: 0.6cm;
|
||||
margin-bottom: -0.1cm;
|
||||
}
|
||||
p {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 4px;
|
||||
color: darkgrey;
|
||||
line-height: 125%;
|
||||
}
|
||||
ul li {
|
||||
color: darkgrey;
|
||||
}
|
||||
a, a:visited {
|
||||
color: dodgerblue;
|
||||
}
|
||||
div.image-container {
|
||||
float: right;
|
||||
width: 128px;
|
||||
margin-left: 0.4cm;
|
||||
font-size: 75%;
|
||||
}
|
||||
img.flow-with-text {
|
||||
vertical-align: top;
|
||||
}
|
||||
.footer {
|
||||
padding-top: 1.1cm;
|
||||
clear: both;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.footer p {
|
||||
text-align: center;
|
||||
}
|
121
src/gui-html/uae.html
Normal file
121
src/gui-html/uae.html
Normal file
@ -0,0 +1,121 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Amiga 500 Emulator: A Portable Native Client demo</title>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<!-- Enable these for development purposes.
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="-1" />
|
||||
-->
|
||||
<meta name="Description" content="An Amiga 500 emulator that works
|
||||
directly in your browser. Play with the demos provided by Cloanto or
|
||||
bring your ROM to play your own good old Amiga games here. This
|
||||
technology demo is based on the Universal Amiga Emulator (UAE) and
|
||||
uses Portable Native Client (PNaCl) to make it run in the browser.">
|
||||
<link href="http://fonts.googleapis.com/css?family=Belleza"
|
||||
rel="stylesheet" type="text/css">
|
||||
<link href="uae.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body id="body">
|
||||
<h1>Amiga 500 Emulator</h1>
|
||||
<h2>A <i>Portable Native Client</i> demo</h2>
|
||||
<p id="status_field"> </p>
|
||||
|
||||
<div id="uae_container">
|
||||
<div id="overlay">
|
||||
<img id="cloanto-logo" src="img/cloanto-amiga-forever-powered-0120.png"
|
||||
alt="Powered by Amiga Forever – Cloanto(tm)" />
|
||||
<div id="progress">
|
||||
<img src="img/loading.gif" id="loading_animation"
|
||||
alt="Animated loading indicator" />
|
||||
<p id='progress_text'>Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="instructions">
|
||||
<h2>Tips</h2>
|
||||
<p>When you see the Workbench screen, click inside the Amiga to
|
||||
take control of the pointer.</p>
|
||||
<img src="img/amiga_pointer.png" width="27px"
|
||||
alt="Amiga mouse pointer screenshot" />
|
||||
<p>Double-click "First Demos".</p>
|
||||
<img src="img/first_demos.png" width="67px" alt="First Demos screenshot" />
|
||||
<p>Then double-click "ReadMe.txt" for instructions.</p>
|
||||
<img src="img/read_me.png" width="67px" alt="ReadMe.txt screenshot" />
|
||||
<br />
|
||||
<p>For better performance press <b>Ctrl-Shift-F</b> for fullscreen.
|
||||
Press Esc to leave fullscreen.</p>
|
||||
</div>
|
||||
|
||||
<div style="width: 720px;">
|
||||
<div style="width: 400px; float: left; padding-right: 10px;">
|
||||
<p id="afe_message">To enable the floppy drives and use your own disks, get the
|
||||
<a
|
||||
href="https://chrome.google.com/webstore/detail/amiga-forever-essentials/aemohkgcfhdkjpoonndfpaikgbpgcogm"
|
||||
target="_new">Amiga
|
||||
Forever Essentials</a> bundle (<a href="uae_faq.html#get_roms">more details</a>).</p>
|
||||
<p>Drive 0 (df0:) <button id="eject0Btn" disabled>Eject</button>
|
||||
<input type="file" id="df0" disabled /></p>
|
||||
<p>Drive 1 (df1:) <button id="eject1Btn" disabled>Eject</button>
|
||||
<input type="file" id="df1" disabled /></p>
|
||||
</div>
|
||||
<div style="width: 300px; float: right; padding-left: 10px;">
|
||||
<p><button id="resetBtn" disabled>Reset Amiga</button>
|
||||
<button id="pauseBtn" disabled>Pause Amiga</button></p>
|
||||
<p>Joy Port 0: <select id="port0" disabled>
|
||||
<option value="mouse" selected>Mouse</option>
|
||||
<option value="joy1">Gamepad 1 (experimental)</option>
|
||||
<option value="kbd1">Keyboard (ADSW tab Q)</option>
|
||||
<option value="none">None</option>
|
||||
</select></p>
|
||||
<p>Joy Port 1: <select id="port1" disabled>
|
||||
<option value="joy0">Gamepad 0 (experimental)</option>
|
||||
<option value="kbd0">Keyboard (←→↓↑ space . , /)
|
||||
</option>
|
||||
<option value="none" selected>None</option>
|
||||
</select></p>
|
||||
<p onClick="javascript:
|
||||
document.getElementById('rom_option').style.display='block';
|
||||
return false;"><a
|
||||
href="#">Advanced options...</a></p>
|
||||
<p id="rom_option" style="display: none;">Kickstart ROM <input
|
||||
type="file" id="rom" disabled /><br />
|
||||
You <i>must</i> have a license for the ROM that you use. If you don't,
|
||||
don't use this. Instead get <a
|
||||
href="https://chrome.google.com/webstore/detail/amiga-forever-essentials/aemohkgcfhdkjpoonndfpaikgbpgcogm"
|
||||
target="_new">Amiga Forever Essentials</a> for Chrome.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p> <a href="uae.html">Home</a> <a
|
||||
href="uae_faq.html">FAQ</a> <a
|
||||
href="https://github.com/cstefansen/PUAE">Source code</a>
|
||||
</p>
|
||||
|
||||
<p>Ported by <a
|
||||
href="https://google.com/+ChristianStefansen">Christian
|
||||
Stefansen</a>. The Amiga ROM, OS, and First Demos files are provided
|
||||
under license by <a
|
||||
href="http://www.amigaforever.com/">Cloanto</a>.</p>
|
||||
|
||||
<p>The port is dedicated to Karsten, Mumm, Wulff, and K.P. in fond
|
||||
remembrance of our childhood and the many hours of playing Amiga 500
|
||||
games together.</p>
|
||||
</div>
|
||||
|
||||
<script src="uae.js"></script>
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-6408327-2', 'pnacl-amiga-emulator.appspot.com');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
460
src/gui-html/uae.js
Normal file
460
src/gui-html/uae.js
Normal file
@ -0,0 +1,460 @@
|
||||
// Global application objects.
|
||||
var module = null;
|
||||
var moduleSuccessfullyLoaded = false;
|
||||
var moduleInitTasks = [];
|
||||
|
||||
// Track whether the emulator is paused by the user (as opposed by
|
||||
// Chrome pausing graphics processing).
|
||||
var isManuallyPaused = false;
|
||||
|
||||
// Upbeat status messages during the looong start-up.
|
||||
var statusMessages = [
|
||||
"reticulating splines",
|
||||
"calling the 80s",
|
||||
"polishing floppy disks",
|
||||
"yes, it'll load faster the second time",
|
||||
"finding Chuck Norris",
|
||||
"debugging guru meditations",
|
||||
"preparing garden gnomes",
|
||||
"warming up Rick Astley's voice",
|
||||
"assembling floppy drive",
|
||||
"waking up Agnus, Denise, and Paula",
|
||||
"loading up Portable Native Client",
|
||||
"unpacking Amiga 500",
|
||||
"calibrating non-interlaced mode",
|
||||
"powering up the Motorola 68000"
|
||||
]
|
||||
var currentMessage = 0;
|
||||
|
||||
|
||||
// Helper functions used throughout.
|
||||
function $(element) {
|
||||
return document.getElementById(element);
|
||||
}
|
||||
|
||||
function mimeHandlerExists (mimeType) {
|
||||
// Though it's possible to enable PNaCl in Chrome 30, the
|
||||
// emulator doesn't run properly on it, so we disable it.
|
||||
// (Don't do this at home. The capability check below is typically
|
||||
// sufficient, and it's better practice.)
|
||||
var chromeVersion = window.navigator.appVersion.match(/Chrome\/(\d+)\./);
|
||||
if (!chromeVersion) {
|
||||
return false;
|
||||
}
|
||||
var majorVersion = parseInt(chromeVersion[1], 10);
|
||||
if (majorVersion <= 30) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Capability check: verify that the browser is able to handle PNaCl content.
|
||||
supportedTypes = navigator.mimeTypes;
|
||||
for (var i = 0; i < supportedTypes.length; i++) {
|
||||
if (supportedTypes[i].type == mimeType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateStatus(opt_message) {
|
||||
var statusField = document.getElementById('status_field');
|
||||
if (statusField) {
|
||||
statusField.innerHTML = opt_message;
|
||||
}
|
||||
}
|
||||
|
||||
function logMessage(message) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
|
||||
// These event handling functions are were taken from the NaCl SDK
|
||||
// examples and slightly adjusted.
|
||||
|
||||
function moduleDidStartLoad() {
|
||||
logMessage('loadstart');
|
||||
}
|
||||
|
||||
var intervalUpdate = setInterval(
|
||||
function() {
|
||||
currentMessage = (currentMessage + 1) % statusMessages.length;
|
||||
document.getElementById('progress_text').innerHTML =
|
||||
'Loading... (' + statusMessages[currentMessage] + ')';
|
||||
}, 2000);
|
||||
|
||||
var lastLoadPercent = 0;
|
||||
|
||||
function moduleLoadProgress(event) {
|
||||
var loadPercent = 0.0;
|
||||
var loadPercentString;
|
||||
var loadStatus;
|
||||
|
||||
if (event.lengthComputable && event.total > 0) {
|
||||
clearInterval(intervalUpdate);
|
||||
loadPercent = (event.loaded / event.total * 100.0).toFixed(0);
|
||||
if (loadPercent == lastLoadPercent) return;
|
||||
lastLoadPercent = loadPercent;
|
||||
loadPercentString = loadPercent + '%';
|
||||
document.getElementById('progress_text').innerHTML =
|
||||
'Loading... (' + loadPercentString + ')';
|
||||
} else {
|
||||
// The total length is not yet known.
|
||||
}
|
||||
}
|
||||
|
||||
// Handler that gets called if an error occurred while loading the NaCl
|
||||
// module. Note that the event does not carry any meaningful data about
|
||||
// the error, you have to check lastError on the <EMBED> element to find
|
||||
// out what happened.
|
||||
function moduleLoadError() {
|
||||
if (module) {
|
||||
logMessage('error: ' + module.lastError);
|
||||
updateStatus('Error: ' + module.lastError);
|
||||
} else {
|
||||
logMessage('Module load error. Field "lastError" not available.');
|
||||
}
|
||||
}
|
||||
|
||||
// Handler that gets called if the NaCl module load is aborted.
|
||||
function moduleLoadAbort() {
|
||||
logMessage('abort');
|
||||
}
|
||||
|
||||
// Indicate success when the NaCl module has loaded.
|
||||
function moduleDidLoad() {
|
||||
module = document.getElementById('uae');
|
||||
// Set up general postMessage handler
|
||||
module.addEventListener('message', handleMessage, true);
|
||||
|
||||
// Set up handler for kickstart ROM upload field
|
||||
document.getElementById('rom').addEventListener(
|
||||
'change', handleFileSelect, false);
|
||||
// Set up handler for disk drive 0 (df0:) upload field
|
||||
document.getElementById('df0').addEventListener(
|
||||
'change', handleFileSelect, false);
|
||||
// Set up handler for disk drive 1 (df1:) upload field
|
||||
document.getElementById('df1').addEventListener(
|
||||
'change', handleFileSelect, false);
|
||||
|
||||
document.getElementById('resetBtn')
|
||||
.addEventListener('click', resetAmiga, true);
|
||||
document.getElementById('pauseBtn')
|
||||
.addEventListener('click', manuallyPauseAmiga, true);
|
||||
document.getElementById('eject0Btn')
|
||||
.addEventListener('click', function(e) { ejectDisk('df0'); }, true);
|
||||
document.getElementById('eject1Btn')
|
||||
.addEventListener('click', function(e) { ejectDisk('df1'); }, true);
|
||||
|
||||
document.getElementById('port0').addEventListener(
|
||||
'change',
|
||||
function(e) {
|
||||
connectJoyPort('port0',
|
||||
e.target.options[e.target.selectedIndex].value); },
|
||||
true);
|
||||
document.getElementById('port1').addEventListener(
|
||||
'change',
|
||||
function(e) {
|
||||
connectJoyPort('port1',
|
||||
e.target.options[e.target.selectedIndex].value); },
|
||||
true);
|
||||
|
||||
document.plugins.uae.focus();
|
||||
|
||||
moduleSuccessfullyLoaded = true;
|
||||
for (var i = 0; i < moduleInitTasks.length; ++i) {
|
||||
moduleInitTasks[i]();
|
||||
}
|
||||
logMessage("Module loaded.");
|
||||
}
|
||||
|
||||
function moduleDidEndLoad() {
|
||||
clearInterval(intervalUpdate);
|
||||
document.getElementById('overlay').style.visibility='hidden'
|
||||
logMessage('loadend');
|
||||
var lastError = event.target.lastError;
|
||||
if (lastError == undefined || lastError.length == 0) {
|
||||
lastError = '<none>';
|
||||
|
||||
window.onbeforeunload = function () {
|
||||
return 'If you leave this page, the state of the Amiga emulator will ' +
|
||||
'be lost.';
|
||||
}
|
||||
if (document.webkitVisibilityState) {
|
||||
document.addEventListener("webkitvisibilitychange",
|
||||
handleVisibilityChange,
|
||||
false);
|
||||
}
|
||||
|
||||
// If we loaded without errors, we can activate the buttons.
|
||||
// Would have been logical to do this in moduleDidLoad, but in Chrome
|
||||
// 32, there is an initial moduleDidLoad event when PNaCl starts loading
|
||||
// itself, which is way before the emulator gets started.
|
||||
|
||||
$('rom').disabled = false;
|
||||
$('resetBtn').disabled = false;
|
||||
$('pauseBtn').disabled = false;
|
||||
$('port0').disabled = false;
|
||||
$('port1').disabled = false;
|
||||
} else {
|
||||
updateStatus('Error: ' + lastError);
|
||||
}
|
||||
logMessage('lastError: ' + lastError);
|
||||
}
|
||||
|
||||
function moduleCrashed() {
|
||||
logMessage('PNaCl module crashed: ' + module.lastError);
|
||||
updateStatus('PNaCl module crashed: ' + module.lastError);
|
||||
}
|
||||
|
||||
// TODO(cstefansen): Connect module's stdout/stderr to this.
|
||||
function handleMessage(message_event) {
|
||||
logMessage(message_event.data);
|
||||
}
|
||||
|
||||
// Functions to operate the Amiga.
|
||||
function resetAmiga() {
|
||||
if (isManuallyPaused) {
|
||||
manuallyResumeAmiga();
|
||||
}
|
||||
module.postMessage('reset');
|
||||
}
|
||||
|
||||
function manuallyPauseAmiga() {
|
||||
pauseAmiga();
|
||||
isManuallyPaused = true;
|
||||
}
|
||||
|
||||
function pauseAmiga() {
|
||||
var pauseBtn = $('pauseBtn');
|
||||
pauseBtn.removeEventListener('click', manuallyPauseAmiga);
|
||||
pauseBtn.addEventListener('click', manuallyResumeAmiga);
|
||||
pauseBtn.innerText = "Resume Amiga";
|
||||
module.postMessage('pause');
|
||||
}
|
||||
|
||||
function manuallyResumeAmiga() {
|
||||
resumeAmiga();
|
||||
isManuallyPaused = false;
|
||||
}
|
||||
|
||||
function resumeAmiga() {
|
||||
var pauseBtn = $('pauseBtn');
|
||||
pauseBtn.removeEventListener('click', manuallyResumeAmiga);
|
||||
pauseBtn.addEventListener('click', manuallyPauseAmiga);
|
||||
pauseBtn.innerText = "Pause Amiga";
|
||||
module.postMessage('resume');
|
||||
}
|
||||
|
||||
// Ejects the disk in the current drive (e.g. 'df0', 'df1'). If there
|
||||
// is no disk in the drive, no action is taken.
|
||||
function ejectDisk(driveDescriptor) {
|
||||
switch (driveDescriptor) {
|
||||
case 'df0':
|
||||
case 'df1':
|
||||
module.postMessage('eject ' + driveDescriptor);
|
||||
document.getElementById(driveDescriptor).value = "";
|
||||
break;
|
||||
default:
|
||||
alert('Internal page error. Try to reload the page. If the ' +
|
||||
'problem persists, please report the issue.');
|
||||
}
|
||||
}
|
||||
|
||||
function connectJoyPort(portId, inputDevice) {
|
||||
module.postMessage('connect ' + portId + ' ' + inputDevice);
|
||||
}
|
||||
|
||||
function enableFloppyDrives() {
|
||||
$('df0').disabled = false;
|
||||
$('eject0Btn').disabled = false;
|
||||
$('df1').disabled = false;
|
||||
$('eject1Btn').disabled = false;
|
||||
}
|
||||
|
||||
// Load a file to targetDevice, a pseudo-device, which can be 'rom',
|
||||
// 'df0', or 'df1'.
|
||||
function loadFromLocal(targetDevice, file) {
|
||||
var fileURL = window.webkitURL.createObjectURL(file);
|
||||
switch (targetDevice) {
|
||||
case 'df0':
|
||||
case 'df1':
|
||||
module.postMessage('insert ' + targetDevice + ' ' + fileURL);
|
||||
break;
|
||||
case 'rom':
|
||||
module.postMessage('rom ' + fileURL);
|
||||
// User brought own ROM; enable floppy drives
|
||||
enableFloppyDrives();
|
||||
break;
|
||||
default:
|
||||
alert('Internal page error. Try to reload the page. If the ' +
|
||||
'problem persists, please report the issue.');
|
||||
}
|
||||
}
|
||||
|
||||
// This functions is called when a kickstart ROM or a disk image is
|
||||
// uploaded via the input fields on the page.
|
||||
function handleFileSelect(evt) {
|
||||
loadFromLocal(evt.target.id, evt.target.files[0]);
|
||||
}
|
||||
|
||||
|
||||
// Pause the emulator (and thus stop playing audio) when not visible.
|
||||
// Documentation:
|
||||
// https://developers.google.com/chrome/whitepapers/pagevisibility?csw=1
|
||||
// TODO(cstefansen): This should just pause the gfx rendering.
|
||||
function handleVisibilityChange() {
|
||||
// Only mess with things if we're not already paused.
|
||||
if (!isManuallyPaused) {
|
||||
if (document.webkitHidden) {
|
||||
pauseAmiga();
|
||||
} else {
|
||||
resumeAmiga();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check for Amiga Forever Essentials.
|
||||
// The callback will be called if and only if the ROM and key files were
|
||||
// found. The argument to the callback will be the blob URL of the ROM file.
|
||||
//
|
||||
// It is said that if you weren't embarrassed by the first launched version of
|
||||
// your product, you didn't launch early enough. The function below
|
||||
// is ample cause for embarrassment.
|
||||
//
|
||||
// TODO(cstefansen): Refactor and clean up checkForAmigaForeverEssentials.
|
||||
function checkForAmigaForeverEssentials(callback) {
|
||||
var amiga13RomFile = null;
|
||||
var amiga13KeyFile = null;
|
||||
// Development app_id:
|
||||
// var afeExtensionId = "jpbmmmmndcncdnbegeofdbakhdhnpcnh";
|
||||
var afeExtensionId = "aemohkgcfhdkjpoonndfpaikgbpgcogm";
|
||||
var afeUrlBase = "chrome-extension://" + afeExtensionId + "/shared/";
|
||||
var afeIndex = afeUrlBase + "index.json";
|
||||
|
||||
console.log('Checking for Amiga Forever Essentials.');
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
// Preferring 'loadend' to 'readystatechange' due to issues like this one:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=159827.
|
||||
// 'loadend' is guaranteed to be called exactly once regardless of outcome.
|
||||
xhr.addEventListener('loadend', function() {
|
||||
if (xhr.readyState == 4 && xhr.status == 200) {
|
||||
console.log('Found Amiga Forever Essentials index.json.');
|
||||
// Find URLs for Amiga 1.3 ROM and key.
|
||||
var index = JSON.parse(xhr.responseText);
|
||||
var romCount = index.files.rom.length;
|
||||
for (var n = 0; n < romCount; n++) {
|
||||
if (index.files.rom[n].name === "Amiga 1.3") {
|
||||
amiga13RomFile = afeUrlBase + index.files.rom[n].romFile;
|
||||
amiga13KeyFile = afeUrlBase + index.files.rom[n].keyFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (amiga13RomFile == '' || amiga13KeyFile == '') {
|
||||
console.log('Amiga 1.3 data not found in index.json.');
|
||||
return;
|
||||
}
|
||||
console.log('Amiga 1.3 data:\nROM File: ' + amiga13RomFile +
|
||||
'\nKey File: ' + amiga13KeyFile);
|
||||
|
||||
// Native Client doesn't like chrome-extension:// URLs, so we fetch the
|
||||
// ROM/key files and make a blob URL out of it.
|
||||
var romReq = new XMLHttpRequest();
|
||||
romReq.responseType = 'arraybuffer';
|
||||
romReq.addEventListener('loadend', function() {
|
||||
if (romReq.readyState == 4 && romReq.status == 200 && romReq.response) {
|
||||
// Now get the key file.
|
||||
var keyReq = new XMLHttpRequest();
|
||||
keyReq.responseType = 'arraybuffer';
|
||||
keyReq.addEventListener('loadend', function() {
|
||||
if (keyReq.readyState == 4 && keyReq.status == 200 && keyReq.response) {
|
||||
console.log('ROM and key files found - yay!');
|
||||
|
||||
var romData = new Int8Array(romReq.response.slice(11));
|
||||
var keyData = new Int8Array(keyReq.response);
|
||||
var keyLength = keyData.length;
|
||||
for (var i = 0; i < romData.length; ++i) {
|
||||
romData[i] ^= keyData[i % keyLength];
|
||||
}
|
||||
romBlob = new Blob([romData], {type: 'application/octet-binary'});
|
||||
callback(romBlob);
|
||||
$('afe_message').innerHTML =
|
||||
'Amiga Forever Essentials detected – enjoy!';
|
||||
} else {
|
||||
console.log('Amiga Forever Essentials key file not found.');
|
||||
}
|
||||
}, false);
|
||||
keyReq.open('GET', amiga13KeyFile, true);
|
||||
keyReq.send(null);
|
||||
} else {
|
||||
console.log('Amiga Forever Essentials ROM file not found.');
|
||||
}
|
||||
}, false);
|
||||
romReq.open("GET", amiga13RomFile, true);
|
||||
romReq.send(null);
|
||||
} else {
|
||||
console.log('Amiga Forever Essentials not found.');
|
||||
}
|
||||
}, false);
|
||||
|
||||
xhr.open("GET", afeIndex, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
|
||||
// Start-up tasks on document loading
|
||||
if (!mimeHandlerExists('application/x-pnacl')) {
|
||||
updateStatus('This page uses <a href=' +
|
||||
'"https://developers.google.com/native-client/pnacl-preview/">Portable ' +
|
||||
'Native Client</a>, a technology currently only supported in Google ' +
|
||||
'Chrome (version 31 or higher; Android and iOS not yet supported).');
|
||||
document.getElementById('progress')
|
||||
.style.visibility='hidden';
|
||||
} else {
|
||||
// The callback is called if and only if the Amiga Forever Essentials files
|
||||
// are found.
|
||||
checkForAmigaForeverEssentials(function(romBlob) {
|
||||
var loadAmigaForeverEssentialsROMAndKey = function() {
|
||||
setTimeout(function() {
|
||||
loadFromLocal('rom', romBlob);
|
||||
}, 3000 /* Work-around for a bug in UAE that causes it too crash if GUI
|
||||
messages are received too quickly after start-up. */);
|
||||
};
|
||||
|
||||
if (moduleSuccessfullyLoaded) {
|
||||
// In the unlikely event that the module loaded before the Amiga Forever
|
||||
// Essentials check completed we post the message directly.
|
||||
loadAmigaForeverEssentialsROMAndKey();
|
||||
} else {
|
||||
// If module hasn't loaded yet, we queue up the function to be
|
||||
// invoked on successful module load.
|
||||
moduleInitTasks.push(loadAmigaForeverEssentialsROMAndKey);
|
||||
}
|
||||
});
|
||||
|
||||
var container = document.getElementById('uae_container');
|
||||
|
||||
container.addEventListener('loadstart', moduleDidStartLoad, true);
|
||||
container.addEventListener('progress', moduleLoadProgress, true);
|
||||
container.addEventListener('error', moduleLoadError, true);
|
||||
container.addEventListener('abort', moduleLoadAbort, true);
|
||||
container.addEventListener('load', moduleDidLoad, true);
|
||||
container.addEventListener('loadend', moduleDidEndLoad, true);
|
||||
container.addEventListener('crash', moduleCrashed, true);
|
||||
container.addEventListener('message', handleMessage, true);
|
||||
|
||||
var moduleElement = document.createElement('embed');
|
||||
// Any attributes are passed as arguments to UAEInstance::Init().
|
||||
moduleElement.setAttribute('name', 'uae');
|
||||
moduleElement.setAttribute('id', 'uae');
|
||||
moduleElement.setAttribute('width', 720);
|
||||
moduleElement.setAttribute('height', 568);
|
||||
moduleElement.setAttribute('src', 'uae.nmf');
|
||||
moduleElement.setAttribute('type', 'application/x-pnacl');
|
||||
|
||||
var containerDiv = document.getElementById('uae_container');
|
||||
containerDiv.appendChild(moduleElement);
|
||||
|
||||
}
|
9
src/gui-html/uae.nmf
Normal file
9
src/gui-html/uae.nmf
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"program": {
|
||||
"portable": {
|
||||
"pnacl-translate": {
|
||||
"url": "uae.pexe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
279
src/gui-html/uae_faq.html
Normal file
279
src/gui-html/uae_faq.html
Normal file
@ -0,0 +1,279 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Amiga 500 Emulator: Frequently Asked Questions</title>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="-1" />
|
||||
<meta name="Description" content="FAQ for the PNaCl-based Amiga
|
||||
Emulator: an Amiga 500 emulator that works directly in your
|
||||
browser. This page has instructions on how to use the emulator,
|
||||
technical background, and details on the technology and the source
|
||||
code.">
|
||||
<link href="http://fonts.googleapis.com/css?family=Belleza"
|
||||
rel="stylesheet" type="text/css">
|
||||
<link href="uae.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body id="body">
|
||||
<div id="one_column">
|
||||
<h1>Amiga 500 Emulator</h1>
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
|
||||
<h3>About the Amiga emulator</h3>
|
||||
|
||||
<h4>What is this?</h4>
|
||||
<div class="image-container">
|
||||
<img class="flow-with-text" src="img/amiga500_128x128.png"
|
||||
alt="The Amiga 500 – the most awesome computer ever made" />
|
||||
<p>The Amiga 500 – the most awesome computer ever made.
|
||||
(Photo copyright: Bill Bertram 2006 CC-BY-2.5)</p>
|
||||
</div>
|
||||
|
||||
<p>The Commodore <a
|
||||
href="http://en.wikipedia.org/wiki/Amiga_500">Amiga 500</a> was a
|
||||
home computer that had its heyday in the late 1980s and early 90s.
|
||||
For a while, at least in Europe, it was <i>the</i> computer for games
|
||||
and for demos – and for a whole generation of programmers who
|
||||
cut their teeth programming Motorola 680x0 assembler, AmigaBASIC, and
|
||||
C.</p>
|
||||
|
||||
<p>This website runs an emulator of the Amiga 500 inside of Chrome by
|
||||
using Portable Native Client, a way to run existing C/C++ in the
|
||||
browser in a safe way across operating systems and across machine
|
||||
architectures. On the main page you can boot the Amiga, insert floppy
|
||||
disks, play the games, and generally pretend it's still the late
|
||||
80s. (We recommend some Enigma music or the soundtrack from the movie
|
||||
Top Gun in the background.)</p>
|
||||
|
||||
<p>The <a href="http://github.com/cstefansen/PUAE">code</a> was forked from PUAE, the Portable Universal Amiga
|
||||
Emulator, from GnoStiC's GitHub <a
|
||||
href="https://github.com/GnoStiC/PUAE">repo</a>. A huge thank you to
|
||||
Bernd Schmidt, Toni Wilen, Mathias Ortmann, Richard Drummond, Mustafa
|
||||
Tufan (GnoStiC), and the many other developers who have contributed to
|
||||
the various generations of UAE over time. You guys rock!</p>
|
||||
|
||||
|
||||
<h3>Using the emulator</h3>
|
||||
|
||||
<a id="get_roms"></a><h4>Why do I need Amiga Forever Essentials to use
|
||||
my own floppy disks?</h4>
|
||||
<p>Cloanto has kindly provided the Amiga Kickstart ROM for use with
|
||||
the Amiga Workbench 1.3 and the First Demos disks in this demo. To use
|
||||
other disks with the emulator, users can obtain a Kickstart ROM
|
||||
license by getting the <a
|
||||
href="https://chrome.google.com/webstore/detail/amiga-forever-essentials/aemohkgcfhdkjpoonndfpaikgbpgcogm">Amiga
|
||||
Forever Essentials</a> bundle for Chrome. If you are looking for a
|
||||
desktop Amiga emulator for Windows, check out <a
|
||||
href="http://www.amigaforever.com/">Amiga Forever</a>.</p>
|
||||
|
||||
<h4>How can I use the games and apps I still own?</h4>
|
||||
<p>If you already have the disks as .adf files, just get a kickstart
|
||||
ROM license (see the previous question), and you should be good to
|
||||
go. If not, well, then it's complicated. Maybe try <a
|
||||
href="https://www.google.com/search?q=create+adf+file+from+disk">this
|
||||
search</a> on Google.</p>
|
||||
|
||||
<h4>What machines are supported?</h4>
|
||||
<p>All Chrome desktop devices, that is, any somewhat recent device
|
||||
running Mac OS, Microsoft Windows, Linux, or Chrome OS.</p>
|
||||
|
||||
<h4>Why do the cursor keys and the space bar not work?</h4>
|
||||
<p>They do, but they are probably assigned to a joystick port. When
|
||||
they are assigned to a joystick port, they will not work as normal
|
||||
keys.</p>
|
||||
|
||||
<h4>I can't use the escape key. It makes Chrome leave full-screen mode. And
|
||||
where is the Help key and the Amiga keys?</h4>
|
||||
<p>By design there is no way to disable Chrome's reservation of the
|
||||
escape key. Here are some substitute key mappings to use inside the
|
||||
Amiga:
|
||||
<ul>
|
||||
<li>F11 → Help
|
||||
<li>F12 → Esc
|
||||
<li>Home → Left Amiga
|
||||
<li>End → Right Amiga
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h4>Why does my favorite key in the whole world not work in the Amiga?</h4>
|
||||
|
||||
<p>JavaScript does not get notifications for all keys that the Amiga
|
||||
knows about. This being a demo, we didn't include a "send special key
|
||||
to emulator" feature except from those mention above.</p>
|
||||
|
||||
<h4>Why don't you support more different Amiga configurations?</h4>
|
||||
<p>We could potentially. File a request (see below).</p>
|
||||
|
||||
<h4>Aargh, why can't I change from PAL (50 Hz) to NTSC (60 Hz)?</h4>
|
||||
<p>Sorries, that feature didn't make the cut in this version. If
|
||||
enough people want to use NTSC content, I'll add that feature. The
|
||||
emulator was set up in PAL mode, as that's the mode that the largest
|
||||
amount of Amiga content was built for.</p>
|
||||
|
||||
<h4>Why does full-screen mode have a big black border? Why not
|
||||
stretch to fill the screen?</h4>
|
||||
<p>If full-screen mode is not actually filling up the entire screen,
|
||||
it's because Chrome does not support hardware accelerated graphics on
|
||||
your machine (or with your current driver).</p>
|
||||
|
||||
<h4>Where is my favorite UAE or Amiga feature?</h4>
|
||||
<p>This port doesn't have all the features that the Universal Amiga
|
||||
Emulator has. For some, such as saving games and connecting
|
||||
cartridges, they weren't deemed important for the demo; for others
|
||||
such as mounting local file systems they don't work in a web context.
|
||||
If you want a full-featured Amiga emulator, look as FS-UAE (free) or
|
||||
Amiga Forever.</p>
|
||||
|
||||
|
||||
<h3>The code, the source, the technology</h3>
|
||||
|
||||
<h4>Where can I find the source?</h4>
|
||||
<p><a href="http://github.com/cstefansen/PUAE">Source</a>.</p>
|
||||
|
||||
<h4>What exactly is Portable Native Client (PNaCl)?</h4>
|
||||
<p>See the official Portable Native Client developer
|
||||
<a href="https://developers.google.com/native-client/pnacl-preview/overview#how-native-client-works">documentation</a>.</p>
|
||||
|
||||
<h4>Can I use the AROS ROM with this demo? Why is it not compiled in?</h4>
|
||||
<p>It doesn't work terribly well, but we were able to boot a few
|
||||
titles with the AROS ROM. YMMV. It is not enabled in the demo because
|
||||
it wasn't working reliably enough with the titles we tried.</p>
|
||||
|
||||
<h4>Why did you base it on PUAE? Why not FS-UAE, WinUAE or something else?</h4>
|
||||
<p>PUAE's code base seemed the most accessible one to me at the time.</p>
|
||||
|
||||
<h4>Could this be made to work in other browsers?</h4>
|
||||
<p>Probably. You could try using Emscripten/<a
|
||||
href="http://asmjs.org/spec/latest/">asm.js</a>. Since the Portable
|
||||
Native Client tools spit out LLVM bitcode, it should be possible to
|
||||
generate asm.js-compliant JavaScript code from that. One challenge is
|
||||
that the Amiga emulator is currently multi-threaded, but it can
|
||||
probably be made single-threaded.</p>
|
||||
|
||||
<h4>How long did it take to port?</h4>
|
||||
<p>The emulator is something like 400,000 lines of C code. The
|
||||
original port to Native Client was done in four days. However, there
|
||||
was a lot of polishing afterwards, taking at least four times as long
|
||||
as the original port.
|
||||
|
||||
<h4>Why not use SDL?</h4>
|
||||
<p>Good point. In fact, an <a href="http://www.libsdl.org/">SDL</a>
|
||||
port for Native Client does exist in the nacl_ports <a
|
||||
href="https://code.google.com/p/naclports/">repository</a>. At the
|
||||
time when the bulk of the porting work was being done, however, the
|
||||
NaCl SDL port didn't have performant support for <i>both</i>
|
||||
Graphics2D and Graphics3D. And, yes, I wanted an excuse to
|
||||
familiarize myself with the Pepper <a
|
||||
href="https://developers.google.com/native-client/peppercpp/">interfaces</a>.</p>
|
||||
|
||||
<h4>Can it be optimzed to run faster?</h4>
|
||||
<p>Certainly. I've left suggestions here and there in the code, so
|
||||
check out the code and go to town! E.g., currently, rendering happens
|
||||
on the emulator thread; an obvious thing to try is to render on a
|
||||
separate thread.</p>
|
||||
|
||||
|
||||
<h3>Issues – known and unknown</h3>
|
||||
|
||||
<h4>Bummer, it's really slow on my machine (AKA I get choppy sound or
|
||||
janky graphics)</h4>
|
||||
<p>Here are some things to try:</p>
|
||||
<ul>
|
||||
<li>If it's a laptop, plug in the power.</li>
|
||||
<li>Try going full-screen. In some cases full-screen mode is faster
|
||||
because Chrome doesn't have to run the compositor.</li>
|
||||
<li>Check out about:gpu (type it in the address bar and hit
|
||||
enter). If it doesn't show that you have support for hardware
|
||||
accelerated graphics in Chrome, consider upgrading the driver for
|
||||
your graphics card. (Make sure you keep a backup of your existing
|
||||
driver. Only do this if you know how to revert to the previous
|
||||
version if something goes wrong.)</li>
|
||||
</ul>
|
||||
<p>In general, look at the line of indicators at the bottom of the
|
||||
Amiga. The third box contains the number of frames per second. This
|
||||
is a PAL-mode Amiga, so your sound will be choppy if it not able to
|
||||
hit 50 frames per second on average. On the other hand, even if you
|
||||
get 50 frames per seconds perfectly, it may still look slightly
|
||||
uneven. This is because a modern display runs 60 Hz, so approximately
|
||||
every fifth frame will be doubled.</p>
|
||||
<p>If you get stutter while choosing disks to insert, that's a known issues.
|
||||
We're working on it. </p>
|
||||
|
||||
<h4>Uh, it seems to run 60 FPS when no audio is playing?</h4>
|
||||
<p>Yes. We'll fix it if it turns out to be a problem, but it's just so
|
||||
nice to have smooth scrolling when it's possible.</p>
|
||||
|
||||
<h4>What's up with the gamepad support?</h4>
|
||||
<p>It sort of works. Use the dropdowns to select gamepad input,
|
||||
<i>then</i> press a few buttons on the gamepad to allow Chrome to
|
||||
connect it. Reset the Amiga if it doesn't work. And please report any
|
||||
issues.</p>
|
||||
|
||||
<h4>How do I report an issue?</h>
|
||||
<p>First check the <a
|
||||
href="https://code.google.com/p/chromium/issues/list?can=2&q=Cr%3APlatform-NaCl+Amiga&colspec=ID+Pri+M+Iteration+ReleaseBlock+Cr+Status+Owner+Summary+OS+Modified&x=m&y=releaseblock&cells=tiles">issue
|
||||
list</a> to see if it has already been reported. If it has, just add
|
||||
any new information you may have and star the issue to vote for
|
||||
it.</p>
|
||||
<p>If it hasn't already been reported use this <a
|
||||
href="https://code.google.com/p/chromium/issues/entry?template=NaCl%20Issue">form</a>.
|
||||
Always remember to include the five 'A's:</p>
|
||||
<ul>
|
||||
<li><b>a</b>bout:version</li>
|
||||
<li><b>a</b>bout:nacl</li>
|
||||
<li><b>a</b>bout:gpu</li>
|
||||
<li><b>A</b>ll output on the JavaScript console</li>
|
||||
<li><b>A</b>ll output seen on stdout</li>
|
||||
</ul>
|
||||
<p>Thanks!</p>
|
||||
|
||||
<h3>Miscellaneous</h3>
|
||||
|
||||
<h4>The small print</h4>
|
||||
|
||||
<p>The emulator code is GPL2 – check out the <a
|
||||
href="http://github.com/cstefansen/PUAE">source</a>. The Amiga ROM,
|
||||
OS, and First Demos files are provided under license by <a
|
||||
href="http://www.amigaforever.com/">Cloanto</a>. Amiga is a
|
||||
trademark of <a
|
||||
href="http://www.amiga.com/sales/index.php?p=brand">Amiga Inc.</a>
|
||||
The favicon is provided with permission by Cloanto.</p>
|
||||
|
||||
<h4>Who are you?</h4>
|
||||
<p>Oh, hai! I work for Google. I was a member of the Chrome team, and
|
||||
more specifically the Native Client team, from August 2010 to June
|
||||
2013. You can find <a
|
||||
href="https://google.com/+ChristianStefansen">me</a> on Google+</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p> <a href="uae.html">Home</a> <a
|
||||
href="uae_faq.html">FAQ</a> <a
|
||||
href="https://github.com/cstefansen/PUAE">Source code</a>
|
||||
</p>
|
||||
|
||||
<p>Ported by <a
|
||||
href="https://google.com/+ChristianStefansen">Christian
|
||||
Stefansen</a>. The Amiga ROM, OS, and First Demos files are provided
|
||||
under license by <a
|
||||
href="http://www.amigaforever.com/">Cloanto</a>.</p>
|
||||
|
||||
<p>The port is dedicated to Karsten, Mumm, Wulff, and K.P. in fond
|
||||
remembrance of our childhood and the many hours of playing Amiga 500
|
||||
games together.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-6408327-2', 'pnacl-amiga-emulator.appspot.com');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
7
src/jd-pepper/Makefile.am
Normal file
7
src/jd-pepper/Makefile.am
Normal file
@ -0,0 +1,7 @@
|
||||
AM_CPPFLAGS = @UAE_CPPFLAGS@
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/include -I$(top_builddir)/src -I$(top_srcdir)/src
|
||||
AM_CFLAGS = @UAE_CFLAGS@
|
||||
|
||||
noinst_LIBRARIES = libjoydep.a
|
||||
|
||||
libjoydep_a_SOURCES = joystick.c
|
201
src/jd-pepper/joystick.c
Normal file
201
src/jd-pepper/joystick.c
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Joystick support via the Pepper Gamepad API
|
||||
*
|
||||
* Copyright 1997 Bernd Schmidt
|
||||
* Copyright 2003-2005 Richard Drummond
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*/
|
||||
|
||||
#include <ppapi/c/ppb_gamepad.h>
|
||||
|
||||
#include "sysconfig.h"
|
||||
#include "sysdeps.h"
|
||||
|
||||
#include "options.h"
|
||||
#include "memory.h"
|
||||
#include "custom.h"
|
||||
#include "inputdevice.h"
|
||||
#include "writelog.h"
|
||||
|
||||
/* guidep == gui-html is currently the only way to build with Pepper. */
|
||||
#include "guidep/ppapi.h"
|
||||
|
||||
static PPB_Gamepad *ppb_gamepad_interface;
|
||||
static PP_Instance pp_instance;
|
||||
|
||||
static int get_joystick_num(void);
|
||||
|
||||
static int init_joysticks (void) {
|
||||
ppb_gamepad_interface = (PPB_Gamepad *)
|
||||
NaCl_GetInterface(PPB_GAMEPAD_INTERFACE);
|
||||
pp_instance = NaCl_GetInstance();
|
||||
|
||||
if (!ppb_gamepad_interface) {
|
||||
DEBUG_LOG("Could not acquire PPB_Gamepad interface.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!pp_instance) {
|
||||
DEBUG_LOG("Could not find current Pepper instance.\n");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void close_joysticks (void) {
|
||||
}
|
||||
|
||||
static int acquire_joystick (int num, int flags) {
|
||||
return num < get_joystick_num();
|
||||
}
|
||||
|
||||
static void unacquire_joystick (int num) {
|
||||
}
|
||||
|
||||
/* TODO(cstefansen): Figure out a proper autofire (see use of
|
||||
lastState below). */
|
||||
/* static lastState = 0; */
|
||||
static void read_joysticks (void)
|
||||
{
|
||||
/* TODO(cstefansen): Support third joystick button (what games use
|
||||
this?) */
|
||||
|
||||
/* Get current gamepad data. */
|
||||
struct PP_GamepadsSampleData gamepad_data;
|
||||
ppb_gamepad_interface->Sample(pp_instance, &gamepad_data);
|
||||
|
||||
/* Update state for each connected gamepad. */
|
||||
size_t p = 0;
|
||||
for (; p < gamepad_data.length; ++p) {
|
||||
struct PP_GamepadSampleData pad = gamepad_data.items[p];
|
||||
|
||||
if (!pad.connected)
|
||||
continue;
|
||||
|
||||
/* Update axes. */
|
||||
size_t i = 0;
|
||||
int axisState[2] = {0, 0};
|
||||
for (; i < pad.axes_length; ++i) {
|
||||
axisState[i % 2] += pad.axes[i] < 0.1f && pad.axes[i] > -0.1f ?
|
||||
0 : (int) (pad.axes[i] * 32767);
|
||||
}
|
||||
/* Buttons 12-15 are up/down/left/right of a small digital
|
||||
joystick on the typical gamepad. */
|
||||
if (pad.buttons_length >= 15) {
|
||||
axisState[0] += (int) ((-pad.buttons[14] + pad.buttons[15]) * 32767);
|
||||
axisState[1] += (int) ((-pad.buttons[12] + pad.buttons[13]) * 32767);
|
||||
}
|
||||
setjoystickstate(p, 0, axisState[0] > 32767 ? 32767 :
|
||||
axisState[0] < -32767 ? -32767 : axisState[0], 32767);
|
||||
setjoystickstate(p, 1, axisState[1] > 32767 ? 32767 :
|
||||
axisState[1] < -32767 ? -32767 : axisState[1], 32767);
|
||||
|
||||
/* Update buttons. */
|
||||
static const int buttons[16] =
|
||||
{0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1};
|
||||
size_t j = 0;
|
||||
int buttonState[2] = {0, 0};
|
||||
/* Buttons 12-15 are considered a joystick (i.e., for movement)
|
||||
and 16 and above are ignored. */
|
||||
for (; j < pad.buttons_length && j < 12; ++j) {
|
||||
buttonState[buttons[j]] |= pad.buttons[j] < 0.1f ? 0 : 1;
|
||||
}
|
||||
/* TODO(cstefansen): Figure out a proper autofire. */
|
||||
/*
|
||||
lastState = (lastState == 1 && buttonState[0] == 1) ? 0 : buttonState[0];
|
||||
setjoybuttonstate(p, 0, lastState);
|
||||
*/
|
||||
setjoybuttonstate(p, 0, buttonState[0]);
|
||||
setjoybuttonstate(p, 1, buttonState[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_joystick_num (void) {
|
||||
return 2;
|
||||
/* TODO(cstefansen): Avoid hardcoding to two joysticks. */
|
||||
/*
|
||||
struct PP_GamepadsSampleData gamepads_data;
|
||||
ppb_gamepad_interface->Sample(pp_instance, &gamepads_data);
|
||||
return gamepads_data.length;
|
||||
*/
|
||||
}
|
||||
|
||||
static char *get_joystick_friendlyname (int joy) {
|
||||
switch (joy) {
|
||||
case 0: return "Joystick 0";
|
||||
case 1: return "Joystick 1";
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_joystick_uniquename (int joy) {
|
||||
switch (joy) {
|
||||
case 0: return "JOY0";
|
||||
case 1: return "JOY1";
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_joystick_widget_num (int joy) {
|
||||
/* Just make all joysticks 2 axes and 2 buttons. */
|
||||
return 2 + 2;
|
||||
}
|
||||
|
||||
static int get_joystick_widget_type (int joy, int num, char *name,
|
||||
uae_u32 *code) {
|
||||
if (num < 2) return IDEV_WIDGET_BUTTON;
|
||||
if (num < 4) return IDEV_WIDGET_AXIS;
|
||||
return IDEV_WIDGET_NONE;
|
||||
}
|
||||
|
||||
static int get_joystick_widget_first (int joy, int type) {
|
||||
switch (type) {
|
||||
case IDEV_WIDGET_BUTTON:
|
||||
return 2;
|
||||
case IDEV_WIDGET_AXIS:
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_joystick_flags (int num) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct inputdevice_functions inputdevicefunc_joystick = {
|
||||
init_joysticks,
|
||||
close_joysticks,
|
||||
acquire_joystick,
|
||||
unacquire_joystick,
|
||||
read_joysticks,
|
||||
get_joystick_num,
|
||||
get_joystick_friendlyname,
|
||||
get_joystick_uniquename,
|
||||
get_joystick_widget_num,
|
||||
get_joystick_widget_type,
|
||||
get_joystick_widget_first,
|
||||
get_joystick_flags
|
||||
};
|
||||
|
||||
/* Set default inputdevice config for Pepper joysticks. */
|
||||
int input_get_default_joystick (struct uae_input_device *uid, int i,
|
||||
int port, int af, int mode) {
|
||||
if (i >= 2)
|
||||
return 0;
|
||||
|
||||
setid (uid, i, ID_AXIS_OFFSET + 0, 0, port,
|
||||
port ? INPUTEVENT_JOY2_HORIZ : INPUTEVENT_JOY1_HORIZ);
|
||||
setid (uid, i, ID_AXIS_OFFSET + 1, 0, port,
|
||||
port ? INPUTEVENT_JOY2_VERT : INPUTEVENT_JOY1_VERT);
|
||||
setid_af (uid, i, ID_BUTTON_OFFSET + 0, 0, port,
|
||||
port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON,
|
||||
af);
|
||||
setid (uid, i, ID_BUTTON_OFFSET + 1, 0, port,
|
||||
port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON);
|
||||
|
||||
return 1;
|
||||
|
||||
if (i == 0) return 1;
|
||||
return 0;
|
||||
}
|
9
src/sd-pepper/Makefile.am
Normal file
9
src/sd-pepper/Makefile.am
Normal file
@ -0,0 +1,9 @@
|
||||
AM_CPPFLAGS = @UAE_CPPFLAGS@
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/include -I$(top_builddir)/src -I$(top_srcdir)/src
|
||||
AM_CFLAGS = @UAE_CFLAGS@
|
||||
|
||||
noinst_LIBRARIES = libsnddep.a
|
||||
|
||||
libsnddep_a_SOURCES = sound.c
|
||||
|
||||
noinst_HEADERS = sound.h
|
266
src/sd-pepper/sound.c
Normal file
266
src/sd-pepper/sound.c
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper audio to be used in Native Client builds.
|
||||
*
|
||||
* Copyright 1997 Bernd Schmidt
|
||||
* Copyright 2003 Richard Drummond
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ppapi/c/ppb_audio.h"
|
||||
#include "ppapi/c/ppb_audio_config.h"
|
||||
|
||||
#include "sysconfig.h"
|
||||
#include "sysdeps.h"
|
||||
|
||||
#include "custom.h"
|
||||
#include "gui.h"
|
||||
#include "options.h"
|
||||
#include "gensound.h"
|
||||
#include "sounddep/sound.h"
|
||||
#include "threaddep/thread.h"
|
||||
#include "writelog.h"
|
||||
|
||||
/* guidep == gui-html is currently the only way to build with Pepper. */
|
||||
#include "guidep/ppapi.h"
|
||||
|
||||
#define min(a,b) \
|
||||
({ __typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a < _b ? _a : _b; })
|
||||
|
||||
static PPB_Audio *ppb_audio_interface;
|
||||
static PPB_AudioConfig *ppb_audio_config_interface;
|
||||
static PP_Resource audio_resource;
|
||||
static PP_Resource audio_config;
|
||||
static PP_Instance pp_instance;
|
||||
|
||||
int have_sound = 0;
|
||||
|
||||
uae_u16 *paula_sndbuffer;
|
||||
uae_u16 *paula_sndbuffer_front_buffer;
|
||||
uae_u16 *paula_sndbufpt;
|
||||
int paula_sndbufsize;
|
||||
|
||||
static uint32_t sample_rate_to_int(PP_AudioSampleRate sample_rate) {
|
||||
switch (sample_rate) {
|
||||
case PP_AUDIOSAMPLERATE_44100: return 44100;
|
||||
case PP_AUDIOSAMPLERATE_48000: return 48000;
|
||||
case PP_AUDIOSAMPLERATE_NONE:
|
||||
write_log("Audio sample rate unavailable (PP_AUDIOSAMPLERATE_NONE).\n");
|
||||
return 0;
|
||||
default:
|
||||
write_log("Unknown PP_AudioSampleRate enum %d.\n", sample_rate);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t audio_config_is_ok(PP_Resource audio_config) {
|
||||
return sample_rate_to_int(ppb_audio_config_interface->
|
||||
GetSampleRate(audio_config));
|
||||
}
|
||||
|
||||
static void adjust_prefs(PP_Resource audio_config) {
|
||||
currprefs.sound_freq =
|
||||
sample_rate_to_int(ppb_audio_config_interface->
|
||||
GetSampleRate(audio_config));
|
||||
currprefs.sound_latency =
|
||||
ppb_audio_config_interface->GetSampleFrameCount(audio_config) *
|
||||
1000 / currprefs.sound_freq;
|
||||
currprefs.sound_stereo = 1;
|
||||
}
|
||||
|
||||
|
||||
int setup_sound(void)
|
||||
{
|
||||
ppb_audio_interface = (PPB_Audio *) NaCl_GetInterface(PPB_AUDIO_INTERFACE);
|
||||
ppb_audio_config_interface = (PPB_AudioConfig *)
|
||||
NaCl_GetInterface(PPB_AUDIO_CONFIG_INTERFACE);
|
||||
pp_instance = NaCl_GetInstance();
|
||||
|
||||
if (!ppb_audio_interface) {
|
||||
write_log("Could not acquire PPB_Audio interface.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!ppb_audio_config_interface) {
|
||||
write_log("Could not acquire PPB_AudioConfig interface.\n");
|
||||
return 0;
|
||||
}
|
||||
if (!pp_instance) {
|
||||
write_log("Could not find current Pepper instance.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!init_sound()) return 0;
|
||||
close_sound();
|
||||
|
||||
write_log("Pepper audio successfully set up.\n");
|
||||
write_log("Frequency: %d\n", currprefs.sound_freq);
|
||||
write_log("Stereo : %d\n", currprefs.sound_stereo);
|
||||
write_log("Latency : %d\n", currprefs.sound_latency);
|
||||
|
||||
init_sound_table16();
|
||||
sample_handler = sample16s_handler;
|
||||
obtainedfreq = currprefs.sound_freq;
|
||||
have_sound = 1;
|
||||
sound_available = 1;
|
||||
update_sound (fake_vblank_hz, 1, currprefs.ntscmode);
|
||||
|
||||
return sound_available;
|
||||
}
|
||||
|
||||
static volatile int ready_to_swap = 1;
|
||||
|
||||
/* The renderer (Chrome) calls this function to refill the
|
||||
* audio buffer. We should never block here.
|
||||
*/
|
||||
static void sound_callback(void* samples,
|
||||
uint32_t buffer_size,
|
||||
void* data) {
|
||||
memcpy(samples, paula_sndbuffer_front_buffer,
|
||||
min(paula_sndbufsize, buffer_size));
|
||||
ready_to_swap = 1;
|
||||
}
|
||||
|
||||
void finish_sound_buffer (void)
|
||||
{
|
||||
if (currprefs.turbo_emulation)
|
||||
return;
|
||||
#ifdef DRIVESOUND
|
||||
driveclick_mix ((uae_s16 *) paula_sndbuffer, paula_sndbufsize / 2,
|
||||
currprefs.dfxclickchannelmask);
|
||||
#endif
|
||||
if (!have_sound)
|
||||
return;
|
||||
|
||||
if (gui_data.sndbuf_status == 3)
|
||||
gui_data.sndbuf_status = 0;
|
||||
|
||||
/* Doing a busy wait to stall the emulator if it's ahead. */
|
||||
/* TODO(cstefansen): Determine if audio busy wait can be avoided. Using
|
||||
* semaphores has been observed to degrade performance severely. */
|
||||
while (!ready_to_swap) { }
|
||||
ready_to_swap = 0;
|
||||
uae_u16 *temp = paula_sndbuffer;
|
||||
paula_sndbuffer = paula_sndbuffer_front_buffer;
|
||||
paula_sndbuffer_front_buffer = temp;
|
||||
}
|
||||
|
||||
int init_sound (void)
|
||||
{
|
||||
/* If setup_sound wasn't called or didn't complete successfully. */
|
||||
if (!ppb_audio_interface) {
|
||||
write_log("init_sound called, but audio not set up yet.\n");
|
||||
return 0;
|
||||
}
|
||||
/* If sound is set to none (0) or interrupts (1). */
|
||||
if (currprefs.produce_sound <= 1) {
|
||||
write_log("init_sound called, but UAE is configured not to produce"
|
||||
" sound.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
PP_AudioSampleRate sample_rate = ppb_audio_config_interface->
|
||||
RecommendSampleRate(pp_instance);
|
||||
uint32_t frame_count = ppb_audio_config_interface->
|
||||
RecommendSampleFrameCount(pp_instance,
|
||||
sample_rate,
|
||||
sample_rate * currprefs.sound_latency / 1000);
|
||||
|
||||
audio_config = ppb_audio_config_interface->
|
||||
CreateStereo16Bit(pp_instance,
|
||||
sample_rate,
|
||||
frame_count);
|
||||
if (!audio_config) {
|
||||
write_log("Could not create Pepper audio config.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Adjust preferences to reflect what the underlying system gave us. */
|
||||
if (!audio_config_is_ok(audio_config)) {
|
||||
write_log("Pepper AudioConfig failed.\n");
|
||||
return 0;
|
||||
}
|
||||
adjust_prefs(audio_config);
|
||||
|
||||
audio_resource = ppb_audio_interface->Create(
|
||||
pp_instance,
|
||||
audio_config,
|
||||
sound_callback,
|
||||
0 /* user_data */);
|
||||
if (!audio_resource) {
|
||||
write_log("Could not create a Pepper audio resource.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
paula_sndbufsize = frame_count * 2 /* stereo */ * 2 /* 16-bit */;
|
||||
paula_sndbufpt = paula_sndbuffer = malloc(paula_sndbufsize);
|
||||
paula_sndbuffer_front_buffer = malloc(paula_sndbufsize);
|
||||
memset(paula_sndbuffer_front_buffer, 0, paula_sndbufsize);
|
||||
|
||||
clear_sound_buffers();
|
||||
#ifdef DRIVESOUND
|
||||
driveclick_reset();
|
||||
#endif
|
||||
|
||||
if (!ppb_audio_interface->StartPlayback(audio_resource)) {
|
||||
write_log("Could not start Pepper audio playback.\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void close_sound (void) {
|
||||
ppb_audio_interface->StopPlayback(audio_resource);
|
||||
|
||||
/* TODO(cstefansen): Proper audio system shutdown. */
|
||||
}
|
||||
|
||||
void reset_sound (void) {
|
||||
clear_sound_buffers();
|
||||
}
|
||||
|
||||
void pause_sound (void) {
|
||||
if (!ppb_audio_interface->StopPlayback(audio_resource)) {
|
||||
write_log("Could not stop Pepper audio playback.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void resume_sound (void) {
|
||||
if (!ppb_audio_interface->StartPlayback(audio_resource)) {
|
||||
write_log("Could not start Pepper audio playback.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sound_volume (int dir) {
|
||||
DEBUG_LOG("Sound volume adjustment not implemented.\n");
|
||||
}
|
||||
|
||||
/* Handle audio specific cfgfile options. */
|
||||
void audio_default_options (struct uae_prefs *p)
|
||||
{
|
||||
}
|
||||
|
||||
void audio_save_options (FILE *f, const struct uae_prefs *p)
|
||||
{
|
||||
}
|
||||
|
||||
int audio_parse_option (struct uae_prefs *p, const char *option,
|
||||
const char *value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void master_sound_volume (int dir)
|
||||
{
|
||||
}
|
||||
|
||||
void sound_mute (int newmute)
|
||||
{
|
||||
}
|
||||
|
||||
void restart_sound_buffer (void)
|
||||
{
|
||||
}
|
||||
|
83
src/sd-pepper/sound.h
Normal file
83
src/sd-pepper/sound.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* UAE - The Un*x Amiga Emulator
|
||||
*
|
||||
* Pepper audio to be used in Native Client builds.
|
||||
*
|
||||
* Copyright 1997 Bernd Schmidt
|
||||
* Copyright 2013 Christian Stefansen
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef UAE_SD_PEPPER_SOUND_H
|
||||
#define UAE_SD_PEPPER_SOUND_H
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
#define SOUNDSTUFF 1
|
||||
#define AUDIO_NAME "pepper"
|
||||
|
||||
extern uae_u16 *paula_sndbuffer;
|
||||
extern uae_u16 *paula_sndbuffer_front_buffer;
|
||||
extern uae_u16 *paula_sndbufpt;
|
||||
extern int paula_sndbufsize;
|
||||
|
||||
extern void finish_sound_buffer (void);
|
||||
|
||||
STATIC_INLINE void check_sound_buffers(void) {
|
||||
if (currprefs.sound_stereo == SND_4CH_CLONEDSTEREO) {
|
||||
((uae_u16*)paula_sndbufpt)[0] = ((uae_u16*)paula_sndbufpt)[-2];
|
||||
((uae_u16*)paula_sndbufpt)[1] = ((uae_u16*)paula_sndbufpt)[-1];
|
||||
paula_sndbufpt = (uae_u16 *)(((uae_u8 *)paula_sndbufpt) + 2 * 2);
|
||||
} else if (currprefs.sound_stereo == SND_6CH_CLONEDSTEREO) {
|
||||
uae_s16 *p = ((uae_s16*)paula_sndbufpt);
|
||||
uae_s32 sum;
|
||||
p[2] = p[-2];
|
||||
p[3] = p[-1];
|
||||
sum = (uae_s32)(p[-2]) + (uae_s32)(p[-1]) +
|
||||
(uae_s32)(p[2]) + (uae_s32)(p[3]);
|
||||
p[0] = sum / 8;
|
||||
p[1] = sum / 8;
|
||||
paula_sndbufpt = (uae_u16 *)(((uae_u8 *)paula_sndbufpt) + 4 * 2);
|
||||
}
|
||||
|
||||
if ((uae_u8*)paula_sndbufpt - (uae_u8*)paula_sndbuffer
|
||||
>= paula_sndbufsize) {
|
||||
finish_sound_buffer ();
|
||||
paula_sndbufpt = paula_sndbuffer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
STATIC_INLINE void set_sound_buffers(void) {
|
||||
}
|
||||
|
||||
STATIC_INLINE void clear_sound_buffers (void) {
|
||||
memset(paula_sndbuffer_front_buffer, 0, paula_sndbufsize);
|
||||
memset(paula_sndbuffer, 0, paula_sndbufsize);
|
||||
paula_sndbufpt = paula_sndbuffer;
|
||||
}
|
||||
|
||||
#define PUT_SOUND_WORD(b) do { *(uae_u16 *)paula_sndbufpt = b; paula_sndbufpt = (uae_u16 *)(((uae_u8 *)paula_sndbufpt) + 2); } while (0)
|
||||
#define PUT_SOUND_WORD_LEFT(b) do { if (currprefs.sound_filter) b = filter (b, &sound_filter_state[0]); PUT_SOUND_WORD(b); } while (0)
|
||||
#define PUT_SOUND_WORD_RIGHT(b) do { if (currprefs.sound_filter) b = filter (b, &sound_filter_state[1]); PUT_SOUND_WORD(b); } while (0)
|
||||
#define PUT_SOUND_WORD_LEFT2(b) do { if (currprefs.sound_filter) b = filter (b, &sound_filter_state[2]); PUT_SOUND_WORD(b); } while (0)
|
||||
#define PUT_SOUND_WORD_RIGHT2(b) do { if (currprefs.sound_filter) b = filter (b, &sound_filter_state[3]); PUT_SOUND_WORD(b); } while (0)
|
||||
|
||||
#define PUT_SOUND_WORD_MONO(b) PUT_SOUND_WORD_LEFT(b)
|
||||
#define SOUND16_BASE_VAL 0
|
||||
#define SOUND8_BASE_VAL 0
|
||||
|
||||
#define DEFAULT_SOUND_MAXB 16384
|
||||
#define DEFAULT_SOUND_MINB 16384
|
||||
#define DEFAULT_SOUND_BITS 16
|
||||
#define DEFAULT_SOUND_FREQ 44100
|
||||
#define HAVE_STEREO_SUPPORT
|
||||
|
||||
#define FILTER_SOUND_OFF 0
|
||||
#define FILTER_SOUND_EMUL 1
|
||||
#define FILTER_SOUND_ON 2
|
||||
|
||||
#define FILTER_SOUND_TYPE_A500 0
|
||||
#define FILTER_SOUND_TYPE_A1200 1
|
||||
|
||||
#endif /* UAE_SD_PEPPER_SOUND_H */
|
Loading…
Reference in New Issue
Block a user