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

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

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

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

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

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

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

  • নিয়মিতভাবে অ্যাপ্লিকেশনটির কাজে বাধা দেয়, যার ফলে ফ্রেমটাইমে ১-৩ মিলিসেকেন্ডের প্রভাব পড়ে, এবং

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

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

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

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

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

আপনার অ্যাপে ডিভাইস রোটেশন পরিচালনা করতে, প্রথমে অ্যাপ্লিকেশনটির AndroidManifest.xml ফাইলটি পরিবর্তন করে অ্যান্ড্রয়েডকে জানান যে আপনার অ্যাপই ওরিয়েন্টেশন এবং স্ক্রিন সাইজের পরিবর্তন পরিচালনা করবে। এর ফলে, ওরিয়েন্টেশন পরিবর্তনের সময় অ্যান্ড্রয়েড Activity নষ্ট করে আবার তৈরি করা এবং বিদ্যমান উইন্ডো সারফেসে onDestroy() ফাংশনটি কল করা থেকে অ্যান্ড্রয়েড বিরত থাকে। এটি করার জন্য, অ্যাক্টিভিটির configChanges সেকশনে orientation (API লেভেল <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 স্ট্রাকচার যা আমরা ডিসপ্লের স্বাভাবিক ওরিয়েন্টেশনে অ্যাপের উইন্ডো সারফেসের আইডেন্টিটি রেজোলিউশন সংরক্ষণ করতে ব্যবহার করি।

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

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

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

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

ডিভাইসের ওরিয়েন্টেশন পরিবর্তন সনাক্ত করুন (অ্যান্ড্রয়েড ১০-এর পূর্ববর্তী সংস্করণ)

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

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

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

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

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

অ্যান্ড্রয়েড ১০ চালিত একটি পিক্সেল ৪-এ vkGetPhysicalDeviceSurfaceCapabilitiesKHR() পোলিং করতে .১২০-.২৫০ মিলিসেকেন্ড সময় লেগেছে এবং অ্যান্ড্রয়েড ৮ চালিত একটি পিক্সেল ১এক্সএল-এ পোলিং করতে .১১০-.৩৫০ মিলিসেকেন্ড সময় লেগেছে।

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

অ্যান্ড্রয়েড ১০-এর নিচের সংস্করণে চালিত ডিভাইসগুলোর জন্য দ্বিতীয় একটি বিকল্প হলো একটি onNativeWindowResized() কলব্যাক রেজিস্টার করা, যা এমন একটি ফাংশনকে কল করে যা orientationChanged ফ্ল্যাগটি সেট করে এবং অ্যাপ্লিকেশনকে ওরিয়েন্টেশন পরিবর্তনের সংকেত দেয়।

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

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

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

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

অভিযোজন পরিবর্তন পরিচালনা

ওরিয়েন্টেশন পরিবর্তনটি পরিচালনা করতে, যখন orientationChanged ভেরিয়েবলটির মান true সেট করা থাকে, তখন প্রধান রেন্ডারিং লুপের শুরুতে ওরিয়েন্টেশন পরিবর্তন রুটিনটি কল করুন। উদাহরণস্বরূপ:

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 ফ্ল্যাগটিকে false-এ রিসেট করেন, এটা বোঝাতে যে আপনি ওরিয়েন্টেশন পরিবর্তনটি সম্পন্ন করেছেন।

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

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

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

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

এটি করার জন্য, VkSwapchainCreateInfo struct-এ নিম্নলিখিত অ্যাট্রিবিউটগুলো উল্লেখ করুন:

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 এর currentTransform ফিল্ডে সেট করা থাকে)। এছাড়াও, যে সোয়াপচেইনটি ধ্বংস করা হবে, সেটির জন্য আপনি oldSwapchain ফিল্ডটি সেট করবেন।

এমভিপি ম্যাট্রিক্স সমন্বয়

সর্বশেষ যে কাজটি আপনাকে অবশ্যই করতে হবে তা হলো আপনার 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 মতো ডেরিভেটিভ কম্পিউটেশন ব্যবহার করে, তাহলে ঘূর্ণিত স্থানাঙ্ক ব্যবস্থার জন্য অতিরিক্ত ট্রান্সফরমেশনের প্রয়োজন হতে পারে, কারণ এই কম্পিউটেশনগুলি পিক্সেল স্পেসে সম্পাদিত হয়। এর জন্য অ্যাপটিকে ফ্র্যাগমেন্ট শেডারে প্রি-ট্রান্সফর্মের কোনো একটি নির্দেশক (যেমন বর্তমান ডিভাইস ওরিয়েন্টেশন নির্দেশকারী একটি পূর্ণসংখ্যা) পাঠাতে হবে এবং ডেরিভেটিভ কম্পিউটেশনগুলিকে সঠিকভাবে ম্যাপ করার জন্য সেটি ব্যবহার করতে হবে।

  • ৯০ ডিগ্রি পূর্ব-ঘূর্ণিত ফ্রেমের জন্য
    • dFdx-কে dFdy- তে ম্যাপ করতে হবে।
    • dFdy-কে অবশ্যই -dFdx- এ ম্যাপ করতে হবে।
  • ২৭০ ডিগ্রি পূর্ব-ঘূর্ণিত ফ্রেমের জন্য
    • dFdx- কে -dFdy- তে ম্যাপ করতে হবে।
    • dFdy-কে dFdx- এ ম্যাপ করতে হবে।
  • ১৮০ ডিগ্রি পূর্ব-ঘূর্ণিত ফ্রেমের জন্য,
    • dFdx-কে অবশ্যই -dFdx- এ ম্যাপ করতে হবে।
    • dFdy- কে -dFdy- তে ম্যাপ করতে হবে।

উপসংহার

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

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

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