-
Notifications
You must be signed in to change notification settings - Fork 35
Compiling OpenGL games with the Flash C Compiler (FlasCC)
In this article I show how to use the Flash C++ Compiler (FlasCC) to port the popular open source OpenGL game Neverball to the Flash runtime with minimal modification of the codebase or build system. Flascc is the new Adobe compiler technology that lets you compile C/C++ code into the SWF file format that can be run within Flash Player.
Flash Player 11 introduced the Stage3D API, which provides access to GPU accelerated 3D graphics with programmable shaders. The API was designed to run well on modern mobile devices and desktop computers. The similarities between the Stage3D and OpenGL ES APIs and features are intentional; Adobe wanted the Stage3D API to be able to take direct advantage of modern hardware features without any need for expensive translation or emulation.
OpenGL ES was designed to be a trimmed down version of the full OpenGL spec without some of the legacy features for which modern programmable GPUs no longer provide fast paths. In order to provide OpenGL support for Flash, Adobe has started an open source project called GLS3D (OpenGL for Stage3D) that implements a subset of the OpenGL API on top of the Stage3D API so that OpenGL games can be ported to the Flash runtime without modification.
You should have previously read some of the flascc documentation and successfully compiled at least a few of the samples. The flascc SDK comes with full documentation explaining how to install and use the SDK on Mac OS and Windows. It also comes with many samples showing the different features of the compiler and some examples of compiling real world codebases (including Box2D, Quake 1, Lua, and the Bullet physics library) with flascc.
Before getting started, comparing the available GPU APIs will help you get an idea of how easy it will be to port OpenGL and OpenGL ES codebases to the Flash runtime. Table 1 describes in high-level terms the different functionality available via the three APIs. Most of the OpenGL functionality is hardware dependent and some functionality comes via OpenGL extensions, so not all of the features listed here are strictly required to be available or accelerated to claim OpenGL conformance. Stage3D is different in that it provides a smaller feature set but guarantees that it will either all be available or unavailable on a given device. Regardless, this table gives some indication of the kinds of functional mismatches you could run into when porting an arbitrary OpenGL or OpenGL ES codebase to Flash Player and Stage3D using the Stage3D baseline profile.
Feature | OpenGL | OpenGL ES | Stage3D |
---|---|---|---|
Shader Language | None/GLSL (source) | GLSL (source) | AGAL (bytecode) |
Immediate Mode | Yes | No | No |
Primitive Types | POINTS, LINES, LINE_STRIP, LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN, QUADS, POLYGON | POINTS, LINES, LINE_STRIP, LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN | TRIANGLES |
Texture Size | Hardware dependent, non-power-of-two (NPOT) | Hardware dependent, NPOT | 2048px POT |
Texture Formats | Numerous | Numerous | BGRA 32bit, (and hardware dependent: DXT1/5, PVRTC, ETC1) |
Flash Player 11.4 supports a new constrained profile that lowers the limits on shader length and texture lookups and places some limitations on how the Stage3D context can be composited. This enables Stage3D to be used on systems with much less powerful GPUs. For more information on the constrained profile, see BASELINE_CONSTRAINED in the Context3DProfile documentation.
GLS3D (OpenGL for Stage3D) is an open source implementation of a subset of the OpenGL API that uses flascc interoperability support to implement the OpenGL API on top of the Stage3D API. At a high level this means that it provides a set of normal OpenGL headers that can be included by your OpenGL codebase and a static library that can be linked into your application to satisfy the OpenGL dependency. When your application calls an OpenGL API, GLS3D invokes the Stage3D API to achieve the same functionality.
In cases where the functionality of OpenGL and Stage3D are similar, the translation is obvious. Here is an example of how glClear is implemented:
// LibGL.cpp
extern void glClear (GLbitfield mask)
{
// using flascc's inline asm support to call into ActionScript
inline_as3("import GLS3D.GLAPI;\n"\
"GLAPI.instance.glClear(%0);\n" : : "r"(mask));
}
// LibGL.as
public function glClear(mask:uint):void
{
contextClearMask = 0
if (mask & GL_COLOR_BUFFER_BIT) contextClearMask |= Context3DClearMask.COLOR
if (mask & GL_STENCIL_BUFFER_BIT) contextClearMask |= Context3DClearMask.STENCIL
if (mask & GL_DEPTH_BUFFER_BIT) contextClearMask |= Context3DClearMask.DEPTH
// context is the current Stage3D context object
context.clear(contextClearR, contextClearG, contextClearB, contextClearA, contextClearDepth, contextClearStencil, contextClearMask)
}
For this particular API the translation is straightforward. The Stage3D API equivalent takes more arguments to specify the various clear values than OpenGL, which relies more on internal state. Each of the possible clearable buffers in OpenGL has an equivalent in Stage3D, so all that needs to be done is to translate the bitfield values from their GL value to the Stage3D equivalent.
Other parts of OpenGL require more complex handling. For example, supporting GL_QUADS requires first transforming vertex and index buffers into the equivalent of GL_TRIANGLES, which Stage3D can handle natively. A more detailed look at the internals of GLS3D is beyond the scope of this tutorial but it is important to know roughly how it works, as you might need to improve or implement additional features to support your OpenGL application.
Assuming you already have your development machine set up to use flascc, the first thing to do is download Neverball. This tutorial was written using Neverball version 1.5.4, which can be downloaded from here.
Like most complex software projects, Neverball is not self-contained; it depends on several other libraries: SDL, SDL_ttf, SDL_image, SDL_mixer, freetype, jpeg, png, and zlib. Most of these libraries are self-contained and will compile with flascc without any further work necessary. However SDL itself is a platform abstraction layer used to abstract graphics, sound, and input handling. To do this SDL has platform specific code for each of the platforms it supports (Mac, Windows, Linux, and so on).
To help facilitate easy ports of SDL applications to Flash runtimes, the flascc SDK includes a version of SDL with a Flash platform layer. This is used by the Quake 1 example in the flascc SDK for input handling and displaying the back buffer. This tutorial uses it for the same purpose to compile Neverball.
All of the other dependencies need to be compiled, but to save you some time I have made precompiled versions available in the alcextra GitHub repository. After you have downloaded this repository either with git or as a zip file from the GitHub website you should see all of the relevant headers and static libraries in the install directory.
Before you can compile the codebase you need to make a small change to the game’s run loop and also implement some Flash specific code to deal with mouse and keyboard input, file system access, and display handling. When you tackle any port with flascc, you’ll typically follow these steps:
1.Break the run loop
2.Implement the PreLoader Class
3.Implement the Console Class
4.Add zip files to the flascc VFS
5.Initialize the Stage3D context
6.Create the GLS3D instance
7.Disable the standard Flash right-click menu
8.Implement keyboard handling
9.Implement mouse handling
10.Invoke Main
11.Invoke the run loop
12.Compile as a SWF
Breaking the run loop
Like most games, Neverball uses a run loop inside the main() function that drives all of the game logic while polling for input events. The Flash runtime works in a slightly different way and requires that any user code return periodically so that the runtime can update the display. This means that you must make one small modification to Neverball so that the code that would normally be run within the run loop can be run by an enterFrame event. This achieves the same end result as a run loop without blocking the main Flash Player thread.
Here is the transformation:
// Original code, with run loop
int main() {
// Initialize game state
while(true) {
// execute game logic
}
}
// Modified code with broken run loop
int main() {
// Initialize game state
AS3_GoAsync();
}
void executeGameLogic() {
// execute game logic
}
When compiled with flascc, the modified version will initialize the game as normal but then the main function will be interrupted by AS3_GoAsync()
in a special way so that static destructors are not run.
In the file neverball-1.5.4/ball/main.c
on line 448 you will see a while loop; this is the main run loop that you need to break. Before this loop place the AS3_GoAsync()
macro inside an ifdef so that it only gets compiled when using the flascc compiler. Then copy the contents of the while loop into a new function named mainLoopTick()
, which will be called on every enterFrame. The sample files for this tutorial include is a modified copy of main.c (named ball_main.c) that you can compare against the unmodified version.
Implementing the PreLoader class The compiled Neverball SWF file will be about 1.5MB and the data files compressed into zip files account for another 10 to 50MB, depending on how many Neverball levels you choose to include. Downloading the SWF and data files without showing any indication of progress would provide a poor user experience so you will use a preloader to display a progress bar to indicate download progress.
Because this is such a common need, flascc injects a simple preloader into every SWF it creates by default. This preloader uses the loaderInfo object to register a progressEvent handler that listens for progress events coming from the loading of the main SWF. Because this code is injected into the SWF at the very beginning, it displays the progress bar after only a few kilobytes of the SWF have loaded.
The code for the default PreLoader class implementation can be found in sdk/usr/share/DefaultPreLoader.as. Using this as a starting point I have modified the PreLoader class so that it downloads three separate zip files in parallel before executing the real contents of the SWF. Splitting large data files into multiple smaller files can improve download speed. It also ensures that the files will be cached in the web browser’s cache. Different browsers have different limits but under 10MB is usually a good size to ensure the files will remain cached.
In SWF terminology, the preloader will be the first frame of the SWF and the rest of the flascc compiled code will be the second frame. So in the onPreloaderComplete event handler you will see the call to gotoAndStop(2), which indicates to Flash Player that it should start executing the code contained in the second frame. If this call was made before the SWF was finished loading, nothing on the screen would update and so no visual indication of progress would be made.
After moving to the second frame, you can call a function that will be defined in the flascc application to hand over the zip files that have been downloaded. As you will see in the section below on implementing the Console class, these zip files will be added to the flascc virtual file system (VFS). After supplying the zip files, the last thing to do is construct an instance of the Console class, which will in turn start executing the flascc application code.
The sample files for this tutorial include PreLoader.as, which contains a working implementation of the PreLoader class described in this section. You should compare it to the default implementation from the SDK to see exactly how it was modified.
Implementing the Console class Every Flash SWF has a root sprite, which is a class that derives from flash.display.Sprite and is constructed and placed on the Stage as soon as the SWF is run. This is the entry point for a SWF, and a flascc SWF is no exception. Flascc will embed a default root Sprite implementation (known as the Console class), which simply calls main() in your C/C++ code and directs printf output to the screen in a TextField.
The implementation for this default Console class is included in the flascc SDK in sdk/usr/share/Console.as
. It is a good starting point, but in most cases you will need to make a copy of this and modify it for your application.
You will need to modify this default Console implementation for Neverball to complete the following additional tasks:
- Attach the Neverball data zip files to the flascc Virtual File System
- Initialize a Stage3D context for GPU accelerated rendering
- Initialize GLS3D
- Optionally disable the standard Flash Player right-click menu
- Handle mouse and keyboard input and pass it to libVGL
- Invoke the broken run loop mainLoopTick function on an enterFrame event
Adding zip files to the flascc VFS
Flascc exposes a simple ActionScript API for creating different backing stores that can be attached to the VFS to handle reading and writing of data to different subdirectories. The most basic of these is InMemoryBackingStore, which simply stores each file as a ByteArray in memory and does not preserve the data between runs of the SWF. An implementation of a backing store that preserves files within a Flash Local Shared Object (LSO) demonstrates one possible way to achieve persistent data storage with a flascc application, but you might also want to implement a backing store that persists data by uploading it to a server.
For this tutorial the backing store does not persist any modified files, instead it uses an in-memory backing store that is populated with the contents of several zip files that are downloaded the first time the SWF is run.
In Console.as I have implemented a small class named ZipBackingStore, which extends the InMemoryBackingStore class and adds a new method named addZip that takes a ByteArray and treats the contents as a zip file. This zip file is decompressed using the zip decompression library that is shipped as part of flascc in the com.adobe.flascc.vfs.zip
package. As you can see, it is very simple to extract the contents of the zip file and provide the InMemoryBackingStore object with the right files and directories:
public function addZip(data:ByteArray) {
var zip = new ZipFile(data)
for (var i = 0; i < zip.entries.length; i++) {
var e = zip.entries[i]
if (e.isDirectory()) {
addDirectory("/root/data/"+e.name)
} else {
addFile("/root/data/"+e.name, zip.getInput(e))
}
}
}
I've also implemented a global function named addVFSZip, which will be called by the preloader once the zip files have been downloaded so that each zip file is added into a single ZipBackingStore object that can later be attached to the flascc VFS and used by the application.
Initialize the Stage3D context
Initializing a Stage3D context is simple; just attach an event listener onto the first Stage3D object in the stage3Ds vector and then call requestContext3D. Either a CONTEXT3D_CREATE event will be dispatched or, if a context cannot be created, an ERROR event will be dispatched.
var s3d:Stage3D = stage.stage3Ds[0];
s3d.addEventListener(Event.CONTEXT3D_CREATE, context_created);
s3d.requestContext3D(Context3DRenderMode.AUTO)
When you receive a CONTEXT3D_CREATE event, you can then access the Context3D object from the Stage3D object that owns this context. The Context3D object provides the interface through which you can create all of your GPU resources and draw 3D objects.
As a fallback, Stage3D can run in Software mode when GPU acceleration is disabled or unavailable for some reason. For some simple content this might be acceptable, but for Neverball, Software mode is too slow, so you will want to catch this case and prevent the application from running:
var ctx3d:Context3D = s3d.context3D
ctx3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, true /*enableDepthAndStencil*/ )
trace("Stage3D context: " + ctx3d.driverInfo);
if(ctx3d.driverInfo.indexOf("Software") != -1) {
trace("Software mode unsupported...");
return;
}
For security and privacy reasons the driverInfo string will only ever contain "DirectX", "OpenGL", or "Software" when running in the release version of Flash Player. When running in the debugger version of Flash Player it will provide more information about the exact version of the driver. When the software fallback is used the driverInfo string will also contain information about why that happened; more information can be found in the driverInfo documentation.
Creating the GLS3D instance
GLS3D has a very simple AS3 interface. Once the Context3D object has been acquired you simply construct a GLAPI object like this:
GLAPI.init(ctx3d, null, stage);
GLAPI.instance.context.clear(0.0, 0.0, 0.0);
GLAPI.instance.context.present();
The GLAPI instance manages the entire OpenGL state and is used by the OpenGL C wrapper to drive the Context3D instance. In your enterFrame handler you will need to call GLAPI.instance.context.present() to cause the current frame to be rendered.
Disable the standard Flash Player right-click menu
For a long time it has been possible to check if a web page element was made with Flash by right-clicking on it to see the standard Flash Player context menu appeared. For gaming this is inconvenient because many game makers want to use the right mouse button to perform actions.
Flash Player 11.2 introduced the ability to override this menu by registering an event handler for either the RIGHT_CLICK or RIGHT_MOUSE_DOWN events. In the console implementation for Neverball, I have implemented this in a way that will silently fail when the SWF is run in older versions of Flash Player. This technique is powerful as it allows you to deliver one game that supports the latest Flash Player features, but falls back to a different behavior when run in an older version of Flash Player.
try {
stage.addEventListener(MouseEvent.RIGHT_CLICK, rightClick);
// Success!
} catch(e:*) {
// Failure: we will still have a right-click menu
}
private function rightClick(e:MouseEvent):void
{
// no legacy right click menu! yay!
}
Keyboard handling
The version of libSDL included with flascc includes a back end that relies on libVGL for keyboard, mouse, and graphics handling. libVGL is a low level library and performs input handling by reading from a C file descriptor and interpreting the bytes it receives as keycodes. The implementation provided with flascc is only suitable for experimentation. If you want to deliver a Flash game based on libSDL you should implement custom input handling.
To ensure input handling works correctly you first add a sprite to the Stage, which ensures there is at least one object in the display list capable of receiving input events:
var inputContainer:Sprite = new Sprite()
addChild(inputContainer)
Then you register event handlers for the key up and key down events:
stage.addEventListener(KeyboardEvent.KEY_DOWN, bufferKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, bufferKeyUp);
When key events are received, buffer them into a ByteArray so that when the libSDL event loop is run it is able to pull out as many keyboard events as it needs:
public function bufferKeyDown(ke:KeyboardEvent)
{
if(Keyboard.capsLock || ke.keyCode >= 127)
return;
keybytes.writeByte(int(ke.keyCode & 0x7F));
}
public function bufferKeyUp(ke:KeyboardEvent)
{
if(Keyboard.capsLock || ke.keyCode > 128)
return;
keybytes.writeByte(int(ke.keyCode | 0x80));
}
One limitation with the current input handling mechanism is that libVGL uses single byte character codes. As a result, you are unable to pass Flash Player keycodes directly; instead you must restrict which keys can be used. For a real game you might want to expand this to two bytes so that Flash Player keycodes could be used directly, or perhaps use a different mechanism for queuing up the key events.
When Neverball's run loop is executed during the enterFrame event, libVGL attempts to perform a single byte read on file descriptor 0. Because the code assigned the Console class as the console for the VFS (CModule.vfs.console = this) all reads and writes to the stdin, stdout and stderr file descriptors are handled by this Console instance. So the call to read a single byte will need to be handled by your read implementation:
public function read(fd:int, bufPtr:int, nbyte:int, errnoPtr:int):int
{
if(fd == 0 && nbyte == 1) {
keybytes.position = kp++
if(keybytes.bytesAvailable) {
CModule.write8(bufPtr, keybytes.readUnsignedByte())
} else {
keybytes.position = 0
kp = 0
}
}
return 0
}
Mouse handling
Mouse handling with libVGL/libSDL is performed by updating the current position and button state every frame. libVGL defines several global integer variables vgl_cur_mx, vgl_cur_my, and vgl_cur_buttons. To set their values from ActionScript you must use the CModule.getPublicSymbol() function to look up the address of the variable and then use CModule.write32() to write a value to that address. Here is a simple example showing how to set the current libVGL mouse X position:
// libVGL mouse handling example
var mouseX:int = ... // get mouse x position from a mouseEvent
var vgl_mx:int = CModule.getPublicSymbol("vgl_cur_mx")
CModule.write32(vgl_mx, mouseX);
Invoking main
Like all C/C++ programs, the entry point is the main() function. To execute main() but leave it interrupted (to allow C++ static initialization to proceed without deinitialization) insert a call to AS3_GoAsync() inside the main function in the C code.
CModule.vfs.console = this
CModule.vfs.addBackingStore(zfs, null)
CModule.startAsync(this, new <String>["/root/neverball.swf"])
To start, first specify that this Console instance should be used to handle reads and writes to stdin, stdout, and stderr by assigning this to CModule.vfs.console. Then add the zip backing store that was previously populated with files to the VFS so that libc file IO can read and write all of the Neverball data files. Finally, call startAsync() passing it a valid value for "argv[0]" to specify the name of the executable (which Neverball uses to decide where in the file system its data files are located).
Once startAsync() returns, you know that the main() function has successfully been executed and interrupted by AS3_GoAsync(). Your flascc code is now in a valid state for you to call other functions and access global public variables in the C/C++ code.
Compiling Neverball as a SWF
Now that all of the pieces are in place you can compile the C code along with your ActionScript support classes. To save time, you can use the working makefile included with this tutorial’s sample files. The makefile can be used along with the other supporting files to compile Neverball into a working SWF.
1.First extract the contents of the Neverball source tarball into a directory named Neverball-1.5.4. From the command line use tar as follows:
tar -zxvf neverball-1.5.4.tar.gz
2.Now you can either modify main.c yourself to break the run loop or copy over the modified version included with the tutorial:
cp ball_main.c neverball-1.5.4/ball/main.c
3.To invoke the makefile you need to specify the location of your flascc SDK, GLS3D repository, and alcextra repository like so:
make FLASCC=/path/to/flascc/sdk ALCEXTRA=/path/to/alcextra GLS3D=/path/to/GLS3D
This can take up to 10 minutes the first time it is run.
Here are the steps performed by the makefile:
1.Make a build directory to hold the final SWF and data zip files.
2.Compile the Console class implementation using the ActionScript compiler shipped as part of the flascc SDK.
3.Compile the PreLoader class implementation using the ActionScript compiler shipped as part of the flascc SDK.
4.Invoke the sols build target in the Neverball source directory to generate all of the Neverball level data.
5.Compress everything in the data directory into a zip file. This file now includes the compiled Neverball level data, 6.which will be read at runtime by Neverball when loading a level.
7.Invoke the neverball.swf build target in the Neverball source directory to compile and link all of the C code into a SWF.
In the main Neverball makefile I override the PATH environment variable such that the bin directory in the flascc SDK comes first. This ensures that when a script invokes gcc or any other low level tool (such as ar or nm) the version from the flascc SDK will be the first one to be found and used.
Several other variables from the makefile are overridden so that the headers and archives for all of the dependent libraries come from either the flascc SDK or the alcextra repository.
The LDFLAGS variable is overridden with some flascc specific options that ensure GCC produces a SWF as its final output file and links in various ABC files such as the console implementation and one of the GLS3D support files. The Preloader to be used is also specified as a GCC command passed in the LDFLAGS variable.
More information on all of the flascc-specific GCC options can be found in the section on GCC in the flascc reference guide. You can also see brief descriptions by running gcc –-target-help.
Debugging the SWF
Flascc has full support for debugging C/C++ code compiled with flascc using the GDB debugger. To understand how this works, it will be helpful to trace through the execution of part of the Neverball renderer.
First you need to modify the makefile so that Neverball is compiled without optimization and with debugging symbols enabled. This can be done by switching the optimization flags used from "-O4" to "-O0 –g". The new rule to build Neverball in the makefile should look like this (note the change to the CFLAGS variable):
cd neverball-1.5.4 && PATH=$(FLASCC)/usr/bin:$(ALCEXTRA)/usr/bin:$(PATH) make \
DATADIR=data \
LDFLAGS="-L$(ALCEXTRA)/install/usr/lib/ $(FLASCC)/usr/lib/AlcVFSZip.abc -swf-preloader=$(BUILD)/neverball/PreLoader.swf -swf-version=17 -symbol-abc=$(BUILD)/neverball/Console.abc -jvmopt=-Xmx4G -emit-swf -swf-size=800x600 " \
CFLAGS="-O0 -g" \
CC="gcc" \
SDL_CPPFLAGS="$(shell $(FLASCC)/usr/bin/sdl-config --cflags) -I$(ALCEXTRA)/install/usr/include -I$(ALCEXTRA)/install/usr/include/SDL -I$(GLS3D)/install/usr/include" \
PNG_CPPFLAGS="$(shell $(FLASCC)/usr/bin/libpng-config --cflags)" \
SDL_LIBS="$(shell $(FLASCC)/usr/bin/sdl-config --libs)" \
PNG_LIBS="$(shell $(FLASCC)/usr/bin/libpng-config --libs) -lz" \
OGL_LIBS="$(GLS3D)/install/usr/lib/libGL.abc -L$(GLS3D)/install/usr/lib/ -lGL -lz -lfreetype -lvorbis -logg -lz" \
EXT=".swf" \
DEBUG=1 ENABLE_NLS=0 \
neverball.swf \
-j8
After you have built the SWF with debugging info you can launch it in GDB. First set an environment variable to tell GDB which Flash Player executable to use to run the SWF:
export FLASCC_GDB_RUNTIME=”/path/to/FlashPlayer”
Then launch GDB, passing it the SWF you want to debug:
/path/to/flascc/sdk/usr/bin/gdb ./build/neverball/neverball.swf
GNU gdb (GDB) 7.3
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin10 --target=avm2-elf".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
(gdb)
Now you can set a breakpoint on the main function and run the SWF:
(gdb) b main
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (main) pending.
(gdb) r
Starting program: ./build/neverball/neverball_debug.swf
0xdddddddd in ?? ()
Breakpoint 1, 0xf0028ae5 in main (argc=1, argv=0x200fd0) at main.c:384
384 SDL_Joystick *joy = NULL;
(gdb)
Now that Neverball is running in the debugger you can inspect any part of the code you want. Start by putting a breakpoint on the function you created to break the run loop (mainLoopTick):
(mainLoopTick):
(gdb) b mainLoopTick
Breakpoint 2 at 0xf0028cba: file main.c, line 523.
(gdb) c
Continuing.
Breakpoint 2, 0xf0028cbb in mainLoopTick () at main.c:523
523 loop();
(gdb)
Continuing a few times with the c command will cause Flash Player to render a few frames, and you should see this reflected in the Flash Player window.
While the debugger is stopped at the beginning of the run loop you can put another breakpoint on the code that Neverball uses to normalize vectors. It’s in a function named v_nrm in the file neverball-1.5.4/share/vec3.c
.
Using the bt command you can see the C call stack at the point where this function is being called:
(gdb) b v_nrm
Breakpoint 4 at 0xf005bca8
(gdb) c
Continuing.
Breakpoint 4, 0xf005bca9 in v_nrm () from remote:290.elf
(gdb) bt
#0 0xf005bca9 in v_nrm () from remote:290.elf
#1 0xf00173c1 in game_set_fly (k=7.70714155e-44, fp=0xc8) at game_server.c:987
#2 0xf00256bc in title_timer (id=18, dt=0.0111111114) at st_title.c:219
#3 0xf000e2ce in st_timer (dt=0.0111111114) at state.c:92
#4 0xf0028cf5 in mainLoopTick () at main.c:545
#5 0x00000000 in ?? ()
(gdb)
The C call stack ends with a mysterious stack frame with no symbol information: #5 0x00000000 in ?? (). This indicates that stack frames above this point are actually not C code, but ActionScript code. To see the full backtrace including ActionScript frames you need to use the special as3bt function supported by the version of GDB that ships with the flascc SDK:
(gdb) as3bt
(*)global/C_Run::F_v_nrm()[/var/folders/hm/ty_1kgz51ds69dgbm_rl7lzc0000gn/T/cckXQgoe.lto.1.as:51]
global/C_Run::F_game_set_fly()[/tmp/llvm_mkdtemp_cxGZor/alctmp-KjePUT.as:5160]
global/C_Run_ball_2F_st_title_2E_o_3A_5F17B85D_2D_D761_2D_4032_2D_9214_2D_D6D354CB2D9C::F_title_timer()[/tmp/llvm_mkdtemp_5IAAke/alctmp-5w3gQB.as:907]
global/C_Run::F_st_timer()[/tmp/llvm_mkdtemp_57bznU/alctmp-mAlwog.as:445]
global/C_Run::F_mainLoopTick()[/tmp/llvm_mkdtemp_z4xDDh/alctmp-5GTYqI.as:2436]
com.adobe.flascc::CModule$/callI()
com.adobe.flascc::Console/enterFrame()
(gdb)
This is less easy to read because the C functions all have mangled names and are located in packages with very long compiler generated names. But you can see quite clearly at the bottom of the call stack where the enterFrame handler defined in Console.as is calling CModule.callI with mainLoopTick as its parameter. More information on the ActionScript and flascc specific commands available in the flascc version of GDB can be found in the GDB section in the flascc reference guide.
The version of GDB shipped with flascc is an enhanced version of the regular GDB you use on any native development platform and supports all of the same commands as well as a few additional flascc specific ones. You can switch between stack frames, print expressions, evaluate functions, and more:
(gdb) f 1
#1 0xf00173c1 in game_set_fly (k=7.70714155e-44, fp=0xc8) at game_server.c:987
987 v_nrm(view_e[0], view_e[0]);
(gdb) p view_e
$1 = {{4.43174553, 0, -3.24232864}, {0, 1, 0}, {3.24232864, -0, 4.43174553}}
(gdb) p *fp
$2 = {ac = 0, mc = 0, vc = 0, ec = 0, sc = 0, tc = 0, gc = 0, lc = 0, nc = 0, pc = 0, bc = 0, hc = 0, zc = 0, jc = 0, xc = 0, rc = 0, uc = 0,
wc = 0, dc = 0, ic = 0, av = 0x0, mv = 0x0, vv = 0x0, ev = 0x0, sv = 0x0, tv = 0x0, gv = 0x0, lv = 0x0, nv = 0x0, pv = 0x0, bv = 0x0, hv = 0x0,
zv = 0x0, jv = 0x0, xv = 0x0, rv = 0x0, uv = 0x0, wv = 0x0, dv = 0x0, iv = 0x0}
(gdb)
Where to go from here
In this tutorial I’ve shown just how easy it is to port existing OpenGL based apps to Flash Player thanks to flascc and GLS3D. You only had to write a very small amount of additional code and make one small tweak to the existing codebase. Maintaining a Flash Player build target in an active codebase should easy and unobtrusive thanks to flascc’s robust support for C and C++, its support for time saving tools like SWIG, and its full GDB based debugger.
Now that Neverball has a Console class, a Preloader class, and a VFS you should be able to run and play the game in Flash Player. The libSDL implementation that ships with flascc doesn't currently have fully working support for audio, but with a few small tweaks to the C code and the Console class it is easy enough to get audio working. Take a look at the code in my GitHub repository to see the changes needed to support audio.
Once the SWF is looking good running in Flash Player standalone you can easily get it embedded in a webpage. Using the SWFObject JavaScript library is the easiest way to embed a SWF in a way that lets you handle corner cases such as the user having the wrong version of Flash Player. For more information, see the SWFObject code and documentation.
Once the basic port is complete you can start improving it by taking advantage of advanced Flash Player features. Flascc applications have access to the entire Flash API so you are not limited in any way when it comes to taking advantage of features like full-screen support, local shared objects, RTMFP, hardware accelerated video, and much more. For a complete list of the new capabilities introduced in each version of the Flash Player see the Flash Player and Adobe AIR feature list.
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License.