Skip to content
forked from brackeen/glfm

OpenGL ES and input for iOS, tvOS, Android, and WebGL

License

Notifications You must be signed in to change notification settings

glimpse-project/glfm

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GLFM

Write OpenGL ES code in C/C++ without writing platform-specific code.

GLFM is an OpenGL ES layer for mobile devices and the web. GLFM supplies an OpenGL ES context and input events. It is largely inspired by GLFW.

GLFM is written in C and runs on iOS 8, tvOS 9, Android 2.3.3 (API 10), and WebGL 1.0 (via Emscripten).

Features

  • OpenGL ES 2.0, 3.0, 3.1, and 3.2 display setup.
  • Retina / high-DPI support.
  • Touch and keyboard events.
  • Events for application state and context loss.

Non-goals

GLFM is limited in scope, and isn't designed to provide everything needed for an app. For example, GLFM doesn't provide (and will never provide) the following:

  • No image loading.
  • No text rendering.
  • No audio.
  • No menus, UI toolkit, or scene graph.
  • No integration with other mobile features like web views, maps, or game scores.

Instead, GLFM can be used with other cross-platform libraries that provide what an app needs.

Example

This example initializes the display in glfmMain() and draws a triangle in onFrame(). A more detailed example is available here.

#include "glfm.h"
#include <string.h>

static GLint program = 0;
static GLuint vertexBuffer = 0;

static void onFrame(GLFMDisplay *display, const double frameTime);
static void onSurfaceCreated(GLFMDisplay *display, const int width, const int height);
static void onSurfaceDestroyed(GLFMDisplay *display);

void glfmMain(GLFMDisplay *display) {
    glfmSetDisplayConfig(display,
                         GLFMRenderingAPIOpenGLES2,
                         GLFMColorFormatRGBA8888,
                         GLFMDepthFormatNone,
                         GLFMStencilFormatNone,
                         GLFMMultisampleNone);
    glfmSetSurfaceCreatedFunc(display, onSurfaceCreated);
    glfmSetSurfaceResizedFunc(display, onSurfaceCreated);
    glfmSetSurfaceDestroyedFunc(display, onSurfaceDestroyed);
    glfmSetMainLoopFunc(display, onFrame);
}

static void onSurfaceCreated(GLFMDisplay *display, const int width, const int height) {
    glViewport(0, 0, width, height);
}

static void onSurfaceDestroyed(GLFMDisplay *display) {
    // When the surface is destroyed, all existing GL resources are no longer valid.
    program = 0;
    vertexBuffer = 0;
}

static GLuint compileShader(const GLenum type, const GLchar *shaderString) {
    const GLint shaderLength = (GLint)strlen(shaderString);
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &shaderString, &shaderLength);
    glCompileShader(shader);
    return shader;
}

static void onFrame(GLFMDisplay *display, const double frameTime) {
    if (program == 0) {
        const GLchar *vertexShader =
            "attribute highp vec4 position;\n"
            "void main() {\n"
            "   gl_Position = position;\n"
            "}";

        const GLchar *fragmentShader =
            "void main() {\n"
            "  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
            "}";

        program = glCreateProgram();
        GLuint vertShader = compileShader(GL_VERTEX_SHADER, vertexShader);
        GLuint fragShader = compileShader(GL_FRAGMENT_SHADER, fragmentShader);

        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);

        glLinkProgram(program);

        glDeleteShader(vertShader);
        glDeleteShader(fragShader);
    }
    if (vertexBuffer == 0) {
        const GLfloat vertices[] = {
             0.0,  0.5, 0.0,
            -0.5, -0.5, 0.0,
             0.5, -0.5, 0.0,
        };
        glGenBuffers(1, &vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    }

    glClearColor(0.4f, 0.0f, 0.6f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(program);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

API

See glfm.h

Build requirements

  • iOS: Xcode 9.0
  • Android: Android Studio 2.3, SDK 25, NDK Bundle 13.1.3345770
  • WebGL: Emscripten 1.35.0

Use GLFM in an existing project

  1. Remove the project's existing void main() function, if any.
  2. Add the GLFM source files (in include and src).
  3. Include a void glfmMain(GLFMDisplay *display) function in a C/C++ file.

Build the example GLFM projects

Use the CMakeLists.txt file with the -DGLFM_BUILD_EXAMPLE=ON option to build the example projects.

Xcode 9.0

mkdir -p build/ios
cd build/ios
cmake -DGLFM_BUILD_EXAMPLE=ON -G Xcode ../..
open GLFM.xcodeproj

Switch to the glfm_example target and run on the simulator or a device.

Emscripten

Assuming EMSCRIPTEN_ROOT_PATH points to active installed version of Emscripten.

mkdir -p build/emscripten
cd build/emscripten
cmake -DGLFM_BUILD_EXAMPLE=ON -DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN_ROOT_PATH/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../..
cmake --build .

If you're opening files locally in Chrome, you may need to enable local file access. Instead, you could use Firefox, which doesn't have this restriction.

Android Studio 2.3

There is no CMake generator for Android Studio projects, but you can include CMakeLists.txt in a new or existing project.

The AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.brackeen.glfmexample">

    <uses-feature android:glEsVersion="0x00020000" android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true">
        <activity android:name="android.app.NativeActivity"
                  android:configChanges="orientation|screenLayout|screenSize|keyboardHidden|keyboard">
            <meta-data
                android:name="android.app.lib_name"
                android:value="glfm_example" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

And the app/build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.brackeen.glfmexample"
        minSdkVersion 10
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        externalNativeBuild {
            cmake {
                arguments "-DGLFM_BUILD_EXAMPLE=ON"
            }
        }
    }
    sourceSets.main {
        assets.srcDirs = ["../../../../example/assets"]
    }
    externalNativeBuild {
        cmake {
            path "../../../../CMakeLists.txt"
        }
    }
}

Future ideas

  • Accelerometer and gyroscope input.
  • Gamepad / MFi controller input.

Caveats

  • OpenGL ES 3.1 and 3.2 support is only available in Android, and the GLFM implementation is currently untested.
  • GLFM is not thread-safe. All GLFM functions must be called on the main thread (that is, from glfmMain or from the callback functions).
  • On iOS, character input works great, but keyboard events are not ideal. Using the keyboard (on an iOS device via Bluetooth keyboard or on the simulator via a Mac's keyboard), only a few keys are detected (arrows keys and the escape key), and key release events are not reported.
  • On Android, keyboard events work great, but character events are not ideal. Some special characters, like emoji characters, will not work. This is due to an issue in the NDK.
  • Orientation lock probably doesn't work on HTML5.

Questions

Why is the entry point glfmMain() and not main()?

Otherwise, it wouldn't work on iOS. To initialize the Objective-C environment, the main() function must create an autorelease pool and call the UIApplicationMain() function, which never returns. On iOS, GLFM doesn't call glfmMain() until after the UIApplicationDelegate and UIViewController are initialized.

Why is GLFM event-driven? Why does GLFM take over the main loop?

Otherwise, it wouldn't work on iOS (see above) or on HTML5, which is event-driven.

About

OpenGL ES and input for iOS, tvOS, Android, and WebGL

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C 71.9%
  • Objective-C 20.0%
  • CMake 7.7%
  • GLSL 0.4%