จัดการการวางแนวอุปกรณ์ด้วยการหมุนล่วงหน้าของ Vulkan

บทความนี้อธิบายวิธีจัดการการหมุนอุปกรณ์อย่างมีประสิทธิภาพในแอปพลิเคชัน Vulkan โดยใช้การหมุนก่อน

เมื่อใช้ Vulkan คุณจะระบุข้อมูลเกี่ยวกับสถานะการแสดงผลได้มากกว่าที่ใช้ OpenGL เมื่อใช้ Vulkan คุณต้องติดตั้งใช้งานสิ่งที่ไดรเวอร์จัดการใน OpenGL อย่างชัดแจ้ง เช่น การวางแนวอุปกรณ์และความสัมพันธ์กับการวางแนวพื้นผิวการแสดงผล Android มี 3 วิธีในการปรับพื้นผิวการแสดงผลของอุปกรณ์ให้สอดคล้องกับการวางแนวของอุปกรณ์

  1. ระบบปฏิบัติการ Android สามารถใช้หน่วยประมวลผลการแสดงผล (DPU) ของอุปกรณ์ ซึ่งจัดการการหมุนพื้นผิวในฮาร์ดแวร์ได้อย่างมีประสิทธิภาพ ใช้ได้กับอุปกรณ์ที่รองรับเท่านั้น
  2. ระบบปฏิบัติการ Android สามารถจัดการการหมุนพื้นผิวได้โดยการเพิ่มคอมโพสิตพาส ซึ่งจะส่งผลต่อประสิทธิภาพโดยขึ้นอยู่กับวิธีที่เครื่องมือทำ Composite ต้องจัดการกับการหมุนภาพเอาต์พุต
  3. แอปพลิเคชันจะจัดการการหมุนพื้นผิวได้โดยการแสดงผลรูปภาพที่หมุนแล้วบนพื้นผิวการแสดงผลที่ตรงกับการวางแนวปัจจุบันของจอแสดงผล

คุณควรใช้วิธีการใด

ปัจจุบันแอปพลิเคชันจะไม่ทราบว่าการหมุนพื้นผิวที่จัดการนอกแอปพลิเคชันนั้นจะเป็นแบบไม่มีค่าใช้จ่ายหรือไม่ แม้ว่าจะมี DPU คอยจัดการเรื่องนี้ให้คุณ แต่คุณก็อาจยังต้องจ่ายค่าปรับประสิทธิภาพที่วัดผลได้ หากแอปพลิเคชันใช้ CPU มากเกินไป ปัญหานี้ก็จะกลายเป็นปัญหาด้านพลังงานเนื่องจาก Android Compositor ใช้ GPU มากขึ้น ซึ่งมักจะทำงานด้วยความถี่ที่เพิ่มขึ้น หากแอปพลิเคชันของคุณใช้ GPU อยู่ คอมโพสิตอร์ของ Android จะแย่งงานของ GPU ในแอปพลิเคชันของคุณได้ ซึ่งจะทำให้ประสิทธิภาพลดลงอีก

เมื่อเรียกใช้ภาพยนตร์/รายการทีวีที่พร้อมให้รับชมใน Pixel 4XL เราพบว่า SurfaceFlinger (งานที่มีความสำคัญสูงกว่าซึ่งขับเคลื่อน Android Compositor)

  • ขัดจังหวะการทำงานของแอปพลิเคชันเป็นประจำ ซึ่งทำให้เฟรมเวลาเพิ่มขึ้น 1-3 มิลลิวินาที และ

  • เพิ่มภาระให้กับหน่วยความจำเวิร์กเทอร์/พื้นผิวของ GPU เนื่องจากคอมโพสิตเตอร์ต้องอ่านเฟรมบัฟเฟอร์ทั้งหมดเพื่อทำงานคอมโพสิต

การจัดการการวางแนวอย่างเหมาะสมจะหยุดการแย่งชิง GPU โดย SurfaceFlinger เกือบทั้งหมด ขณะที่ความถี่ของ GPU จะลดลง 40% เนื่องจากไม่จำเป็นต้องใช้ความถี่ที่เพิ่มซึ่ง Android Compositor ใช้

คุณควรใช้วิธีที่ 3 ดังที่แสดงในเคสก่อนหน้า เพื่อให้ระบบจัดการการหมุนพื้นผิวอย่างถูกต้องโดยมีค่าใช้จ่ายเพิ่มเติมน้อยที่สุด ซึ่งเรียกว่าการหมุนเวียนก่อนแสดง ซึ่งจะบอกระบบปฏิบัติการ Android ว่าแอปของคุณจะจัดการการหมุนของแพลตฟอร์ม ซึ่งทำได้โดยการส่ง Flag การแปลงพื้นผิวที่ระบุการวางแนวระหว่างการสร้าง Swapchain ซึ่งจะหยุดไม่ให้โปรแกรมคอมโพสิตของ Android ทำการหมุนเอง

การทราบวิธีตั้งค่า Flag การเปลี่ยนรูปแบบพื้นผิวเป็นสิ่งสําคัญสําหรับแอปพลิเคชัน Vulkan ทั้งหมด แอปพลิเคชันมักจะรองรับการวางแนวหลายแบบหรือรองรับการวางแนวเดียวที่พื้นผิวการแสดงผลอยู่ในการวางแนวที่แตกต่างจากสิ่งที่อุปกรณ์พิจารณาว่าเป็นการวางแนวของข้อมูลประจำตัว เช่น แอปพลิเคชันแบบแนวนอนเท่านั้นในโทรศัพท์ที่มีการแสดงผลเป็นแนวตั้ง หรือแอปพลิเคชันแบบแนวตั้งเท่านั้นในแท็บเล็ตที่มีการแสดงผลเป็นแนวนอน

แก้ไข AndroidManifest.xml

หากต้องการจัดการการหมุนอุปกรณ์ในแอป ให้เริ่มด้วยการเปลี่ยนไฟล์ AndroidManifest.xml ของแอปพลิเคชันเพื่อบอก Android ว่าแอปของคุณจะจัดการการวางแนวและการเปลี่ยนแปลงขนาดหน้าจอ ซึ่งจะป้องกันไม่ให้ Android ทำลายและสร้าง Activity ของ Android ขึ้นมาใหม่ และเรียกใช้ฟังก์ชัน onDestroy() บนพื้นผิวหน้าต่างที่มีอยู่เมื่อมีการเปลี่ยนแปลงการวางแนว ซึ่งทำได้โดยเพิ่มแอตทริบิวต์ orientation (เพื่อรองรับ API ระดับต่ำกว่า 13) และ screenSize ลงในส่วน configChanges ของกิจกรรม

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

หากแอปพลิเคชันของคุณกำหนดการวางแนวหน้าจอโดยใช้แอตทริบิวต์ screenOrientation คุณไม่จำเป็นต้องดำเนินการนี้ นอกจากนี้ หากแอปพลิเคชันใช้การวางแนวแบบคงที่ ก็จะต้องตั้งค่า Swapchain เพียงครั้งเดียวเมื่อแอปพลิเคชันเริ่มต้น/กลับมาทำงานอีกครั้ง

รับความละเอียดของหน้าจอระบุตัวตนและพารามิเตอร์ของกล้อง

ถัดไป ให้ตรวจหาความละเอียดหน้าจอของอุปกรณ์ที่เชื่อมโยงกับค่า VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR ซึ่งความละเอียดนี้เชื่อมโยงกับการวางแนวข้อมูลประจำตัวของอุปกรณ์ จึงเป็นสิ่งที่ต้องตั้งค่า Swapchain เสมอ วิธีที่เชื่อถือได้มากที่สุดในการรับค่านี้คือโทรไปที่ 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 เวอร์ชันเหล่านี้จะแสดงVK_SUBOPTIMAL_KHRจาก vkQueuePresentKHR() เราจะจัดเก็บผลการตรวจสอบนี้ใน orientationChanged ซึ่งเป็น boolean ที่เข้าถึงได้จากลูปการแสดงผลหลักของแอปพลิเคชัน

ตรวจหาการเปลี่ยนแปลงการวางแนวของอุปกรณ์ (ก่อน Android 10)

สำหรับอุปกรณ์ที่ใช้ Android 10 หรือเก่ากว่า คุณต้องใช้การติดตั้งใช้งานแบบอื่นเนื่องจากระบบไม่รองรับ VK_SUBOPTIMAL_KHR

การใช้การสำรวจ

ในอุปกรณ์ก่อน Android 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;
}

ใน Pixel 4 ที่ใช้ Android 10 การป้อนข้อมูลvkGetPhysicalDeviceSurfaceCapabilitiesKHR()ใช้เวลา 0.120-0.250 มิลลิวินาที และใน Pixel 1XL ที่ใช้ Android 8 การป้อนข้อมูลใช้เวลา 0.110-0.350 มิลลิวินาที

การใช้การเรียกกลับ

ตัวเลือกที่ 2 สำหรับอุปกรณ์ที่ใช้ Android ต่ำกว่า 10 คือลงทะเบียนการเรียกกลับ onNativeWindowResized() เพื่อเรียกใช้ฟังก์ชันที่ตั้งค่า Flag orientationChanged ซึ่งจะส่งสัญญาณให้แอปพลิเคชันทราบว่ามีการเปลี่ยนแปลงการวางแนว

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

โดยที่ ResizeCallback มีคำจำกัดความดังนี้

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

ปัญหาของโซลูชันนี้คือ onNativeWindowResized() จะถูกเรียกใช้เฉพาะเมื่อมีการเปลี่ยนแปลงการวางแนว 90 องศา เช่น จากแนวนอนเป็นแนวตั้งหรือในทางกลับกัน การเปลี่ยนแปลงการวางแนวอื่นๆ จะไม่ทริกเกอร์การสร้าง Swapchain อีกครั้ง เช่น การเปลี่ยนแปลงจากแนวนอนเป็นแนวตั้งกลับด้านจะไม่ทริกเกอร์การพลิก ทำให้คอมโพสิต Android ต้องพลิกแอปพลิเคชันของคุณ

การจัดการกับการเปลี่ยนแปลงการวางแนว

หากต้องการจัดการการเปลี่ยนแปลงการวางแนว ให้เรียกใช้รูทีนการเปลี่ยนแปลงการวางแนวที่ด้านบนของลูปการแสดงผลหลักเมื่อตั้งค่าตัวแปร orientationChanged เป็น "จริง" เช่น

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

คุณทํางานทั้งหมดที่จําเป็นในการสร้าง Swapchain อีกครั้งภายในฟังก์ชัน OnOrientationChange() ซึ่งหมายความว่าคุณจะทำสิ่งต่อไปนี้ได้

  1. ทำลายอินสแตนซ์ Framebuffer และ ImageView ที่มีอยู่

  2. สร้างสวิตช์เชนใหม่ขณะทำลายสวิตช์เชนเก่า (ซึ่งจะกล่าวถึงในลำดับถัดไป) และ

  3. สร้าง Framebuffer ใหม่ด้วย DisplayImages ของ Swapchain ใหม่ หมายเหตุ: โดยทั่วไปแล้วรูปภาพไฟล์แนบ (เช่น รูปภาพความลึก/สเตนซิล) ไม่จำเป็นต้องสร้างใหม่เนื่องจากอิงตามความละเอียดของรูปภาพ Swapchain ที่หมุนไว้ล่วงหน้า

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;
}

และในตอนท้ายของฟังก์ชัน คุณจะรีเซ็ต Flag orientationChanged เป็น false เพื่อแสดงว่าคุณจัดการกับการเปลี่ยนแปลงการวางแนวแล้ว

การสร้าง Swapchain ขึ้นมาใหม่

ในส่วนก่อนหน้านี้ เราได้พูดถึงการสร้าง Swapchain ขึ้นมาใหม่ ขั้นตอนแรกในการดำเนินการนี้เกี่ยวข้องกับการรับลักษณะใหม่ของพื้นผิวการแสดงผล ดังนี้

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 (ซึ่งตั้งค่าเป็นช่อง currentTransform ของ surfaceCapabilities) นอกจากนี้ คุณยังตั้งค่าช่อง oldSwapchain เป็น Swapchain ที่จะทำลายได้ด้วย

การปรับเมตริก MVP

ขั้นตอนสุดท้ายที่ต้องทำคือใช้การเปลี่ยนรูปแบบก่อนการแปลงโดยการใช้เมทริกซ์การหมุนกับเมทริกซ์ MVP สิ่งที่การดำเนินการนี้ทำโดยพื้นฐานคือการหมุนในพื้นที่คลิปเพื่อให้รูปภาพที่ได้หมุนตามการวางแนวอุปกรณ์ปัจจุบัน จากนั้นคุณก็ส่งเมทริกซ์ MVP ที่อัปเดตแล้วนี้ไปยังเวิร์กเทกซ์ Shader และใช้ตามปกติได้โดยไม่ต้องแก้ไข Shader

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;

สิ่งที่ควรพิจารณา - วิวพอร์ตและกรรไกรที่ไม่ใช่แบบเต็มหน้าจอ

หากแอปพลิเคชันของคุณใช้วิวพอร์ต/ภูมิภาคกรรไกรที่ไม่ใช่แบบเต็มหน้าจอ คุณจะต้องอัปเดตแอปพลิเคชันตามการวางแนวของอุปกรณ์ ซึ่งคุณจะต้องเปิดใช้ตัวเลือก Viewport และ Scissor แบบไดนามิกระหว่างการสร้างไปป์ไลน์ของ Vulkan

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);

ข้อควรพิจารณา - อนุพันธ์ของ Shader ระดับเศษส่วน

หากแอปพลิเคชันใช้การคํานวณอนุพันธ์ เช่น dFdx และ dFdy คุณอาจต้องเปลี่ยนรูปแบบเพิ่มเติมเพื่อพิจารณาระบบพิกัดที่เปลี่ยนมุม เนื่องจากการคํานวณเหล่านี้จะดำเนินการในพื้นที่พิกเซล ซึ่งแอปจะต้องส่งตัวบ่งชี้บางอย่างของ preTransform ไปยัง Shader ระดับเศษ (เช่น จำนวนเต็มที่ใช้แสดงการวางแนวอุปกรณ์ปัจจุบัน) และใช้ข้อมูลดังกล่าวเพื่อแมปการคำนวณอนุพันธ์อย่างถูกต้อง

  • สำหรับเฟรมที่ผ่านการหมุนล่วงหน้า 90 องศา
    • dFdx ต้องแมปกับ dFdy
    • dFdy ต้องแมปกับ -dFdx
  • สำหรับเฟรมที่ผ่านการหมุนล่วงหน้าเป็น270 องศา
    • dFdx ต้องจับคู่กับ -dFdy
    • dFdy ต้องแมปกับ dFdx
  • สำหรับเฟรมที่ผ่านการหมุนล่วงหน้า 180 องศา ให้ทำดังนี้
    • dFdx ต้องแมปกับ -dFdx
    • dFdy ต้องแมปกับ -dFdy

บทสรุป

คุณต้องใช้การหมุนภาพล่วงหน้าเพื่อให้แอปพลิเคชันได้รับประโยชน์สูงสุดจาก Vulkan ใน Android สรุปสำคัญที่สุดจากบทความนี้มีดังนี้

  • ตรวจสอบว่าในระหว่างการสร้างหรือสร้าง Swapchain ใหม่ มีการตั้งค่า Flag การเปลี่ยนรูปแบบล่วงหน้าให้ตรงกับ Flag ที่ระบบปฏิบัติการ Android แสดง ซึ่งจะช่วยหลีกเลี่ยงค่าใช้จ่ายเพิ่มเติมของคอมโพสิต
  • คงขนาดสวิตช์เชนไว้กับความละเอียดของข้อมูลประจำตัวของพื้นผิวหน้าต่างแอปในแนวการวางแนวตามปกติของจอแสดงผล
  • หมุนเมทริกซ์ MVP ในคลิปสเปซเพื่อพิจารณาการวางแนวของอุปกรณ์ เนื่องจากความละเอียด/ขอบเขตของ Swapchain จะไม่อัปเดตตามการวางแนวของจอแสดงผลอีกต่อไป
  • อัปเดตวิวพอร์ตและสี่เหลี่ยมผืนผ้ากรรไกรตามต้องการของแอปพลิเคชัน

ตัวอย่างแอป: การหมุนก่อนแสดงผลขั้นต่ำของ Android