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

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

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 রেজোলিউশন/ব্যাপ্তি আর ডিসপ্লের ওরিয়েন্টেশনের সাথে আপডেট হয় না।
  • আপনার অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী ভিউপোর্ট এবং কাঁচি আয়তক্ষেত্র আপডেট করুন।

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