SDL2库(3)-Android 端源码简要分析(VideoSu
项目位置 https://github.com/deepsadness/SDLCmakeDemo
系列内容导读
- SDL2-移植Android Studio+CMakeList集成
- Android端FFmpeg +SDL2的简单播放器
- SDL2 Android端的简要分析(VideoSubSystem)
- SDL2 Android端的简要分析(AudioSubSystem)
Android 部分源码分析
暂时只包括视频系统的部分。
1. Android上SDLThread启动初始化
2.SDL初始化
SDL_Init(): 初始化SDL。
SDL_CreateWindow(): 创建窗口(Window)。
SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。
SDL_CreateTexture(): 创建纹理(Texture)。
3. SDL循环渲染数据
SDL_UpdateTexture(): 设置纹理的数据。
SDL_RenderCopy(): 纹理复制给渲染器。
SDL_RenderPresent(): 显示。
SDLThread 启动的初始化
根据SDLActivity的初始化流程。来看一下SDL的初始化。
SDLActivity::onCreate
方法
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "Device: " + Build.DEVICE);
Log.v(TAG, "Model: " + Build.MODEL);
Log.v(TAG, "onCreate()");
super.onCreate(savedInstanceState);
// Load shared libraries
String errorMsgBrokenLib = "";
try {
//加载库
loadLibraries();
} catch (UnsatisfiedLinkError e) {
System.err.println(e.getMessage());
mBrokenLibraries = true;
errorMsgBrokenLib = e.getMessage();
} catch (Exception e) {
System.err.println(e.getMessage());
mBrokenLibraries = true;
errorMsgBrokenLib = e.getMessage();
}
//没加载成功。就弹出框提示。
if (mBrokenLibraries) {
mSingleton = this;
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
+ System.getProperty("line.separator")
+ System.getProperty("line.separator")
+ "Error: " + errorMsgBrokenLib);
dlgAlert.setTitle("SDL Error");
dlgAlert.setPositiveButton("Exit",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close current activity
SDLActivity.mSingleton.finish();
}
});
dlgAlert.setCancelable(false);
dlgAlert.create().show();
return;
}
// Set up JNI
SDL.setupJNI();
// Initialize state
SDL.initialize();
// So we can call stuff from static callbacks
mSingleton = this;
SDL.setContext(this);
// 剪贴板
if (Build.VERSION.SDK_INT >= 11) {
mClipboardHandler = new SDLClipboardHandler_API11();
} else {
/* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
mClipboardHandler = new SDLClipboardHandler_Old();
}
//HID device
mHIDDeviceManager = HIDDeviceManager.acquire(this);
//创建Surface
mSurface = new SDLSurface(getApplication());
mLayout = new RelativeLayout(this);
mLayout.addView(mSurface);
// Get our current screen orientation and pass it down.
mCurrentOrientation = SDLActivity.getCurrentOrientation();
SDLActivity.onNativeOrientationChanged(mCurrentOrientation);
setContentView(mLayout);
setWindowStyle(false);
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
// Get filename from "Open with" of another application
Intent intent = getIntent();
if (intent != null && intent.getData() != null) {
String filename = intent.getData().getPath();
if (filename != null) {
Log.v(TAG, "Got filename: " + filename);
SDLActivity.onNativeDropFile(filename);
}
}
}
- loadLibraries
//这个方法,可以看到就是我们之前的定义的库的名称。如果我们在CMakeList.txt中修改了自己编译的库的名称。那这里也要记得修改。
protected String[] getLibraries() {
return new String[]{
"SDL2",
// "SDL2_image",
// "SDL2_mixer",
// "SDL2_net",
// "SDL2_ttf",
"main"
};
}
// Load the .so
public void loadLibraries() {
for (String lib : getLibraries()) {
SDL.loadLibrary(lib);
}
}
//如果添加了com.getkeepsafe.relinker.ReLinker这个库,就会用它就加载。没有的话,就系统默认的方法。
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
if (libraryName == null) {
throw new NullPointerException("No library name provided.");
}
try {
Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
Class contextClass = mContext.getClassLoader().loadClass("android.content.Context");
Class stringClass = mContext.getClassLoader().loadClass("java.lang.String");
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
// they've changed during updates.
Method forceMethod = relinkClass.getDeclaredMethod("force");
Object relinkInstance = forceMethod.invoke(null);
Class relinkInstanceClass = relinkInstance.getClass();
// Actually load the library!
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
}
catch (final Throwable e) {
// Fall back
try {
System.loadLibrary(libraryName);
}
catch (final UnsatisfiedLinkError ule) {
throw ule;
}
catch (final SecurityException se) {
throw se;
}
}
}
SDL.setupJNI()
这个方法会对SDL中的模块进行初始化。
public static void setupJNI() {
//SDLActivity中的JNI方法进行初始化。可以一定程度的认为是音频系统的初始化
SDLActivity.nativeSetupJNI();
//音频系统的初始化
SDLAudioManager.nativeSetupJNI();
// 控制系统的初始化
SDLControllerManager.nativeSetupJNI();
}
SDLActivity::nativeSetUpJNI
进入到SDL_android.c
文件中。
我们首先来看一下方法签名的定义方式。这里和通常直接写方法签名的方式不同。
SDL
使用拼接的方式,来完成我们的JNI方法的定义的。
#define SDL_JAVA_PREFIX org_libsdl_app
#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function)
#define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function
#define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
这样的好处,是我们可以方便的修改JNI方法的类。
我们可以看到
SDL_JAVA_PREFIX
这个宏对应的是 我们的包名。SDL_JAVA_INTERFACE
的值CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
中的SDLActivity
对应的就是我们的类名。
如果我们修改了包名和类名。只要过来修改这两个宏的值就可以了。
是不是超级方便~
其实如果熟悉JNI
的话,也会很清楚JNI
方法名定义的套路的。就和这里的CONCAT2
宏定义的一样。这儿就不细说了。
定义的话,就接着自己补齐JNIEXPORT 和返回的变量 和变量名称和参数签名就可以了。
- nativeSetupJNI的完整方法签名
/* Java class SDLActivity */
JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
JNIEnv* mEnv, jclass cls);
因为这些都和包名和类没有关系,所以通过这样的宏,就可以把两者解耦,达到随意改类名和包名了。
- nativeSetupJNI的方法实现
- 将当前的线程attached到当前APP的JVM线程当中
JNIEnv* Android_JNI_GetEnv(void)
{
/*根据JNI的调试,所有的线程都是Linux线程,在c中创建了线程,并且attached 到
JavaVM上。
在该线程上就可以有了JVM环境,可以调用JNI的方法。
如果attached 一个原生创建的线程会直接在main 线程组创建一个线程对象。
AttachCurrentThread可以随意调用。不管是否已经attached过。
*/
JNIEnv *env;
int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
if(status < 0) {
LOGE("failed to attach current thread");
return 0;
}
/* 调用了这个方法,就是在thread_local中把JNIEnv保存起来。
这样的话,会自动创建一个解构方法,在线程终止时。该解构方法,会自动调用DetachCurrentThread 。
因为AttachCurrentThread 和DetachCurrentThread 方法是期待成对出现的。我们用了下面方法,就可以不自己写DetachCurrentThread 了。
*/
pthread_setspecific(mThreadKey, (void*) env);
return env;
}
//上面的mThreadKey,是在加载库初始化的是,创建的如下。对应的解构函数也是自己传入的。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
mJavaVM = vm;
LOGI("JNI_OnLoad called");
if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("Failed to get the environment using GetEnv()");
return -1;
}
if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
__android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
}
Android_JNI_SetupThread();
return JNI_VERSION_1_4;
}
static void Android_JNI_ThreadDestroyed(void* value)
{
/* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
JNIEnv *env = (JNIEnv*) value;
if (env != NULL) {
(*mJavaVM)->DetachCurrentThread(mJavaVM);
pthread_setspecific(mThreadKey, NULL);
}
}
总结下来,创建mThreadKey有两个好处。
- 可以在JVM调试的时候,对给线程进行调试
- 可以传入解构函数
- 就是讲需要通过JNI调用的Native方法的函数都缓存起来。
mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"getNativeSurface","()Landroid/view/Surface;");
midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"setActivityTitle","(Ljava/lang/String;)Z");
midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"setWindowStyle","(Z)V");
midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"setOrientation","(IIZLjava/lang/String;)V");
midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"getContext","()Landroid/content/Context;");
midIsTablet = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"isTablet", "()Z");
midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"isAndroidTV","()Z");
midIsChromebook = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"isChromebook", "()Z");
midIsDeXMode = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"isDeXMode", "()Z");
midManualBackButton = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"manualBackButton", "()V");
midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"inputGetInputDeviceIds", "(I)[I");
midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"sendMessage", "(II)Z");
midShowTextInput = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"showTextInput", "(IIII)Z");
midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"isScreenKeyboardShown","()Z");
midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"clipboardSetText", "(Ljava/lang/String;)V");
midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"clipboardGetText", "()Ljava/lang/String;");
midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"clipboardHasText", "()Z");
midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
"getManifestEnvironmentVariables", "()Z");
midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
midCreateCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "createCustomCursor", "([IIIII)I");
midSetCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setCustomCursor", "(I)Z");
midSetSystemCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setSystemCursor", "(I)Z");
midSupportsRelativeMouse = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "supportsRelativeMouse", "()Z");
midSetRelativeMouseEnabled = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setRelativeMouseEnabled", "(Z)Z");
if (!midGetNativeSurface ||
!midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsTablet || !midIsAndroidTV || !midInputGetInputDeviceIds ||
!midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown ||
!midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
!midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables || !midGetDisplayDPI ||
!midCreateCustomCursor || !midSetCustomCursor || !midSetSystemCursor || !midSupportsRelativeMouse || !midSetRelativeMouseEnabled ||
!midIsChromebook || !midIsDeXMode || !midManualBackButton) {
__android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
}
fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
if (!fidSeparateMouseAndTouch) {
__android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
}
- 检查是否初始化完成。
这里要上面三个模块,全部初始化完整之后,就会将进入SDL_SetMainReady状态
void checkJNIReady()
{
if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
// We aren't fully initialized, let's just return.
return;
}
SDL_SetMainReady();
}
剩下的SDLAudioManager.nativeSetupJNI()
和SDLControllerManager.nativeSetupJNI()
也基本上一样。就不看了。
SDL.initialize()
初始化上面三个的java对象
创建SDLSurface,并添加到跟布局中。
后面还有一些参数设置。
下面就进入SDLSurface的生命周期当中。
在SDLSurface的对应的生命周期中,会调用handleNativeState
对Native的State进行修改。
/* Transition to next state */
public static void handleNativeState() {
if (mNextNativeState == mCurrentNativeState) {
// Already in same state, discard.
return;
}
// Try a transition to init state
if (mNextNativeState == NativeState.INIT) {
mCurrentNativeState = mNextNativeState;
return;
}
// Try a transition to paused state
if (mNextNativeState == NativeState.PAUSED) {
nativePause();
if (mSurface != null)
mSurface.handlePause();
mCurrentNativeState = mNextNativeState;
return;
}
// Try a transition to resumed state
if (mNextNativeState == NativeState.RESUMED) {
if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
if (mSDLThread == null) {
// This is the entry point to the C app.
// Start up the C app thread and enable sensor input for the first time
// FIXME: Why aren't we enabling sensor input at start?
mSDLThread = new Thread(new SDLMain(), "SDLThread");
mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
mSDLThread.start();
}
nativeResume();
mSurface.handleResume();
mCurrentNativeState = mNextNativeState;
}
}
}
在Resume的状态中,会启动一个SDLThread
线程。
线程运行下面这个Runnable .
class SDLMain implements Runnable {
@Override
public void run() {
//得到我们在Activity中定义的参数。
String library = SDLActivity.mSingleton.getMainSharedObject();
//确定运行的朱主函数名称。这里是SDL_main
String function = SDLActivity.mSingleton.getMainFunction();
//我们定义的getArguments 这个在上一遍文章,我们就见过了。并且我们传递了自己的视频路径
String[] arguments = SDLActivity.mSingleton.getArguments();
// 运行nativeRunMain JNI方法
Log.v("SDL", "Running main function " + function + " from library " + library);
SDLActivity.nativeRunMain(library, function, arguments);
Log.v("SDL", "Finished main function");
// Native thread has finished, let's finish the Activity
if (!SDLActivity.mExitCalledFromJava) {
SDLActivity.handleNativeExit();
}
}
}
SDLActivity.java::getMainSharedObject()
这个方法会把我们原来写的 main拼接成正确的名字 libmain.so
注意这个函数,是去取数组的最后一个库,作为主函数坐在的库的。所以如果修改传递的时候,一定要小心。我们现在的主函数库就是libmain
protected String getMainSharedObject() {
String library;
String[] libraries = SDLActivity.mSingleton.getLibraries();
if (libraries.length > 0) {
library = "lib" + libraries[libraries.length - 1] + ".so";
} else {
library = "libmain.so";
}
return getContext().getApplicationInfo().nativeLibraryDir + "/" + library;
}
这里对应的定义了主函数的library和主函数的名称。就是对应了当前项目下的
这个库的名字是我们在CMakeList
当中配置的。
我们这里传入的主函数名称是SDL_Main
,在SDL_main.h
中,由宏定义的。其实对应的是main
方法
项目中
native-lib-su.cpp
中对应的主函数名称是main
.png
进入到nativeRunMain 这个主函数中取看看
/* 开启整个 SDL app的方法,运行在SDLThread当中 */
JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
{
int status = -1;
const char *library_file;
void *library_handle;
__android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
// 会使用dlopen打开我们传入的library
library_file = (*env)->GetStringUTFChars(env, library, NULL);
library_handle = dlopen(library_file, RTLD_GLOBAL);
if (library_handle) {
const char *function_name;
SDL_main_func SDL_main;
function_name = (*env)->GetStringUTFChars(env, function, NULL);
//并且用dlsym 根据我们的函数库和函数名,打开我们定义的主方法。
SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
if (SDL_main) {
int i;
int argc;
int len;
char **argv;
//传入参数
/* Prepare the arguments. */
len = (*env)->GetArrayLength(env, array);
argv = SDL_stack_alloc(char*, 1 + len + 1);
argc = 0;
//这里上一遍文章说过,第一个参数是app_process
/* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
*/
argv[argc++] = SDL_strdup("app_process");
for (i = 0; i < len; ++i) {
const char* utf;
char* arg = NULL;
jstring string = (*env)->GetObjectArrayElement(env, array, i);
if (string) {
utf = (*env)->GetStringUTFChars(env, string, 0);
if (utf) {
arg = SDL_strdup(utf);
(*env)->ReleaseStringUTFChars(env, string, utf);
}
(*env)->DeleteLocalRef(env, string);
}
if (!arg) {
arg = SDL_strdup("");
}
argv[argc++] = arg;
}
argv[argc] = NULL;
/*最后开始运行 */
status = SDL_main(argc, argv);
/* Release the arguments. */
for (i = 0; i < argc; ++i) {
SDL_free(argv[i]);
}
SDL_stack_free(argv);
} else {
__android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
}
(*env)->ReleaseStringUTFChars(env, function, function_name);
//循环退出之后。dlclose这个库
dlclose(library_handle);
} else {
__android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
}
(*env)->ReleaseStringUTFChars(env, library, library_file);
//这一这里,只是终止的是我们的SDLThread而已。
/* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
/* exit(status); */
return status;
}
这样就开始进入到我们自己的
native-lib-su.cpp
中的main方法了。
SDL流程
SDL_Init 方法
参考雷博士的图,只保留了视频和Android的部分.png从SDL_InitSubSystem
(SDL.c)开始初始化。
- SDL_TicksInit()
进入到unix/SDL_systimer.c
方法只是记录了开始的时间。
在这个文件中,我们可以看到经常用的SDL_Delay 就在这里。 - SDL_Delay
void
SDL_Delay(Uint32 ms)
{
int was_error;
#if HAVE_NANOSLEEP
struct timespec elapsed, tv;
#else
struct timeval tv;
Uint32 then, now, elapsed;
#endif
/* Set the timeout interval */
#if HAVE_NANOSLEEP
elapsed.tv_sec = ms / 1000;
elapsed.tv_nsec = (ms % 1000) * 1000000;
#else
then = SDL_GetTicks();
#endif
do {
errno = 0;
#if HAVE_NANOSLEEP
tv.tv_sec = elapsed.tv_sec;
tv.tv_nsec = elapsed.tv_nsec;
was_error = nanosleep(&tv, &elapsed);
#else
/* Calculate the time interval left (in case of interrupt) */
now = SDL_GetTicks();
elapsed = (now - then);
then = now;
if (elapsed >= ms) {
break;
}
ms -= elapsed;
tv.tv_sec = ms / 1000;
tv.tv_usec = (ms % 1000) * 1000;
was_error = select(0, NULL, NULL, NULL, &tv);
#endif /* HAVE_NANOSLEEP */
} while (was_error && (errno == EINTR));
}
#endif /* SDL_TIMER_UNIX */
/* vi: set ts=4 sw=4 expandtab: */
- SDL_StartEventLoop
这个方法中,如果是多线程的话,就会创建互斥锁。
然后通常的配置是禁用掉。
下面三种事件
SDL_EventState(SDL_TEXTINPUT, SDL_DISABLE);
SDL_EventState(SDL_TEXTEDITING, SDL_DISABLE);
SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE);
- SDL_TimerInit
简单的理解就是通过SDL_CreateThreadInternal
创建了一个Timer线程。
下面就进入视频系统的初始化
- SDL_VideoInit(SDL_video.c)
-
Android_InitKeyboard
、Android_InitTouch
、Android_InitMouse
都是通过JNI方法,分别初始化键盘的数组和touch device Id 和Cursor.(setCustomCursor 对应的是android.view.PointerIcon?)
-
VideoBootStrap->available()
和VideoBootStrap->create()
VideoBootStrap
,因为在Android平台下,所以对应的是Android_bootstrap
分别调用
在SDL_androidvideo.c中Android_Available
方法和Android_CreateDevice
方法。 - Android_Available
Android_Available
方法总是返回1,表示可用。因为只有编译在Android平台时,才会去初始化这个Android_bootstrap。 - Android_CreateDevice
static SDL_VideoDevice *
Android_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
SDL_VideoData *data;
/* Initialize all variables that we clean on shutdown */
device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
if (!device) {
SDL_OutOfMemory();
return NULL;
}
data = (SDL_VideoData*) SDL_calloc(1, sizeof(SDL_VideoData));
if (!data) {
SDL_OutOfMemory();
SDL_free(device);
return NULL;
}
device->driverdata = data;
/* Set the function pointers */
device->VideoInit = Android_VideoInit;
device->VideoQuit = Android_VideoQuit;
device->PumpEvents = Android_PumpEvents;
device->GetDisplayDPI = Android_GetDisplayDPI;
device->CreateSDLWindow = Android_CreateWindow;
device->SetWindowTitle = Android_SetWindowTitle;
device->SetWindowFullscreen = Android_SetWindowFullscreen;
device->DestroyWindow = Android_DestroyWindow;
device->GetWindowWMInfo = Android_GetWindowWMInfo;
device->free = Android_DeleteDevice;
/* GL pointers */
device->GL_LoadLibrary = Android_GLES_LoadLibrary;
device->GL_GetProcAddress = Android_GLES_GetProcAddress;
device->GL_UnloadLibrary = Android_GLES_UnloadLibrary;
device->GL_CreateContext = Android_GLES_CreateContext;
device->GL_MakeCurrent = Android_GLES_MakeCurrent;
device->GL_SetSwapInterval = Android_GLES_SetSwapInterval;
device->GL_GetSwapInterval = Android_GLES_GetSwapInterval;
device->GL_SwapWindow = Android_GLES_SwapWindow;
device->GL_DeleteContext = Android_GLES_DeleteContext;
#if SDL_VIDEO_VULKAN
device->Vulkan_LoadLibrary = Android_Vulkan_LoadLibrary;
device->Vulkan_UnloadLibrary = Android_Vulkan_UnloadLibrary;
device->Vulkan_GetInstanceExtensions = Android_Vulkan_GetInstanceExtensions;
device->Vulkan_CreateSurface = Android_Vulkan_CreateSurface;
#endif
/* Screensaver */
device->SuspendScreenSaver = Android_SuspendScreenSaver;
/* Text input */
device->StartTextInput = Android_StartTextInput;
device->StopTextInput = Android_StopTextInput;
device->SetTextInputRect = Android_SetTextInputRect;
/* Screen keyboard */
device->HasScreenKeyboardSupport = Android_HasScreenKeyboardSupport;
device->IsScreenKeyboardShown = Android_IsScreenKeyboardShown;
/* Clipboard */
device->SetClipboardText = Android_SetClipboardText;
device->GetClipboardText = Android_GetClipboardText;
device->HasClipboardText = Android_HasClipboardText;
return device;
}
其实可以看到这里如果不是使用Vulcan的话,就使用的是OpenGLes
这些个方法指针,其实提前都被定义好了。
基本上都是GL的方法,其他的都在对应的java类中。最开始初始化的时候,能够看到。
- SDL_AudioInit
同样进入SDL_androidaudio.c
中。
ANDROIDAUDIO_Init
相关的方法,都在AudioManager当中。暂时省略不提。
其他的暂时不看了。
SDL_Window 初始化
简化只显示Android部分.pngSDL_init
之后,我们就创建了SDL_window
- SDL_CreateWindow 方法
参数含义如下。
title :窗口标题
x :窗口位置x坐标。也可以设置为SDL_WINDOWPOS_CENTERED或SDL_WINDOWPOS_UNDEFINED。
y :窗口位置y坐标。同上。
w :窗口的宽
h :窗口的高
flags :支持下列标识。包括了窗口的是否最大化、最小化,能否调整边界等等属性。
::SDL_WINDOW_FULLSCREEN, ::SDL_WINDOW_OPENGL,
::SDL_WINDOW_HIDDEN, ::SDL_WINDOW_BORDERLESS,
::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED,
::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_INPUT_GRABBED,
::SDL_WINDOW_ALLOW_HIGHDPI.
返回创建完成的窗口的ID。如果创建失败则返回0。
会进入SDL_androidwindow.c中去创建Window
给window 设置上各种Flag
会通过SDLSurface中的getNativeSurface,然后通过ANativeWindow_fromSurface来获取一个NativeWindow。
SDL_EGL_CreateSurface 然后创建传递给OpenGL 作为Surface
在SDL_egl.c中
EGLSurface *
SDL_EGL_CreateSurface(_THIS, NativeWindowType nw)
{
/* max 2 values plus terminator. */
EGLint attribs[3];
int attr = 0;
EGLSurface * surface;
if (SDL_EGL_ChooseConfig(_this) != 0) {
return EGL_NO_SURFACE;
}
#if SDL_VIDEO_DRIVER_ANDROID
{
/* Android docs recommend doing this!
* Ref: http://developer.android.com/reference/android/app/NativeActivity.html
*/
EGLint format;
_this->egl_data->eglGetConfigAttrib(_this->egl_data->egl_display,
_this->egl_data->egl_config,
EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(nw, 0, 0, format);
}
#endif
if (_this->gl_config.framebuffer_srgb_capable) {
#ifdef EGL_KHR_gl_colorspace
if (SDL_EGL_HasExtension(_this, SDL_EGL_DISPLAY_EXTENSION, "EGL_KHR_gl_colorspace")) {
attribs[attr++] = EGL_GL_COLORSPACE_KHR;
attribs[attr++] = EGL_GL_COLORSPACE_SRGB_KHR;
} else
#endif
{
SDL_SetError("EGL implementation does not support sRGB system framebuffers");
return EGL_NO_SURFACE;
}
}
attribs[attr++] = EGL_NONE;
surface = _this->egl_data->eglCreateWindowSurface(
_this->egl_data->egl_display,
_this->egl_data->egl_config,
nw, &attribs[0]);
if (surface == EGL_NO_SURFACE) {
SDL_EGL_SetError("unable to create an EGL window surface", "eglCreateWindowSurface");
}
return surface;
}
可以看到这个过程中,创建OpenGL的步骤其实和java中创建基本上一样的。
额外需要做的是,NativeWindow需要ANativeWindow_setBuffersGeometry 方法,先设置一次Buffer的大小。
接下来是
SDL_SetWindowTitle
也是调用SDLActivity中的方法
接下来是SDL _FinishWindowCreation
static void
SDL_FinishWindowCreation(SDL_Window *window, Uint32 flags)
{
PrepareDragAndDropSupport(window);
if (flags & SDL_WINDOW_MAXIMIZED) {
SDL_MaximizeWindow(window);
}
if (flags & SDL_WINDOW_MINIMIZED) {
SDL_MinimizeWindow(window);
}
if (flags & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(window, flags);
}
if (flags & SDL_WINDOW_INPUT_GRABBED) {
SDL_SetWindowGrab(window, SDL_TRUE);
}
if (!(flags & SDL_WINDOW_HIDDEN)) {
SDL_ShowWindow(window);
}
}
其实就是设置window的各种状态
这几个方法,android中基本上都没有。只有设置SetWindowFullscreen 有对应的方法
先会去调用setWindowStyle方法,然后修改nativeWindow 的大小。
SDL_androidwindow.c
void
Android_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
{
/* If the window is being destroyed don't change visible state */
if (!window->is_destroying) {
Android_JNI_SetWindowStyle(fullscreen);
}
/* Ensure our size matches reality after we've executed the window style change.
*
* It is possible that we've set width and height to the full-size display, but on
* Samsung DeX or Chromebooks or other windowed Android environemtns, our window may
* still not be the full display size.
*/
if (!SDL_IsDeXMode() && !SDL_IsChromebook()) {
return;
}
SDL_WindowData * data = (SDL_WindowData *)window->driverdata;
if (!data || !data->native_window) {
return;
}
int old_w = window->w;
int old_h = window->h;
int new_w = ANativeWindow_getWidth(data->native_window);
int new_h = ANativeWindow_getHeight(data->native_window);
if (old_w != new_w || old_h != new_h) {
SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, new_w, new_h);
}
}
SDL_window 结构
/**
* \brief The type used to identify a window
*
* \sa SDL_CreateWindow()
* \sa SDL_CreateWindowFrom()
* \sa SDL_DestroyWindow()
* \sa SDL_GetWindowData()
* \sa SDL_GetWindowFlags()
* \sa SDL_GetWindowGrab()
* \sa SDL_GetWindowPosition()
* \sa SDL_GetWindowSize()
* \sa SDL_GetWindowTitle()
* \sa SDL_HideWindow()
* \sa SDL_MaximizeWindow()
* \sa SDL_MinimizeWindow()
* \sa SDL_RaiseWindow()
* \sa SDL_RestoreWindow()
* \sa SDL_SetWindowData()
* \sa SDL_SetWindowFullscreen()
* \sa SDL_SetWindowGrab()
* \sa SDL_SetWindowIcon()
* \sa SDL_SetWindowPosition()
* \sa SDL_SetWindowSize()
* \sa SDL_SetWindowBordered()
* \sa SDL_SetWindowResizable()
* \sa SDL_SetWindowTitle()
* \sa SDL_ShowWindow()
*/
typedef struct SDL_Window SDL_Window;
typedef struct
{
EGLSurface egl_surface;
EGLContext egl_context; /* We use this to preserve the context when losing focus */
ANativeWindow* native_window;
} SDL_WindowData;
-
渲染器的初始化
SDL_CreateRenderer.png
SDL_CreateRenderer
/**
* \brief Create a 2D rendering context for a window.
*
* \param window The window where rendering is displayed.
* \param index The index of the rendering driver to initialize, or -1 to
* initialize the first one supporting the requested flags.
* \param flags ::SDL_RendererFlags.
*
* \return A valid rendering context or NULL if there was an error.
*
* \sa SDL_CreateSoftwareRenderer()
* \sa SDL_GetRendererInfo()
* \sa SDL_DestroyRenderer()
*/
- 判断是否已经被连接了window 了
- 同样去driver中,去得到争取的Renderer
可以看到Android中,用的是GLRenderer
#if SDL_VIDEO_RENDER_OGL_ES2
&GLES2_RenderDriver,
#endif
#if SDL_VIDEO_RENDER_OGL_ES
&GLES_RenderDriver,
#endif
GLES2_RenderDriver
SDL_RenderDriver GLES2_RenderDriver = {
GLES2_CreateRenderer,
{
"opengles2",
(SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
4,
{
SDL_PIXELFORMAT_ARGB8888,
SDL_PIXELFORMAT_ABGR8888,
SDL_PIXELFORMAT_RGB888,
SDL_PIXELFORMAT_BGR888
},
0,
0
}
};
SDL_render_gles2 中
GLES2_CreateRenderer 方法
static SDL_Renderer *
GLES2_CreateRenderer(SDL_Window *window, Uint32 flags)
{
SDL_Renderer *renderer;
GLES2_DriverContext *data;
GLint nFormats;
#ifndef ZUNE_HD
GLboolean hasCompiler;
#endif
Uint32 window_flags = 0; /* -Wconditional-uninitialized */
GLint window_framebuffer;
GLint value;
int profile_mask = 0, major = 0, minor = 0;
SDL_bool changed_window = SDL_FALSE;
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask) < 0) {
goto error;
}
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major) < 0) {
goto error;
}
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor) < 0) {
goto error;
}
window_flags = SDL_GetWindowFlags(window);
/* OpenGL ES 3.0 is a superset of OpenGL ES 2.0 */
if (!(window_flags & SDL_WINDOW_OPENGL) ||
profile_mask != SDL_GL_CONTEXT_PROFILE_ES || major < RENDERER_CONTEXT_MAJOR) {
changed_window = SDL_TRUE;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR);
if (SDL_RecreateWindow(window, window_flags | SDL_WINDOW_OPENGL) < 0) {
goto error;
}
}
/* Create the renderer struct */
renderer = (SDL_Renderer *)SDL_calloc(1, sizeof(SDL_Renderer));
if (!renderer) {
SDL_OutOfMemory();
goto error;
}
data = (GLES2_DriverContext *)SDL_calloc(1, sizeof(GLES2_DriverContext));
if (!data) {
GLES2_DestroyRenderer(renderer);
SDL_OutOfMemory();
goto error;
}
renderer->info = GLES2_RenderDriver.info;
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
renderer->driverdata = data;
renderer->window = window;
/* Create an OpenGL ES 2.0 context */
data->context = SDL_GL_CreateContext(window);
if (!data->context) {
GLES2_DestroyRenderer(renderer);
goto error;
}
if (SDL_GL_MakeCurrent(window, data->context) < 0) {
GLES2_DestroyRenderer(renderer);
goto error;
}
if (GLES2_LoadFunctions(data) < 0) {
GLES2_DestroyRenderer(renderer);
goto error;
}
#if __WINRT__
/* DLudwig, 2013-11-29: ANGLE for WinRT doesn't seem to work unless VSync
* is turned on. Not doing so will freeze the screen's contents to that
* of the first drawn frame.
*/
flags |= SDL_RENDERER_PRESENTVSYNC;
#endif
if (flags & SDL_RENDERER_PRESENTVSYNC) {
SDL_GL_SetSwapInterval(1);
} else {
SDL_GL_SetSwapInterval(0);
}
if (SDL_GL_GetSwapInterval() > 0) {
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
}
/* Check for debug output support */
if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &value) == 0 &&
(value & SDL_GL_CONTEXT_DEBUG_FLAG)) {
data->debug_enabled = SDL_TRUE;
}
value = 0;
data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
renderer->info.max_texture_width = value;
value = 0;
data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
renderer->info.max_texture_height = value;
/* Determine supported shader formats */
/* HACK: glGetInteger is broken on the Zune HD's compositor, so we just hardcode this */
#ifdef ZUNE_HD
nFormats = 1;
#else /* !ZUNE_HD */
data->glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &nFormats);
data->glGetBooleanv(GL_SHADER_COMPILER, &hasCompiler);
if (hasCompiler) {
++nFormats;
}
#endif /* ZUNE_HD */
data->shader_formats = (GLenum *)SDL_calloc(nFormats, sizeof(GLenum));
if (!data->shader_formats) {
GLES2_DestroyRenderer(renderer);
SDL_OutOfMemory();
goto error;
}
data->shader_format_count = nFormats;
#ifdef ZUNE_HD
data->shader_formats[0] = GL_NVIDIA_PLATFORM_BINARY_NV;
#else /* !ZUNE_HD */
data->glGetIntegerv(GL_SHADER_BINARY_FORMATS, (GLint *)data->shader_formats);
if (hasCompiler) {
data->shader_formats[nFormats - 1] = (GLenum)-1;
}
#endif /* ZUNE_HD */
data->framebuffers = NULL;
data->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &window_framebuffer);
data->window_framebuffer = (GLuint)window_framebuffer;
/* Populate the function pointers for the module */
renderer->WindowEvent = GLES2_WindowEvent;
renderer->GetOutputSize = GLES2_GetOutputSize;
renderer->SupportsBlendMode = GLES2_SupportsBlendMode;
renderer->CreateTexture = GLES2_CreateTexture;
renderer->UpdateTexture = GLES2_UpdateTexture;
renderer->UpdateTextureYUV = GLES2_UpdateTextureYUV;
renderer->LockTexture = GLES2_LockTexture;
renderer->UnlockTexture = GLES2_UnlockTexture;
renderer->SetRenderTarget = GLES2_SetRenderTarget;
renderer->UpdateViewport = GLES2_UpdateViewport;
renderer->UpdateClipRect = GLES2_UpdateClipRect;
renderer->RenderClear = GLES2_RenderClear;
renderer->RenderDrawPoints = GLES2_RenderDrawPoints;
renderer->RenderDrawLines = GLES2_RenderDrawLines;
renderer->RenderFillRects = GLES2_RenderFillRects;
renderer->RenderCopy = GLES2_RenderCopy;
renderer->RenderCopyEx = GLES2_RenderCopyEx;
renderer->RenderReadPixels = GLES2_RenderReadPixels;
renderer->RenderPresent = GLES2_RenderPresent;
renderer->DestroyTexture = GLES2_DestroyTexture;
renderer->DestroyRenderer = GLES2_DestroyRenderer;
renderer->GL_BindTexture = GLES2_BindTexture;
renderer->GL_UnbindTexture = GLES2_UnbindTexture;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV12;
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV21;
#ifdef GL_TEXTURE_EXTERNAL_OES
renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_EXTERNAL_OES;
#endif
GLES2_ResetState(renderer);
return renderer;
error:
if (changed_window) {
/* Uh oh, better try to put it back... */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
SDL_RecreateWindow(window, window_flags);
}
return NULL;
}
#endif /* SDL_VIDEO_RENDER_OGL_ES2 && !SDL_RENDER_DISABLED */
经过一系列对window flag的判断,最终走到了创建GLContext 和 MakeCurrent 上
GLES2_LoadFunctions
接着设置刷新率
SDL_GL_SetSwapInterval 如果是跟着设备的v ysn同步信号的话,就是1 否则是0
最后将各种renderer中的各种gl方法,进行初始化。
GLES2_CreateTexture 方法,是创建纹理的方法,可以看到,确实是熟悉的问题,创建纹理的过程。不同的是,如果是yuv就会创建3个纹理 。
static int
GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture)
{
GLES2_DriverContext *renderdata = (GLES2_DriverContext *)renderer->driverdata;
GLES2_TextureData *data;
GLenum format;
GLenum type;
GLenum scaleMode;
GLES2_ActivateRenderer(renderer);
/* Determine the corresponding GLES texture format params */
switch (texture->format)
{
case SDL_PIXELFORMAT_ARGB8888:
case SDL_PIXELFORMAT_ABGR8888:
case SDL_PIXELFORMAT_RGB888:
case SDL_PIXELFORMAT_BGR888:
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
case SDL_PIXELFORMAT_IYUV:
case SDL_PIXELFORMAT_YV12:
case SDL_PIXELFORMAT_NV12:
case SDL_PIXELFORMAT_NV21:
format = GL_LUMINANCE;
type = GL_UNSIGNED_BYTE;
break;
#ifdef GL_TEXTURE_EXTERNAL_OES
case SDL_PIXELFORMAT_EXTERNAL_OES:
format = GL_NONE;
type = GL_NONE;
break;
#endif
default:
return SDL_SetError("Texture format not supported");
}
if (texture->format == SDL_PIXELFORMAT_EXTERNAL_OES &&
texture->access != SDL_TEXTUREACCESS_STATIC) {
return SDL_SetError("Unsupported texture access for SDL_PIXELFORMAT_EXTERNAL_OES");
}
/* Allocate a texture struct */
data = (GLES2_TextureData *)SDL_calloc(1, sizeof(GLES2_TextureData));
if (!data) {
return SDL_OutOfMemory();
}
data->texture = 0;
#ifdef GL_TEXTURE_EXTERNAL_OES
data->texture_type = (texture->format == SDL_PIXELFORMAT_EXTERNAL_OES) ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
#else
data->texture_type = GL_TEXTURE_2D;
#endif
data->pixel_format = format;
data->pixel_type = type;
data->yuv = ((texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12));
data->nv12 = ((texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21));
data->texture_u = 0;
data->texture_v = 0;
scaleMode = (texture->scaleMode == SDL_ScaleModeNearest) ? GL_NEAREST : GL_LINEAR;
/* Allocate a blob for image renderdata */
if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
size_t size;
data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
size = texture->h * data->pitch;
if (data->yuv) {
/* Need to add size for the U and V planes */
size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
}
if (data->nv12) {
/* Need to add size for the U/V plane */
size += 2 * ((texture->h + 1) / 2) * ((data->pitch + 1) / 2);
}
data->pixel_data = SDL_calloc(1, size);
if (!data->pixel_data) {
SDL_free(data);
return SDL_OutOfMemory();
}
}
/* Allocate the texture */
GL_CheckError("", renderer);
if (data->yuv) {
renderdata->glGenTextures(1, &data->texture_v);
if (GL_CheckError("glGenTexures()", renderer) < 0) {
return -1;
}
renderdata->glActiveTexture(GL_TEXTURE2);
renderdata->glBindTexture(data->texture_type, data->texture_v);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
renderdata->glGenTextures(1, &data->texture_u);
if (GL_CheckError("glGenTexures()", renderer) < 0) {
return -1;
}
renderdata->glActiveTexture(GL_TEXTURE1);
renderdata->glBindTexture(data->texture_type, data->texture_u);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
renderdata->glTexImage2D(data->texture_type, 0, format, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, format, type, NULL);
if (GL_CheckError("glTexImage2D()", renderer) < 0) {
return -1;
}
}
if (data->nv12) {
renderdata->glGenTextures(1, &data->texture_u);
if (GL_CheckError("glGenTexures()", renderer) < 0) {
return -1;
}
renderdata->glActiveTexture(GL_TEXTURE1);
renderdata->glBindTexture(data->texture_type, data->texture_u);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
renderdata->glTexImage2D(data->texture_type, 0, GL_LUMINANCE_ALPHA, (texture->w + 1) / 2, (texture->h + 1) / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, NULL);
if (GL_CheckError("glTexImage2D()", renderer) < 0) {
return -1;
}
}
renderdata->glGenTextures(1, &data->texture);
if (GL_CheckError("glGenTexures()", renderer) < 0) {
return -1;
}
texture->driverdata = data;
renderdata->glActiveTexture(GL_TEXTURE0);
renderdata->glBindTexture(data->texture_type, data->texture);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MIN_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_MAG_FILTER, scaleMode);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
renderdata->glTexParameteri(data->texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (texture->format != SDL_PIXELFORMAT_EXTERNAL_OES) {
renderdata->glTexImage2D(data->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL);
if (GL_CheckError("glTexImage2D()", renderer) < 0) {
return -1;
}
}
if (texture->access == SDL_TEXTUREACCESS_TARGET) {
data->fbo = GLES2_GetFBO(renderer->driverdata, texture->w, texture->h);
} else {
data->fbo = NULL;
}
return GL_CheckError("", renderer);
}
update
static int
GLES2_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect,
const void *pixels, int pitch)
{
GLES2_DriverContext *data = (GLES2_DriverContext *)renderer->driverdata;
GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata;
GLES2_ActivateRenderer(renderer);
/* Bail out if we're supposed to update an empty rectangle */
if (rect->w <= 0 || rect->h <= 0) {
return 0;
}
/* Create a texture subimage with the supplied data */
data->glBindTexture(tdata->texture_type, tdata->texture);
GLES2_TexSubImage2D(data, tdata->texture_type,
rect->x,
rect->y,
rect->w,
rect->h,
tdata->pixel_format,
tdata->pixel_type,
pixels, pitch, SDL_BYTESPERPIXEL(texture->format));
if (tdata->yuv) {
/* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
if (texture->format == SDL_PIXELFORMAT_YV12) {
data->glBindTexture(tdata->texture_type, tdata->texture_v);
} else {
data->glBindTexture(tdata->texture_type, tdata->texture_u);
}
GLES2_TexSubImage2D(data, tdata->texture_type,
rect->x / 2,
rect->y / 2,
(rect->w + 1) / 2,
(rect->h + 1) / 2,
tdata->pixel_format,
tdata->pixel_type,
pixels, (pitch + 1) / 2, 1);
/* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
if (texture->format == SDL_PIXELFORMAT_YV12) {
data->glBindTexture(tdata->texture_type, tdata->texture_u);
} else {
data->glBindTexture(tdata->texture_type, tdata->texture_v);
}
GLES2_TexSubImage2D(data, tdata->texture_type,
rect->x / 2,
rect->y / 2,
(rect->w + 1) / 2,
(rect->h + 1) / 2,
tdata->pixel_format,
tdata->pixel_type,
pixels, (pitch + 1) / 2, 1);
}
if (tdata->nv12) {
/* Skip to the correct offset into the next texture */
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
data->glBindTexture(tdata->texture_type, tdata->texture_u);
GLES2_TexSubImage2D(data, tdata->texture_type,
rect->x / 2,
rect->y / 2,
(rect->w + 1) / 2,
(rect->h + 1) / 2,
GL_LUMINANCE_ALPHA,
GL_UNSIGNED_BYTE,
pixels, 2 * ((pitch + 1) / 2), 2);
}
return GL_CheckError("glTexSubImage2D()", renderer);
}
GLES2_TexSubImage2D 方法
static int
GLES2_TexSubImage2D(GLES2_DriverContext *data, GLenum target, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels, GLint pitch, GLint bpp)
{
Uint8 *blob = NULL;
Uint8 *src;
int src_pitch;
int y;
if ((width == 0) || (height == 0) || (bpp == 0)) {
return 0; /* nothing to do */
}
/* Reformat the texture data into a tightly packed array */
src_pitch = width * bpp;
src = (Uint8 *)pixels;
if (pitch != src_pitch) {
blob = (Uint8 *)SDL_malloc(src_pitch * height);
if (!blob) {
return SDL_OutOfMemory();
}
src = blob;
for (y = 0; y < height; ++y)
{
SDL_memcpy(src, pixels, src_pitch);
src += src_pitch;
pixels = (Uint8 *)pixels + pitch;
}
src = blob;
}
data->glTexSubImage2D(target, 0, xoffset, yoffset, width, height, format, type, src);
if (blob) {
SDL_free(blob);
}
return 0;
}
GLES2_UpdateTextureYUV 方法中,就是减少了判断。
SDL_RenderCopy 方法会将render 中的参数,使用texture来进行绘制。
GLES2_SetupCopy-》GLES2_SelectProgram
这个过程中,会进行创建Shader Fragment着色器,创建program 来进行使用。
同时绑定纹理,坐标系和blendMode 做好绘制的准备
GLES2_RenderReadPixels 读取像素
GLES2_RenderPresent 调用Swap方法 SwapBuffers
-
纹理SDL_Texture
image.png
/**
* \brief Create a texture for a rendering context.
*
* \param renderer The renderer.
* \param format The format of the texture.
* \param access One of the enumerated values in ::SDL_TextureAccess.
* \param w The width of the texture in pixels.
* \param h The height of the texture in pixels.
*
* \return The created texture is returned, or NULL if no rendering context was
* active, the format was unsupported, or the width or height were out
* of range.
*
* \note The contents of the texture are not defined at creation.
*
* \sa SDL_QueryTexture()
* \sa SDL_UpdateTexture()
* \sa SDL_DestroyTexture()
*/
SDL循环渲染数据
update_circle.png总结
image.png