This commit is contained in:
Mustafa Tufan 2014-02-06 02:05:25 +02:00
parent 1574c898ee
commit a988ca0541
28 changed files with 3813 additions and 0 deletions

240
build_native_client.sh Executable file
View 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

View 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

View 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;
}

View 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
View 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
View 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
View 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

View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

362
src/gui-html/ppapi.cc Normal file
View 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
View 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
View 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
View 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">&nbsp;</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 &ndash; 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> &nbsp;
<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 (&#8592;&#8594;&#8595;&#8593; 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> &nbsp;&nbsp; <a
href="uae_faq.html">FAQ</a> &nbsp;&nbsp; <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
View 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 &ndash; 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
View File

@ -0,0 +1,9 @@
{
"program": {
"portable": {
"pnacl-translate": {
"url": "uae.pexe"
}
}
}
}

279
src/gui-html/uae_faq.html Normal file
View 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 &ndash; the most awesome computer ever made" />
<p>The Amiga 500 &ndash; 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 &ndash; 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 &rarr; Help
<li>F12 &rarr; Esc
<li>Home &rarr; Left Amiga
<li>End &rarr; 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 &ndash; 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 &ndash; 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> &nbsp;&nbsp; <a
href="uae_faq.html">FAQ</a> &nbsp;&nbsp; <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>

View 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
View 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;
}

View 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
View 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
View 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 */