SDL2库(3)-Android(安卓)端源码简要分析(VideoSubSystem)
项目位置 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
voidSDL_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 voidSDL_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
voidAndroid_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 intGLES2_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 intGLES2_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 intGLES2_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
参考
雷神SDL2源代码分析系列
更多相关文章
- Android四种联网方式
- Android中对话框(Dialog)的创建方法
- Android窗口机制之由setContentView引发的Window,PhoneWindow,Deco
- Android完全关闭应用程序
- Android官方架构组件ViewModel+LiveData+DataBinding架构属于自
- Android(安卓)MediaPlayer 字幕同步
- Android(安卓)8.1 来电默认全屏显示 如何修改
- Android(安卓)Studio finish()方法的使用与解决app点击“返回”,
- Android(安卓)EventBus 架构设计