美文网首页OpenGL渲染
SDL2库(3)-Android 端源码简要分析(VideoSu

SDL2库(3)-Android 端源码简要分析(VideoSu

作者: deep_sadness | 来源:发表于2018-11-19 13:58 被阅读51次
    SDL.png

    项目位置 https://github.com/deepsadness/SDLCmakeDemo

    系列内容导读

    1. SDL2-移植Android Studio+CMakeList集成
    2. Android端FFmpeg +SDL2的简单播放器
    3. SDL2 Android端的简要分析(VideoSubSystem)
    4. 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方法的类。

    目前的包名和类名.png
    我们可以看到
    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的方法实现
    1. 将当前的线程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有两个好处。

    1. 可以在JVM调试的时候,对给线程进行调试
    2. 可以传入解构函数
    1. 就是讲需要通过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?");
        }
    
    1. 检查是否初始化完成。
      这里要上面三个模块,全部初始化完整之后,就会将进入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当中配置的。

    CMakeList中的配置

    我们这里传入的主函数名称是SDL_Main,在SDL_main.h中,由宏定义的。其实对应的是main方法

    SDL_main中的宏 SDL_main指向main.png
    项目中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_InitKeyboardAndroid_InitTouchAndroid_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部分.png

    SDL_init之后,我们就创建了SDL_window

    在SDL_Init之后,就进入了SDL_CreateWindow .png
    • 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;
    
    1. 渲染器的初始化


      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()
     */
    
    1. 判断是否已经被连接了window 了
    2. 同样去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

    1. 纹理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源代码分析系列

    相关文章

      网友评论

        本文标题:SDL2库(3)-Android 端源码简要分析(VideoSu

        本文链接:https://www.haomeiwen.com/subject/htzrfqtx.html