จัดการการวางแนวอุปกรณ์ด้วยการหมุนล่วงหน้าของ 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