উদাহরণ: টি-পট

NDK ইনস্টলেশনের রুট ডিরেক্টরির অধীনে samples/Teapot/ ডিরেক্টরিতে টিপট স্যাম্পেলটি রয়েছে। এই স্যাম্পেলটি আইকনিক Utah teapot রেন্ডার করতে OpenGL লাইব্রেরি ব্যবহার করে। বিশেষ করে, এটি ndk_helper হেল্পার ক্লাস দেখায়, যা নেটিভ অ্যাপ্লিকেশন হিসেবে গেম এবং অনুরূপ অ্যাপ্লিকেশন বাস্তবায়নের জন্য প্রয়োজনীয় নেটিভ হেল্পার ফাংশনের একটি সংগ্রহ। এই ক্লাস থেকে আপনি এগুলি পাবেন:

  • একটি অ্যাবস্ট্রাকশন লেয়ার, GLContext, যা নির্দিষ্ট NDK-নির্দিষ্ট আচরণ ম্যানেজ করে।
  • সহায়ক ফাংশন যা উপযোগী কিন্তু NDK-তে নেই, যেমন ট্যাপ শনাক্তকরণ।
  • টেক্সচার লোডিংয়ের মতো প্ল্যাটফর্ম ফিচারের জন্য JNI কলের র‍্যাপার।

AndroidManifest.xml

এখানে অ্যাক্টিভিটি ডিক্লারেশন NativeActivity নয়, বরং এর সাবক্লাস: TeapotNativeActivity।

<activity android:name="com.sample.teapot.TeapotNativeActivity"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden">

শেষ পর্যন্ত, বিল্ড সিস্টেম যে শেয়ার করা অবজেক্ট ফাইল তৈরি করে তার নাম হল libTeapotNativeActivity.so। বিল্ড সিস্টেম lib প্রিফিক্স এবং .so এক্সটেনশন যোগ করে; কোনওটিই সেই মানের অংশ নয় যা ম্যানিফেস্ট মূলত android:value-এ অ্যাসাইন করে।

<meta-data android:name="android.app.lib_name"
        android:value="TeapotNativeActivity" />

Application.mk

NativeActivity ফ্রেমওয়ার্ক ক্লাস ব্যবহার করে এমন অ্যাপের ক্ষেত্রে Android API লেভেল ৯-এর কম হলে চলবে না, কারণ এই ক্লাসের সূচনা এই লেভেলেই হয়েছে। NativeActivity ক্লাস সম্পর্কে আরও তথ্যের জন্য, নেটিভ অ্যাক্টিভিটি ও অ্যাপ্লিকেশন দেখুন।

APP_PLATFORM := android-9

পরের লাইনটি বিল্ড সিস্টেমকে বলে যে কাজ করে এমন সব আর্কিটেকচারের জন্য বিল্ড করতে।

APP_ABI := all

এরপর, ফাইলটি বিল্ড সিস্টেমকে বলে যে কোন C++ রানটাইম সাপোর্ট লাইব্রেরি ব্যবহার করতে হবে।

APP_STL := stlport_static

জাভা-সাইড ইমপ্লিমেন্টেশন

TeapotNativeActivity ফাইলটি teapots/classic-teapot/src/com/sample/teapot-এ, GitHub-এ NDK রেপো রুট ডিরেক্টরি-এর অধীনে অবস্থিত। এটি অ্যাক্টিভিটি লাইফসাইকেল ইভেন্ট ম্যানেজ করে, ShowUI() ফাংশনের মাধ্যমে স্ক্রিনে টেক্সট দেখানোর জন্য একটি পপ-আপ উইন্ডো তৈরি করে এবং updateFPS() ফাংশনের মাধ্যমে ডায়নামিকভাবে ফ্রেম রেট আপডেট করে। নিম্নলিখিত কোডটি আপনার কাছে আকর্ষণীয় হতে পারে কারণ এটি অ্যাপের অ্যাক্টিভিটিকে ফুল-স্ক্রিন, ইমার্সিভ এবং সিস্টেম নেভিগেশন বার ছাড়াই প্রস্তুত করে, যাতে পুরো স্ক্রিনটি রেন্ডার করা টিপট ফ্রেম দেখানোর জন্য ব্যবহার করা যেতে পারে:

Kotlin

fun setImmersiveSticky() {
    window.decorView.systemUiVisibility = (
            View.SYSTEM_UI_FLAG_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            )
}

জাভা

void setImmersiveSticky() {
    View decorView = getWindow().getDecorView();
    decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}

নেটিভ-সাইড ইমপ্লিমেন্টেশন

এই বিভাগে C++-এ প্রয়োগ করা Teapot অ্যাপের অংশটি এক্সপ্লোর করা হয়েছে।

TeapotRenderer.h

এইসব ফাংশন কল টিপটের আসল রেন্ডারিং পারফর্ম করে। এটি ম্যাট্রিক্স গণনা করতে এবং ব্যবহারকারী যেখানে ট্যাপ করেন তার উপর ভিত্তি করে ক্যামেরার অবস্থান পরিবর্তন করতে ndk_helper ব্যবহার করে।

ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
ndk_helper::Mat4 mat_model_;


ndk_helper::TapCamera* camera_;

TeapotNativeActivity.cpp

নিম্নলিখিত লাইনে নেটিভ সোর্স ফাইলে ndk_helper অন্তর্ভুক্ত করা আছে এবং হেল্পার-ক্লাসের নাম উল্লেখ করা আছে।

#include "NDKHelper.h"

//-------------------------------------------------------------------------
//Preprocessor
//-------------------------------------------------------------------------
#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper
function

ndk_helper ক্লাসের প্রথম ব্যবহার হল EGL-সম্পর্কিত লাইফসাইকেল ম্যানেজ করা, EGL কনটেক্সট স্টেট (তৈরি/হারানো) Android লাইফসাইকেল ইভেন্টের সাথে যুক্ত করা। ndk_helper ক্লাস অ্যাপ্লিকেশনকে প্রসঙ্গ তথ্য সংরক্ষণ করতে সক্ষম করে যাতে সিস্টেম একটি ধ্বংস করা অ্যাক্টিভিটি পুনরুদ্ধার করতে পারে। এই ক্ষমতাটি উপযোগী, উদাহরণস্বরূপ, যখন টার্গেট মেশিনটি ঘোরানো হয় (যার ফলে একটি অ্যাক্টিভিটি ধ্বংস হয়ে যায়, তারপরে অবিলম্বে নতুন ওরিয়েন্টেশনে রিস্টোর করা হয়), বা যখন লক স্ক্রিনটি উপস্থিত হয়।

ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.

এরপর, ndk_helper টাচ কন্ট্রোল প্রদান করে।

ndk_helper::DoubletapDetector doubletap_detector_;
ndk_helper::PinchDetector pinch_detector_;
ndk_helper::DragDetector drag_detector_;
ndk_helper::PerfMonitor monitor_;

এটি ক্যামেরা কন্ট্রোলও (openGL ভিউ ফ্রাস্টাম) প্রদান করে।

ndk_helper::TapCamera tap_camera_;

তারপর অ্যাপটি NDK-তে দেওয়া নেটিভ API ব্যবহার করে ডিভাইসের সেন্সর ব্যবহার করার জন্য প্রস্তুত হয়।

ASensorManager* sensor_manager_;
const ASensor* accelerometer_sensor_;
ASensorEventQueue* sensor_event_queue_;

অ্যাপটি বিভিন্ন Android লাইফসাইকেল ইভেন্ট এবং EGL কনটেক্সট স্টেট পরিবর্তনের উত্তরে নিম্নলিখিত ফাংশনগুলিকে কল করে, Engine ক্লাসের মাধ্যমে ndk_helper দ্বারা প্রদত্ত বিভিন্ন কার্যকারিতা ব্যবহার করে।

void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay();
void TrimMemory();
bool IsReady();

তারপর, UI ডিসপ্লে আপডেট করার জন্য নিম্নলিখিত ফাংশন Java সাইডে কল ব্যাক করে।

void Engine::ShowUI()
{
    JNIEnv *jni;
    app_->activity->vm->AttachCurrentThread( &jni, NULL );


    //Default class retrieval
    jclass clazz = jni->GetObjectClass( app_->activity->clazz );
    jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" );
    jni->CallVoidMethod( app_->activity->clazz, methodID );


    app_->activity->vm->DetachCurrentThread();
    return;
}

এরপর, এই ফাংশনটি জাভা সাইডে কল ব্যাক করে যাতে নেটিভ সাইডে রেন্ডার করা স্ক্রিনের উপর সুপারইম্পোজ করা একটি টেক্সট বক্স আঁকা যায় এবং ফ্রেমের সংখ্যা দেখানো যায়।

void Engine::UpdateFPS( float fFPS )
{
    JNIEnv *jni;
    app_->activity->vm->AttachCurrentThread( &jni, NULL );


    //Default class retrieval
    jclass clazz = jni->GetObjectClass( app_->activity->clazz );
    jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" );
    jni->CallVoidMethod( app_->activity->clazz, methodID, fFPS );


    app_->activity->vm->DetachCurrentThread();
    return;
}

অ্যাপ্লিকেশনটি সিস্টেম ক্লক থেকে সময় নেয় এবং রিয়েল-টাইম ক্লকের উপর ভিত্তি করে সময়-ভিত্তিক অ্যানিমেশনের জন্য এটি রেন্ডারারকে প্রদান করে। এই তথ্য ব্যবহার করা হয়, যেমন, মোমেন্টাম গণনা করার সময়, যেখানে সময়ের সাথে সাথে স্পিড কমে যায়।

renderer_.Update( monitor_.GetCurrentTime() );

অ্যাপ্লিকেশনটি এখন GLcontext::Swap() ফাংশনের মাধ্যমে ডিসপ্লে করার জন্য রেন্ডার করা ফ্রেমটিকে ফ্রন্ট বাফারে ফ্লিপ করে; এটি ফ্লিপিং প্রসেসের সময় হওয়া সম্ভাব্য সমস্যাগুলিও ম্যানেজ করে।

if( EGL_SUCCESS != gl_context_->Swap() )  // swaps
buffer.

প্রোগ্রামটি ndk_helper ক্লাসে সংজ্ঞায়িত জেসচার ডিটেক্টরে টাচ-মোশন ইভেন্ট পাস করে। জেসচার ডিটেক্টর মাল্টিটাচ জেসচার ট্র্যাক করে, যেমন পিঞ্চ-অ্যান্ড-ড্র্যাগ এবং এই ধরনের কোনও ইভেন্ট ট্রিগার হলে বিজ্ঞপ্তি পাঠায়।

if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION )
{
    ndk_helper::GESTURE_STATE doubleTapState =
        eng->doubletap_detector_.Detect( event );
    ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event );
    ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event );

    //Double tap detector has a priority over other detectors
    if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION )
    {
        //Detect double tap
        eng->tap_camera_.Reset( true );
    }
    else
    {
        //Handle drag state
        if( dragState & ndk_helper::GESTURE_STATE_START )
        {
             //Otherwise, start dragging
             ndk_helper::Vec2 v;
             eng->drag_detector_.GetPointer( v );
             eng->TransformPosition( v );
             eng->tap_camera_.BeginDrag( v );
        }
        // ...else other possible drag states...

        //Handle pinch state
        if( pinchState & ndk_helper::GESTURE_STATE_START )
        {
            //Start new pinch
            ndk_helper::Vec2 v1;
            ndk_helper::Vec2 v2;
            eng->pinch_detector_.GetPointers( v1, v2 );
            eng->TransformPosition( v1 );
            eng->TransformPosition( v2 );
            eng->tap_camera_.BeginPinch( v1, v2 );
        }
        // ...else other possible pinch states...
    }
    return 1;
}

ndk_helper ক্লাসটি ভেক্টর-ম্যাথ লাইব্রেরিতেও (vecmath.h) অ্যাক্সেস প্রদান করে, যা এখানে টাচ কোঅর্ডিনেট ট্রান্সফর্ম করতে ব্যবহার করা হচ্ছে।

void Engine::TransformPosition( ndk_helper::Vec2& vec )
{
    vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec
            / ndk_helper::Vec2( gl_context_->GetScreenWidth(),
            gl_context_->GetScreenHeight() ) - ndk_helper::Vec2( 1.f, 1.f );
}

HandleCmd() মেথড android_native_app_glue লাইব্রেরি থেকে পোস্ট করা কমান্ড ম্যানেজ করে। মেসেজের অর্থ কী, সেই সম্পর্কে আরও তথ্যের জন্য, android_native_app_glue.h এবং .c সোর্স ফাইলের কমেন্ট দেখুন।

void Engine::HandleCmd( struct android_app* app,
        int32_t cmd )
{
    Engine* eng = (Engine*) app->userData;
    switch( cmd )
    {
    case APP_CMD_SAVE_STATE:
        break;
    case APP_CMD_INIT_WINDOW:
        // The window is being shown, get it ready.
        if( app->window != NULL )
        {
            eng->InitDisplay();
            eng->DrawFrame();
        }
        break;
    case APP_CMD_TERM_WINDOW:
        // The window is being hidden or closed, clean it up.
        eng->TermDisplay();
        eng->has_focus_ = false;
        break;
    case APP_CMD_STOP:
        break;
    case APP_CMD_GAINED_FOCUS:
        eng->ResumeSensors();
        //Start animation
        eng->has_focus_ = true;
        break;
    case APP_CMD_LOST_FOCUS:
        eng->SuspendSensors();
        // Also stop animating.
        eng->has_focus_ = false;
        eng->DrawFrame();
        break;
    case APP_CMD_LOW_MEMORY:
        //Free up GL resources
        eng->TrimMemory();
        break;
    }
}

ndk_helper ক্লাস APP_CMD_INIT_WINDOW পোস্ট করে যখন android_app_glue সিস্টেম থেকে onNativeWindowCreated() কলব্যাক পায়। অ্যাপ্লিকেশন সাধারণত উইন্ডো ইনিশিয়ালাইজেশন পারফর্ম করতে পারে, যেমন EGL ইনিশিয়ালাইজেশন। তারা অ্যাক্টিভিটি লাইফসাইকেলের বাইরে এটি করে, কারণ অ্যাক্টিভিটি এখনও প্রস্তুত নয়।

//Init helper functions
ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME );

state->userData = &g_engine;
state->onAppCmd = Engine::HandleCmd;
state->onInputEvent = Engine::HandleInput;