Raylib: Couple of improvements from my project.

Created on 16 Feb 2020  路  12Comments  路  Source: raysan5/raylib

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.

screen3

Running on my GamePad:
screen_gamepad

  1. my smoothed GetFPS routine:
// 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);
}
  1. in miniaudio.h, log2 is available in Android API 18+:
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
}
  1. I am using dead-simple incbin for embedding resources into the executable and needed to add some read-from-memory routines:
// 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;
}

  1. I am using KTX for textures and loading they required some more changes since I think the original code is not complete (at least I was not able to load my textures). Sadly it required more changes to Image structure to keep the mipmaps list (I can post also mipmaps loader if you are interested but I have a feeling that this could be made ina better way :))
// 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;
}
  1. For embedding resources I am using simple resource builder app and slightly modified incbin.h file:
// 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 };
}
  1. I have also some problems with reading GamePad buttons. Here are the key-codes of DPad and additional buttons (there are also some small changes to keyboard handler I post later):
// 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

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:

screen6

screen_lua

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

All 12 comments

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

  1. I'm already considering moving all file-access functions to a buffer-based approximation...
  2. That's a big change, no plans to implement it for the moment...
  3. Very nice tool! Need to review this carefully. Also working on rres for custom packed data.
  4. I tried to keep input references on 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:

screen6

screen_lua

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")

screen_luajit
screen_luajit2

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:

  1. GUI is too big, so I have added a function InitWindowScaled that initially scales the content
  2. added simple ini configuration file support
  3. gui_file_dialog.h changed - keyboard support, file/path preselection etc

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

gui_file_dialog.h.txt

@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. 馃槃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Geams picture Geams  路  3Comments

raysan5 picture raysan5  路  12Comments

yashrk picture yashrk  路  5Comments

Triangle345 picture Triangle345  路  8Comments

carlsmith picture carlsmith  路  8Comments