GLFM 是用于使用 OpenGL ES 进行移动应用程序开发的 C API。它很大程度上受到 GLFW 的启发。
GLFM 在 iOS 9、tvOS 9、Android 4.1 (API 16) 和 WebGL 1.0(通过 Emscripten)上运行。
此外,GLFM 在 iOS 和 tvOS 上提供 Metal 支持。
iOS系统 | 电视操作系统 | 安卓 | 网络 | |
---|---|---|---|---|
OpenGL ES 2、OpenGL ES 3 | ✔️ | ✔️ | ✔️ | ✔️ |
金属 | ✔️ | ✔️ | 不适用 | 不适用 |
视网膜/高 DPI | ✔️ | ✔️ | ✔️ | ✔️ |
设备方向 | ✔️ | 不适用 | ✔️ | |
触摸事件 | ✔️ | ✔️ | ✔️ | ✔️ |
鼠标悬停事件 | ✔️1 | ✔️ | ||
鼠标滚轮事件 | ✔️ | |||
鼠标光标样式 | ✔️1 | ✔️ | ||
关键代码事件 | ✔️2 | ✔️ | ✔️ | ✔️ |
关键重复事件 | ✔️ | ✔️ | ||
字符输入事件 | ✔️ | ✔️3 | ✔️ | ✔️ |
虚拟键盘 | ✔️ | ✔️ | ||
虚拟键盘可见性事件 | ✔️ | ✔️ | ||
加速计、磁力计、陀螺仪、设备旋转 | ✔️ | 不适用 | ✔️ | |
触觉反馈 | ✔️4 | 不适用 | ✔️ | |
剪贴板 | ✔️ | 不适用 | ✔️ | ✔️ |
Chrome 插图(“安全区域”) | ✔️5 | ✔️ | ✔️ | |
Chrome 插入更改事件 | ✔️5 | ✔️ | ✔️ | |
焦点事件 | ✔️ | ✔️ | ✔️ | ✔️ |
调整事件大小 | ✔️ | ✔️ | ✔️ | ✔️ |
内存警告事件 | ✔️ | ✔️ | ✔️ | |
OpenGL 上下文丢失事件(表面被破坏) | ✔️ | ✔️ | ✔️ | ✔️ |
1. 仅限 iPad。需要 iOS 13.4 或更高版本
2. 需要 iOS/tvOS 13.4 或更高版本
3. 需要 tvOS 13.4 或更高版本
4. 需要 iOS 13 或更高版本
5. 需要 iOS/tvOS 11 或更高版本
此外,还初步支持带有 OpenGL 3.2 的 macOS。 macOS 版本可用于开发目的,但不具备发布质量。例如,没有设置窗口大小的功能。
GLFM 的范围有限,并非旨在提供应用程序所需的一切。例如,GLFM 不提供(也永远不会提供)以下内容:
相反,GLFM 可以与其他跨平台库一起使用,提供应用程序所需的内容。
为了方便起见,提供了CMakeLists.txt
文件,尽管 CMake 不是必需的。
没有 CMake:
include
和src
中)添加到您的项目中。void glfmMain(GLFMDisplay *display)
函数。对于发布版本,定义NDEBUG
以删除多余的日志记录语句。 NDEBUG
是在 Android Studio 中为发布版本自动定义的,但在 Xcode 中则不然。
此示例在glfmMain()
中初始化显示并在onDraw()
中绘制三角形。此处提供了更详细的示例。
#include "glfm.h"
static GLint program = 0 ;
static GLuint vertexBuffer = 0 ;
static GLuint vertexArray = 0 ;
static void onDraw ( GLFMDisplay * display );
static void onSurfaceDestroyed ( GLFMDisplay * display );
void glfmMain ( GLFMDisplay * display ) {
glfmSetDisplayConfig ( display ,
GLFMRenderingAPIOpenGLES2 ,
GLFMColorFormatRGBA8888 ,
GLFMDepthFormatNone ,
GLFMStencilFormatNone ,
GLFMMultisampleNone );
glfmSetRenderFunc ( display , onDraw );
glfmSetSurfaceDestroyedFunc ( display , onSurfaceDestroyed );
}
static void onSurfaceDestroyed ( GLFMDisplay * display ) {
// When the surface is destroyed, all existing GL resources are no longer valid.
program = 0 ;
vertexBuffer = 0 ;
vertexArray = 0 ;
}
static GLuint compileShader ( const GLenum type , const GLchar * shaderString , GLint shaderLength ) {
GLuint shader = glCreateShader ( type );
glShaderSource ( shader , 1 , & shaderString , & shaderLength );
glCompileShader ( shader );
return shader ;
}
static void onDraw ( GLFMDisplay * display ) {
if ( program == 0 ) {
const GLchar vertexShader [] =
"#version 100n"
"attribute highp vec4 position;n"
"void main() {n"
" gl_Position = position;n"
"}" ;
const GLchar fragmentShader [] =
"#version 100n"
"void main() {n"
" gl_FragColor = vec4(0.85, 0.80, 0.75, 1.0);n"
"}" ;
program = glCreateProgram ();
GLuint vertShader = compileShader ( GL_VERTEX_SHADER , vertexShader , sizeof ( vertexShader ) - 1 );
GLuint fragShader = compileShader ( GL_FRAGMENT_SHADER , fragmentShader , sizeof ( fragmentShader ) - 1 );
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 );
}
int width , height ;
glfmGetDisplaySize ( display , & width , & height );
glViewport ( 0 , 0 , width , height );
glClearColor ( 0.08f , 0.07f , 0.07f , 1.0f );
glClear ( GL_COLOR_BUFFER_BIT );
#if defined( GL_VERSION_3_0 ) && GL_VERSION_3_0
if ( vertexArray == 0 ) {
glGenVertexArrays ( 1 , & vertexArray );
}
glBindVertexArray ( vertexArray );
#endif
glUseProgram ( program );
glBindBuffer ( GL_ARRAY_BUFFER , vertexBuffer );
glEnableVertexAttribArray ( 0 );
glVertexAttribPointer ( 0 , 3 , GL_FLOAT , GL_FALSE , 0 , 0 );
glDrawArrays ( GL_TRIANGLES , 0 , 3 );
glfmSwapBuffers ( display );
}
参见glfm.h
使用cmake
生成Xcode项目:
cmake -D GLFM_BUILD_EXAMPLES=ON -B build/apple -G Xcode
open build/apple/GLFM.xcodeproj
在 Xcode 中,切换到glfm_touch
目标并在模拟器或设备上运行。
使用emcmake
设置cmake
的环境变量,然后构建:
emcmake cmake -D GLFM_BUILD_EXAMPLES=ON -B build/emscripten && cmake --build build/emscripten
然后本地运行:
emrun build/emscripten/examples
或者运行一个具体的例子:
emrun build/emscripten/examples/glfm_touch.html
Android Studio 项目没有 CMake 生成器,但您可以在新项目或现有项目中包含CMakeLists.txt
。
[path/to/glfm]/build/android
然后按“完成”。AndroidManifest.xml
中,添加主要
如下所示: xml version = " 1.0 " encoding = " utf-8 " ?>
< manifest xmlns : android = " http://schemas.android.com/apk/res/android " >
< 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 : exported = " true "
android : configChanges = " orientation|screenLayout|screenSize|keyboardHidden|keyboard " >
< meta-data
android : name = " android.app.lib_name "
android : value = " glfm_touch " />
< intent-filter >
< action android : name = " android.intent.action.MAIN " />
< category android : name = " android.intent.category.LAUNCHER " />
intent-filter >
activity >
application >
manifest >
app/build.gradle
中,添加externalNativeBuild
和sourceSets.main
部分,如下所示: apply plugin : ' com.android.application '
android {
compileSdkVersion 34
ndkVersion ' 23.2.8568313 ' // Last version to support API 16, 17, 18
defaultConfig {
applicationId " com.brackeen.glfmexample "
minSdkVersion 16
targetSdkVersion 34
versionCode 1
versionName " 1.0 "
// Add externalNativeBuild in defaultConfig (1/2)
externalNativeBuild {
cmake {
arguments " -DGLFM_BUILD_EXAMPLES=ON "
}
}
}
// Add sourceSets.main and externalNativeBuild (2/2)
sourceSets . main {
assets . srcDirs = [ " ../../../examples/assets " ]
}
externalNativeBuild {
cmake {
path " ../../../CMakeLists.txt "
}
}
namespace ' com.brackeen.glfmexample '
}
glfmMain
或从回调函数)。 我应该使用什么 IDE?为什么没有桌面实现?使用 Xcode 或 Android Studio。对于桌面,请将 GLFW 与您选择的 IDE 结合使用。
如果您不喜欢使用移动模拟器进行日常开发,一个好的解决方案是使用 GLFW,然后将您的应用程序移植到 GLFM。并非所有 OpenGL 调用都能完美移植到 OpenGL ES,但为了获得最大程度的 OpenGL 可移植性,请在桌面上使用 OpenGL 3.2 Core Profile,在移动设备上使用 OpenGL ES 2.0。
为什么入口点是glfmMain()
而不是main()
?
否则,它无法在 iOS 上运行。要初始化 Objective-C 环境, main()
函数必须创建一个自动释放池并调用UIApplicationMain()
函数,该函数永远不会返回。在 iOS 上,GLFM 在UIApplicationDelegate
和UIViewController
初始化之前不会调用glfmMain()
。
为什么 GLFM 是事件驱动的?为什么 GLFM 接管主循环?
否则,它无法在 iOS(见上文)或事件驱动的 HTML5 上运行。