Tích hợp các tính năng về Khả năng thích ứng vào Trò chơi gốc

1. Giới thiệu

70748189a60ed450.png

Tại sao tôi cần tích hợp các tính năng về Khả năng thích ứng vào trò chơi?

API Khả năng thích ứng cho phép bạn nhận phản hồi về trạng thái của thiết bị trong thời gian chạy ứng dụng và cho phép bạn linh hoạt điều chỉnh khối lượng công việc của mình để tối ưu hoá hiệu suất của trò chơi. API này cũng cho phép bạn thông báo cho hệ thống về khối lượng công việc để hệ thống có thể phân bổ tài nguyên một cách tối ưu.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ mở một trò chơi mẫu gốc trên Android, chạy trò chơi này và tích hợp các tính năng về Khả năng thích ứng với trò chơi đó. Sau khi thiết lập và thêm mã cần thiết, bạn sẽ có thể thử nghiệm sự khác biệt về hiệu suất trực quan giữa trò chơi trước đó và phiên bản có Khả năng thích ứng.

Kiến thức bạn sẽ học được

  • Cách tích hợp API Nhiệt vào một trò chơi hiện có và thích ứng với trạng thái nhiệt để tránh hiện tượng quá nhiệt.
  • Cách tích hợp API Chế độ trò chơi và phản ứng trước các thay đổi về lựa chọn.
  • Cách tích hợp API Trạng thái chơi để cho hệ thống biết trò chơi đang ở trạng thái nào.
  • Cách tích hợp API Gợi ý về hiệu suất để cho hệ thống biết mô hình phân luồng cũng như khối lượng công việc của bạn.

Bạn cần có

2. Thiết lập

Thiết lập môi trường phát triển

Nếu trước đây chưa từng làm việc với các dự án gốc trong Android Studio, thì có thể bạn cần phải cài đặt Android NDK và CMake. Nếu bạn đã cài đặt các công cụ này, hãy chuyển sang bước Thiết lập dự án.

Kiểm tra để đảm bảo rằng bạn đã cài đặt SDK, NDK và CMake

Mở Android Studio. Khi cửa sổ Chào mừng bạn đến với Android Studio hiển thị, hãy mở trình đơn thả xuống Định cấu hình rồi chọn tuỳ chọn Trình quản lý SDK.

3b7b47a139bc456.png

Nếu đã mở một dự án hiện có, bạn có thể mở Trình quản lý SDK qua trình đơn Công cụ. Nhấp vào trình đơn Tools (Công cụ) rồi chọn SDK Manager (Trình quản lý SDK), cửa sổ Trình quản lý SDK sẽ mở ra.

Trong thanh bên, chọn theo thứ tự: Appearance & Behavior > System Settings > Android SDK (Giao diện và hành vi > Cài đặt hệ thống > Android SDK). Chọn thẻ SDK Platforms (Nền tảng SDK) trong ngăn SDK Android để hiển thị danh sách các tuỳ chọn công cụ đã cài đặt. Đảm bảo bạn đã cài đặt SDK Android 12.0 trở lên.

931f6ae02822f417.png

Tiếp theo, hãy chọn thẻ SDK Tools (Bộ công cụ SDK) và đảm bảo bạn đã cài đặt NDK cũng như CMake.

Lưu ý: Phiên bản chính xác không quan trọng, miễn đó là phiên bản mới. Tuy nhiên, chúng ta hiện dùng phiên bản NDK 25.2.9519653 và CMake 3.24.0. Phiên bản NDK đang được cài đặt theo mặc định sẽ thay đổi theo thời gian tuỳ theo các bản phát hành NDK tiếp theo. Nếu bạn cần cài đặt một phiên bản NDK cụ thể, hãy làm theo hướng dẫn trong tài liệu tham khảo của Android Studio về cách cài đặt NDK trong phần "Install a specific version of the NDK" (Cài đặt phiên bản NDK cụ thể).

d28adf9279adec4.png

Sau khi chọn tất cả các công cụ cần thiết, hãy nhấp vào nút Apply (Áp dụng) ở cuối cửa sổ để cài đặt các công cụ đó. Sau đó, bạn có thể nhấp vào nút OK để đóng cửa sổ SDK Android.

Thiết lập dự án

Dự án mẫu là một trò chơi mô phỏng thực tế dạng 3D đơn giản được phát triển bằng Swappy cho OpenGL. Không có nhiều thay đổi về cấu trúc thư mục so với dự án mới được tạo từ mẫu, nhưng bạn vẫn cần làm một số việc để khởi chạy vòng lặp thực và kết xuất, vì vậy hãy tiếp tục sao chép kho lưu trữ.

Sao chép kho lưu trữ

Từ dòng lệnh, hãy thay đổi thư mục bạn muốn chứa thư mục gốc của trò chơi và sao chép thư mục đó từ GitHub:

git clone -b codelab/start https://github.com/android/adpf-game-adaptability-codelab.git --recurse-submodules

Hãy đảm bảo rằng bạn đang bắt đầu từ cam kết ban đầu của kho lưu trữ có tiêu đề [codelab] start: simple game.

Thiết lập phần phụ thuộc

Dự án mẫu sử dụng thư viện Dear ImGui cho giao diện người dùng của dự án. Dự án này cũng sử dụng Bullet Physics cho quy trình mô phỏng thực tế dạng 3D. Theo giả định, các thư viện này nằm trong thư mục third_party ở gốc của dự án. Chúng ta đã xem các thư viện tương ứng thông qua --recurse-submodules được chỉ định trong lệnh sao chép ở trên.

Kiểm thử dự án

Trong Android Studio, hãy mở dự án từ gốc của thư mục. Hãy đảm bảo bạn đã kết nối một thiết bị, sau đó chọn Build > Make Project (Tạo > Tạo dự án) và Run > Run 'app' (Chạy > Chạy "ứng dụng") để kiểm thử bản minh hoạ. Kết quả cuối cùng trên thiết bị sẽ có dạng như sau

f1f33674819909f1.png

Giới thiệu về dự án

Trò chơi được thiết kế theo phong cách tối giản nhằm tập trung vào các chi tiết cụ thể của việc triển khai các tính năng về Khả năng thích ứng. Trò chơi đang chạy một số khối lượng công việc về đồ hoạ và vật lý có thể dễ dàng định cấu hình, vì vậy, chúng ta có thể điều chỉnh cấu hình một cách linh hoạt trong thời gian chạy khi điều kiện của thiết bị thay đổi.

3. Tích hợp Thermal API (API Nhiệt)

a7e07127f1e5c37d.png

ce607eb5a2322d6b.png

Theo dõi trạng thái nhiệt đã thay đổi trong Java

Kể từ Android 10 (API cấp 29), thiết bị Android phải báo cáo cho ứng dụng đang chạy bất cứ khi nào trạng thái nhiệt thay đổi. Các ứng dụng có thể theo dõi sự thay đổi này bằng cách cung cấp OnThermalStatusChangedListener cho PowerManager.

PowerManager.addThermalStatusListener chỉ có trên API cấp 29 trở lên, nên bạn cần kiểm tra trước khi gọi nó:

// CODELAB: ADPFSampleActivity.java onResume
if (Build.VERSION.SDK_INT >= VERSION_CODES.Q) {
   PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
      if (pm != null) {
         pm.addThermalStatusListener(this);
      }
}

Và từ API cấp 30 trở lên, bạn có thể đăng ký lệnh gọi lại trong mã C++ bằng cách sử dụng AThermal_registerThermalStatusListener, nhờ đó bạn có thể xác định một phương thức gốc và gọi phương thức đó từ Java như sau:

// CODELAB: ADPFSampleActivity.java onResume
if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
   // Use NDK Thermal API.
   nativeRegisterThermalStatusListener();
}

Bạn cần thêm trình nghe vào chức năng vòng đời onResume của Hoạt động.

Xin lưu ý rằng bạn cũng cần xoá mọi thứ mình thêm vào onResume của Hoạt động trong phần onPause của Hoạt động. Vì vậy, hãy xác định mã dọn dẹp của chúng ta để gọi PowerManager.removeThermalStatusListenerAThermal_unregisterThermalStatusListener:

// CODELAB: ADPFSampleActivity.java onPause
// unregister the listener when it is no longer needed
// Remove the thermal state change listener on pause.
if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
   // Use NDK Thermal API.
   nativeUnregisterThermalStatusListener();
} else if (Build.VERSION.SDK_INT >= VERSION_CODES.Q) {
   PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
   if (pm != null) {
      pm.removeThermalStatusListener(this);
   }
}

Hãy tóm tắt các chức năng này bằng cách di chuyển chúng sang ADPFManager.java để chúng ta có thể dễ dàng sử dụng lại cho các dự án khác.

Trong lớp hoạt động của trò chơi, hãy tạo và giữ một phiên bản của ADPFManager rồi kết nối trình nghe nhiệt thêm/xoá bằng phương thức vòng đời hoạt động tương ứng.

// CODELAB: ADPFSampleActivity.java
// Keep a copy of ADPFManager instance
private ADPFManager adpfManager;

// in onCreate, create an instance of ADPFManager
@Override
protected void onCreate(Bundle savedInstanceState) {
   // Instantiate ADPF manager.
   this.adpfManager = new ADPFManager();
   super.onCreate(savedInstanceState);
}

@Override
protected void onResume() {
   // Register ADPF thermal status listener on resume.
   this.adpfManager.registerListener(getApplicationContext());
   super.onResume();
}

@Override
protected void onPause() {
   // Remove ADPF thermal status listener on resume.
  this.adpfManager.unregisterListener(getApplicationContext());
   super.onPause();
}

Đăng ký các phương thức gốc của lớp C++ trong JNI_OnLoad

Ở API cấp 30 trở lên, chúng ta có thể sử dụng API Nhiệt NDK AThermal_*. Như vậy, bạn có thể ánh xạ trình nghe Java để gọi các phương thức C++ tương tự. Đối với các phương thức Java để gọi vào mã C++, bạn cần đăng ký các phương thức C++ trong JNI_OnLoad. Bạn có thể xem thêm phần Mẹo về JNI để tìm hiểu thêm.

// CODELAB: android_main.cpp
// Remove the thermal state change listener on pause.
// Register classes to Java.
jint JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
  JNIEnv *env;
  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
  }

  // Find your class. JNI_OnLoad is called from the correct class loader context
  // for this to work.
  jclass c = env->FindClass("com/android/example/games/ADPFManager");
  if (c == nullptr) return JNI_ERR;

  // Register your class' native methods.
  static const JNINativeMethod methods[] = {
      {"nativeThermalStatusChanged", "(I)V",
       reinterpret_cast<void *>(nativeThermalStatusChanged)},
      {"nativeRegisterThermalStatusListener", "()V",
       reinterpret_cast<void *>(nativeRegisterThermalStatusListener)},
      {"nativeUnregisterThermalStatusListener", "()V",
       reinterpret_cast<void *>(nativeUnregisterThermalStatusListener)},
  };
  int rc = env->RegisterNatives(c, methods,
                                sizeof(methods) / sizeof(JNINativeMethod));

  if (rc != JNI_OK) return rc;

  return JNI_VERSION_1_6;
}

Kết nối trình nghe gốc vào trò chơi của bạn

Trò chơi C++ của chúng ta sẽ cần biết khi nào trạng thái nhiệt tương ứng đã thay đổi, vì vậy, hãy tạo lớp adpf_manager tương ứng trong C++.

Trong thư mục cpp của nguồn ứng dụng ($ROOT/app/src/main/cpp), hãy tạo một cặp tệp adpf_manager.hadpf_manager.cpp

// CODELAB: adpf_manager.h
// Forward declarations of functions that need to be in C decl.
extern "C" {
   void nativeThermalStatusChanged(JNIEnv* env, jclass cls, int32_t thermalState);
   void nativeRegisterThermalStatusListener(JNIEnv* env, jclass cls);
   void nativeUnregisterThermalStatusListener(JNIEnv* env, jclass cls);
}

typedef void (*thermalStateChangeListener)(int32_t, int32_t);

Xác định các hàm C trong tệp cpp bên ngoài lớp ADPFManager.

// CODELAB: adpf_manager.cpp
// Native callback for thermal status change listener.
// The function is called from Activity implementation in Java.
void nativeThermalStatusChanged(JNIEnv *env, jclass cls, jint thermalState) {
  ALOGI("Thermal Status updated to:%d", thermalState);
  ADPFManager::getInstance().SetThermalStatus(thermalState);
}

void nativeRegisterThermalStatusListener(JNIEnv *env, jclass cls) {
  auto manager = ADPFManager::getInstance().GetThermalManager();
  if (manager != nullptr) {
    auto ret = AThermal_registerThermalStatusListener(manager, thermal_callback,
                                                      nullptr);
    ALOGI("Thermal Status callback registered to:%d", ret);
  }
}

void nativeUnregisterThermalStatusListener(JNIEnv *env, jclass cls) {
  auto manager = ADPFManager::getInstance().GetThermalManager();
  if (manager != nullptr) {
    auto ret = AThermal_unregisterThermalStatusListener(
        manager, thermal_callback, nullptr);
    ALOGI("Thermal Status callback unregistered to:%d", ret);
  }
}

Khởi chạy PowerManager và các hàm cần thiết để truy xuất khoảng nhiệt

Từ API cấp 30 trở lên, chúng ta có thể sử dụng API Nhiệt NDK AThermal_*. Như vậy, khi khởi chạy, hãy gọi AThermal_acquireManager và giữ lại để sử dụng sau này. Ở API cấp 29, chúng ta sẽ phải tìm và giữ lại các tệp tham chiếu Java cần thiết.

// CODELAB: adpf_manager.cpp
// Initialize JNI calls for the powermanager.
bool ADPFManager::InitializePowerManager() {
  if (android_get_device_api_level() >= 30) {
    // Initialize the powermanager using NDK API.
    thermal_manager_ = AThermal_acquireManager();
    return true;
  }

  JNIEnv *env = NativeEngine::GetInstance()->GetJniEnv();

  // Retrieve class information
  jclass context = env->FindClass("android/content/Context");

  // Get the value of a constant
  jfieldID fid =
      env->GetStaticFieldID(context, "POWER_SERVICE", "Ljava/lang/String;");
  jobject str_svc = env->GetStaticObjectField(context, fid);

  // Get the method 'getSystemService' and call it
  jmethodID mid_getss = env->GetMethodID(
      context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
  jobject obj_power_service = env->CallObjectMethod(
      app_->activity->javaGameActivity, mid_getss, str_svc);

  // Add global reference to the power service object.
  obj_power_service_ = env->NewGlobalRef(obj_power_service);

  jclass cls_power_service = env->GetObjectClass(obj_power_service_);
  get_thermal_headroom_ =
      env->GetMethodID(cls_power_service, "getThermalHeadroom", "(I)F");

  // Free references
  env->DeleteLocalRef(cls_power_service);
  env->DeleteLocalRef(obj_power_service);
  env->DeleteLocalRef(str_svc);
  env->DeleteLocalRef(context);

  if (get_thermal_headroom_ == 0) {
    // The API is not supported in the platform version.
    return false;
  }

  return true;
}

Đảm bảo gọi phương thức khởi chạy

Trong mẫu của chúng ta, phương thức khởi chạy được gọi từ SetApplication và SetApplication được gọi từ android_main. Quy trình thiết lập này dành riêng cho khung của chúng ta, vì vậy, nếu đang tích hợp vào trò chơi của mình, bạn cần tìm đúng vị trí để gọi phương thức Khởi chạy

// CODELAB: adpf_manager.cpp
// Invoke the API first to set the android_app instance.
void ADPFManager::SetApplication(android_app *app) {
  app_.reset(app);

  // Initialize PowerManager reference.
  InitializePowerManager();
}
// CODELAB: android_main.cpp
void android_main(struct android_app *app) {
  std::shared_ptr<NativeEngine> engine(new NativeEngine(app));

  // Set android_app to ADPF manager, which in turn will call InitializePowerManager
  ADPFManager::getInstance().SetApplication(app);

  ndk_helper::JNIHelper::Init(app);

  engine->GameLoop();
}

Thường xuyên theo dõi khoảng nhiệt

Thông thường, bạn nên ngăn trạng thái nhiệt tăng lên mức cao hơn vì sẽ khó giảm mức này nếu không dừng toàn bộ khối lượng công việc. Ngay cả sau khi tắt hoàn toàn, thiết bị vẫn sẽ mất một khoảng thời gian để tản nhiệt và làm mát. Chúng ta có thể thường xuyên theo dõi khoảng nhiệt và điều chỉnh khối lượng công việc để kiểm soát khoảng nhiệt và tránh làm tăng trạng thái nhiệt.

Trong ADPFManager, hãy đưa ra các phương thức để kiểm tra khoảng nhiệt.

// CODELAB: adpf_manager.cpp
// Invoke the method periodically (once a frame) to monitor
// the device's thermal throttling status.
void ADPFManager::Monitor() {
  float current_clock = Clock();
  if (current_clock - last_clock_ >= kThermalHeadroomUpdateThreshold) {
    // Update thermal headroom.
    UpdateThermalStatusHeadRoom();
    last_clock_ = current_clock;
  }
}
// CODELAB: adpf_manager.cpp
// Retrieve current thermal headroom using JNI call.
float ADPFManager::UpdateThermalStatusHeadRoom() {
  if (android_get_device_api_level() >= 30) {
    // Use NDK API to retrieve thermal status headroom.
    thermal_headroom_ = AThermal_getThermalHeadroom(
        thermal_manager_, kThermalHeadroomUpdateThreshold);
    return thermal_headroom_;
  }

  if (app_ == nullptr || get_thermal_headroom_ == 0) {
    return 0.f;
  }
  JNIEnv *env = NativeEngine::GetInstance()->GetJniEnv();

  // Get thermal headroom!
  thermal_headroom_ =
      env->CallFloatMethod(obj_power_service_, get_thermal_headroom_,
                           kThermalHeadroomUpdateThreshold);
  ALOGE("Current thermal Headroom %f", thermal_headroom_);
  return thermal_headroom_;
}

Cuối cùng, chúng ta cần đưa ra các phương thức để đặt trạng thái nhiệt và trình nghe trạng thái nhiệt. Chúng ta sẽ lấy giá trị của trạng thái nhiệt từ API Nhiệt của NDK hoặc SDK Java gọi vào mã gốc.

// CODELAB: adpf_manager.cpp
thermalStateChangeListener thermalListener = NULL;

void ADPFManager::SetThermalStatus(int32_t i) {
  int32_t prev_status_ = thermal_status_;
  int32_t current_status_ = i;
  thermal_status_ = i;
  if ( thermalListener != NULL ) {
    thermalListener(prev_status_, current_status_ );
  }
}

void ADPFManager::SetThermalListener(thermalStateChangeListener listener)
{
  thermalListener = listener;
}

Thêm adpf_manager.cpp vào đơn vị biên dịch trong CMakeLists.txt

Hãy nhớ thêm adpf_manager.cpp mới tạo vào đơn vị biên dịch của bạn.

Chúng ta đã hoàn thành các lớp javacpp của ADPFManager có thể tái sử dụng, do vậy, bạn có thể lấy các tệp này và tái sử dụng trong các dự án khác thay vì viết lại mã kết nối (glue code).

// CODELAB: CMakeLists.txt
# now build app's shared lib
add_library(game SHARED
        adpf_manager.cpp # add this line
        android_main.cpp
        box_renderer.cpp
        demo_scene.cpp

Xác định các tham số trong trò chơi cần thay đổi khi trạng thái nhiệt giảm đi

Phần này trở đi sẽ dành riêng cho trò chơi. Trong ví dụ này, chúng ta sẽ giảm các bước vật lý và số lượng hộp mỗi khi trạng thái nhiệt tăng lên.

Chúng ta cũng sẽ theo dõi khoảng nhiệt, nhưng sẽ không làm gì khác ngoài việc hiển thị giá trị trên HUD. Trong trò chơi, bạn có thể phản hồi giá trị này bằng cách điều chỉnh mức độ xử lý hậu kỳ do thẻ đồ hoạ thực hiện, giảm mức độ chi tiết, v.v.

// CODELAB: demo_scene.cpp
// String labels that represents thermal states.
const char* thermal_state_label[] = {
    "THERMAL_STATUS_NONE",     "THERMAL_STATUS_LIGHT",
    "THERMAL_STATUS_MODERATE", "THERMAL_STATUS_SEVERE",
    "THERMAL_STATUS_CRITICAL", "THERMAL_STATUS_EMERGENCY",
    "THERMAL_STATUS_SHUTDOWN"};

const int32_t thermal_state_physics_steps[] = {
        16, 12, 8, 4,
};
const int32_t thermal_state_array_size[] = {
        8, 6, 4, 2,
};

Tạo một hàm trình nghe có trạng thái nhiệt thay đổi trong trò chơi

Bây giờ, chúng ta cần tạo một thermalListener cpp để ADPFManager gọi mỗi khi phát hiện thấy mức nhiệt của thiết bị đã thay đổi. Hãy tạo hàm này trong trò chơi của bạn để theo dõi các giá trị đã thay đổi trạng thái. Chúng ta đang theo dõi last_state để có thể biết được mức nhiệt tăng hay giảm.

// CODELAB: demo_scene.cpp
// Dedicate a function to listen to the thermal state changed
void DemoScene::on_thermal_state_changed(int32_t last_state, int32_t current_state)
{
  if ( last_state != current_state ) {
    demo_scene_instance_->AdaptThermalLevel(current_state);
  }
}

...

// remember to pass it to the ADPFManager class we've just created, place this in DemoScene constructor:
ADPFManager::getInstance().SetThermalListener(on_thermal_state_changed);

Khi trạng thái nhiệt trong trò chơi của bạn thay đổi, hãy điều chỉnh sao cho phù hợp

Mỗi trò chơi sẽ có những nhu cầu và mức độ ưu tiên khác nhau, điều rất quan trọng đối với trò chơi này có thể không cần thiết đối với trò chơi khác. Vì vậy, bạn cần phải tự quyết định cách tối ưu hoá để ngăn chặn tình trạng tăng nhiệt.

Trong mẫu của mình, chúng ta đang giảm số lượng đối tượng trên màn hình và giảm độ chân thực vật lý. Điều này sẽ giúp giảm cả khối lượng công việc của CPU và GPU, đồng thời hy vọng có thể giảm nhiệt một chút. Xin lưu ý rằng thường rất khó để giảm nhiệt xuống trừ phi người chơi nghỉ giải lao và để thiết bị nguội. Đây chính là lý do chúng ta theo dõi chặt chẽ khoảng nhiệt và ngăn không cho thiết bị đạt đến trạng thái điều tiết nhiệt.

// CODELAB: demo_scene.cpp
// Adapt your game when the thermal status has changed
void DemoScene::AdaptThermalLevel(int32_t index) {
  int32_t current_index = index;
  int32_t array_size = sizeof(thermal_state_physics_steps) / sizeof(thermal_state_physics_steps[0]);
  if ( current_index < 0 ) {
    current_index = 0;
  } else if ( current_index >= array_size ) {
    current_index = array_size - 1;
  }

  ALOGI("AdaptThermalLevel: %d", current_index);

  // in this sample, we are reducing the physics step when the device heated
  current_physics_step_ = thermal_state_physics_steps[current_index];
  // and also reduce the number of objects in the world
  // your situation may be different, you can reduce LOD to remove some calculations for example...
  int32_t new_array_size_ = thermal_state_array_size[current_index];

  if ( new_array_size_ != array_size_ ) {
      recreate_physics_obj_ = true;
  }
}

Ngoài ra, xin lưu ý rằng hiệu suất cao nhất do chip CPU và GPU mang lại thường không hiệu quả, nghĩa là chip thường mang lại hiệu suất tối đa ở mức tiêu thụ năng lượng cao hơn rất nhiều và phân tán một lượng nhiệt lớn. Ngược lại với hiệu suất duy trì, đây là hiệu suất tối ưu nhất trên mỗi đơn vị năng lượng tiêu thụ và lượng nhiệt được phân tán. Bạn có thể đọc thêm về phần này trong bài viết Quản lý hiệu suất của Dự án nguồn mở Android.

Tạo và chạy dự án của bạn, Trạng thái nhiệt hiện tại và Khoảng nhiệt sẽ hiển thị. Nếu trạng thái nhiệt giảm, các bước vật lý và số lượng đối tượng sẽ giảm đi.

4bdcfe567fc603c0.png

Nếu xảy ra sự cố, bạn có thể so sánh công việc của mình với cam kết của kho lưu trữ có tiêu đề [codelab] step: integrated thermal-api.

4. Tích hợp Game Mode API (API Chế độ trò chơi)

Game Mode API (API Chế độ trò chơi) cho phép bạn tối ưu hoá trò chơi để có hiệu suất tốt nhất hoặc thời lượng pin lâu nhất theo lựa chọn của người chơi. API này có trên một số thiết bị Android 12 và tất cả các thiết bị Android 13 trở lên.

Cập nhật tệp kê khai Android

Đặt appCategory

Để sử dụng Game Mode API (API Chế độ trò chơi), danh mục ứng dụng của bạn phải là một trò chơi, hãy cho biết điều đó trong thẻ <application> của bạn

// CODELAB: AndroidManifest.xml
<application
   android:appCategory="game">

Đối với Android 13 trở lên

Bạn nên nhắm đến người dùng Android 13 thông qua 2 bước phụ tiếp theo:

Thêm game_mode_config <meta-data> và tệp xml tương ứng

// CODELAB: AndroidManifest.xml
   <!-- ENABLING GAME MODES -->
   <!-- Add this <meta-data> under your <application> tag to enable Game Mode API if you're targeting API Level 33 (recommended) -->
   <meta-data android:name="android.game_mode_config"
        android:resource="@xml/game_mode_config" />
// CODELAB: app/src/main/res/xml/game_mode_config.xml
<?xml version="1.0" encoding="UTF-8"?>
<game-mode-config
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:supportsBatteryGameMode="true"
    android:supportsPerformanceGameMode="true" />

Nếu bạn đang nhắm đến các thiết bị Android 12

Thêm mỗi chế độ trò chơi <meta-data> trực tiếp trong AndroidManifest

// CODELAB: AndroidManifest.xml
   <!-- Use meta-data below instead if you are targeting API Level 31 or 32; you do not need to apply the 2 steps prior -->
   <meta-data android:name="com.android.app.gamemode.performance.enabled"
      android:value="true"/>
   <meta-data
      android:name="com.android.app.gamemode.battery.enabled"
      android:value="true"/>

Triển khai GameModeManager.java để tóm tắt tính năng GameMode

Vì Game Mode API (API Chế độ trò chơi) chưa có giao diện cpp nên chúng ta sẽ cần sử dụng giao diện Java và cung cấp giao diện JNI. Hãy tóm tắt API này trong GameModeManager.java để chúng ta có thể tái sử dụng chức năng này trong các dự án khác.

// CODELAB: GameModeManager.java
// Abstract the functionality in GameModeManager so we can easily reuse in other games
public class GameModeManager {

    private Context context;
    private int gameMode;

    public void initialize(Context context) {
        this.context = context;
        this.gameMode = GameManager.GAME_MODE_UNSUPPORTED;
        if ( context != null ) {
            if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ) {
                // Get GameManager from SystemService
                GameManager gameManager = context.getSystemService(GameManager.class);

                // Returns the selected GameMode
                gameMode = gameManager.getGameMode();
            }
        }
        // tell the native game about the selected gameMode
        this.retrieveGameMode(this.gameMode);
    }

    protected native void retrieveGameMode(int gameMode);
}

Điều chỉnh Hoạt động của bạn để khởi chạy GameModeManager và truy xuất GameMode trong onResume

Kết nối với Vòng đời hoạt động. Mỗi khi Chế độ trò chơi bị thay đổi, Hoạt động của bạn sẽ được bắt đầu lại để chúng ta có thể thu thập giá trị trong onResume.

// CODELAB: ADPFSampleActivity.java
// we may keep and cache the object as class member variable
private GameModeManager gameModeManager;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
   ...
   // Instantiate our GameModeManager
   this.gameModeManager = new GameModeManager();
   ...
   super.onCreate(savedInstanceState);
}

...

@Override
protected void onResume() {
   ...
   this.gameModeManager.initialize(getApplicationContext());
   ...
   super.onResume();
}

Triển khai lớp GameModeManager để lưu trữ gameMode do người dùng chọn cho quá trình truy xuất trong trò chơi

Hãy tạo một trình bao bọc cpp và lưu trữ giá trị Chế độ trò chơi trong cpp để dễ dàng truy xuất.

// CODELAB: game_mode_manager.h
class GameModeManager {
 public:
  // Singleton function.
  static GameModeManager& getInstance() {
    static GameModeManager instance;
    return instance;
  }
  // Dtor. Remove global reference (if any).
  ~GameModeManager() {}

  // Delete copy constructor since the class is used as a singleton.
  GameModeManager(GameModeManager const&) = delete;
  void operator=(GameModeManager const&) = delete;

  void SetGameMode(int game_mode) { game_mode_ = game_mode; }
  int GetGameMode() { return game_mode_; }

 private:
  // Ctor. It's private since the class is designed as a singleton.
  GameModeManager() {}

  int game_mode_ = 0;
};

Triển khai retrieveGameMode trong mã gốc để chuyển giá trị GameMode vào trò chơi của bạn

Đây là cách đơn giản và hiệu quả nhất. Khi bắt đầu, hãy truy xuất giá trị Chế độ trò chơi và chuyển giá trị đó vào biến cpp của bạn để dễ dàng truy cập. Chúng ta có thể dựa vào giá trị đã lưu vào bộ nhớ đệm mà không cần thực hiện lệnh gọi JNI mỗi lần.

// CODELAB: game_mode_manager.cpp
extern "C" {

void Java_com_android_example_games_GameModeManager_retrieveGameMode(
    JNIEnv* env, jobject obj, jint game_mode) {
  GameModeManager& gmm = GameModeManager::getInstance();
  int old_game_mode = gmm.GetGameMode();
  ALOGI("GameMode updated from %d to:%d", old_game_mode, game_mode);
  GameModeManager::getInstance().SetGameMode(game_mode);
}

}

Thêm game_mode_manager.cpp vào đơn vị biên dịch trong CMakeLists.txt

Hãy nhớ thêm game_mode_manager.cpp mới tạo vào đơn vị biên dịch.

Chúng ta đã hoàn thành các lớp GameModeManager javacpp có thể tái sử dụng, do vậy, bạn có thể lấy các tệp này và tái sử dụng trong các dự án khác thay vì viết lại mã kết nối.

// CODELAB: CMakeLists.txt
# now build app's shared lib
add_library(game SHARED
        game_mode_manager.cpp # add this line
        android_main.cpp
        box_renderer.cpp
        demo_scene.cpp

Điều chỉnh trò chơi của bạn theo GameMode do người dùng chọn

Sau khi truy xuất Chế độ trò chơi, bạn phải tạo sự khác biệt cho trò chơi của mình theo giá trị do người dùng chọn. Sự khác biệt đáng kể nhất (và các giá trị phân cực nhất) nằm giữa chế độ HIỆU SUẤT và chế độ PIN. Ở chế độ HIỆU SUẤT CAO, người dùng thường muốn đắm chìm trong trò chơi và có được trải nghiệm tốt nhất mà không phải lo về thời lượng pin. Do đó, bạn có thể đạt được độ chân thực cao nhất miễn là tốc độ khung hình ổn định. Ở chế độ PIN, người dùng muốn chơi trò chơi của bạn trong thời gian dài hơn và họ có thể chấp nhận các chế độ cài đặt có hiệu suất thấp hơn. Đảm bảo rằng tốc độ khung hình luôn ổn định vì tốc độ khung hình không ổn định sẽ mang lại trải nghiệm kém nhất cho người chơi.

// CODELAB: demo_scene.cpp
void DemoScene::RenderPanel() {
  ...
  GameModeManager& game_mode_manager = GameModeManager::getInstance();
  // Show the stat changes according to selected Game Mode
  ImGui::Text("Game Mode: %d", game_mode_manager.GetGameMode());
}
// CODELAB: native_engine.h
// Add this function to NativeEngine class, we're going to check the gameMode and set the preferred frame rate and resolution cap accordingly
void CheckGameMode();

Trong mẫu này, chúng ta đang kết xuất hình ảnh ở độ phân giải tối đa và 60 khung hình trên giây ở chế độ HIỆU SUẤT. Vì đây là một ví dụ khá đơn giản, nên hầu hết các thiết bị sẽ có thể chạy trơn tru ở tốc độ khung hình trên giây tối đa, vì vậy, chúng ta không áp dụng thêm các bước kiểm tra để mọi thứ trở nên đơn giản. Ở chế độ PIN, chúng ta sẽ giới hạn kết xuất ở 30 khung hình trên giây và độ phân giải một phần tư. Bạn cần tìm những điểm ưu thế của riêng mình để tối ưu hoá. Hãy luôn nhớ rằng trải nghiệm chơi trò chơi và việc tiết kiệm năng lượng không chỉ giới hạn ở khung hình trên giây và độ phân giải! Để có cảm hứng về cách tối ưu hoá, hãy xem câu chuyện thành công của nhà phát triển.

// CODELAB: native_engine.cpp
void NativeEngine::CheckGameMode() {
  GameModeManager &gameModeManager = GameModeManager::getInstance();
  int game_mode = gameModeManager.GetGameMode();
  if (game_mode != mGameMode) {
    // game mode changed, make necessary adjustments to the game
    // in this sample, we are capping the frame rate and the resolution
    // we're also hardcoding configs on the engine 🫣
    SceneManager *sceneManager = SceneManager::GetInstance();
    NativeEngine *nativeEngine = NativeEngine::GetInstance();
    int native_width = nativeEngine->GetNativeWidth();
    int native_height = nativeEngine->GetNativeHeight();
    int preferred_width;
    int preferred_height;
    int32_t preferredSwapInterval = SWAPPY_SWAP_30FPS;
    if (game_mode == GAME_MODE_STANDARD) {
      // GAME_MODE_STANDARD : fps: 30, res: 1/2
      preferredSwapInterval = SWAPPY_SWAP_30FPS;
      preferred_width = native_width / 2;
      preferred_height = native_height / 2;
    } else if (game_mode == GAME_MODE_PERFORMANCE) {
      // GAME_MODE_PERFORMANCE : fps: 60, res: 1/1
      preferredSwapInterval = SWAPPY_SWAP_60FPS;
      preferred_width = native_width;
      preferred_height = native_height;
    } else if (game_mode == GAME_MODE_BATTERY) {
      // GAME_MODE_BATTERY : fps: 30, res: 1/4
      preferred_height = SWAPPY_SWAP_30FPS;
      preferred_width = native_width / 4;
      preferred_height = native_height / 4;
    } else {  // game_mode == 0 : fps: 30, res: 1/2
      // GAME_MODE_UNSUPPORTED
      preferredSwapInterval = SWAPPY_SWAP_30FPS;
      preferred_width = native_width / 2;
      preferred_height = native_height / 2;
    }
    ALOGI("GameMode SetPreferredSizeAndFPS: %d, %d, %d", preferred_width,
          preferred_height, preferredSwapInterval);
    sceneManager->SetPreferredSize(preferred_width, preferred_height);
    sceneManager->SetPreferredSwapInterval(preferredSwapInterval);
    mGameMode = game_mode;
  }
}
// CODELAB: native_engine.cpp
void NativeEngine::DoFrame() {
  ...

  // here, we are checking on every frame for simplicity
  // but you can hook it to your onResume callback only
  // as gameMode changes will trigger Activity restart
  CheckGameMode();
  SwitchToPreferredDisplaySize();

  ...
}

Nhớ kiểm tra sdkVersions trong tệp bản dựng Gradle của bạn

Game Mode API (API Chế độ trò chơi) có trên tất cả các thiết bị Android bắt đầu từ Android 13. Các thiết bị Android 12 đã chọn cũng sẽ bật tính năng này.

// CODELAB: app/build.gradle
android {
    compileSdk 33
    ...

    defaultConfig {
        minSdkVersion 30
        targetSdkVersion 33 // you can use 31 if you're targeting Android 12 and follow the Note section above
        ...
    }

Nếu xảy ra sự cố, bạn có thể so sánh công việc của mình với cam kết của kho lưu trữ có tiêu đề [codelab] step: integrate game-mode-api.

496c76415d12cbed.png

5. Tích hợp Game State API (API Trạng thái trò chơi)

Game State API (API Trạng thái trò chơi) giúp bạn báo cho hệ thống biết trạng thái cấp cao nhất của trò chơi, hỗ trợ bạn cho biết liệu nội dung hiện tại có thể bị gián đoạn mà không can thiệp vào việc chơi trò chơi không thể tạm dừng hay không. API này cũng cho phép bạn chỉ rõ liệu trò chơi của bạn hiện có đang thực hiện I/O nặng hay không, chẳng hạn như tải nội dung từ ổ đĩa hoặc mạng. Khi biết tất cả dữ liệu này, hệ thống sẽ phân bổ đúng tài nguyên vào đúng thời điểm cho bạn (ví dụ: phân bổ lượng băng thông CPU và bộ nhớ phù hợp khi bạn đang tải hoặc ưu tiên lưu lượng truy cập mạng cho trò chơi nhiều người chơi khi người chơi ở phiên nhiều người chơi quan trọng).

Xác định enum để dễ dàng ánh xạ đến các hằng số GameState

Vì Game State API (API Trạng thái trò chơi) không có giao diện cpp, hãy bắt đầu bằng cách sao chép các giá trị vào cpp.

// CODELAB: game_mode_manager.h
enum GAME_STATE_DEFINITION {
    GAME_STATE_UNKNOWN = 0,
    GAME_STATE_NONE = 1,
    GAME_STATE_GAMEPLAY_INTERRUPTIBLE = 2,
    GAME_STATE_GAMEPLAY_UNINTERRUPTIBLE = 3,
    GAME_STATE_CONTENT = 4,
};

Xác định SetGameState trong mã cpp sẽ gọi API Java thông qua JNI

Chúng ta chỉ cần hàm cpp để chuyển mọi thông tin đến API Java.

// CODELAB: game_mode_manager.h
void SetGameState(bool is_loading, GAME_STATE_DEFINITION game_state);

Đừng hoang mang, bạn chỉ cần một lệnh gọi JNI...

// CODELAB: game_mode_manager.cpp
void GameModeManager::SetGameState(bool is_loading,
                                   GAME_STATE_DEFINITION game_state) {
  if (android_get_device_api_level() >= 33) {
    ALOGI("GameModeManager::SetGameState: %d => %d", is_loading, game_state);

    JNIEnv* env = NativeEngine::GetInstance()->GetJniEnv();

    jclass cls_gamestate = env->FindClass("android/app/GameState");

    jmethodID ctor_gamestate =
        env->GetMethodID(cls_gamestate, "<init>", "(ZI)V");
    jobject obj_gamestate = env->NewObject(
        cls_gamestate, ctor_gamestate, (jboolean)is_loading, (jint)game_state);

    env->CallVoidMethod(obj_gamemanager_, gamemgr_setgamestate_, obj_gamestate);

    env->DeleteLocalRef(obj_gamestate);
    env->DeleteLocalRef(cls_gamestate);
  }
}

Gọi SetGameState mỗi khi trạng thái chơi trò chơi của bạn thay đổi

Chỉ cần gọi hàm cpp của chúng ta trong thời gian thích hợp, chẳng hạn như bắt đầu tải, ngừng tải hoặc nhập và thoát khỏi các trạng thái khác nhau trong trò chơi của bạn.

// CODELAB: welcome_scene.cpp
void WelcomeScene::OnInstall() {
  // 1. Game State: Start Loading
  GameModeManager::getInstance().SetGameState(true, GAME_STATE_NONE);
}
// CODELAB: welcome_scene.cpp
void WelcomeScene::OnStartGraphics() {
  // 2. Game State: Finish Loading, showing the attract screen which is interruptible
  GameModeManager::getInstance().SetGameState(
      false, GAME_STATE_GAMEPLAY_INTERRUPTIBLE);
}
// CODELAB: welcome_scene.cpp
void WelcomeScene::OnKillGraphics() {
  // 3. Game State: exiting, cleaning up and preparing to load the next scene
  GameModeManager::getInstance().SetGameState(true, GAME_STATE_NONE);
}

Bạn có thể chuyển trạng thái UNKNOWN (KHÔNG XÁC ĐỊNH) nếu không biết trạng thái tại thời điểm đó. Trong trường hợp này, chúng ta sẽ huỷ tải cảnh và không biết người dùng sẽ chuyển đến đâu tiếp theo. Tuy nhiên, cảnh tiếp theo sẽ tải và chúng ta có thể gọi một SetGameState khác với trạng thái mới đã biết.

// CODELAB: welcome_scene.cpp
void WelcomeScene::OnUninstall() {
  // 4. Game State: Finished unloading this scene, it will be immediately followed by loading the next scene
  GameModeManager::getInstance().SetGameState(false, GAME_STATE_UNKNOWN);
}

Thật dễ dàng đúng không? Sau khi bạn tích hợp Game State API (API Trạng thái trò chơi), hệ thống sẽ nhận biết các trạng thái trong ứng dụng và bắt đầu tối ưu hoá tài nguyên để mang lại hiệu suất và hiệu quả tốt hơn cho người chơi.

Hãy chú ý theo dõi thông tin cập nhật tiếp theo về Game State API (API Trạng thái trò chơi), trong đó chúng ta sẽ giải thích cách bạn có thể sử dụng nhãn và chất lượng để tối ưu hoá trò chơi hơn nữa!

6. Tích hợp Performance Hint API (API Gợi ý về hiệu suất)

API Gợi ý về hiệu suất cho phép bạn gửi gợi ý về hiệu suất đến hệ thống bằng cách tạo Phiên cho từng nhóm luồng chịu trách nhiệm về một nhiệm vụ cụ thể, đặt thời lượng công việc mục tiêu ban đầu, sau đó trên mỗi khung, báo cáo thời lượng công việc thực tế và cập nhật thời lượng công việc dự kiến cho khung tiếp theo.

Ví dụ: Nếu bạn có một nhóm các luồng chịu trách nhiệm về AI của kẻ thù, phép tính vật lý và luồng kết xuất. Bạn phải hoàn thành tất cả các nhiệm vụ phụ này ở mọi khung hình và việc vượt quá một trong các nhiệm vụ phụ này sẽ khiến khung hình của bạn bị trễ và thiếu tốc độ khung hình/giây mục tiêu. Bạn có thể tạo một Phiên PerformanceHint cho nhóm luồng này rồi đặt targetWorkDuration thành FPS mục tiêu mà bạn mong muốn. Khi bạn thực hiện reportActualWorkDuration sau khi hoàn thành công việc trên từng khung hình, hệ thống có thể phân tích xu hướng này và điều chỉnh tài nguyên CPU cho phù hợp để đảm bảo bạn có thể đạt được mục tiêu mong muốn ở mọi khung hình. Điều này giúp cải thiện độ ổn định của khung hình và mức tiêu thụ điện năng hiệu quả hơn cho trò chơi.

ADPFManager InitializePerformanceHintManager

Chúng ta sẽ cần tạo phiên gợi ý cho các luồng, có API C++ nhưng chỉ dành cho API cấp 33 trở lên. Đối với API cấp 31 và 32, chúng ta sẽ cần sử dụng API Java, hãy lưu vào bộ nhớ đệm một vài phương thức JNI để có thể sử dụng sau này.

// CODELAB: adpf_manager.cpp
// Initialize JNI calls for the PowerHintManager.
bool ADPFManager::InitializePerformanceHintManager() {
  #if __ANDROID_API__ >= 33
    if ( hint_manager_ == nullptr ) {
        hint_manager_ = APerformanceHint_getManager();
    }
    if ( hint_session_ == nullptr && hint_manager_ != nullptr ) {
        int32_t tid = gettid();
        thread_ids_.push_back(tid);
        int32_t tids[1];
        tids[0] = tid;
        hint_session_ = APerformanceHint_createSession(hint_manager_, tids, 1, last_target_);
    }
    ALOGI("ADPFManager::InitializePerformanceHintManager __ANDROID_API__ 33");
    return true;
#else
  ALOGI("ADPFManager::InitializePerformanceHintManager __ANDROID_API__ < 33");
  JNIEnv *env = NativeEngine::GetInstance()->GetJniEnv();

  // Retrieve class information
  jclass context = env->FindClass("android/content/Context");

  // Get the value of a constant
  jfieldID fid = env->GetStaticFieldID(context, "PERFORMANCE_HINT_SERVICE",
                                       "Ljava/lang/String;");
  jobject str_svc = env->GetStaticObjectField(context, fid);

  // Get the method 'getSystemService' and call it
  jmethodID mid_getss = env->GetMethodID(
      context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
  jobject obj_perfhint_service = env->CallObjectMethod(
      app_->activity->javaGameActivity, mid_getss, str_svc);

  // Add global reference to the power service object.
  obj_perfhint_service_ = env->NewGlobalRef(obj_perfhint_service);

  // Retrieve methods IDs for the APIs.
  jclass cls_perfhint_service = env->GetObjectClass(obj_perfhint_service_);
  create_hint_session_ =
      env->GetMethodID(cls_perfhint_service, "createHintSession",
                       "([IJ)Landroid/os/PerformanceHintManager$Session;");
  jmethodID mid_preferedupdaterate = env->GetMethodID(
      cls_perfhint_service, "getPreferredUpdateRateNanos", "()J");

  // Create int array which contain current tid.
  jintArray array = env->NewIntArray(1);
  int32_t tid = gettid();
  env->SetIntArrayRegion(array, 0, 1, &tid);
  const jlong DEFAULT_TARGET_NS = 16666666;

  // Create Hint session for the thread.
  jobject obj_hintsession = env->CallObjectMethod(
      obj_perfhint_service_, create_hint_session_, array, DEFAULT_TARGET_NS);
  if (obj_hintsession == nullptr) {
    ALOGI("Failed to create a perf hint session.");
  } else {
    obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession);
    preferred_update_rate_ =
        env->CallLongMethod(obj_perfhint_service_, mid_preferedupdaterate);

    // Retrieve mid of Session APIs.
    jclass cls_perfhint_session = env->GetObjectClass(obj_perfhint_session_);
    report_actual_work_duration_ = env->GetMethodID(
        cls_perfhint_session, "reportActualWorkDuration", "(J)V");
    update_target_work_duration_ = env->GetMethodID(
        cls_perfhint_session, "updateTargetWorkDuration", "(J)V");
    set_threads_ = env->GetMethodID(
        cls_perfhint_session, "setThreads", "([I)V");
  }

  // Free local references
  env->DeleteLocalRef(obj_hintsession);
  env->DeleteLocalRef(array);
  env->DeleteLocalRef(cls_perfhint_service);
  env->DeleteLocalRef(obj_perfhint_service);
  env->DeleteLocalRef(str_svc);
  env->DeleteLocalRef(context);

  if (report_actual_work_duration_ == 0 || update_target_work_duration_ == 0) {
    // The API is not supported in the platform version.
    return false;
  }

  return true;
#endif // __ANDROID_API__ >= 33

}

Gọi trong ADPFManager::SetApplication

Hãy nhớ gọi hàm khởi chạy mà chúng ta đã xác định trong android_main

// CODELAB: adpf_manager.cpp
// Invoke the API first to set the android_app instance.
void ADPFManager::SetApplication(android_app *app) {
  ...

  // Initialize PowerHintManager reference.
  InitializePerformanceHintManager();
}
// CODELAB: android_main.cpp
void android_main(struct android_app *app) {
  ...

  // Set android_app to ADPF manager & call InitializePerformanceHintManager
  ADPFManager::getInstance().SetApplication(app);

  ...
}

Xác định ADPFManager::BeginPerfHintSession & ADPFManager::EndPerfHintSession

Xác định các phương thức cpp để thực sự gọi API thông qua JNI, chấp nhận tất cả các tham số bắt buộc của chúng ta.

// CODELAB: adpf_manager.h
// Indicates the start and end of the performance intensive task.
// The methods call performance hint API to tell the performance
// hint to the system.
void BeginPerfHintSession();
void EndPerfHintSession(jlong target_duration_ns);
// CODELAB: adpf_manager.cpp
// Indicates the start and end of the performance intensive task.
// The methods call performance hint API to tell the performance hint to the system.
void ADPFManager::BeginPerfHintSession() {
  perf_start_ = std::chrono::high_resolution_clock::now();
}

void ADPFManager::EndPerfHintSession(jlong target_duration_ns) {
#if __ANDROID_API__ >= 33
    auto perf_end = std::chrono::high_resolution_clock::now();
    auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(perf_end - perf_start_).count();
    int64_t actual_duration_ns = static_cast<int64_t>(dur);
    APerformanceHint_reportActualWorkDuration(hint_session_, actual_duration_ns);
    APerformanceHint_updateTargetWorkDuration(hint_session_, target_duration_ns);
#else
  if (obj_perfhint_session_) {
    auto perf_end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(perf_end - perf_start_).count();
    int64_t duration_ns = static_cast<int64_t>(duration);
    JNIEnv *env = NativeEngine::GetInstance()->GetJniEnv();

    // Report and update the target work duration using JNI calls.
    env->CallVoidMethod(obj_perfhint_session_, report_actual_work_duration_,
                        duration_ns);
    env->CallVoidMethod(obj_perfhint_session_, update_target_work_duration_,
                        target_duration_ns);
  }
#endif // __ANDROID_API__ >= 33

  }
}

Gọi các API ở đầu và cuối mỗi khung

Trên mỗi khung, chúng ta cần ghi lại thời gian bắt đầu ở đầu khung và báo cáo thời gian thực tế ở cuối khung. Chúng ta sẽ gọi cả reportActualWorkDurationupdateTargetWorkDuration ở cuối khung. Trong mẫu đơn giản này, khối lượng công việc của chúng ta không thay đổi giữa các khung, chúng ta sẽ cập nhật updateTargetWorkDuration bằng giá trị nhắm mục tiêu nhất quán.

// CODELAB: demo_scene.cpp
void DemoScene::DoFrame() {
  // Tell ADPF manager beginning of the perf intensive task.
  ADPFManager::getInstance().BeginPerfHintSession();

  ...

  // Tell ADPF manager end of the perf intensive tasks.
  ADPFManager::getInstance().EndPerfHintSession(jlong target_duration_ns);
}

7. Xin chúc mừng

Xin chúc mừng! Bạn đã tích hợp thành công các tính năng về Khả năng thích ứng vào một trò chơi.

Hãy chú ý theo dõi vì chúng tôi sẽ bổ sung thêm nhiều tính năng trong khung Khả năng thích ứng vào Android.