Skip to content

Commit

Permalink
Add examples and README
Browse files Browse the repository at this point in the history
This is the first commit. Examples:

- Game Boy
- Game Boy + SDL 2
- SNES
- SNES + SDL 2
- GTK 3
- Java
- PHP Plus
- Text to Speech
  • Loading branch information
gabrielrcouto committed Oct 1, 2019
0 parents commit a999e48
Show file tree
Hide file tree
Showing 37 changed files with 4,993 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
*.bin
.DS_Store
*.dylib
*.gb
*.gbc
*.smc
27 changes: 27 additions & 0 deletions README.md
@@ -0,0 +1,27 @@
# Awesome PHP FFI

[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](http://gabrielrcouto.mit-license.org/)

This is a repository showing some use cases of [PHP FFI](https://wiki.php.net/rfc/ffi).

Examples in this repository:

- [Game Boy Emulator](gameboy)
- [Game Boy Emulator with SDL2 rendering](gameboy-sdl)
- [GTK3 window with a button](gtk3)
- [Calling a function from .class Java file](java-itaucripto)
- [Modifying the PHP Core to auto inject PHP Open Tag](php-plus)
- [Simple printf example](printf)
- [Creating window and rendering pixels with SDL2](sdl)
- [SNES Emulator](snes)
- [SNES Emulator with SDL2 rendering](snes-sdl)
- [text-to-speech using Mac library](speech)

Other repositories about PHP FFI:

- [DBus access via FFI and PHP 7.4 POC](https://github.com/paxal/php-dbus)
- [Driving webview using PHP 7.4 FFI](https://github.com/andyvanee/php-webview-ffi)
- [PHP wrapper for libsass using FFI](https://github.com/shyim/php-sass)
- [PHP TensorFlow Binding](https://github.com/dstogov/php-tensorflow)
- [FFIMe](https://github.com/ircmaxell/FFIMe)
- [A compiler. For PHP](https://github.com/ircmaxell/php-compiler)
9 changes: 9 additions & 0 deletions gameboy-sdl/README.md
@@ -0,0 +1,9 @@
# Game Boy Emulator + SDL2

![sameboy-sdl](https://user-images.githubusercontent.com/2197005/65930680-fab53280-e3dc-11e9-98bb-f02c6cd94368.gif)

This example uses the sameboy library for Game Boy emulation + SDL2 library for rendering.

You'll need to compile the [sameboy library](https://github.com/LIJI32/SameBoy) and change the `FFI_LIB` on `sameboy_libretro.h` file.

You'll need to install the [SDL2 library](https://www.libsdl.org/download-2.0.php) and change the `FFI_LIB` on `sdl.h` file.
181 changes: 181 additions & 0 deletions gameboy-sdl/run.php
@@ -0,0 +1,181 @@
<?php
const ROM = 'pokemon.gbc';

const RETRO_ENVIRONMENT_SET_PIXEL_FORMAT = 10;
const RETRO_ENVIRONMENT_GET_VARIABLE = 15;
const RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE = 17;
const RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE = 23;
const RETRO_ENVIRONMENT_GET_LOG_INTERFACE = 27;

const RETRO_DEVICE_ID_JOYPAD_B = 0;
const RETRO_DEVICE_ID_JOYPAD_Y = 1;
const RETRO_DEVICE_ID_JOYPAD_SELECT = 2;
const RETRO_DEVICE_ID_JOYPAD_START = 3;
const RETRO_DEVICE_ID_JOYPAD_UP = 4;
const RETRO_DEVICE_ID_JOYPAD_DOWN = 5;
const RETRO_DEVICE_ID_JOYPAD_LEFT = 6;
const RETRO_DEVICE_ID_JOYPAD_RIGHT = 7;
const RETRO_DEVICE_ID_JOYPAD_A = 8;
const RETRO_DEVICE_ID_JOYPAD_X = 9;
const RETRO_DEVICE_ID_JOYPAD_L = 10;
const RETRO_DEVICE_ID_JOYPAD_R = 11;
const RETRO_DEVICE_ID_JOYPAD_L2 = 12;
const RETRO_DEVICE_ID_JOYPAD_R2 = 13;
const RETRO_DEVICE_ID_JOYPAD_L3 = 14;
const RETRO_DEVICE_ID_JOYPAD_R3 = 15;

const KEYBOARD_MAPPING = [
RETRO_DEVICE_ID_JOYPAD_RIGHT => 'd',
RETRO_DEVICE_ID_JOYPAD_LEFT => 'a',
RETRO_DEVICE_ID_JOYPAD_UP => 'w',
RETRO_DEVICE_ID_JOYPAD_DOWN => 's',
RETRO_DEVICE_ID_JOYPAD_A => ',',
RETRO_DEVICE_ID_JOYPAD_B => '.',
RETRO_DEVICE_ID_JOYPAD_SELECT => 'n',
RETRO_DEVICE_ID_JOYPAD_START => 'm'
];

const SDL_INIT_VIDEO = 0x00000020;
const SDL_RENDERER_ACCELERATED = 0x00000002;
const SDL_RENDERER_PRESENTVSYNC = 0x00000004;
const SDL_QUIT = 0x100;
const SDL_WINDOW_SHOWN = 0x00000004;

const ZOOM_LEVEL = 4;
const SCREEN_HEIGHT = 144;
const SCREEN_WIDTH = 160;

$currentSecond = 0;
$fps = 0;
$framesInSecond = 0;

$sdl = FFI::load('sdl.h');
$sameboy = FFI::load('sameboy_libretro.h');

if ($sdl->SDL_Init(SDL_INIT_VIDEO) != 0) {
throw new Exception('SDL_Init Error: ' . $sdl->SDL_GetError(), 1);
}

$win = $sdl->SDL_CreateWindow('PHP FFI Boy', 0, 0, SCREEN_WIDTH * ZOOM_LEVEL, SCREEN_HEIGHT * ZOOM_LEVEL, SDL_WINDOW_SHOWN);

if ($win == null){
throw new Exception('SDL_CreateWindow Error: ' . $sdl->SDL_GetError(), 1);
$sdl->SDL_Quit();
}

$ren = $sdl->SDL_CreateRenderer($win, -1, 0);

$sdl->SDL_SetRenderDrawColor($ren, 0, 0, 0, 255);
$sdl->SDL_RenderClear($ren);

$event = $sdl->new('SDL_Event', false);

$gameinfo = $sameboy->new('struct retro_game_info', false);
$gameinfo->path = $sameboy->strdup(ROM);

$sameboy->retro_set_environment(function($cmd, $data) use ($sameboy) {
if ($cmd === RETRO_ENVIRONMENT_SET_PIXEL_FORMAT) {
return true;
}

if ($cmd === RETRO_ENVIRONMENT_GET_VARIABLE) {
$argument = $sameboy->cast('struct retro_variable', $data);

if ($argument->key === 'sameboy_model') {
$argument->value = $sameboy->strdup('Game Boy');
$data = FFI::addr($argument);
}

return true;
}

if ($cmd === RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE) {
$argument = $sameboy->cast('bool', $data);
$data = FFI::addr($argument);
return false;
}

if ($cmd === RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE) {
$argument = $sameboy->cast('struct retro_rumble_interface', $data);
$argument->set_rumble_state = function($port, $effect, $strength) {
return;
};
$data = FFI::addr($argument);
return false;
}

if ($cmd === RETRO_ENVIRONMENT_GET_LOG_INTERFACE) {
return false;
}

return;
});

$sameboy->retro_set_audio_sample(function($a, $b) {
return;
});

$sameboy->retro_set_input_poll(function () {
return;
});

$sameboy->retro_set_input_state(function ($port, $device, $index, $id) use (&$key) {
if ($key === KEYBOARD_MAPPING[$id]) {
return true;
}

return false;
});

$sameboy->retro_set_video_refresh(function ($data, $width, $height, $pitch) use (&$ren, $sdl) {
$pixels = FFI::cast('uint32_t[' . ($width * $height) . ']', $data);

$sdl->SDL_SetRenderDrawColor($ren, 142, 134, 9, 255);
$sdl->SDL_RenderClear($ren);
$sdl->SDL_SetRenderDrawColor($ren, 37, 57, 61, 255);

for ($y = 0; $y < SCREEN_HEIGHT; $y++) {
for ($x = 0; $x < SCREEN_WIDTH; $x++) {
$pixel = $pixels[$x + (SCREEN_WIDTH * $y)];

if ($pixel === 0) {
$rect = $sdl->new('SDL_Rect', true);
$rect->x = $x * ZOOM_LEVEL;
$rect->y = $y * ZOOM_LEVEL;
$rect->w = $rect->h = ZOOM_LEVEL;
$sdl->SDL_RenderFillRect($ren, FFI::addr($rect));
$sdl->SDL_RenderDrawRect($ren, FFI::addr($rect));
}
}
}

$sdl->SDL_RenderPresent($ren);

return;
});

$sameboy->retro_init();
$sameboy->retro_load_game(FFI::addr($gameinfo));

$isRunning = true;

while ($isRunning) {
//Calculate current FPS
if ($currentSecond != time()) {
$fps = $framesInSecond;
$currentSecond = time();
$framesInSecond = 1;
} else {
++$framesInSecond;
}

$sameboy->retro_run();

if ($sdl->SDL_PollEvent(FFI::addr($event))) {
if ($event->type == SDL_QUIT) {
$isRunning = false;
}
}

$sdl->SDL_SetWindowTitle($win, 'PHP FFI Boy - FPS: ' . $fps);
}
62 changes: 62 additions & 0 deletions gameboy-sdl/sameboy_libretro.h
@@ -0,0 +1,62 @@
#define FFI_LIB "sameboy_libretro.dylib"

int sprintf(char *str, const char *format, ...);
char *strdup(const char *s);

typedef void (*retro_audio_sample_t)(int16_t left, int16_t right);
typedef bool (*retro_environment_t)(unsigned cmd, void *data);
typedef void (*retro_input_poll_t)(void);
typedef int16_t (*retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id);
typedef void (*retro_log_printf_t)(enum retro_log_level level, const char *fmt, ...);
typedef bool (*retro_set_rumble_state_t)(unsigned port, enum retro_rumble_effect effect, uint16_t strength);
typedef void (*retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch);

bool vblank1_occurred = false, vblank2_occurred = false;

void retro_init(void);
unsigned retro_api_version(void);
bool retro_load_game(const struct retro_game_info *info);
void retro_run(void);

void retro_set_audio_sample(retro_audio_sample_t cb);

void retro_set_environment(retro_environment_t cb);

void retro_set_input_poll(retro_input_poll_t cb);
void retro_set_input_state(retro_input_state_t cb);

void retro_set_video_refresh(retro_video_refresh_t cb);

struct retro_game_info
{
const char *path; /* Path to game, UTF-8 encoded.
* Usually used as a reference.
* May be NULL if rom was loaded from stdin
* or similar.
* retro_system_info::need_fullpath guaranteed
* that this path is valid. */
const void *data; /* Memory buffer of loaded game. Will be NULL
* if need_fullpath was set. */
size_t size; /* Size of memory buffer. */
const char *meta; /* String of implementation specific meta-data. */
};

struct retro_rumble_interface
{
retro_set_rumble_state_t set_rumble_state;
};

struct retro_variable
{
/* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE.
* If NULL, obtains the complete environment string if more
* complex parsing is necessary.
* The environment string is formatted as key-value pairs
* delimited by semicolons as so:
* "key1=value1;key2=value2;..."
*/
const char *key;

/* Value to be obtained. If key does not exist, it is set to NULL. */
const char *value;
};

0 comments on commit a999e48

Please sign in to comment.