ভলকান প্রাক-ঘূর্ণন সহ ডিভাইস অভিযোজন পরিচালনা করুন

এই নিবন্ধটি বর্ণনা করে যে কীভাবে পূর্ব-ঘূর্ণন প্রয়োগ করে আপনার ভলকান অ্যাপ্লিকেশনে ডিভাইসের ঘূর্ণন দক্ষতার সাথে পরিচালনা করা যায়।

Vulkan এর সাথে, আপনি রেন্ডারিং স্টেট সম্পর্কে ওপেনজিএল এর চেয়ে অনেক বেশি তথ্য নির্দিষ্ট করতে পারেন। Vulkan এর সাথে, আপনাকে অবশ্যই স্পষ্টভাবে প্রয়োগ করতে হবে যেগুলি OpenGL-এ ড্রাইভার দ্বারা পরিচালিত হয়, যেমন ডিভাইসের অভিযোজন এবং পৃষ্ঠের অভিযোজন রেন্ডারের সাথে এর সম্পর্ক। তিনটি উপায়ে Android ডিভাইসের স্থিতিবিন্যাসের সাথে ডিভাইসের রেন্ডার পৃষ্ঠের সমন্বয় সাধন করতে পারে:

  1. অ্যান্ড্রয়েড ওএস ডিভাইসের ডিসপ্লে প্রসেসিং ইউনিট (DPU) ব্যবহার করতে পারে, যা দক্ষতার সাথে হার্ডওয়্যারে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। শুধুমাত্র সমর্থিত ডিভাইসগুলিতে উপলব্ধ।
  2. Android OS একটি কম্পোজিটর পাস যোগ করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। আউটপুট ইমেজ ঘোরানোর সাথে কম্পোজিটরকে কীভাবে মোকাবেলা করতে হবে তার উপর নির্ভর করে এটির একটি কার্যক্ষমতা খরচ হবে।
  3. অ্যাপ্লিকেশনটি নিজেই একটি ঘূর্ণিত চিত্রকে একটি রেন্ডার পৃষ্ঠে রেন্ডার করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে যা প্রদর্শনের বর্তমান অভিযোজনের সাথে মেলে।

এই পদ্ধতিগুলির মধ্যে কোনটি আপনার ব্যবহার করা উচিত?

বর্তমানে, অ্যাপ্লিকেশনের বাইরে সারফেস রোটেশন পরিচালিত হবে কিনা তা জানার কোনো উপায় নেই। এমনকি যদি আপনার জন্য এটির যত্ন নেওয়ার জন্য একটি DPU থাকে, তবুও একটি পরিমাপযোগ্য পারফরম্যান্স পেনাল্টি দিতে হবে। আপনার অ্যাপ্লিকেশানটি যদি CPU-বাউন্ড হয়, তাহলে Android Compositor দ্বারা GPU-এর ব্যবহার বৃদ্ধির কারণে এটি একটি পাওয়ার সমস্যা হয়ে দাঁড়ায়, যা সাধারণত একটি বর্ধিত ফ্রিকোয়েন্সিতে চলছে। যদি আপনার অ্যাপ্লিকেশানটি GPU আবদ্ধ হয়, তাহলে Android কম্পোজিটর আপনার অ্যাপ্লিকেশনের GPU-এর কাজকে প্রিম্পট করতে পারে, যার ফলে অতিরিক্ত কর্মক্ষমতা ক্ষতি হয়৷

Pixel 4XL-এ শিপিং শিরোনাম চালানোর সময়, আমরা দেখেছি যে SurfaceFlinger (উচ্চ অগ্রাধিকারের কাজ যা Android কম্পোজিটরকে চালিত করে):

  • নিয়মিতভাবে অ্যাপ্লিকেশানের কাজকে অগ্রাহ্য করে, যার ফলে ফ্রেমটাইমে 1-3ms হিট হয় এবং

  • GPU এর ভার্টেক্স/টেক্সচার মেমরির উপর চাপ বাড়ায়, কারণ কম্পোজিটরকে তার কম্পোজিশনের কাজ করতে পুরো ফ্রেমবাফার পড়তে হয়।

সারফেসফ্লিংগার দ্বারা সঠিকভাবে অভিযোজন পরিচালনা করা GPU প্রিম্পশনকে প্রায় সম্পূর্ণরূপে বন্ধ করে দেয়, যখন GPU ফ্রিকোয়েন্সি 40% কমে যায় কারণ Android কম্পোজিটর দ্বারা ব্যবহৃত বুস্টেড ফ্রিকোয়েন্সি আর প্রয়োজন হয় না।

পৃষ্ঠের ঘূর্ণনগুলি যথাসম্ভব সামান্য ওভারহেডের সাথে সঠিকভাবে পরিচালনা করা হয় তা নিশ্চিত করার জন্য, যেমনটি আগের ক্ষেত্রে দেখা গেছে, আপনাকে পদ্ধতি 3 প্রয়োগ করতে হবে। এটি প্রাক-ঘূর্ণন হিসাবে পরিচিত। এটি Android OS কে বলে যে আপনার অ্যাপ পৃষ্ঠের ঘূর্ণন পরিচালনা করে। আপনি সারফেস ট্রান্সফর্ম পতাকাগুলি পাস করে এটি করতে পারেন যা সোয়াপচেইন তৈরির সময় অভিযোজন নির্দিষ্ট করে। এটি অ্যান্ড্রয়েড কম্পোজিটরকে নিজেই ঘূর্ণন করা থেকে বিরত করে

সারফেস ট্রান্সফর্ম পতাকা কিভাবে সেট করবেন তা জানা প্রতিটি ভলকান অ্যাপ্লিকেশনের জন্য গুরুত্বপূর্ণ। অ্যাপ্লিকেশনগুলি হয় একাধিক অভিযোজন সমর্থন করে বা একটি একক অভিযোজন সমর্থন করে যেখানে রেন্ডার পৃষ্ঠটি ডিভাইসটি তার পরিচয় অভিযোজন হিসাবে বিবেচনা করে তার থেকে ভিন্ন অভিযোজনে থাকে। উদাহরণস্বরূপ, একটি পোর্ট্রেট-পরিচয় ফোনে একটি ল্যান্ডস্কেপ-শুধু অ্যাপ্লিকেশন, অথবা একটি ল্যান্ডস্কেপ-পরিচয় ট্যাবলেটে একটি শুধুমাত্র-পোর্ট্রেট অ্যাপ্লিকেশন৷

AndroidManifest.xml পরিবর্তন করুন

আপনার অ্যাপ্লিকেশানে ডিভাইসের ঘূর্ণন পরিচালনা করতে, অ্যাপ্লিকেশানের AndroidManifest.xml ফাইলটি পরিবর্তন করে Android কে জানাতে শুরু করুন যে আপনার অ্যাপ ওরিয়েন্টেশন এবং স্ক্রীনের আকার পরিবর্তনগুলি পরিচালনা করবে৷ এটি অ্যান্ড্রয়েডকে অ্যান্ড্রয়েড Activity ধ্বংস এবং পুনরায় তৈরি করা থেকে বাধা দেয় এবং যখন একটি অভিযোজন পরিবর্তন ঘটে তখন বিদ্যমান উইন্ডো পৃষ্ঠে onDestroy() ফাংশনকে কল করা। অ্যাক্টিভিটির configChanges বিভাগে orientation (এপিআই লেভেল <13 সমর্থন করার জন্য) এবং screenSize অ্যাট্রিবিউট যোগ করে এটি করা হয়:

<activity android:name="android.app.NativeActivity"
          android:configChanges="orientation|screenSize">

যদি আপনার অ্যাপ্লিকেশনটি screenOrientation বৈশিষ্ট্য ব্যবহার করে তার স্ক্রীন অভিযোজন ঠিক করে, তাহলে আপনাকে এটি করতে হবে না। এছাড়াও, যদি আপনার অ্যাপ্লিকেশান একটি নির্দিষ্ট অভিযোজন ব্যবহার করে তবে এটি শুধুমাত্র একবার অ্যাপ্লিকেশন স্টার্টআপ/রিজুমে সোয়াপচেইন সেট আপ করতে হবে।

আইডেন্টিটি স্ক্রিন রেজোলিউশন এবং ক্যামেরা প্যারামিটার পান

এরপরে, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR মানের সাথে যুক্ত ডিভাইসের স্ক্রীন রেজোলিউশন সনাক্ত করুন৷ এই রেজোলিউশনটি ডিভাইসের আইডেন্টিটি অরিয়েন্টেশনের সাথে যুক্ত, এবং সেইজন্য সোয়াপচেনকে সবসময় সেট করতে হবে। এটি পাওয়ার সবচেয়ে নির্ভরযোগ্য উপায় হল অ্যাপ্লিকেশন স্টার্টআপের সময় vkGetPhysicalDeviceSurfaceCapabilitiesKHR() এ একটি কল করা এবং ফিরে আসা পরিমাণ সংরক্ষণ করা। আপনি পরিচয় স্ক্রীন রেজোলিউশনটি সংরক্ষণ করছেন তা নিশ্চিত করার জন্য currentTransform উপর ভিত্তি করে প্রস্থ এবং উচ্চতা অদলবদল করুন যা ফিরে এসেছে:

VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

uint32_t width = capabilities.currentExtent.width;
uint32_t height = capabilities.currentExtent.height;
if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
    capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  // Swap to get identity width and height
  capabilities.currentExtent.height = width;
  capabilities.currentExtent.width = height;
}

displaySizeIdentity = capabilities.currentExtent;

displaySizeIdentity হল একটি VkExtent2D স্ট্রাকচার যা আমরা ডিসপ্লের ন্যাচারাল ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের উল্লিখিত আইডেন্টিটি রেজোলিউশন সংরক্ষণ করতে ব্যবহার করি।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (Android 10+)

আপনার অ্যাপ্লিকেশনে একটি অভিযোজন পরিবর্তন সনাক্ত করার সবচেয়ে নির্ভরযোগ্য উপায় হল vkQueuePresentKHR() ফাংশন VK_SUBOPTIMAL_KHR প্রদান করে কিনা তা যাচাই করা। যেমন:

auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
  orientationChanged = true;
}

দ্রষ্টব্য: এই সমাধানটি শুধুমাত্র Android 10 এবং তার পরে চলমান ডিভাইসগুলিতে কাজ করে৷ Android এর এই সংস্করণগুলি vkQueuePresentKHR() থেকে VK_SUBOPTIMAL_KHR ফেরত দেয়। আমরা এই চেকের ফলাফল orientationChanged এ সংরক্ষণ করি, একটি boolean যা অ্যাপ্লিকেশনগুলির প্রধান রেন্ডারিং লুপ থেকে অ্যাক্সেসযোগ্য।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (প্রি-অ্যান্ড্রয়েড 10)

Android 10 বা তার বেশি চলমান ডিভাইসগুলির জন্য, একটি ভিন্ন বাস্তবায়ন প্রয়োজন, কারণ VK_SUBOPTIMAL_KHR সমর্থিত নয়৷

পোলিং ব্যবহার করে

প্রি-অ্যান্ড্রয়েড 10 ডিভাইসে আপনি বর্তমান ডিভাইসের প্রতিটি pollingInterval ফ্রেমে রূপান্তর করতে পারেন, যেখানে pollingInterval প্রোগ্রামার দ্বারা নির্ধারিত একটি গ্রানুলারিটি। আপনি যেভাবে এটি করবেন তা হল vkGetPhysicalDeviceSurfaceCapabilitiesKHR() কল করে এবং তারপরে প্রত্যাবর্তিত currentTransform ক্ষেত্রটিকে বর্তমানে সঞ্চিত পৃষ্ঠের রূপান্তরের সাথে তুলনা করা (এই কোড উদাহরণে pretransformFlag এ সংরক্ষিত)।

currFrameCount++;
if (currFrameCount >= pollInterval){
  VkSurfaceCapabilitiesKHR capabilities;
  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

  if (pretransformFlag != capabilities.currentTransform) {
    window_resized = true;
  }
  currFrameCount = 0;
}

Android 10 চালিত একটি Pixel 4-এ, vkGetPhysicalDeviceSurfaceCapabilitiesKHR() .120-.250ms এবং Android 8 চালিত Pixel 1XL-এ পোলিং .110-.350ms লেগেছে৷

কলব্যাক ব্যবহার করে

অ্যান্ড্রয়েড 10 এর নিচে চলমান ডিভাইসগুলির জন্য একটি দ্বিতীয় বিকল্প হল একটি ফাংশন কল করার জন্য একটি onNativeWindowResized() কলব্যাক নিবন্ধন করা যা orientationChanged পতাকা সেট করে, অ্যাপ্লিকেশনটিতে একটি ওরিয়েন্টেশন পরিবর্তন ঘটেছে বলে সংকেত দেয়:

void android_main(struct android_app *app) {
  ...
  app->activity->callbacks->onNativeWindowResized = ResizeCallback;
}

যেখানে ResizeCallback কে সংজ্ঞায়িত করা হয়েছে:

void ResizeCallback(ANativeActivity *activity, ANativeWindow *window){
  orientationChanged = true;
}

এই সমাধানের সমস্যা হল যে onNativeWindowResized() শুধুমাত্র 90-ডিগ্রি ওরিয়েন্টেশন পরিবর্তনের জন্য ডাকা হয়, যেমন ল্যান্ডস্কেপ থেকে প্রতিকৃতিতে যাওয়া বা এর বিপরীতে। অন্যান্য অভিযোজন পরিবর্তনগুলি সোয়াপচেন বিনোদনকে ট্রিগার করবে না। উদাহরণস্বরূপ, ল্যান্ডস্কেপ থেকে রিভার্স-ল্যান্ডস্কেপে পরিবর্তন এটিকে ট্রিগার করবে না, আপনার অ্যাপ্লিকেশনের জন্য অ্যান্ড্রয়েড কম্পোজিটরকে ফ্লিপ করতে হবে।

ওরিয়েন্টেশন পরিবর্তন পরিচালনা করা

অরিয়েন্টেশন পরিবর্তন পরিচালনা করতে, মূল রেন্ডারিং লুপের শীর্ষে অরিয়েন্টেশন পরিবর্তনের রুটিনটিকে কল করুন যখন orientationChanged ভেরিয়েবল সত্যে সেট করা থাকে। যেমন:

bool VulkanDrawFrame() {
 if (orientationChanged) {
   OnOrientationChange();
}

আপনি OnOrientationChange() ফাংশনের মধ্যে swapchain পুনরায় তৈরি করার জন্য প্রয়োজনীয় সমস্ত কাজ করেন। এর মানে হল আপনি:

  1. Framebuffer এবং ImageView এর যেকোন বিদ্যমান দৃষ্টান্ত ধ্বংস করুন,

  2. পুরানো সোয়াপচেইন (যা পরবর্তী আলোচনা করা হবে) ধ্বংস করার সময় সোয়াপচেন পুনরায় তৈরি করুন এবং

  3. নতুন সোয়াপচেইনের ডিসপ্লে ইমেজগুলির সাথে ফ্রেমবাফারগুলি পুনরায় তৈরি করুন৷ দ্রষ্টব্য: সংযুক্তি চিত্রগুলি (উদাহরণস্বরূপ) গভীরতা/স্টেনসিল চিত্রগুলিকে সাধারণত পুনরায় তৈরি করার প্রয়োজন হয় না কারণ সেগুলি পূর্ব-ঘোরানো সোয়াপচেন চিত্রগুলির পরিচয় রেজোলিউশনের উপর ভিত্তি করে।

void OnOrientationChange() {
 vkDeviceWaitIdle(getDevice());

 for (int i = 0; i < getSwapchainLength(); ++i) {
   vkDestroyImageView(getDevice(), displayViews_[i], nullptr);
   vkDestroyFramebuffer(getDevice(), framebuffers_[i], nullptr);
 }

 createSwapChain(getSwapchain());
 createFrameBuffers(render_pass, depthBuffer.image_view);
 orientationChanged = false;
}

এবং ফাংশনের শেষে আপনি orientationChanged পতাকাটিকে মিথ্যাতে রিসেট করে দেখান যে আপনি ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করেছেন।

সোয়াপচেইন রিক্রিয়েশন

পূর্ববর্তী বিভাগে আমরা সোয়াপচেন পুনরায় তৈরি করার কথা উল্লেখ করেছি। এটি করার প্রথম ধাপে রেন্ডারিং পৃষ্ঠের নতুন বৈশিষ্ট্যগুলি পাওয়া জড়িত:

void createSwapChain(VkSwapchainKHR oldSwapchain) {
   VkSurfaceCapabilitiesKHR capabilities;
   vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
   pretransformFlag = capabilities.currentTransform;

নতুন তথ্যের সাথে VkSurfaceCapabilities স্ট্রাকট তৈরি করা হয়েছে, আপনি এখন currentTransform ক্ষেত্রটি পরীক্ষা করে একটি ওরিয়েন্টেশন পরিবর্তন হয়েছে কিনা তা দেখতে পারেন। আপনি এটিকে pretransformFlag ক্ষেত্রের জন্য সংরক্ষণ করবেন কারণ আপনি যখন MVP ম্যাট্রিক্সে সামঞ্জস্য করবেন তখন আপনার এটির প্রয়োজন হবে।

এটি করার জন্য, VkSwapchainCreateInfo কাঠামোতে নিম্নলিখিত বৈশিষ্ট্যগুলি উল্লেখ করুন:

VkSwapchainCreateInfoKHR swapchainCreateInfo{
  ...
  .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
  .imageExtent = displaySizeIdentity,
  .preTransform = pretransformFlag,
  .oldSwapchain = oldSwapchain,
};

vkCreateSwapchainKHR(device_, &swapchainCreateInfo, nullptr, &swapchain_));

if (oldSwapchain != VK_NULL_HANDLE) {
  vkDestroySwapchainKHR(device_, oldSwapchain, nullptr);
}

imageExtent ক্ষেত্রটি displaySizeIdentity ব্যাপ্তির সাথে পপুলেট করা হবে যা আপনি অ্যাপ্লিকেশন স্টার্টআপে সংরক্ষণ করেছিলেন। preTransform ক্ষেত্রটি pretransformFlag ভেরিয়েবল (যা surfaceCapabilities বর্তমান ট্রান্সফর্ম ক্ষেত্রে সেট করা হয়েছে) দিয়ে পপুলেট করা হবে। এছাড়াও আপনি oldSwapchain ক্ষেত্রটিকে সোয়াপচেইনে সেট করেছেন যা ধ্বংস হয়ে যাবে।

MVP ম্যাট্রিক্স সমন্বয়

আপনাকে যা করতে হবে তা হল আপনার MVP ম্যাট্রিক্সে একটি ঘূর্ণন ম্যাট্রিক্স প্রয়োগ করে প্রি-ট্রান্সফরমেশন প্রয়োগ করা। এটি মূলত যা করে তা হল ক্লিপ স্পেসে ঘূর্ণন প্রয়োগ করা যাতে ফলস্বরূপ চিত্রটি বর্তমান ডিভাইসের অভিযোজনে ঘোরানো হয়। তারপরে আপনি এই আপডেট করা MVP ম্যাট্রিক্সটিকে আপনার ভার্টেক্স শেডারে পাস করতে পারেন এবং আপনার শেডারগুলিকে পরিবর্তন করার প্রয়োজন ছাড়াই এটি স্বাভাবিক হিসাবে ব্যবহার করতে পারেন।

glm::mat4 pre_rotate_mat = glm::mat4(1.0f);
glm::vec3 rotation_axis = glm::vec3(0.0f, 0.0f, 1.0f);

if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(90.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(270.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(180.0f), rotation_axis);
}

MVP = pre_rotate_mat * MVP;

বিবেচনা - নন-ফুল স্ক্রিন ভিউপোর্ট এবং কাঁচি

যদি আপনার অ্যাপ্লিকেশনটি একটি নন-ফুল স্ক্রিন ভিউপোর্ট/কাঁচি অঞ্চল ব্যবহার করে, তবে তাদের ডিভাইসের অভিযোজন অনুযায়ী আপডেট করতে হবে। এটির জন্য আপনাকে ভলকানের পাইপলাইন তৈরির সময় গতিশীল ভিউপোর্ট এবং কাঁচি বিকল্পগুলি সক্ষম করতে হবে:

VkDynamicState dynamicStates[2] = {
  VK_DYNAMIC_STATE_VIEWPORT,
  VK_DYNAMIC_STATE_SCISSOR,
};

VkPipelineDynamicStateCreateInfo dynamicInfo = {
  .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .dynamicStateCount = 2,
  .pDynamicStates = dynamicStates,
};

VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
  .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
  ...
  .pDynamicState = &dynamicInfo,
  ...
};

VkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &mPipeline);

কমান্ড বাফার রেকর্ডিংয়ের সময় ভিউপোর্ট ব্যাপ্তির প্রকৃত গণনা এইরকম দেখায়:

int x = 0, y = 0, w = 500, h = 400;

glm::vec4 viewportData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    viewportData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    viewportData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    viewportData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    viewportData = {x, y, w, h};
    break;
}

const VkViewport viewport = {
    .x = viewportData.x,
    .y = viewportData.y,
    .width = viewportData.z,
    .height = viewportData.w,
    .minDepth = 0.0F,
    .maxDepth = 1.0F,
};

vkCmdSetViewport(renderer->GetCurrentCommandBuffer(), 0, 1, &viewport);

x এবং y ভেরিয়েবল ভিউপোর্টের উপরের বাম কোণের স্থানাঙ্কগুলিকে সংজ্ঞায়িত করে, যখন w এবং h যথাক্রমে ভিউপোর্টের প্রস্থ এবং উচ্চতা নির্ধারণ করে। একই গণনা কাঁচি পরীক্ষা সেট করতেও ব্যবহার করা যেতে পারে, এবং সম্পূর্ণতার জন্য এখানে অন্তর্ভুক্ত করা হয়েছে:

int x = 0, y = 0, w = 500, h = 400;
glm::vec4 scissorData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    scissorData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    scissorData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    scissorData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    scissorData = {x, y, w, h};
    break;
}

const VkRect2D scissor = {
    .offset =
        {
            .x = (int32_t)viewportData.x,
            .y = (int32_t)viewportData.y,
        },
    .extent =
        {
            .width = (uint32_t)viewportData.z,
            .height = (uint32_t)viewportData.w,
        },
};

vkCmdSetScissor(renderer->GetCurrentCommandBuffer(), 0, 1, &scissor);

বিবেচনা - ফ্র্যাগমেন্ট শেডার ডেরিভেটিভস

আপনার অ্যাপ্লিকেশন যদি dFdx এবং dFdy মতো ডেরিভেটিভ কম্পিউটেশন ব্যবহার করে, তাহলে ঘূর্ণিত স্থানাঙ্ক সিস্টেমের জন্য অতিরিক্ত রূপান্তরের প্রয়োজন হতে পারে কারণ এই গণনাগুলি পিক্সেল স্পেসে কার্যকর করা হয়। এর জন্য অ্যাপটিকে ফ্র্যাগমেন্ট শেডারে প্রি-ট্রান্সফর্মের কিছু ইঙ্গিত পাস করতে হবে (যেমন একটি পূর্ণসংখ্যা বর্তমান ডিভাইসের অভিযোজন প্রতিনিধিত্ব করে) এবং ডেরিভেটিভ কম্পিউটেশনগুলি সঠিকভাবে ম্যাপ করতে এটি ব্যবহার করুন:

  • একটি 90 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য
    • dFdx অবশ্যই dFdy- তে ম্যাপ করতে হবে
    • dFdy-কে অবশ্যই -dFdx- এ ম্যাপ করতে হবে
  • একটি 270 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য
    • dFdx -dFdy- এ ম্যাপ করা আবশ্যক
    • dFdy অবশ্যই dFdx এ ম্যাপ করতে হবে
  • একটি 180 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য,
    • dFdx -dFdx- এ ম্যাপ করা আবশ্যক
    • dFdy-কে -dFdy- এ ম্যাপ করা আবশ্যক

উপসংহার

আপনার অ্যাপ্লিকেশানের জন্য Android-এ Vulkan থেকে সর্বাধিক সুবিধা পেতে, প্রি-রোটেশন বাস্তবায়ন করা আবশ্যক। এই নিবন্ধ থেকে সবচেয়ে গুরুত্বপূর্ণ টেকওয়ে হল:

  • নিশ্চিত করুন যে swapchain তৈরি বা বিনোদনের সময়, প্রিট্রান্সফর্ম ফ্ল্যাগটি Android অপারেটিং সিস্টেম দ্বারা প্রত্যাবর্তিত পতাকার সাথে মেলে। এটি কম্পোজিটর ওভারহেড এড়াবে।
  • ডিসপ্লের ন্যাচারাল ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের আইডেন্টিটি রেজোলিউশনে সোয়াপচেন সাইজ স্থির রাখুন।
  • ক্লিপ স্পেসে MVP ম্যাট্রিক্স ঘোরান ডিভাইসের ওরিয়েন্টেশনের জন্য, কারণ swapchain রেজোলিউশন/ব্যাপ্তি আর ডিসপ্লের ওরিয়েন্টেশনের সাথে আপডেট হয় না।
  • আপনার অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী ভিউপোর্ট এবং কাঁচি আয়তক্ষেত্র আপডেট করুন।

নমুনা অ্যাপ: ন্যূনতম অ্যান্ড্রয়েড প্রি-রোটেশন

,

এই নিবন্ধটি বর্ণনা করে যে কীভাবে পূর্ব-ঘূর্ণন প্রয়োগ করে আপনার ভলকান অ্যাপ্লিকেশনে ডিভাইসের ঘূর্ণন দক্ষতার সাথে পরিচালনা করা যায়।

Vulkan এর সাথে, আপনি রেন্ডারিং স্টেট সম্পর্কে ওপেনজিএল এর চেয়ে অনেক বেশি তথ্য নির্দিষ্ট করতে পারেন। Vulkan এর সাথে, আপনাকে অবশ্যই স্পষ্টভাবে প্রয়োগ করতে হবে যেগুলি OpenGL-এ ড্রাইভার দ্বারা পরিচালিত হয়, যেমন ডিভাইসের অভিযোজন এবং পৃষ্ঠের অভিযোজন রেন্ডারের সাথে এর সম্পর্ক। তিনটি উপায়ে Android ডিভাইসের স্থিতিবিন্যাসের সাথে ডিভাইসের রেন্ডার পৃষ্ঠের সমন্বয় সাধন করতে পারে:

  1. অ্যান্ড্রয়েড ওএস ডিভাইসের ডিসপ্লে প্রসেসিং ইউনিট (DPU) ব্যবহার করতে পারে, যা দক্ষতার সাথে হার্ডওয়্যারে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। শুধুমাত্র সমর্থিত ডিভাইসগুলিতে উপলব্ধ।
  2. Android OS একটি কম্পোজিটর পাস যোগ করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। আউটপুট ইমেজ ঘোরানোর সাথে কম্পোজিটরকে কীভাবে মোকাবেলা করতে হবে তার উপর নির্ভর করে এটির একটি কার্যক্ষমতা খরচ হবে।
  3. অ্যাপ্লিকেশনটি নিজেই একটি ঘূর্ণিত চিত্রকে একটি রেন্ডার পৃষ্ঠে রেন্ডার করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে যা প্রদর্শনের বর্তমান অভিযোজনের সাথে মেলে।

এই পদ্ধতিগুলির মধ্যে কোনটি আপনার ব্যবহার করা উচিত?

বর্তমানে, অ্যাপ্লিকেশনের বাইরে সারফেস রোটেশন পরিচালিত হবে কিনা তা জানার কোনো উপায় নেই। এমনকি যদি আপনার জন্য এটির যত্ন নেওয়ার জন্য একটি DPU থাকে, তবুও একটি পরিমাপযোগ্য পারফরম্যান্স পেনাল্টি দিতে হবে। আপনার অ্যাপ্লিকেশানটি যদি CPU-বাউন্ড হয়, তাহলে Android Compositor দ্বারা GPU-এর ব্যবহার বৃদ্ধির কারণে এটি একটি পাওয়ার সমস্যা হয়ে দাঁড়ায়, যা সাধারণত একটি বর্ধিত ফ্রিকোয়েন্সিতে চলছে। যদি আপনার অ্যাপ্লিকেশানটি GPU আবদ্ধ হয়, তাহলে Android কম্পোজিটর আপনার অ্যাপ্লিকেশনের GPU-এর কাজকে প্রিম্পট করতে পারে, যার ফলে অতিরিক্ত কর্মক্ষমতা ক্ষতি হয়৷

Pixel 4XL-এ শিপিং শিরোনাম চালানোর সময়, আমরা দেখেছি যে SurfaceFlinger (উচ্চ অগ্রাধিকারের কাজ যা Android কম্পোজিটরকে চালিত করে):

  • নিয়মিতভাবে অ্যাপ্লিকেশানের কাজকে অগ্রাহ্য করে, যার ফলে ফ্রেমটাইমে 1-3ms হিট হয় এবং

  • GPU এর ভার্টেক্স/টেক্সচার মেমরির উপর চাপ বাড়ায়, কারণ কম্পোজিটরকে তার কম্পোজিশনের কাজ করতে পুরো ফ্রেমবাফার পড়তে হয়।

সারফেসফ্লিংগার দ্বারা সঠিকভাবে অভিযোজন পরিচালনা করা GPU প্রিম্পশনকে প্রায় সম্পূর্ণরূপে বন্ধ করে দেয়, যখন GPU ফ্রিকোয়েন্সি 40% কমে যায় কারণ Android কম্পোজিটর দ্বারা ব্যবহৃত বুস্টেড ফ্রিকোয়েন্সি আর প্রয়োজন হয় না।

পৃষ্ঠের ঘূর্ণনগুলি যথাসম্ভব সামান্য ওভারহেডের সাথে সঠিকভাবে পরিচালনা করা হয় তা নিশ্চিত করার জন্য, যেমনটি আগের ক্ষেত্রে দেখা গেছে, আপনাকে পদ্ধতি 3 প্রয়োগ করতে হবে। এটি প্রাক-ঘূর্ণন হিসাবে পরিচিত। এটি Android OS কে বলে যে আপনার অ্যাপ পৃষ্ঠের ঘূর্ণন পরিচালনা করে। আপনি সারফেস ট্রান্সফর্ম পতাকাগুলি পাস করে এটি করতে পারেন যা সোয়াপচেইন তৈরির সময় অভিযোজন নির্দিষ্ট করে। এটি অ্যান্ড্রয়েড কম্পোজিটরকে নিজেই ঘূর্ণন করা থেকে বিরত করে

সারফেস ট্রান্সফর্ম পতাকা কিভাবে সেট করবেন তা জানা প্রতিটি ভলকান অ্যাপ্লিকেশনের জন্য গুরুত্বপূর্ণ। অ্যাপ্লিকেশনগুলি হয় একাধিক অভিযোজন সমর্থন করে বা একটি একক অভিযোজন সমর্থন করে যেখানে রেন্ডার পৃষ্ঠটি ডিভাইসটি তার পরিচয় অভিযোজন হিসাবে বিবেচনা করে তার থেকে ভিন্ন অভিযোজনে থাকে। উদাহরণস্বরূপ, একটি পোর্ট্রেট-পরিচয় ফোনে একটি ল্যান্ডস্কেপ-শুধু অ্যাপ্লিকেশন, অথবা একটি ল্যান্ডস্কেপ-পরিচয় ট্যাবলেটে একটি শুধুমাত্র-পোর্ট্রেট অ্যাপ্লিকেশন৷

AndroidManifest.xml পরিবর্তন করুন

আপনার অ্যাপ্লিকেশানে ডিভাইসের ঘূর্ণন পরিচালনা করতে, অ্যাপ্লিকেশানের AndroidManifest.xml ফাইলটি পরিবর্তন করে Android কে জানাতে শুরু করুন যে আপনার অ্যাপ ওরিয়েন্টেশন এবং স্ক্রীনের আকার পরিবর্তনগুলি পরিচালনা করবে৷ এটি অ্যান্ড্রয়েডকে অ্যান্ড্রয়েড Activity ধ্বংস এবং পুনরায় তৈরি করা থেকে বাধা দেয় এবং যখন একটি অভিযোজন পরিবর্তন ঘটে তখন বিদ্যমান উইন্ডো পৃষ্ঠে onDestroy() ফাংশনকে কল করা। অ্যাক্টিভিটির configChanges বিভাগে orientation (এপিআই লেভেল <13 সমর্থন করার জন্য) এবং screenSize অ্যাট্রিবিউট যোগ করে এটি করা হয়:

<activity android:name="android.app.NativeActivity"
          android:configChanges="orientation|screenSize">

যদি আপনার অ্যাপ্লিকেশনটি screenOrientation বৈশিষ্ট্য ব্যবহার করে তার স্ক্রীন অভিযোজন ঠিক করে, তাহলে আপনাকে এটি করতে হবে না। এছাড়াও, যদি আপনার অ্যাপ্লিকেশন একটি নির্দিষ্ট অভিযোজন ব্যবহার করে তবে এটি শুধুমাত্র একবার অ্যাপ্লিকেশন স্টার্টআপ/রিজুমে সোয়াপচেইন সেট আপ করতে হবে।

আইডেন্টিটি স্ক্রিন রেজোলিউশন এবং ক্যামেরা প্যারামিটার পান

এরপরে, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR মানের সাথে যুক্ত ডিভাইসের স্ক্রীন রেজোলিউশন সনাক্ত করুন৷ এই রেজোলিউশনটি ডিভাইসের আইডেন্টিটি অরিয়েন্টেশনের সাথে যুক্ত, এবং সেইজন্য সোয়াপচেনকে সবসময় সেট করতে হবে। এটি পাওয়ার সবচেয়ে নির্ভরযোগ্য উপায় হল অ্যাপ্লিকেশন স্টার্টআপের সময় vkGetPhysicalDeviceSurfaceCapabilitiesKHR() এ একটি কল করা এবং ফিরে আসা পরিমাণ সংরক্ষণ করা। আপনি পরিচয় স্ক্রীন রেজোলিউশনটি সংরক্ষণ করছেন তা নিশ্চিত করার জন্য currentTransform উপর ভিত্তি করে প্রস্থ এবং উচ্চতা অদলবদল করুন যা ফিরে এসেছে:

VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

uint32_t width = capabilities.currentExtent.width;
uint32_t height = capabilities.currentExtent.height;
if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
    capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  // Swap to get identity width and height
  capabilities.currentExtent.height = width;
  capabilities.currentExtent.width = height;
}

displaySizeIdentity = capabilities.currentExtent;

displaySizeIdentity হল একটি VkExtent2D স্ট্রাকচার যা আমরা ডিসপ্লের ন্যাচারাল ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের উল্লিখিত আইডেন্টিটি রেজোলিউশন সংরক্ষণ করতে ব্যবহার করি।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (Android 10+)

আপনার অ্যাপ্লিকেশনে একটি অভিযোজন পরিবর্তন সনাক্ত করার সবচেয়ে নির্ভরযোগ্য উপায় হল vkQueuePresentKHR() ফাংশন VK_SUBOPTIMAL_KHR প্রদান করে কিনা তা যাচাই করা। যেমন:

auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
  orientationChanged = true;
}

দ্রষ্টব্য: এই সমাধানটি শুধুমাত্র Android 10 এবং তার পরে চলমান ডিভাইসগুলিতে কাজ করে৷ Android এর এই সংস্করণগুলি vkQueuePresentKHR() থেকে VK_SUBOPTIMAL_KHR ফেরত দেয়। আমরা এই চেকের ফলাফল orientationChanged এ সংরক্ষণ করি, একটি boolean যা অ্যাপ্লিকেশনগুলির প্রধান রেন্ডারিং লুপ থেকে অ্যাক্সেসযোগ্য।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (প্রি-অ্যান্ড্রয়েড 10)

Android 10 বা তার বেশি চলমান ডিভাইসগুলির জন্য, একটি ভিন্ন বাস্তবায়ন প্রয়োজন, কারণ VK_SUBOPTIMAL_KHR সমর্থিত নয়৷

পোলিং ব্যবহার করে

প্রি-অ্যান্ড্রয়েড 10 ডিভাইসে আপনি বর্তমান ডিভাইসের প্রতিটি pollingInterval ফ্রেমে রূপান্তর করতে পারেন, যেখানে pollingInterval প্রোগ্রামার দ্বারা নির্ধারিত একটি গ্রানুলারিটি। আপনি যেভাবে এটি করবেন তা হল vkGetPhysicalDeviceSurfaceCapabilitiesKHR() কল করে এবং তারপরে প্রত্যাবর্তিত currentTransform ক্ষেত্রটিকে বর্তমানে সঞ্চিত পৃষ্ঠের রূপান্তরের সাথে তুলনা করা (এই কোড উদাহরণে pretransformFlag এ সংরক্ষিত)।

currFrameCount++;
if (currFrameCount >= pollInterval){
  VkSurfaceCapabilitiesKHR capabilities;
  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

  if (pretransformFlag != capabilities.currentTransform) {
    window_resized = true;
  }
  currFrameCount = 0;
}

Android 10 চালিত একটি Pixel 4-এ, vkGetPhysicalDeviceSurfaceCapabilitiesKHR() .120-.250ms এবং Android 8 চালিত Pixel 1XL-এ পোলিং .110-.350ms লেগেছে৷

কলব্যাক ব্যবহার করে

অ্যান্ড্রয়েড 10 এর নিচে চলমান ডিভাইসগুলির জন্য একটি দ্বিতীয় বিকল্প হল একটি ফাংশন কল করার জন্য একটি onNativeWindowResized() কলব্যাক নিবন্ধন করা যা orientationChanged পতাকা সেট করে, অ্যাপ্লিকেশনটিতে একটি ওরিয়েন্টেশন পরিবর্তন ঘটেছে বলে সংকেত দেয়:

void android_main(struct android_app *app) {
  ...
  app->activity->callbacks->onNativeWindowResized = ResizeCallback;
}

যেখানে ResizeCallback কে সংজ্ঞায়িত করা হয়েছে:

void ResizeCallback(ANativeActivity *activity, ANativeWindow *window){
  orientationChanged = true;
}

এই সমাধানের সমস্যা হল যে onNativeWindowResized() শুধুমাত্র 90-ডিগ্রি ওরিয়েন্টেশন পরিবর্তনের জন্য ডাকা হয়, যেমন ল্যান্ডস্কেপ থেকে প্রতিকৃতিতে যাওয়া বা এর বিপরীতে। অন্যান্য অভিযোজন পরিবর্তনগুলি সোয়াপচেন বিনোদনকে ট্রিগার করবে না। উদাহরণস্বরূপ, ল্যান্ডস্কেপ থেকে রিভার্স-ল্যান্ডস্কেপে পরিবর্তন এটিকে ট্রিগার করবে না, আপনার অ্যাপ্লিকেশনের জন্য অ্যান্ড্রয়েড কম্পোজিটরকে ফ্লিপ করতে হবে।

ওরিয়েন্টেশন পরিবর্তন পরিচালনা করা

অরিয়েন্টেশন পরিবর্তন পরিচালনা করতে, মূল রেন্ডারিং লুপের শীর্ষে অরিয়েন্টেশন পরিবর্তনের রুটিনটিকে কল করুন যখন orientationChanged ভেরিয়েবল সত্যে সেট করা থাকে। যেমন:

bool VulkanDrawFrame() {
 if (orientationChanged) {
   OnOrientationChange();
}

আপনি OnOrientationChange() ফাংশনের মধ্যে swapchain পুনরায় তৈরি করার জন্য প্রয়োজনীয় সমস্ত কাজ করেন। এর মানে হল আপনি:

  1. Framebuffer এবং ImageView এর যেকোন বিদ্যমান দৃষ্টান্ত ধ্বংস করুন,

  2. পুরানো সোয়াপচেইন (যা পরবর্তী আলোচনা করা হবে) ধ্বংস করার সময় সোয়াপচেন পুনরায় তৈরি করুন এবং

  3. নতুন সোয়াপচেইনের ডিসপ্লে ইমেজগুলির সাথে ফ্রেমবাফারগুলি পুনরায় তৈরি করুন৷ দ্রষ্টব্য: সংযুক্তি চিত্রগুলি (উদাহরণস্বরূপ) গভীরতা/স্টেনসিল চিত্রগুলিকে সাধারণত পুনরায় তৈরি করার প্রয়োজন হয় না কারণ সেগুলি পূর্ব-ঘোরানো সোয়াপচেন চিত্রগুলির পরিচয় রেজোলিউশনের উপর ভিত্তি করে।

void OnOrientationChange() {
 vkDeviceWaitIdle(getDevice());

 for (int i = 0; i < getSwapchainLength(); ++i) {
   vkDestroyImageView(getDevice(), displayViews_[i], nullptr);
   vkDestroyFramebuffer(getDevice(), framebuffers_[i], nullptr);
 }

 createSwapChain(getSwapchain());
 createFrameBuffers(render_pass, depthBuffer.image_view);
 orientationChanged = false;
}

এবং ফাংশনের শেষে আপনি orientationChanged পতাকাটিকে মিথ্যাতে রিসেট করে দেখান যে আপনি ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করেছেন।

সোয়াপচেইন রিক্রিয়েশন

পূর্ববর্তী বিভাগে আমরা সোয়াপচেন পুনরায় তৈরি করার কথা উল্লেখ করেছি। এটি করার প্রথম ধাপে রেন্ডারিং পৃষ্ঠের নতুন বৈশিষ্ট্যগুলি পাওয়া জড়িত:

void createSwapChain(VkSwapchainKHR oldSwapchain) {
   VkSurfaceCapabilitiesKHR capabilities;
   vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
   pretransformFlag = capabilities.currentTransform;

নতুন তথ্যের সাথে VkSurfaceCapabilities স্ট্রাকট তৈরি করা হয়েছে, আপনি এখন currentTransform ক্ষেত্রটি পরীক্ষা করে একটি ওরিয়েন্টেশন পরিবর্তন হয়েছে কিনা তা দেখতে পারেন। আপনি এটিকে pretransformFlag ক্ষেত্রের জন্য সংরক্ষণ করবেন কারণ আপনি যখন MVP ম্যাট্রিক্সে সামঞ্জস্য করবেন তখন আপনার এটির প্রয়োজন হবে।

এটি করার জন্য, VkSwapchainCreateInfo কাঠামোতে নিম্নলিখিত বৈশিষ্ট্যগুলি উল্লেখ করুন:

VkSwapchainCreateInfoKHR swapchainCreateInfo{
  ...
  .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
  .imageExtent = displaySizeIdentity,
  .preTransform = pretransformFlag,
  .oldSwapchain = oldSwapchain,
};

vkCreateSwapchainKHR(device_, &swapchainCreateInfo, nullptr, &swapchain_));

if (oldSwapchain != VK_NULL_HANDLE) {
  vkDestroySwapchainKHR(device_, oldSwapchain, nullptr);
}

imageExtent ক্ষেত্রটি displaySizeIdentity ব্যাপ্তির সাথে পপুলেট করা হবে যা আপনি অ্যাপ্লিকেশন স্টার্টআপে সংরক্ষণ করেছিলেন। preTransform ক্ষেত্রটি pretransformFlag ভেরিয়েবল (যা surfaceCapabilities বর্তমান ট্রান্সফর্ম ক্ষেত্রে সেট করা হয়েছে) দিয়ে পপুলেট করা হবে। এছাড়াও আপনি oldSwapchain ক্ষেত্রটিকে সোয়াপচেইনে সেট করেছেন যা ধ্বংস হয়ে যাবে।

MVP ম্যাট্রিক্স সমন্বয়

আপনাকে যা করতে হবে তা হল আপনার MVP ম্যাট্রিক্সে একটি ঘূর্ণন ম্যাট্রিক্স প্রয়োগ করে প্রি-ট্রান্সফরমেশন প্রয়োগ করা। এটি মূলত যা করে তা হল ক্লিপ স্পেসে ঘূর্ণন প্রয়োগ করা যাতে ফলস্বরূপ চিত্রটি বর্তমান ডিভাইসের অভিযোজনে ঘোরানো হয়। তারপরে আপনি এই আপডেট করা MVP ম্যাট্রিক্সটিকে আপনার ভার্টেক্স শেডারে পাস করতে পারেন এবং আপনার শেডারগুলিকে পরিবর্তন করার প্রয়োজন ছাড়াই এটি স্বাভাবিক হিসাবে ব্যবহার করতে পারেন।

glm::mat4 pre_rotate_mat = glm::mat4(1.0f);
glm::vec3 rotation_axis = glm::vec3(0.0f, 0.0f, 1.0f);

if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(90.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(270.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(180.0f), rotation_axis);
}

MVP = pre_rotate_mat * MVP;

বিবেচনা - নন-ফুল স্ক্রিন ভিউপোর্ট এবং কাঁচি

যদি আপনার অ্যাপ্লিকেশনটি একটি নন-ফুল স্ক্রিন ভিউপোর্ট/কাঁচি অঞ্চল ব্যবহার করে, তবে তাদের ডিভাইসের অভিযোজন অনুযায়ী আপডেট করতে হবে। এটির জন্য আপনাকে ভলকানের পাইপলাইন তৈরির সময় গতিশীল ভিউপোর্ট এবং কাঁচি বিকল্পগুলি সক্ষম করতে হবে:

VkDynamicState dynamicStates[2] = {
  VK_DYNAMIC_STATE_VIEWPORT,
  VK_DYNAMIC_STATE_SCISSOR,
};

VkPipelineDynamicStateCreateInfo dynamicInfo = {
  .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .dynamicStateCount = 2,
  .pDynamicStates = dynamicStates,
};

VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
  .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
  ...
  .pDynamicState = &dynamicInfo,
  ...
};

VkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &mPipeline);

কমান্ড বাফার রেকর্ডিংয়ের সময় ভিউপোর্ট ব্যাপ্তির প্রকৃত গণনা এইরকম দেখায়:

int x = 0, y = 0, w = 500, h = 400;

glm::vec4 viewportData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    viewportData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    viewportData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    viewportData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    viewportData = {x, y, w, h};
    break;
}

const VkViewport viewport = {
    .x = viewportData.x,
    .y = viewportData.y,
    .width = viewportData.z,
    .height = viewportData.w,
    .minDepth = 0.0F,
    .maxDepth = 1.0F,
};

vkCmdSetViewport(renderer->GetCurrentCommandBuffer(), 0, 1, &viewport);

x এবং y ভেরিয়েবল ভিউপোর্টের উপরের বাম কোণের স্থানাঙ্কগুলিকে সংজ্ঞায়িত করে, যখন w এবং h যথাক্রমে ভিউপোর্টের প্রস্থ এবং উচ্চতা নির্ধারণ করে। একই গণনা কাঁচি পরীক্ষা সেট করতেও ব্যবহার করা যেতে পারে, এবং সম্পূর্ণতার জন্য এখানে অন্তর্ভুক্ত করা হয়েছে:

int x = 0, y = 0, w = 500, h = 400;
glm::vec4 scissorData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    scissorData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    scissorData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    scissorData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    scissorData = {x, y, w, h};
    break;
}

const VkRect2D scissor = {
    .offset =
        {
            .x = (int32_t)viewportData.x,
            .y = (int32_t)viewportData.y,
        },
    .extent =
        {
            .width = (uint32_t)viewportData.z,
            .height = (uint32_t)viewportData.w,
        },
};

vkCmdSetScissor(renderer->GetCurrentCommandBuffer(), 0, 1, &scissor);

বিবেচনা - ফ্র্যাগমেন্ট শেডার ডেরিভেটিভস

আপনার অ্যাপ্লিকেশন যদি dFdx এবং dFdy মতো ডেরিভেটিভ কম্পিউটেশন ব্যবহার করে, তাহলে ঘূর্ণিত স্থানাঙ্ক সিস্টেমের জন্য অতিরিক্ত রূপান্তরের প্রয়োজন হতে পারে কারণ এই গণনাগুলি পিক্সেল স্পেসে কার্যকর করা হয়। এর জন্য অ্যাপটিকে ফ্র্যাগমেন্ট শেডারে প্রি-ট্রান্সফর্মের কিছু ইঙ্গিত পাস করতে হবে (যেমন একটি পূর্ণসংখ্যা বর্তমান ডিভাইসের অভিযোজন প্রতিনিধিত্ব করে) এবং ডেরিভেটিভ কম্পিউটেশনগুলি সঠিকভাবে ম্যাপ করতে এটি ব্যবহার করুন:

  • একটি 90 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য
    • dFdx অবশ্যই dFdy- তে ম্যাপ করতে হবে
    • dFdy-কে অবশ্যই -dFdx- এ ম্যাপ করতে হবে
  • একটি 270 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য
    • dFdx -dFdy- এ ম্যাপ করা আবশ্যক
    • dFdy অবশ্যই dFdx এ ম্যাপ করতে হবে
  • একটি 180 ডিগ্রী প্রাক-ঘোরানো ফ্রেমের জন্য,
    • dFdx -dFdx- এ ম্যাপ করা আবশ্যক
    • dFdy-কে -dFdy- এ ম্যাপ করা আবশ্যক

উপসংহার

আপনার অ্যাপ্লিকেশানের জন্য Android-এ Vulkan থেকে সর্বাধিক সুবিধা পেতে, প্রি-রোটেশন বাস্তবায়ন করা আবশ্যক। এই নিবন্ধ থেকে সবচেয়ে গুরুত্বপূর্ণ টেকওয়ে হল:

  • নিশ্চিত করুন যে swapchain তৈরি বা বিনোদনের সময়, প্রিট্রান্সফর্ম ফ্ল্যাগটি Android অপারেটিং সিস্টেম দ্বারা প্রত্যাবর্তিত পতাকার সাথে মেলে। এটি কম্পোজিটর ওভারহেড এড়াবে।
  • ডিসপ্লের ন্যাচারাল ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের আইডেন্টিটি রেজোলিউশনে সোয়াপচেন সাইজ স্থির রাখুন।
  • ক্লিপ স্পেসে MVP ম্যাট্রিক্স ঘোরান ডিভাইসের ওরিয়েন্টেশনের জন্য, কারণ swapchain রেজোলিউশন/ব্যাপ্তি আর ডিসপ্লের ওরিয়েন্টেশনের সাথে আপডেট হয় না।
  • আপনার অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী ভিউপোর্ট এবং কাঁচি আয়তক্ষেত্র আপডেট করুন।

নমুনা অ্যাপ: ন্যূনতম অ্যান্ড্রয়েড প্রি-রোটেশন

,

এই নিবন্ধটি বর্ণনা করে যে কীভাবে পূর্ব-ঘূর্ণন প্রয়োগ করে আপনার ভলকান অ্যাপ্লিকেশনে ডিভাইসের ঘূর্ণন দক্ষতার সাথে পরিচালনা করা যায়।

Vulkan এর সাথে, আপনি রেন্ডারিং স্টেট সম্পর্কে ওপেনজিএল এর চেয়ে অনেক বেশি তথ্য নির্দিষ্ট করতে পারেন। Vulkan এর সাথে, আপনাকে অবশ্যই স্পষ্টভাবে প্রয়োগ করতে হবে যেগুলি OpenGL-এ ড্রাইভার দ্বারা পরিচালিত হয়, যেমন ডিভাইসের অভিযোজন এবং পৃষ্ঠের অভিযোজন রেন্ডারের সাথে এর সম্পর্ক। তিনটি উপায়ে Android ডিভাইসের স্থিতিবিন্যাসের সাথে ডিভাইসের রেন্ডার পৃষ্ঠের সমন্বয় সাধন করতে পারে:

  1. অ্যান্ড্রয়েড ওএস ডিভাইসের ডিসপ্লে প্রসেসিং ইউনিট (DPU) ব্যবহার করতে পারে, যা দক্ষতার সাথে হার্ডওয়্যারে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। শুধুমাত্র সমর্থিত ডিভাইসগুলিতে উপলব্ধ।
  2. Android OS একটি কম্পোজিটর পাস যোগ করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। আউটপুট ইমেজ ঘোরানোর সাথে কম্পোজিটরকে কীভাবে মোকাবেলা করতে হবে তার উপর নির্ভর করে এটির একটি কার্যক্ষমতা খরচ হবে।
  3. অ্যাপ্লিকেশনটি নিজেই একটি ঘূর্ণিত চিত্রকে একটি রেন্ডার পৃষ্ঠে রেন্ডার করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে যা প্রদর্শনের বর্তমান অভিযোজনের সাথে মেলে।

এই পদ্ধতিগুলির মধ্যে কোনটি আপনার ব্যবহার করা উচিত?

বর্তমানে, অ্যাপ্লিকেশনের বাইরে সারফেস রোটেশন পরিচালিত হবে কিনা তা জানার কোনো উপায় নেই। এমনকি যদি আপনার জন্য এটির যত্ন নেওয়ার জন্য একটি DPU থাকে, তবুও একটি পরিমাপযোগ্য পারফরম্যান্স পেনাল্টি দিতে হবে। আপনার অ্যাপ্লিকেশানটি যদি CPU-বাউন্ড হয়, তাহলে Android Compositor দ্বারা GPU-এর ব্যবহার বৃদ্ধির কারণে এটি একটি পাওয়ার সমস্যা হয়ে দাঁড়ায়, যা সাধারণত একটি বর্ধিত ফ্রিকোয়েন্সিতে চলছে। যদি আপনার অ্যাপ্লিকেশানটি GPU আবদ্ধ হয়, তাহলে Android কম্পোজিটর আপনার অ্যাপ্লিকেশনের GPU-এর কাজকে প্রিম্পট করতে পারে, যার ফলে অতিরিক্ত কর্মক্ষমতা ক্ষতি হয়৷

Pixel 4XL-এ শিপিং শিরোনাম চালানোর সময়, আমরা দেখেছি যে SurfaceFlinger (উচ্চ অগ্রাধিকারের কাজ যা Android কম্পোজিটরকে চালিত করে):

  • নিয়মিতভাবে অ্যাপ্লিকেশানের কাজকে অগ্রাহ্য করে, যার ফলে ফ্রেমটাইমে 1-3ms হিট হয় এবং

  • GPU এর ভার্টেক্স/টেক্সচার মেমরির উপর চাপ বাড়ায়, কারণ কম্পোজিটরকে তার কম্পোজিশনের কাজ করতে পুরো ফ্রেমবাফার পড়তে হয়।

সারফেসফ্লিংগার দ্বারা সঠিকভাবে অভিযোজন পরিচালনা করা GPU প্রিম্পশনকে প্রায় সম্পূর্ণরূপে বন্ধ করে দেয়, যখন GPU ফ্রিকোয়েন্সি 40% কমে যায় কারণ Android কম্পোজিটর দ্বারা ব্যবহৃত বুস্টেড ফ্রিকোয়েন্সি আর প্রয়োজন হয় না।

পৃষ্ঠের ঘূর্ণনগুলি যথাসম্ভব সামান্য ওভারহেডের সাথে সঠিকভাবে পরিচালনা করা হয় তা নিশ্চিত করার জন্য, যেমনটি আগের ক্ষেত্রে দেখা গেছে, আপনাকে পদ্ধতি 3 প্রয়োগ করতে হবে। এটি প্রাক-ঘূর্ণন হিসাবে পরিচিত। এটি Android OS কে বলে যে আপনার অ্যাপ পৃষ্ঠের ঘূর্ণন পরিচালনা করে। আপনি সারফেস ট্রান্সফর্ম পতাকাগুলি পাস করে এটি করতে পারেন যা সোয়াপচেইন তৈরির সময় অভিযোজন নির্দিষ্ট করে। এটি অ্যান্ড্রয়েড কম্পোজিটরকে নিজেই ঘূর্ণন করা থেকে বিরত করে

সারফেস ট্রান্সফর্ম পতাকা কিভাবে সেট করবেন তা জানা প্রতিটি ভলকান অ্যাপ্লিকেশনের জন্য গুরুত্বপূর্ণ। অ্যাপ্লিকেশনগুলি হয় একাধিক অভিযোজন সমর্থন করে বা একটি একক অভিযোজন সমর্থন করে যেখানে রেন্ডার পৃষ্ঠটি ডিভাইসটি তার পরিচয় অভিযোজন হিসাবে বিবেচনা করে তার থেকে ভিন্ন অভিযোজনে থাকে। উদাহরণস্বরূপ, একটি পোর্ট্রেট-পরিচয় ফোনে একটি ল্যান্ডস্কেপ-শুধু অ্যাপ্লিকেশন, অথবা একটি ল্যান্ডস্কেপ-পরিচয় ট্যাবলেটে একটি শুধুমাত্র-পোর্ট্রেট অ্যাপ্লিকেশন৷

AndroidManifest.xml পরিবর্তন করুন

আপনার অ্যাপ্লিকেশানে ডিভাইসের ঘূর্ণন পরিচালনা করতে, অ্যাপ্লিকেশানের AndroidManifest.xml ফাইলটি পরিবর্তন করে Android কে জানাতে শুরু করুন যে আপনার অ্যাপ ওরিয়েন্টেশন এবং স্ক্রীনের আকার পরিবর্তনগুলি পরিচালনা করবে৷ এটি অ্যান্ড্রয়েডকে অ্যান্ড্রয়েড Activity ধ্বংস এবং পুনরায় তৈরি করা থেকে বাধা দেয় এবং যখন একটি অভিযোজন পরিবর্তন ঘটে তখন বিদ্যমান উইন্ডো পৃষ্ঠে onDestroy() ফাংশনকে কল করা। অ্যাক্টিভিটির configChanges বিভাগে orientation (এপিআই লেভেল <13 সমর্থন করার জন্য) এবং screenSize অ্যাট্রিবিউট যোগ করে এটি করা হয়:

<activity android:name="android.app.NativeActivity"
          android:configChanges="orientation|screenSize">

যদি আপনার অ্যাপ্লিকেশনটি screenOrientation বৈশিষ্ট্য ব্যবহার করে তার স্ক্রীন অভিযোজন ঠিক করে, তাহলে আপনাকে এটি করতে হবে না। এছাড়াও, যদি আপনার অ্যাপ্লিকেশন একটি নির্দিষ্ট অভিযোজন ব্যবহার করে তবে এটি শুধুমাত্র একবার অ্যাপ্লিকেশন স্টার্টআপ/রিজুমে সোয়াপচেইন সেট আপ করতে হবে।

আইডেন্টিটি স্ক্রিন রেজোলিউশন এবং ক্যামেরা প্যারামিটার পান

এরপরে, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR মানের সাথে যুক্ত ডিভাইসের স্ক্রীন রেজোলিউশন সনাক্ত করুন৷ এই রেজোলিউশনটি ডিভাইসের আইডেন্টিটি অরিয়েন্টেশনের সাথে যুক্ত, এবং সেইজন্য সোয়াপচেনকে সবসময় সেট করতে হবে। এটি পাওয়ার সবচেয়ে নির্ভরযোগ্য উপায় হল অ্যাপ্লিকেশন স্টার্টআপের সময় vkGetPhysicalDeviceSurfaceCapabilitiesKHR() এ একটি কল করা এবং ফিরে আসা পরিমাণ সংরক্ষণ করা। আপনি পরিচয় স্ক্রীন রেজোলিউশনটি সংরক্ষণ করছেন তা নিশ্চিত করার জন্য currentTransform উপর ভিত্তি করে প্রস্থ এবং উচ্চতা অদলবদল করুন যা ফিরে এসেছে:

VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

uint32_t width = capabilities.currentExtent.width;
uint32_t height = capabilities.currentExtent.height;
if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
    capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  // Swap to get identity width and height
  capabilities.currentExtent.height = width;
  capabilities.currentExtent.width = height;
}

displaySizeIdentity = capabilities.currentExtent;

displaySizeIdentity হল একটি VkExtent2D স্ট্রাকচার যা আমরা ডিসপ্লের ন্যাচারাল ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের উল্লিখিত আইডেন্টিটি রেজোলিউশন সংরক্ষণ করতে ব্যবহার করি।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (Android 10+)

আপনার অ্যাপ্লিকেশনে একটি অভিযোজন পরিবর্তন সনাক্ত করার সবচেয়ে নির্ভরযোগ্য উপায় হল vkQueuePresentKHR() ফাংশন VK_SUBOPTIMAL_KHR প্রদান করে কিনা তা যাচাই করা। যেমন:

auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
  orientationChanged = true;
}

দ্রষ্টব্য: এই সমাধানটি শুধুমাত্র Android 10 এবং তার পরে চলমান ডিভাইসগুলিতে কাজ করে৷ Android এর এই সংস্করণগুলি vkQueuePresentKHR() থেকে VK_SUBOPTIMAL_KHR ফেরত দেয়। আমরা এই চেকের ফলাফল orientationChanged এ সংরক্ষণ করি, একটি boolean যা অ্যাপ্লিকেশনগুলির প্রধান রেন্ডারিং লুপ থেকে অ্যাক্সেসযোগ্য।

ডিভাইস ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (প্রি-অ্যান্ড্রয়েড 10)

Android 10 বা তার বেশি চলমান ডিভাইসগুলির জন্য, একটি ভিন্ন বাস্তবায়ন প্রয়োজন, কারণ VK_SUBOPTIMAL_KHR সমর্থিত নয়৷

পোলিং ব্যবহার করে

প্রি-অ্যান্ড্রয়েড 10 ডিভাইসে আপনি বর্তমান ডিভাইসের প্রতিটি pollingInterval ফ্রেমে রূপান্তর করতে পারেন, যেখানে pollingInterval প্রোগ্রামার দ্বারা নির্ধারিত একটি গ্রানুলারিটি। আপনি যেভাবে এটি করবেন তা হল vkGetPhysicalDeviceSurfaceCapabilitiesKHR() কল করে এবং তারপরে প্রত্যাবর্তিত currentTransform ক্ষেত্রটিকে বর্তমানে সঞ্চিত পৃষ্ঠের রূপান্তরের সাথে তুলনা করা (এই কোড উদাহরণে pretransformFlag এ সংরক্ষিত)।

currFrameCount++;
if (currFrameCount >= pollInterval){
  VkSurfaceCapabilitiesKHR capabilities;
  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

  if (pretransformFlag != capabilities.currentTransform) {
    window_resized = true;
  }
  currFrameCount = 0;
}

Android 10 চালিত একটি Pixel 4-এ, vkGetPhysicalDeviceSurfaceCapabilitiesKHR() .120-.250ms এবং Android 8 চালিত Pixel 1XL-এ পোলিং .110-.350ms লেগেছে৷

কলব্যাক ব্যবহার করে

অ্যান্ড্রয়েড 10 এর নিচে চলমান ডিভাইসগুলির জন্য একটি দ্বিতীয় বিকল্প হল একটি ফাংশন কল করার জন্য একটি onNativeWindowResized() কলব্যাক নিবন্ধন করা যা orientationChanged পতাকা সেট করে, অ্যাপ্লিকেশনটিতে একটি ওরিয়েন্টেশন পরিবর্তন ঘটেছে বলে সংকেত দেয়:

void android_main(struct android_app *app) {
  ...
  app->activity->callbacks->onNativeWindowResized = ResizeCallback;
}

যেখানে ResizeCallback কে সংজ্ঞায়িত করা হয়েছে:

void ResizeCallback(ANativeActivity *activity, ANativeWindow *window){
  orientationChanged = true;
}

এই সমাধানের সমস্যা হল যে onNativeWindowResized() শুধুমাত্র 90-ডিগ্রি ওরিয়েন্টেশন পরিবর্তনের জন্য ডাকা হয়, যেমন ল্যান্ডস্কেপ থেকে প্রতিকৃতিতে যাওয়া বা এর বিপরীতে। অন্যান্য অভিযোজন পরিবর্তনগুলি সোয়াপচেন বিনোদনকে ট্রিগার করবে না। উদাহরণস্বরূপ, ল্যান্ডস্কেপ থেকে রিভার্স-ল্যান্ডস্কেপে পরিবর্তন এটিকে ট্রিগার করবে না, আপনার অ্যাপ্লিকেশনের জন্য অ্যান্ড্রয়েড কম্পোজিটরকে ফ্লিপ করতে হবে।

ওরিয়েন্টেশন পরিবর্তন পরিচালনা করা

অরিয়েন্টেশন পরিবর্তন পরিচালনা করতে, মূল রেন্ডারিং লুপের শীর্ষে অরিয়েন্টেশন পরিবর্তনের রুটিনটিকে কল করুন যখন orientationChanged ভেরিয়েবল সত্যে সেট করা থাকে। যেমন:

bool VulkanDrawFrame() {
 if (orientationChanged) {
   OnOrientationChange();
}

আপনি OnOrientationChange() ফাংশনের মধ্যে swapchain পুনরায় তৈরি করার জন্য প্রয়োজনীয় সমস্ত কাজ করেন। এর মানে হল আপনি:

  1. Framebuffer এবং ImageView এর যেকোন বিদ্যমান দৃষ্টান্ত ধ্বংস করুন,

  2. পুরানো সোয়াপচেইন (যা পরবর্তী আলোচনা করা হবে) ধ্বংস করার সময় সোয়াপচেন পুনরায় তৈরি করুন এবং

  3. নতুন সোয়াপচেইনের ডিসপ্লে ইমেজগুলির সাথে ফ্রেমবাফারগুলি পুনরায় তৈরি করুন৷ দ্রষ্টব্য: সংযুক্তি চিত্রগুলি (উদাহরণস্বরূপ) গভীরতা/স্টেনসিল চিত্রগুলিকে সাধারণত পুনরায় তৈরি করার প্রয়োজন হয় না কারণ সেগুলি পূর্ব-ঘোরানো সোয়াপচেন চিত্রগুলির পরিচয় রেজোলিউশনের উপর ভিত্তি করে।

void OnOrientationChange() {
 vkDeviceWaitIdle(getDevice());

 for (int i = 0; i < getSwapchainLength(); ++i) {
   vkDestroyImageView(getDevice(), displayViews_[i], nullptr);
   vkDestroyFramebuffer(getDevice(), framebuffers_[i], nullptr);
 }

 createSwapChain(getSwapchain());
 createFrameBuffers(render_pass, depthBuffer.image_view);
 orientationChanged = false;
}

এবং ফাংশনের শেষে আপনি orientationChanged পতাকাটিকে মিথ্যাতে রিসেট করে দেখান যে আপনি ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করেছেন।

সোয়াপচেইন রিক্রিয়েশন

পূর্ববর্তী বিভাগে আমরা সোয়াপচেন পুনরায় তৈরি করার কথা উল্লেখ করেছি। এটি করার প্রথম ধাপে রেন্ডারিং পৃষ্ঠের নতুন বৈশিষ্ট্যগুলি পাওয়া জড়িত:

void createSwapChain(VkSwapchainKHR oldSwapchain) {
   VkSurfaceCapabilitiesKHR capabilities;
   vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
   pretransformFlag = capabilities.currentTransform;

নতুন তথ্যের সাথে VkSurfaceCapabilities স্ট্রাকট তৈরি করা হয়েছে, আপনি এখন currentTransform ক্ষেত্রটি পরীক্ষা করে একটি ওরিয়েন্টেশন পরিবর্তন হয়েছে কিনা তা দেখতে পারেন। আপনি এটিকে pretransformFlag ক্ষেত্রের জন্য সংরক্ষণ করবেন কারণ আপনি যখন MVP ম্যাট্রিক্সে সামঞ্জস্য করবেন তখন আপনার এটির প্রয়োজন হবে।

এটি করার জন্য, VkSwapchainCreateInfo কাঠামোতে নিম্নলিখিত বৈশিষ্ট্যগুলি উল্লেখ করুন:

VkSwapchainCreateInfoKHR swapchainCreateInfo{
  ...
  .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
  .imageExtent = displaySizeIdentity,
  .preTransform = pretransformFlag,
  .oldSwapchain = oldSwapchain,
};

vkCreateSwapchainKHR(device_, &swapchainCreateInfo, nullptr, &swapchain_));

if (oldSwapchain != VK_NULL_HANDLE) {
  vkDestroySwapchainKHR(device_, oldSwapchain, nullptr);
}

imageExtent ক্ষেত্রটি displaySizeIdentity ব্যাপ্তির সাথে পপুলেট করা হবে যা আপনি অ্যাপ্লিকেশন স্টার্টআপে সংরক্ষণ করেছিলেন। preTransform ক্ষেত্রটি pretransformFlag ভেরিয়েবল (যা surfaceCapabilities বর্তমান ট্রান্সফর্ম ক্ষেত্রে সেট করা হয়েছে) দিয়ে পপুলেট করা হবে। আপনি oldSwapchain ক্ষেত্রটি সোয়াপচেইনেও সেট করেছেন যা ধ্বংস হয়ে যাবে।

এমভিপি ম্যাট্রিক্স অ্যাডজাস্টমেন্ট

আপনার সর্বশেষ কাজটি হ'ল আপনার এমভিপি ম্যাট্রিক্সে একটি ঘূর্ণন ম্যাট্রিক্স প্রয়োগ করে প্রাক-ট্রান্সফর্মেশন প্রয়োগ করা। এটি মূলত যা করে তা হ'ল ক্লিপ স্পেসে ঘূর্ণনটি প্রয়োগ করা যাতে ফলাফল চিত্রটি বর্তমান ডিভাইস ওরিয়েন্টেশনে ঘোরানো হয়। তারপরে আপনি কেবল এই আপডেট হওয়া এমভিপি ম্যাট্রিক্সকে আপনার ভার্টেক্স শেডারে পাস করতে পারেন এবং আপনার শেডারগুলি সংশোধন করার প্রয়োজন ছাড়াই এটিকে স্বাভাবিক হিসাবে ব্যবহার করতে পারেন।

glm::mat4 pre_rotate_mat = glm::mat4(1.0f);
glm::vec3 rotation_axis = glm::vec3(0.0f, 0.0f, 1.0f);

if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(90.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(270.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(180.0f), rotation_axis);
}

MVP = pre_rotate_mat * MVP;

বিবেচনা - অ -পূর্ণ স্ক্রিন ভিউপোর্ট এবং কাঁচি

যদি আপনার অ্যাপ্লিকেশনটি একটি পূর্ণ-পূর্ণ স্ক্রিন ভিউপোর্ট/কাঁচি অঞ্চল ব্যবহার করে তবে তাদের ডিভাইসের ওরিয়েন্টেশন অনুযায়ী আপডেট করা দরকার। এর জন্য আপনি ভলকানের পাইপলাইন তৈরির সময় গতিশীল ভিউপোর্ট এবং কাঁচি বিকল্পগুলি সক্ষম করতে হবে:

VkDynamicState dynamicStates[2] = {
  VK_DYNAMIC_STATE_VIEWPORT,
  VK_DYNAMIC_STATE_SCISSOR,
};

VkPipelineDynamicStateCreateInfo dynamicInfo = {
  .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .dynamicStateCount = 2,
  .pDynamicStates = dynamicStates,
};

VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
  .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
  ...
  .pDynamicState = &dynamicInfo,
  ...
};

VkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &mPipeline);

কমান্ড বাফার রেকর্ডিংয়ের সময় ভিউপোর্টের পরিমাণের প্রকৃত গণনা এটির মতো দেখায়:

int x = 0, y = 0, w = 500, h = 400;

glm::vec4 viewportData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    viewportData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    viewportData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    viewportData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    viewportData = {x, y, w, h};
    break;
}

const VkViewport viewport = {
    .x = viewportData.x,
    .y = viewportData.y,
    .width = viewportData.z,
    .height = viewportData.w,
    .minDepth = 0.0F,
    .maxDepth = 1.0F,
};

vkCmdSetViewport(renderer->GetCurrentCommandBuffer(), 0, 1, &viewport);

x এবং y ভেরিয়েবলগুলি ভিউপোর্টের উপরের বাম কোণার স্থানাঙ্কগুলি সংজ্ঞায়িত করে, যখন w এবং h যথাক্রমে ভিউপোর্টের প্রস্থ এবং উচ্চতা সংজ্ঞায়িত করে। একই গণনাটি কাঁচি পরীক্ষা সেট করতেও ব্যবহার করা যেতে পারে এবং সম্পূর্ণতার জন্য এখানে অন্তর্ভুক্ত রয়েছে:

int x = 0, y = 0, w = 500, h = 400;
glm::vec4 scissorData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    scissorData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    scissorData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    scissorData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    scissorData = {x, y, w, h};
    break;
}

const VkRect2D scissor = {
    .offset =
        {
            .x = (int32_t)viewportData.x,
            .y = (int32_t)viewportData.y,
        },
    .extent =
        {
            .width = (uint32_t)viewportData.z,
            .height = (uint32_t)viewportData.w,
        },
};

vkCmdSetScissor(renderer->GetCurrentCommandBuffer(), 0, 1, &scissor);

বিবেচনা - খণ্ড শেডার ডেরিভেটিভস

যদি আপনার অ্যাপ্লিকেশনটি dFdx এবং dFdy এর মতো ডেরাইভেটিভ গণনাগুলি ব্যবহার করে থাকে তবে পিক্সেল স্পেসে এই গণনাগুলি কার্যকর করা হওয়ায় ঘোরানো স্থানাঙ্ক সিস্টেমের জন্য অ্যাকাউন্টে অতিরিক্ত রূপান্তর প্রয়োজন হতে পারে। এর জন্য অ্যাপ্লিকেশনটির জন্য প্রিট্রান্সফর্মের কিছু ইঙ্গিতটি টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো

  • 90 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য
    • ডিএফডিএক্স অবশ্যই ডিএফডিওয়াইতে ম্যাপ করা উচিত
    • dfdy অবশ্যই -dfdx এ ম্যাপ করা উচিত
  • 270 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য
    • ডিএফডিএক্স অবশ্যই ম্যাপ করা উচিত -dfdy
    • ডিএফডি অবশ্যই ডিএফডিএক্সে ম্যাপ করা উচিত
  • 180 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য,
    • ডিএফডিএক্স অবশ্যই -dfdx এ ম্যাপ করা উচিত
    • dfdy অবশ্যই ম্যাপ করা উচিত -dfdy

উপসংহার

আপনার অ্যাপ্লিকেশন অ্যান্ড্রয়েডে ভলকান থেকে সর্বাধিক উপার্জনের জন্য, প্রাক-ঘূর্ণন বাস্তবায়ন করা আবশ্যক। এই নিবন্ধটি থেকে সর্বাধিক গুরুত্বপূর্ণ গ্রহণযোগ্যতাগুলি হ'ল:

  • নিশ্চিত করুন যে সোয়াপচেইন তৈরি বা বিনোদন চলাকালীন, প্রিট্রান্সফর্ম পতাকাটি অ্যান্ড্রয়েড অপারেটিং সিস্টেম দ্বারা ফিরে আসা পতাকাটির সাথে মেলে সেট করা আছে। এটি কম্পোজিটর ওভারহেড এড়াবে।
  • ডিসপ্লেটির প্রাকৃতিক দৃষ্টিভঙ্গিতে অ্যাপের উইন্ডো পৃষ্ঠের পরিচয় রেজোলিউশনে সোয়াপচেইন আকারটি স্থির রাখুন।
  • ডিভাইস ওরিয়েন্টেশনের জন্য অ্যাকাউন্টে ক্লিপ স্পেসে এমভিপি ম্যাট্রিক্সটি ঘোরান, কারণ সোয়াপচেইন রেজোলিউশন/সীমাটি আর ডিসপ্লেটির ওরিয়েন্টেশন সহ আপডেট হয় না।
  • আপনার অ্যাপ্লিকেশনটির প্রয়োজন অনুসারে ভিউপোর্ট এবং কাঁচি আয়তক্ষেত্রগুলি আপডেট করুন।

নমুনা অ্যাপ্লিকেশন: ন্যূনতম অ্যান্ড্রয়েড প্রাক-ঘূর্ণন

,

এই নিবন্ধটি প্রাক-ঘূর্ণন বাস্তবায়নের মাধ্যমে আপনার ভলকান অ্যাপ্লিকেশনটিতে কীভাবে দক্ষতার সাথে ডিভাইস ঘূর্ণন পরিচালনা করতে হবে তা বর্ণনা করে।

ভলকানের সাথে, আপনি ওপেনজিএল -এর সাথে যতটা সম্ভব রেন্ডারিং স্টেট সম্পর্কে আরও অনেক তথ্য নির্দিষ্ট করতে পারেন। ভলকানের সাথে, আপনাকে অবশ্যই ওপেনজিএল -তে ড্রাইভার দ্বারা পরিচালিত জিনিসগুলি স্পষ্টভাবে প্রয়োগ করতে হবে, যেমন ডিভাইস ওরিয়েন্টেশন এবং পৃষ্ঠের ওরিয়েন্টেশন রেন্ডার করার সাথে এর সম্পর্ক। অ্যান্ড্রয়েড ডিভাইসের রেন্ডার পৃষ্ঠের সাথে ডিভাইস ওরিয়েন্টেশনের সাথে পুনর্মিলন পরিচালনা করতে পারে এমন তিনটি উপায় রয়েছে:

  1. অ্যান্ড্রয়েড ওএস ডিভাইসের ডিসপ্লে প্রসেসিং ইউনিট (ডিপিইউ) ব্যবহার করতে পারে, যা হার্ডওয়্যারে পৃষ্ঠের ঘূর্ণন দক্ষতার সাথে পরিচালনা করতে পারে। শুধুমাত্র সমর্থিত ডিভাইসগুলিতে উপলব্ধ।
  2. অ্যান্ড্রয়েড ওএস একটি কম্পোজিটার পাস যুক্ত করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে। কম্পোজিটারের আউটপুট চিত্রটি ঘোরানোর সাথে কীভাবে মোকাবেলা করতে হবে তার উপর নির্ভর করে এটির একটি পারফরম্যান্স ব্যয় হবে।
  3. অ্যাপ্লিকেশনটি নিজেই একটি রেন্ডার পৃষ্ঠের উপরে একটি ঘোরানো চিত্রটি রেন্ডার করে পৃষ্ঠের ঘূর্ণন পরিচালনা করতে পারে যা প্রদর্শনের বর্তমান ওরিয়েন্টেশনের সাথে মেলে।

এই পদ্ধতিগুলির মধ্যে কোনটি আপনার ব্যবহার করা উচিত?

বর্তমানে, অ্যাপ্লিকেশনটির বাইরে পরিচালিত সারফেস রোটেশনটি নিখরচায় থাকবে কিনা তা জানার কোনও অ্যাপ্লিকেশনটির কোনও উপায় নেই। এমনকি যদি আপনার জন্য এটির যত্ন নেওয়ার জন্য কোনও ডিপিইউ থাকে তবে এখনও প্রদানের জন্য একটি পরিমাপযোগ্য পারফরম্যান্স জরিমানা থাকবে। যদি আপনার অ্যাপ্লিকেশনটি সিপিইউ-আবদ্ধ হয় তবে অ্যান্ড্রয়েড কম্পোজিটারের দ্বারা বর্ধিত জিপিইউ ব্যবহারের কারণে এটি একটি পাওয়ার ইস্যুতে পরিণত হয়, যা সাধারণত একটি বুস্টেড ফ্রিকোয়েন্সিতে চলমান থাকে। যদি আপনার অ্যাপ্লিকেশনটি জিপিইউ আবদ্ধ হয়, তবে অ্যান্ড্রয়েড কম্পোজিটার আপনার অ্যাপ্লিকেশনটির জিপিইউর কাজটিও প্রিপ্ট করতে পারে, যা অতিরিক্ত পারফরম্যান্সের ক্ষতি করে।

পিক্সেল 4 এক্সএল-তে শিপিং শিরোনাম চালানোর সময়, আমরা দেখেছি সেই সারফেসফ্লিংগার (উচ্চ-অগ্রাধিকার টাস্ক যা অ্যান্ড্রয়েড কম্পোজিটারকে চালিত করে):

  • নিয়মিতভাবে আবেদনের কাজকে প্রিপ্ট করে, ফ্রেমটাইমগুলিতে 1-3 মিমি হিট করে এবং

  • জিপিইউর ভার্টেক্স/টেক্সচার মেমরির উপর চাপ বাড়িয়ে তোলে, কারণ কম্পোজিটারকে তার রচনা কাজটি করতে পুরো ফ্রেমবফারটি পড়তে হয়।

হ্যান্ডলিং ওরিয়েন্টেশন সঠিকভাবে সারফেসফ্লিংগার দ্বারা জিপিইউ প্রিম্পশন বন্ধ করে দেয়, যখন অ্যান্ড্রয়েড কম্পোজিটারের দ্বারা ব্যবহৃত বুস্টেড ফ্রিকোয়েন্সি আর প্রয়োজন হয় না বলে জিপিইউ ফ্রিকোয়েন্সি 40% হ্রাস পায়।

পূর্ববর্তী ক্ষেত্রে যেমন দেখা যায় তেমন সামান্য ওভারহেডের সাথে পৃষ্ঠের ঘূর্ণনগুলি সঠিকভাবে পরিচালনা করা হয়েছে তা নিশ্চিত করার জন্য আপনার পদ্ধতি 3 প্রয়োগ করা উচিত। এটি প্রাক-ঘূর্ণন হিসাবে পরিচিত। এটি অ্যান্ড্রয়েড ওএসকে বলে যে আপনার অ্যাপ্লিকেশনটি পৃষ্ঠের ঘূর্ণন পরিচালনা করে। আপনি সোয়াপচেইন তৈরির সময় ওরিয়েন্টেশন নির্দিষ্ট করে এমন পৃষ্ঠতল রূপান্তর পতাকাগুলি পাস করে এটি করতে পারেন। এটি অ্যান্ড্রয়েড সংমিশ্রণকারীকে নিজেই ঘূর্ণন করতে বাধা দেয়

প্রতিটি ভলকান অ্যাপ্লিকেশনটির জন্য কীভাবে সারফেস ট্রান্সফর্ম পতাকা সেট করবেন তা জানা গুরুত্বপূর্ণ। অ্যাপ্লিকেশনগুলি হয় একাধিক ওরিয়েন্টেশনগুলিকে সমর্থন করে বা একটি একক ওরিয়েন্টেশনকে সমর্থন করে যেখানে ডিভাইসটি তার পরিচয় ওরিয়েন্টেশনকে বিবেচনা করে তার জন্য রেন্ডার পৃষ্ঠটি আলাদা ওরিয়েন্টেশনে থাকে। উদাহরণস্বরূপ, প্রতিকৃতি-পরিচয় ফোনে কেবল একটি ল্যান্ডস্কেপ-অ্যাপ্লিকেশন, বা ল্যান্ডস্কেপ-পরিচয় ট্যাবলেটে কেবল একটি প্রতিকৃতি-অ্যাপ্লিকেশন অ্যাপ্লিকেশন।

অ্যান্ড্রয়েডম্যানিফেস্ট.এক্সএমএল সংশোধন করুন

আপনার অ্যাপ্লিকেশনটিতে ডিভাইস ঘূর্ণন পরিচালনা করতে, অ্যান্ড্রয়েডকে বলতে অ্যাপ্লিকেশনটির AndroidManifest.xml ফাইল পরিবর্তন করে শুরু করুন যে আপনার অ্যাপ্লিকেশনটি ওরিয়েন্টেশন এবং স্ক্রিনের আকারের পরিবর্তনগুলি পরিচালনা করবে। এটি অ্যান্ড্রয়েডকে অ্যান্ড্রয়েড Activity ধ্বংস এবং পুনরুদ্ধার করতে এবং বিদ্যমান উইন্ডো পৃষ্ঠের উপর onDestroy() ফাংশন কল করা থেকে বিরত রাখে যখন কোনও ওরিয়েন্টেশন পরিবর্তন ঘটে। এটি orientation যুক্ত করে (এপিআই স্তর <13 সমর্থন করার জন্য) এবং ক্রিয়াকলাপের configChanges বিভাগে screenSize বৈশিষ্ট্যগুলি যুক্ত করে করা হয়:

<activity android:name="android.app.NativeActivity"
          android:configChanges="orientation|screenSize">

যদি আপনার অ্যাপ্লিকেশনটি screenOrientation বৈশিষ্ট্যটি ব্যবহার করে এর স্ক্রিন ওরিয়েন্টেশনটি ঠিক করে তবে আপনার এটি করার দরকার নেই। এছাড়াও, যদি আপনার অ্যাপ্লিকেশনটি একটি নির্দিষ্ট ওরিয়েন্টেশন ব্যবহার করে তবে এটি কেবল অ্যাপ্লিকেশন স্টার্টআপ/পুনরায় শুরু করার জন্য একবার সোয়াপচেইন সেট আপ করতে হবে।

পরিচয় স্ক্রিন রেজোলিউশন এবং ক্যামেরা পরামিতি পান

এরপরে, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR মানের সাথে সম্পর্কিত ডিভাইসের স্ক্রিন রেজোলিউশনটি সনাক্ত করুন। এই রেজোলিউশনটি ডিভাইসের পরিচয় ওরিয়েন্টেশনের সাথে সম্পর্কিত, এবং তাই সোয়াপচেইনকে সর্বদা সেট করা দরকার। এটি পাওয়ার সবচেয়ে নির্ভরযোগ্য উপায় হ'ল অ্যাপ্লিকেশন স্টার্টআপে vkGetPhysicalDeviceSurfaceCapabilitiesKHR() এ কল করা এবং ফিরে আসা সীমাটি সঞ্চয় করা। আপনি পরিচয় স্ক্রিন রেজোলিউশনটি সংরক্ষণ করছেন তা নিশ্চিত করার জন্য যে currentTransform উপর ভিত্তি করে প্রস্থ এবং উচ্চতা অদলবদল করুন:

VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

uint32_t width = capabilities.currentExtent.width;
uint32_t height = capabilities.currentExtent.height;
if (capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
    capabilities.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  // Swap to get identity width and height
  capabilities.currentExtent.height = width;
  capabilities.currentExtent.width = height;
}

displaySizeIdentity = capabilities.currentExtent;

ডিসপ্লেসাইজডেন্টিটি হ'ল একটি VkExtent2D কাঠামো যা আমরা ডিসপ্লেটির প্রাকৃতিক দৃষ্টিভঙ্গিতে অ্যাপের উইন্ডো পৃষ্ঠের পরিচয় রেজোলিউশনটি সঞ্চয় করতে ব্যবহার করি।

ডিভাইস ওরিয়েন্টেশন পরিবর্তনগুলি সনাক্ত করুন (অ্যান্ড্রয়েড 10+)

আপনার অ্যাপ্লিকেশনটিতে একটি ওরিয়েন্টেশন পরিবর্তন সনাক্ত করার সবচেয়ে নির্ভরযোগ্য উপায় হ'ল vkQueuePresentKHR() ফাংশনটি VK_SUBOPTIMAL_KHR ফেরত দেয় কিনা তা যাচাই করা। যেমন:

auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
  orientationChanged = true;
}

দ্রষ্টব্য: এই সমাধানটি কেবলমাত্র অ্যান্ড্রয়েড 10 এবং পরে চলমান ডিভাইসে কাজ করে। অ্যান্ড্রয়েড রিটার্নের এই সংস্করণগুলি vkQueuePresentKHR() থেকে VK_SUBOPTIMAL_KHR । আমরা এই চেকের ফলাফলটি orientationChanged সঞ্চয় করি, এটি একটি boolean যা অ্যাপ্লিকেশনগুলির মূল রেন্ডারিং লুপ থেকে অ্যাক্সেসযোগ্য।

ডিভাইস ওরিয়েন্টেশন পরিবর্তনগুলি সনাক্ত করুন (প্রাক-অ্যান্ড্রয়েড 10)

অ্যান্ড্রয়েড 10 বা তার বেশি বয়সী ডিভাইসগুলির জন্য, একটি ভিন্ন বাস্তবায়ন প্রয়োজন, কারণ VK_SUBOPTIMAL_KHR সমর্থিত নয়।

পোলিং ব্যবহার করে

প্রাক-অ্যান্ড্রয়েড 10 ডিভাইসে আপনি বর্তমান ডিভাইসটি পোল করতে পারেন প্রতিটি pollingInterval ফ্রেমকে রূপান্তর করতে পারেন, যেখানে pollingInterval প্রোগ্রামার দ্বারা সিদ্ধান্ত নেওয়া একটি গ্রানুলারিটি। আপনি যেভাবে এটি করেন তা হ'ল vkGetPhysicalDeviceSurfaceCapabilitiesKHR() কল করা এবং তারপরে বর্তমানে সঞ্চিত পৃষ্ঠের রূপান্তর ( pretransformFlag সঞ্চিত এই কোড উদাহরণে) এর সাথে ফিরে আসা currentTransform ক্ষেত্রটির তুলনা করা।

currFrameCount++;
if (currFrameCount >= pollInterval){
  VkSurfaceCapabilitiesKHR capabilities;
  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

  if (pretransformFlag != capabilities.currentTransform) {
    window_resized = true;
  }
  currFrameCount = 0;
}

অ্যান্ড্রয়েড 10 চলমান একটি পিক্সেল 4-এ, পোলিং vkGetPhysicalDeviceSurfaceCapabilitiesKHR() এর মধ্যে .120-.250 মিমি এবং একটি পিক্সেল 1 এক্সএল চলমান অ্যান্ড্রয়েড 8 চলমান, পোলিংয়ে .110 -.350 মিমি নিয়েছিল।

কলব্যাক ব্যবহার করে

অ্যান্ড্রয়েড 10 এর নীচে চলমান ডিভাইসগুলির জন্য একটি দ্বিতীয় বিকল্প হ'ল একটি ফাংশন কল করার জন্য একটি onNativeWindowResized() কলব্যাক নিবন্ধন করা যা orientationChanged পতাকা সেট করে, অ্যাপ্লিকেশনটিতে সংকেত দেয় একটি ওরিয়েন্টেশন পরিবর্তন ঘটেছে:

void android_main(struct android_app *app) {
  ...
  app->activity->callbacks->onNativeWindowResized = ResizeCallback;
}

যেখানে রেজিজিক্যালব্যাক হিসাবে সংজ্ঞায়িত করা হয়েছে:

void ResizeCallback(ANativeActivity *activity, ANativeWindow *window){
  orientationChanged = true;
}

এই সমাধানের সাথে সমস্যাটি হ'ল onNativeWindowResized() কেবলমাত্র 90-ডিগ্রি ওরিয়েন্টেশন পরিবর্তনের জন্য ডাকা হয়, যেমন ল্যান্ডস্কেপ থেকে প্রতিকৃতিতে যাওয়া বা তদ্বিপরীত। অন্যান্য ওরিয়েন্টেশন পরিবর্তনগুলি অদলবদল বিনোদনকে ট্রিগার করবে না। উদাহরণস্বরূপ, ল্যান্ডস্কেপ থেকে রিভার্স-ল্যান্ডস্কেপ পর্যন্ত পরিবর্তন এটি ট্রিগার করবে না, আপনার অ্যাপ্লিকেশনটির জন্য ফ্লিপটি করতে অ্যান্ড্রয়েড কম্পোজিটারের প্রয়োজন হবে।

ওরিয়েন্টেশন পরিবর্তন পরিচালনা করা

ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করতে, যখন orientationChanged ভেরিয়েবলটি সত্যে সেট করা থাকে তখন মূল রেন্ডারিং লুপের শীর্ষে ওরিয়েন্টেশন পরিবর্তন রুটিনকে কল করুন। যেমন:

bool VulkanDrawFrame() {
 if (orientationChanged) {
   OnOrientationChange();
}

আপনি OnOrientationChange() ফাংশনের মধ্যে সোয়াপচেইন পুনরায় তৈরি করতে প্রয়োজনীয় সমস্ত কাজ করেন। এর অর্থ আপনি:

  1. Framebuffer এবং ImageView যে কোনও বিদ্যমান উদাহরণগুলি ধ্বংস করুন,

  2. পুরানো সোয়াপচেইনটি ধ্বংস করার সময় সোয়াপচেইনটি পুনরায় তৈরি করুন (যা পরবর্তী আলোচনা করা হবে), এবং

  3. নতুন সোয়াপচেইনের ডিসপ্লে ইমেজগুলির সাথে ফ্রেমবফারগুলি পুনরায় তৈরি করুন। দ্রষ্টব্য: সংযুক্তি চিত্রগুলি (উদাহরণস্বরূপ গভীরতা/স্টেনসিল চিত্রগুলি) সাধারণত প্রাক-ঘূর্ণিত অদলবদল চিত্রগুলির পরিচয় সমাধানের উপর ভিত্তি করে তারা পুনরায় তৈরি করার প্রয়োজন হয় না।

void OnOrientationChange() {
 vkDeviceWaitIdle(getDevice());

 for (int i = 0; i < getSwapchainLength(); ++i) {
   vkDestroyImageView(getDevice(), displayViews_[i], nullptr);
   vkDestroyFramebuffer(getDevice(), framebuffers_[i], nullptr);
 }

 createSwapChain(getSwapchain());
 createFrameBuffers(render_pass, depthBuffer.image_view);
 orientationChanged = false;
}

এবং ফাংশনের শেষে আপনি ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করেছেন তা দেখানোর জন্য orientationChanged ফ্ল্যাগটি মিথ্যা বলে পুনরায় সেট করুন।

সোয়াপচেইন বিনোদন

পূর্ববর্তী বিভাগে আমরা সোয়াপচেইনটি পুনরায় তৈরি করার কথা উল্লেখ করেছি। এটি করার প্রথম পদক্ষেপগুলির মধ্যে রেন্ডারিং পৃষ্ঠের নতুন বৈশিষ্ট্যগুলি পাওয়া জড়িত:

void createSwapChain(VkSwapchainKHR oldSwapchain) {
   VkSurfaceCapabilitiesKHR capabilities;
   vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
   pretransformFlag = capabilities.currentTransform;

নতুন তথ্যের সাথে জনবহুল VkSurfaceCapabilities স্ট্রাক্ট সহ, আপনি এখন currentTransform ক্ষেত্রটি পরীক্ষা করে একটি ওরিয়েন্টেশন পরিবর্তন হয়েছে কিনা তা পরীক্ষা করে দেখতে পারেন। আপনি এটি pretransformFlag ক্ষেত্রে পরে সংরক্ষণ করবেন কারণ আপনি যখন এমভিপি ম্যাট্রিক্সে সামঞ্জস্য করবেন তখন আপনার এটির জন্য প্রয়োজন হবে।

এটি করার জন্য, VkSwapchainCreateInfo স্ট্রাক্টে নিম্নলিখিত বৈশিষ্ট্যগুলি নির্দিষ্ট করুন:

VkSwapchainCreateInfoKHR swapchainCreateInfo{
  ...
  .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
  .imageExtent = displaySizeIdentity,
  .preTransform = pretransformFlag,
  .oldSwapchain = oldSwapchain,
};

vkCreateSwapchainKHR(device_, &swapchainCreateInfo, nullptr, &swapchain_));

if (oldSwapchain != VK_NULL_HANDLE) {
  vkDestroySwapchainKHR(device_, oldSwapchain, nullptr);
}

আপনি অ্যাপ্লিকেশন স্টার্টআপে সংরক্ষণ করেছেন এমন displaySizeIdentity সাথে imageExtent ক্ষেত্রটি পপুলেশন করা হবে। preTransform ক্ষেত্রটি pretransformFlag ভেরিয়েবলের সাথে জনবহুল হবে (যা surfaceCapabilities কারেন্ট ট্রান্সফর্ম ক্ষেত্রে সেট করা আছে)। আপনি oldSwapchain ক্ষেত্রটি সোয়াপচেইনেও সেট করেছেন যা ধ্বংস হয়ে যাবে।

এমভিপি ম্যাট্রিক্স অ্যাডজাস্টমেন্ট

আপনার সর্বশেষ কাজটি হ'ল আপনার এমভিপি ম্যাট্রিক্সে একটি ঘূর্ণন ম্যাট্রিক্স প্রয়োগ করে প্রাক-ট্রান্সফর্মেশন প্রয়োগ করা। এটি মূলত যা করে তা হ'ল ক্লিপ স্পেসে ঘূর্ণনটি প্রয়োগ করা যাতে ফলাফল চিত্রটি বর্তমান ডিভাইস ওরিয়েন্টেশনে ঘোরানো হয়। তারপরে আপনি কেবল এই আপডেট হওয়া এমভিপি ম্যাট্রিক্সকে আপনার ভার্টেক্স শেডারে পাস করতে পারেন এবং আপনার শেডারগুলি সংশোধন করার প্রয়োজন ছাড়াই এটিকে স্বাভাবিক হিসাবে ব্যবহার করতে পারেন।

glm::mat4 pre_rotate_mat = glm::mat4(1.0f);
glm::vec3 rotation_axis = glm::vec3(0.0f, 0.0f, 1.0f);

if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(90.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(270.0f), rotation_axis);
}

else if (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
  pre_rotate_mat = glm::rotate(pre_rotate_mat, glm::radians(180.0f), rotation_axis);
}

MVP = pre_rotate_mat * MVP;

বিবেচনা - অ -পূর্ণ স্ক্রিন ভিউপোর্ট এবং কাঁচি

যদি আপনার অ্যাপ্লিকেশনটি একটি পূর্ণ-পূর্ণ স্ক্রিন ভিউপোর্ট/কাঁচি অঞ্চল ব্যবহার করে তবে তাদের ডিভাইসের ওরিয়েন্টেশন অনুযায়ী আপডেট করা দরকার। এর জন্য আপনি ভলকানের পাইপলাইন তৈরির সময় গতিশীল ভিউপোর্ট এবং কাঁচি বিকল্পগুলি সক্ষম করতে হবে:

VkDynamicState dynamicStates[2] = {
  VK_DYNAMIC_STATE_VIEWPORT,
  VK_DYNAMIC_STATE_SCISSOR,
};

VkPipelineDynamicStateCreateInfo dynamicInfo = {
  .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .dynamicStateCount = 2,
  .pDynamicStates = dynamicStates,
};

VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
  .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
  ...
  .pDynamicState = &dynamicInfo,
  ...
};

VkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &mPipeline);

কমান্ড বাফার রেকর্ডিংয়ের সময় ভিউপোর্টের পরিমাণের প্রকৃত গণনা এটির মতো দেখায়:

int x = 0, y = 0, w = 500, h = 400;

glm::vec4 viewportData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    viewportData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    viewportData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    viewportData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    viewportData = {x, y, w, h};
    break;
}

const VkViewport viewport = {
    .x = viewportData.x,
    .y = viewportData.y,
    .width = viewportData.z,
    .height = viewportData.w,
    .minDepth = 0.0F,
    .maxDepth = 1.0F,
};

vkCmdSetViewport(renderer->GetCurrentCommandBuffer(), 0, 1, &viewport);

x এবং y ভেরিয়েবলগুলি ভিউপোর্টের উপরের বাম কোণার স্থানাঙ্কগুলি সংজ্ঞায়িত করে, যখন w এবং h যথাক্রমে ভিউপোর্টের প্রস্থ এবং উচ্চতা সংজ্ঞায়িত করে। একই গণনাটি কাঁচি পরীক্ষা সেট করতেও ব্যবহার করা যেতে পারে এবং সম্পূর্ণতার জন্য এখানে অন্তর্ভুক্ত রয়েছে:

int x = 0, y = 0, w = 500, h = 400;
glm::vec4 scissorData;

switch (device->GetPretransformFlag()) {
  case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
    scissorData = {bufferWidth - h - y, x, h, w};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
    scissorData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
    break;
  case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
    scissorData = {y, bufferHeight - w - x, h, w};
    break;
  default:
    scissorData = {x, y, w, h};
    break;
}

const VkRect2D scissor = {
    .offset =
        {
            .x = (int32_t)viewportData.x,
            .y = (int32_t)viewportData.y,
        },
    .extent =
        {
            .width = (uint32_t)viewportData.z,
            .height = (uint32_t)viewportData.w,
        },
};

vkCmdSetScissor(renderer->GetCurrentCommandBuffer(), 0, 1, &scissor);

বিবেচনা - খণ্ড শেডার ডেরিভেটিভস

যদি আপনার অ্যাপ্লিকেশনটি dFdx এবং dFdy এর মতো ডেরাইভেটিভ গণনাগুলি ব্যবহার করে থাকে তবে পিক্সেল স্পেসে এই গণনাগুলি কার্যকর করা হওয়ায় ঘোরানো স্থানাঙ্ক সিস্টেমের জন্য অ্যাকাউন্টে অতিরিক্ত রূপান্তর প্রয়োজন হতে পারে। এর জন্য অ্যাপ্লিকেশনটির জন্য প্রিট্রান্সফর্মের কিছু ইঙ্গিতটি টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো

  • 90 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য
    • ডিএফডিএক্স অবশ্যই ডিএফডিওয়াইতে ম্যাপ করা উচিত
    • dfdy অবশ্যই -dfdx এ ম্যাপ করা উচিত
  • 270 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য
    • ডিএফডিএক্স অবশ্যই ম্যাপ করা উচিত -dfdy
    • ডিএফডি অবশ্যই ডিএফডিএক্সে ম্যাপ করা উচিত
  • 180 ডিগ্রি প্রাক-ঘূর্ণিত ফ্রেমের জন্য,
    • ডিএফডিএক্স অবশ্যই -dfdx এ ম্যাপ করা উচিত
    • dfdy অবশ্যই ম্যাপ করা উচিত -dfdy

উপসংহার

আপনার অ্যাপ্লিকেশন অ্যান্ড্রয়েডে ভলকান থেকে সর্বাধিক উপার্জনের জন্য, প্রাক-ঘূর্ণন বাস্তবায়ন করা আবশ্যক। এই নিবন্ধটি থেকে সর্বাধিক গুরুত্বপূর্ণ গ্রহণযোগ্যতাগুলি হ'ল:

  • নিশ্চিত করুন যে সোয়াপচেইন তৈরি বা বিনোদন চলাকালীন, প্রিট্রান্সফর্ম পতাকাটি অ্যান্ড্রয়েড অপারেটিং সিস্টেম দ্বারা ফিরে আসা পতাকাটির সাথে মেলে সেট করা আছে। এটি কম্পোজিটর ওভারহেড এড়াবে।
  • ডিসপ্লেটির প্রাকৃতিক দৃষ্টিভঙ্গিতে অ্যাপের উইন্ডো পৃষ্ঠের পরিচয় রেজোলিউশনে সোয়াপচেইন আকারটি স্থির রাখুন।
  • ডিভাইস ওরিয়েন্টেশনের জন্য অ্যাকাউন্টে ক্লিপ স্পেসে এমভিপি ম্যাট্রিক্সটি ঘোরান, কারণ সোয়াপচেইন রেজোলিউশন/সীমাটি আর ডিসপ্লেটির ওরিয়েন্টেশন সহ আপডেট হয় না।
  • আপনার অ্যাপ্লিকেশনটির প্রয়োজন অনুসারে ভিউপোর্ট এবং কাঁচি আয়তক্ষেত্রগুলি আপডেট করুন।

নমুনা অ্যাপ্লিকেশন: ন্যূনতম অ্যান্ড্রয়েড প্রাক-ঘূর্ণন