I am porting to _raylib_ this simple RPi benchmark app (https://www.raspberrypi.org/forums/viewtopic.php?t=88424) and I came up with couple of improvements that you might be interested.

Running on my GamePad:

// Returns current FPS
int GetFPS(void)
{
static int const CAPTURED_FRAMES_NUM = 30; // 30 captures
static float const AVG_TIME = 0.5; // 500 millisecondes
static float const step = AVG_TIME / CAPTURED_FRAMES_NUM;
static float history[CAPTURED_FRAMES_NUM] = { 0 };
static int indx = 0;
static float average = 0, last = 0;
float fpsFrame = GetFrameTime();
if (fpsFrame == 0){
return 0;
}
if (GetTime() - last > step)
{
last = GetTime();
indx = (indx+1) % CAPTURED_FRAMES_NUM;
average -= history[indx];
history[indx] = fpsFrame / CAPTURED_FRAMES_NUM;
average += history[indx];
}
return (int)roundf(1/average);
}
static MA_INLINE double ma_log2(double x)
{
/* TODO: Implement custom log2(x). */
#if defined(PLATFORM_ANDROID) && __ANDROID_API__ <= 18
return log(x) / log(2);
#else
return log2(x);
#endif
}
// Binary blob
typedef struct BinaryData {
const void *data;
unsigned int size;
} BinaryData;
// Load image from memory into CPU memory (RAM)
Image LoadImageFromMemory(BinaryData blob)
{
Image image = { 0 };
#if defined(SUPPORT_FILEFORMAT_PNG) || \
defined(SUPPORT_FILEFORMAT_BMP) || \
defined(SUPPORT_FILEFORMAT_TGA) || \
defined(SUPPORT_FILEFORMAT_GIF) || \
defined(SUPPORT_FILEFORMAT_PIC) || \
defined(SUPPORT_FILEFORMAT_PSD)
#define STBI_REQUIRED
#endif
if (blob.data != NULL)
{
#if defined(SUPPORT_FILEFORMAT_KTX)
if ((image = LoadKTXFromMemory(blob)).data) return image;
#endif
#if defined(STBI_REQUIRED)
int imgWidth = 0;
int imgHeight = 0;
int imgBpp = 0;
// NOTE: Using stb_image to load images (Supports multiple image formats)
image.data = stbi_load_from_memory(blob.data, blob.size, &imgWidth, &imgHeight, &imgBpp, 0);
image.width = imgWidth;
image.height = imgHeight;
image.mipmaps = 1;
if (imgBpp == 1) image.format = UNCOMPRESSED_GRAYSCALE;
else if (imgBpp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA;
else if (imgBpp == 3) image.format = UNCOMPRESSED_R8G8B8;
else if (imgBpp == 4) image.format = UNCOMPRESSED_R8G8B8A8;
#endif
}
if (image.data != NULL) TraceLog(LOG_INFO, "Image loaded successfully from memory (%ix%i)",image.width, image.height);
else TraceLog(LOG_WARNING, "Image could not be loaded from memory");
return image;
}
// Image type, bpp always RGBA (32bit)
// NOTE: Data stored in CPU memory (RAM)
typedef struct MipmapHeader {
unsigned int dataOffset;
unsigned int dataSize;
} MipmapHeader;
typedef struct Image {
void *data; // Image raw data
int width; // Image base width
int height; // Image base height
int mipmaps; // Mipmap levels, 1 by default
struct MipmapHeader *mipmapsHdr; // Mipmaps layout within data
int format; // Data format (PixelFormat type)
bool memmap; // Data are memory mapped and cannot be free
} Image;
static Image LoadKTXFromMemory(BinaryData blob)
{
typedef struct {
char id[12]; // Identifier: "芦KTX 11禄\r\n\x1A\n"
unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04
unsigned int glType; // For compressed textures, glType must equal 0
unsigned int glTypeSize; // For compressed texture data, usually 1
unsigned int glFormat; // For compressed textures is 0
unsigned int glInternalFormat; // Compressed internal format
unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...)
unsigned int width; // Texture image width in pixels
unsigned int height; // Texture image height in pixels
unsigned int depth; // For 2D textures is 0
unsigned int elements; // Number of array elements, usually 0
unsigned int faces; // Cubemap faces, for no-cubemap = 1
unsigned int mipmapLevels; // Non-mipmapped textures = 1
unsigned int keyValueDataSize; // Used to encode any arbitrary data...
} KTXHeader;
// NOTE: Before start of every mipmap data block, we have: unsigned int dataSize
Image image = { 0 };
if (blob.data == NULL)
{
TraceLog(LOG_WARNING, "KTX image data are empty");
}
else
{
void *dataPtr = blob.data;
KTXHeader *ktxHeader = dataPtr; dataPtr += sizeof(KTXHeader);
if ((ktxHeader->id[1] != 'K') || (ktxHeader->id[2] != 'T') || (ktxHeader->id[3] != 'X') ||
(ktxHeader->id[4] != ' ') || (ktxHeader->id[5] != '1') || (ktxHeader->id[6] != '1'))
{
TraceLog(LOG_DEBUG, "Data does not seem to be a valid KTX");
}
else
{
image.width = ktxHeader->width;
image.height = ktxHeader->height;
image.mipmaps = ktxHeader->mipmapLevels;
if (image.mipmaps > 1) {
image.mipmapsHdr = (MipmapHeader *)RL_MALLOC(image.mipmaps*sizeof(MipmapHeader));
}
TraceLog(LOG_DEBUG, "KTX (ETC) image width: %i", ktxHeader->width);
TraceLog(LOG_DEBUG, "KTX (ETC) image height: %i", ktxHeader->height);
TraceLog(LOG_DEBUG, "KTX (ETC) image format: 0x%x", ktxHeader->glInternalFormat);
dataPtr += ktxHeader->keyValueDataSize;
if (image.mipmaps > 1)
{
image.data = dataPtr;
unsigned int mipOffset = 0, mipSize = 0;
for(int m=0; m<image.mipmaps; ++m) {
mipSize = *(int*)(dataPtr+mipOffset); mipOffset += sizeof(int);
image.mipmapsHdr[m].dataSize = mipSize;
image.mipmapsHdr[m].dataOffset = mipOffset;
TraceLog(LOG_DEBUG, "KTX (ETC) mipmap level %i data size %i at offset %i", m, mipSize, mipOffset);
int padSize = ((mipSize + 3) / 4) * 4 - mipSize;
mipOffset += mipSize + padSize;
}
}
else
{
int dataSize = *(int*)dataPtr; dataPtr += sizeof(int);
TraceLog(LOG_DEBUG, "KTX (ETC) image data size: %i", dataSize);
image.data = dataPtr;
}
if (ktxHeader->glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB;
else if (ktxHeader->glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB;
else if (ktxHeader->glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA;
else TraceLog(LOG_WARNING, "KTX (ETC) image not recognized: %i", ktxHeader->glInternalFormat);
image.memmap = true;
}
}
return image;
}
// This program is used to embed arbitrary data into a C binary. It takes
// a list of files as an input, and produces a .c data file that contains
// contents of all these files as collection of char arrays.
// Usage:
// 1. Compile this file:
// cc -o embed embed.c
//
// 2. Convert list of files into single .c:
// ./embed file1.data;file1;alias.data file2.data;file2 > embedded_data.c
//
// 3. In your application code, you can access files using this function:
//
// const char *find_embedded_file(const char *file_name, size_t *size);
// size_t size;
// const char *data = find_embedded_file("file1.data", &size);
//
// 4. Build your app with embedded_data.c:
// cc -o my_app my_app.c embedded_data.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
int main(int argc, char *argv[]) {
FILE *fp;
int i, j, ch;
if (argc < 1) {
fprintf(stderr, "*** Missing arguments. Terminating.\n");
exit(EXIT_FAILURE);
}
printf("#include <string.h>\n");
printf("#include <incbin/incbin.h>\n\n");
for (i = 1; i < argc; i++) {
char *filepath = strdup(argv[i]), *fileid = strchr(filepath, ';'), *filealias = fileid&&*fileid ? strchr(fileid+1, ';') : 0;
if (fileid) *fileid++ = 0;
if (filealias) *filealias++ = 0;
printf("INCBIN(%s, \"%s\");\n", fileid, filepath);
}
printf("\n\n");
printf("%s", "typedef struct BinaryData {\n");
printf("%s", " const void *data;\n");
printf("%s", " unsigned int size;\n");
printf("%s", "} BinaryData;\n\n");
printf("%s", "BinaryData rlFindEmbeddedData(const char *name, unsigned int *size) {\n");
for (i = 1; i < argc; i++) {
char *filepath = strdup(argv[i]), *fileid = strchr(filepath, ';'), *filealias = fileid&&*fileid ? strchr(fileid+1, ';') : 0;
if (fileid) *fileid++ = 0;
if (filealias) *filealias++ = 0;
if (filealias && *filealias)
printf(" if (!strcmp(\"%s\", name)) return (BinaryData){g%sData, g%sSize};\n", filealias, fileid, fileid);
else
printf(" if (!strcmp(\"%s\", name)) return (BinaryData){g%sData, g%sSize};\n", basename(filepath), fileid, fileid);
}
printf("%s", " return (BinaryData){ 0 };\n");
printf("%s", "}\n");
return EXIT_SUCCESS;
}
it creates simple resource file that can be compiled into the application:
#include <string.h>
#include <incbin/incbin.h>
INCBIN(flat_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/flat.fragmentShader");
INCBIN(flat_vertexShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/flat.vertexShader");
INCBIN(gouraud_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/gouraud.fragmentShader");
INCBIN(gouraud_vertexShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/gouraud.vertexShader");
INCBIN(phong_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/phong.fragmentShader");
INCBIN(phong_vertexShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/phong.vertexShader");
INCBIN(text_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/text.fragmentShader");
INCBIN(text_vertexShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/text.vertexShader");
INCBIN(untextured_gouraud_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/untextured-gouraud.fragmentShader");
INCBIN(untextured_phong_fragmentShader, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/shaders/untextured-phong.fragmentShader");
INCBIN(cube_ktx, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/textures/cube.ktx");
INCBIN(frog_ktx, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/textures/frog.ktx");
INCBIN(kid_ktx, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/textures/kid.ktx");
INCBIN(robot_ktx, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/textures/robot.ktx");
INCBIN(trex_ktx, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/textures/trex.ktx");
INCBIN(bloom_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/bloom.fs");
INCBIN(blur_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/blur.fs");
INCBIN(cross_hatching_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/cross_hatching.fs");
INCBIN(cross_stitching_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/cross_stitching.fs");
INCBIN(dream_vision_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/dream_vision.fs");
INCBIN(fisheye_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/fisheye.fs");
INCBIN(fxaa_frag_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/fxaa.frag.fs");
INCBIN(fxaa_vert_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/fxaa.vert.fs");
INCBIN(grayscale_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/grayscale.fs");
INCBIN(pixelizer_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/pixelizer.fs");
INCBIN(posterization_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/posterization.fs");
INCBIN(predator_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/predator.fs");
INCBIN(scanlines_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/scanlines.fs");
INCBIN(sobel_fs, "/Users/piecuchp/Private/Workspace/raylib-projects/raylib/build-scripts/../../raylib/examples/benchmark/res/postprocess/sobel.fs");
typedef struct BinaryData {
const void *data;
unsigned int size;
} BinaryData;
BinaryData rlFindEmbeddedData(const char *name, unsigned int *size) {
if (!strcmp("shaders/flat.fragmentShader", name)) return (BinaryData){gflat_fragmentShaderData, gflat_fragmentShaderSize};
if (!strcmp("shaders/flat.vertexShader", name)) return (BinaryData){gflat_vertexShaderData, gflat_vertexShaderSize};
if (!strcmp("shaders/gouraud.fragmentShader", name)) return (BinaryData){ggouraud_fragmentShaderData, ggouraud_fragmentShaderSize};
if (!strcmp("shaders/gouraud.vertexShader", name)) return (BinaryData){ggouraud_vertexShaderData, ggouraud_vertexShaderSize};
if (!strcmp("shaders/phong.fragmentShader", name)) return (BinaryData){gphong_fragmentShaderData, gphong_fragmentShaderSize};
if (!strcmp("shaders/phong.vertexShader", name)) return (BinaryData){gphong_vertexShaderData, gphong_vertexShaderSize};
if (!strcmp("shaders/text.fragmentShader", name)) return (BinaryData){gtext_fragmentShaderData, gtext_fragmentShaderSize};
if (!strcmp("shaders/text.vertexShader", name)) return (BinaryData){gtext_vertexShaderData, gtext_vertexShaderSize};
if (!strcmp("shaders/untextured-gouraud.fragmentShader", name)) return (BinaryData){guntextured_gouraud_fragmentShaderData, guntextured_gouraud_fragmentShaderSize};
if (!strcmp("shaders/untextured-phong.fragmentShader", name)) return (BinaryData){guntextured_phong_fragmentShaderData, guntextured_phong_fragmentShaderSize};
if (!strcmp("textures/cube.ktx", name)) return (BinaryData){gcube_ktxData, gcube_ktxSize};
if (!strcmp("textures/frog.ktx", name)) return (BinaryData){gfrog_ktxData, gfrog_ktxSize};
if (!strcmp("textures/kid.ktx", name)) return (BinaryData){gkid_ktxData, gkid_ktxSize};
if (!strcmp("textures/robot.ktx", name)) return (BinaryData){grobot_ktxData, grobot_ktxSize};
if (!strcmp("textures/trex.ktx", name)) return (BinaryData){gtrex_ktxData, gtrex_ktxSize};
if (!strcmp("postprocess/bloom.fs", name)) return (BinaryData){gbloom_fsData, gbloom_fsSize};
if (!strcmp("postprocess/blur.fs", name)) return (BinaryData){gblur_fsData, gblur_fsSize};
if (!strcmp("postprocess/cross_hatching.fs", name)) return (BinaryData){gcross_hatching_fsData, gcross_hatching_fsSize};
if (!strcmp("postprocess/cross_stitching.fs", name)) return (BinaryData){gcross_stitching_fsData, gcross_stitching_fsSize};
if (!strcmp("postprocess/dream_vision.fs", name)) return (BinaryData){gdream_vision_fsData, gdream_vision_fsSize};
if (!strcmp("postprocess/fisheye.fs", name)) return (BinaryData){gfisheye_fsData, gfisheye_fsSize};
if (!strcmp("postprocess/fxaa.frag.fs", name)) return (BinaryData){gfxaa_frag_fsData, gfxaa_frag_fsSize};
if (!strcmp("postprocess/fxaa.vert.fs", name)) return (BinaryData){gfxaa_vert_fsData, gfxaa_vert_fsSize};
if (!strcmp("postprocess/grayscale.fs", name)) return (BinaryData){ggrayscale_fsData, ggrayscale_fsSize};
if (!strcmp("postprocess/pixelizer.fs", name)) return (BinaryData){gpixelizer_fsData, gpixelizer_fsSize};
if (!strcmp("postprocess/posterization.fs", name)) return (BinaryData){gposterization_fsData, gposterization_fsSize};
if (!strcmp("postprocess/predator.fs", name)) return (BinaryData){gpredator_fsData, gpredator_fsSize};
if (!strcmp("postprocess/scanlines.fs", name)) return (BinaryData){gscanlines_fsData, gscanlines_fsSize};
if (!strcmp("postprocess/sobel.fs", name)) return (BinaryData){gsobel_fsData, gsobel_fsSize};
return (BinaryData){ 0 };
}
// Android buttons
typedef enum {
KEY_BACK = 4,
KEY_MENU = 82,
KEY_DPAD_UP = 19,
KEY_DPAD_DOWN = 20,
KEY_DPAD_LEFT = 21,
KEY_DPAD_RIGHT = 22,
KEY_VOLUME_UP = 24,
KEY_VOLUME_DOWN = 25,
KEY_DPAD_A = 96,
KEY_DPAD_B = 97,
KEY_DPAD_X = 99,
KEY_DPAD_Y = 100,
KEY_DPAD_SELECT = 109,
KEY_DPAD_START = 108
} AndroidButton;
Regards
Pawel
Hi @ppiecuch! This is a big batch of improvements! I'll need some time to review them... in any case, thanks for sharing!
Glad to see you got raylib working on RPI and Android! How was the experience?
FYI, the ma_log2() thing from miniaudio has already been fixed in the dev-0.10 branch so that'll be fixed naturally when you update miniaudio next.
I'm listing here the improvements and the resolution for them, I'll keep updating this list
GetFPS() routine -> Implemented in commit https://github.com/raysan5/raylib/commit/9eefcb7939fd6189037412c8269feab27e5c2036log2() usage -> Updated in commit https://github.com/raysan5/raylib/commit/d62a2f793fdf10f677a2bf8a31a6734c9788a0ddincbinrres for custom packed data.raylib.h platform agnostic, I think it can be re-mapped inside core module when asking for a gamepad button.+1 for improved file access mechanisms.
Good to know that you find some changes useful :) Now some more changes related to Android input. I noticed two problems: gamepad (and in general keyboard input) crashed the app frequently and gui elements not responded do touches. These was a problems since I am using different gamepads + I wanted to have a dead-simple onscreen keyboard for benchmark app. Here is an example of osk and simple guide for Lua launcher:


I have added simple gesture to IsMouseButtonReleased:
// Detect if a mouse button has been released once
bool IsMouseButtonReleased(int button)
{
bool released = false;
#if defined(PLATFORM_ANDROID)
# if defined(SUPPORT_GESTURES_SYSTEM)
released = GetGestureDetected() == GESTURE_TAP;
# endif
#else
if ((CORE.Input.Mouse.currentButtonState[button] != CORE.Input.Mouse.previousButtonState[button]) &&
(GetMouseButtonStatus(button) == 0)) released = true;
#endif
return released;
}
Here is slightly refactored AndroidInputCallback - do not crash and gui controls work for android:
// Android: Get input events
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event)
{
// If additional inputs are required check:
// https://developer.android.com/ndk/reference/group/input
// https://developer.android.com/training/game-controllers/controller-input
int type = AInputEvent_getType(event);
int source = AInputEvent_getSource(event);
if (type == AINPUT_EVENT_TYPE_MOTION)
{
if ((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK || (source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)
{
// Get first touch position
CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0);
CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0);
// Get second touch position
CORE.Input.Touch.position[1].x = AMotionEvent_getX(event, 1);
CORE.Input.Touch.position[1].y = AMotionEvent_getY(event, 1);
int32_t keycode = AKeyEvent_getKeyCode(event);
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
{
CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
CORE.Input.Keyboard.keyPressedQueueCount++;
}
else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up
// Stop processing gamepad buttons
return 1;
}
int32_t action = AMotionEvent_getAction(event);
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
// Simple touch position
if (flags == AMOTION_EVENT_ACTION_DOWN)
{
// Get first touch position
CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0);
CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0);
}
#if defined(SUPPORT_GESTURES_SYSTEM)
GestureEvent gestureEvent;
// Register touch actions
if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN;
else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP;
else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE;
// Register touch points count
// NOTE: Documentation says pointerCount is Always >= 1,
// but in practice it can be 0 or over a million
gestureEvent.pointCount = AMotionEvent_getPointerCount(event);
// Only enable gestures for 1-3 touch points
if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4))
{
// Register touch points id
// NOTE: Only two points registered
gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0);
gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1);
// Register touch points position
gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) };
gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) };
// Normalize gestureEvent.position[x] for screenWidth and screenHeight
gestureEvent.position[0].x /= (float)GetScreenWidth();
gestureEvent.position[0].y /= (float)GetScreenHeight();
gestureEvent.position[1].x /= (float)GetScreenWidth();
gestureEvent.position[1].y /= (float)GetScreenHeight();
// Gesture data is sent to gestures system for processing
ProcessGestureEvent(gestureEvent);
}
#endif
}
else if (type == AINPUT_EVENT_TYPE_KEY)
{
int32_t keycode = AKeyEvent_getKeyCode(event);
//int32_t AKeyEvent_getMetaState(event);
// Save current button and its state
// NOTE: Android key action is 0 for down and 1 for up
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
{
CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
CORE.Input.Keyboard.keyPressedQueueCount++;
}
else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up
if (keycode == AKEYCODE_POWER)
{
// Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS
// Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS
// It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected.
// NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" >
// Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour
return 0;
}
else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU))
{
// Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS!
return 1;
}
else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN))
{
// Set default OS behaviour
return 0;
}
return 0;
}
int32_t action = AMotionEvent_getAction(event);
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
// Support only simple touch position
if (flags == AMOTION_EVENT_ACTION_DOWN)
{
// Get first touch position
CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0);
CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0);
}
else if (flags == AMOTION_EVENT_ACTION_UP)
{
// Get first touch position
CORE.Input.Touch.position[0].x = 0;
CORE.Input.Touch.position[0].y = 0;
}
else // TODO:Not sure what else should be handled
return 0;
#if defined(SUPPORT_GESTURES_SYSTEM)
GestureEvent gestureEvent = { 0 };
// Register touch actions
if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN;
else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP;
else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE;
// Register touch points count
// NOTE: Documentation says pointerCount is Always >= 1,
// but in practice it can be 0 or over a million
gestureEvent.pointCount = AMotionEvent_getPointerCount(event);
// Only enable gestures for 1-3 touch points
if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4))
{
// Register touch points id
// NOTE: Only two points registered
gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0);
gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1);
// Register touch points position
gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) };
gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) };
// Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height
gestureEvent.position[0].x /= (float)GetScreenWidth();
gestureEvent.position[0].y /= (float)GetScreenHeight();
gestureEvent.position[1].x /= (float)GetScreenWidth();
gestureEvent.position[1].y /= (float)GetScreenHeight();
// Gesture data is sent to gestures system for processing
ProcessGestureEvent(gestureEvent);
}
#endif
return 0;
}
@ppiecuch Very nice! This is a great improvement! Thank you very much! You're sending really great contributions! Just merged them into raylib in commit https://github.com/raysan5/raylib/commit/30f3e49f3dc38465c25052f987a81184901acb7b.
I see you're using the raygui GuiFileDialog() on Android! How does it work? Were you able to access the SDCard or just the APK assets directory?
Please, keep me updated with your progress, it looks really nice!
I am still working on this launcher - it will be a general lua launcher so access to sdcard is necessary. I will give some feedback when I get back to this project.
Hi @ppiecuch! Any news on your project?
Hello - finally I focused on on luajit - integration of raylib using ffi is very flexible:
local ffi = require 'ffi'
local reflect = require 'reflect'
ffi.cdef[[
// Vector2 type
typedef struct Vector2 {
float x;
float y;
} Vector2;
// Vector3 type
typedef struct Vector3 {
float x;
float y;
float z;
} Vector3;
void InitWindow(unsigned int width, unsigned int height, const char *title); // Initialize window and OpenGL context
void InitWindowScaled(unsigned int width, unsigned int height, float scale, const char *title); // Initialize scaled window and OpenGL context
void CloseWindow(void); // Close window and unload OpenGL context
...
I can easily add/modify declarations. And using these is simple too:
local ffi = require 'ffi'
ffi.cdef[[
int printf(const char *fmt, ...);
]]
local printf = ffi.C.printf
local rl = RL
ffi.C.printf("Hello %s from ffi!\n", "world")
SetTraceLogLevel(RL.LOG_INFO)
print("trying raylib:")
TraceLog(RL.LOG_INFO, "Hello %s from ffi/raylib!", "world")


I am using single-file luajit, so it is quite easy to build the project:
-rw-r--r-- 1 piecuchp staff 5.9K Mar 8 22:36 lauxlib.h
-rw-r--r-- 1 piecuchp staff 1.7M Mar 8 22:36 lj-android-arm.c
-rw-r--r-- 1 piecuchp staff 295K Mar 8 22:36 lj-android-arm.h
Because everything is c, it is fast and easy to build using a single script:
$CC $CFLAGS $DEFINES $RAYLIB_INC -c -o $PROJECT_BUILD_PATH/obj/raylibcore.o $RAYLIB/src/core.c
$CC $CFLAGS $DEFINES $RAYLIB_INC -c -o $PROJECT_BUILD_PATH/obj/raylib-luajit.o $RAYLUA/raylib-luajit.c
$CC $CFLAGS $DEFINES $RAYLIB_INC -c -o $PROJECT_BUILD_PATH/obj/raylib.o raylib.c
$CC $CFLAGS $DEFINES $RAYLIB_INC -c -o $PROJECT_BUILD_PATH/obj/resources.o resources.c
$CC $CFLAGS $DEFINES $RAYLIB_INC \
-DLUAJIT_TARGET=LUAJIT_ARCH_arm -DLJ_ARCH_HASFPU=1 -DLJ_ABI_SOFTFP=1 \
-c -o $PROJECT_BUILD_PATH/obj/lj.o $LUAJIT/lj-android-arm.c
$CC $CFLAGS $DEFINES $RAYLIB_INC \
-DLUAJIT_TARGET=LUAJIT_ARCH_arm -DLJ_ARCH_HASFPU=1 -DLJ_ABI_SOFTFP=1 \
-c -o $PROJECT_BUILD_PATH/obj/lj_vm.o $LUAJIT/ljvm-android-arm.S
$CC -o $PROJECT_BUILD_PATH/lib/$ANDROID_ARCH_NAME/lib${PROJECT_LIBRARY_NAME}.so $OBJS -shared $INCLUDE_PATHS $LDFLAGS $LDLIBS
Still there is a few changes and improvements:
I can share source codes if someone is interested but it is spread across several projects :)
Regards
Pawel
Hi @ppiecuch! Thanks for the update! :)
I see you implemented support for reading/writting to SDCard on Android, that would be a nice addition for raylib. Also, it would be very interesting to have the improved gui_file_dialog.h.
Keep up the good work!
Attached my guy_file_dialog. As for the file reading not much changed - for android I removed #define open ... from utils.c (I am using single-file/amalgamated build and that #define messes a lot with rest of the code) - I am using:
#if defined(PLATFORM_ANDROID)
#define std_fopen(path,mode) android_fopen(path,mode)
#else
#define std_fopen(path,mode) fopen(path,mode)
#endif
and at the end of android_fopen I am doing regular fopen if the file is not found in the assets:
// Replacement for fopen
// Ref: https://developer.android.com/ndk/reference/group/asset
FILE *android_fopen(const char *fileName, const char *mode)
{
if (mode[0] == 'w') // TODO: Test!
{
// TODO: fopen() is mapped to android_fopen() that only grants read access
// to assets directory through AAssetManager but we want to also be able to
// write data when required using the standard stdio FILE access functions
// Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only
return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode);
}
else
{
// NOTE: AAsset provides access to read-only asset
AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN);
if (asset != NULL) return funopen(asset, android_read, android_write, android_seek, android_close);
else return **fopen(TextFormat("%s/%s", internalDataPath, fileName), mode);**
}
}
Regards
Pawel
@ppiecuch Thank you very much for all the improvements. Hope you are enjoying raylib and it fitted your needs. I'm closing this issue for now.
Feel free to open another one if you send further improvements to the library. 馃槃
Most helpful comment
Good to know that you find some changes useful :) Now some more changes related to Android input. I noticed two problems: gamepad (and in general keyboard input) crashed the app frequently and gui elements not responded do touches. These was a problems since I am using different gamepads + I wanted to have a dead-simple onscreen keyboard for benchmark app. Here is an example of osk and simple guide for Lua launcher:
I have added simple gesture to IsMouseButtonReleased:
Here is slightly refactored AndroidInputCallback - do not crash and gui controls work for android: