บทความนี้อธิบายวิธีจัดการการหมุนอุปกรณ์อย่างมีประสิทธิภาพในแอปพลิเคชัน Vulkan โดยการใช้การหมุนล่วงหน้า
Vulkan ช่วยให้คุณระบุข้อมูลเกี่ยวกับสถานะการแสดงผลได้มากกว่า OpenGL เมื่อใช้ Vulkan คุณต้องใช้สิ่งต่างๆ ที่ไดรเวอร์จัดการใน OpenGL อย่างชัดเจน เช่น การวางแนวอุปกรณ์และความสัมพันธ์กับการวางแนวพื้นผิวการแสดงผล Android มี 3 วิธีในการ จัดการการปรับพื้นผิวการแสดงผลของอุปกรณ์ให้สอดคล้องกับการวางแนวของอุปกรณ์
- ระบบปฏิบัติการ Android สามารถใช้หน่วยประมวลผลการแสดงผล (DPU) ของอุปกรณ์ ซึ่งจัดการการหมุนพื้นผิวในฮาร์ดแวร์ได้อย่างมีประสิทธิภาพ พร้อมใช้งานใน อุปกรณ์ที่รองรับเท่านั้น
- ระบบปฏิบัติการ Android สามารถจัดการการหมุนพื้นผิวได้โดยการเพิ่มการส่งผ่าน Compositor ซึ่ง จะมีต้นทุนด้านประสิทธิภาพขึ้นอยู่กับวิธีที่เครื่องมือทำ Composite ต้องจัดการกับ การหมุนรูปภาพเอาต์พุต
- แอปพลิเคชันสามารถจัดการการหมุนพื้นผิวได้ด้วยการแสดงผล รูปภาพที่หมุนแล้วบนพื้นผิวการแสดงผลที่ตรงกับการวางแนวปัจจุบันของ จอแสดงผล
คุณควรใช้วิธีใด
ปัจจุบันยังไม่มีวิธีที่แอปพลิเคชันจะทราบว่าการหมุนพื้นผิวที่จัดการภายนอกแอปพลิเคชันจะฟรีหรือไม่ แม้ว่าจะมี DPU ที่ดูแลเรื่องนี้ให้คุณ แต่ก็ยังอาจมีค่าปรับด้านประสิทธิภาพที่วัดได้ ที่คุณต้องจ่าย หากแอปพลิเคชันของคุณขึ้นอยู่กับ CPU ปัญหานี้จะกลายเป็นปัญหาด้านพลังงานเนื่องจาก การใช้งาน GPU ที่เพิ่มขึ้นโดย Compositor ของ Android ซึ่งมักจะทำงานที่ ความถี่ที่เพิ่มขึ้น หากแอปพลิเคชันของคุณขึ้นอยู่กับ GPU คอมโพสิตเตอร์ Android ก็สามารถขัดจังหวะการทำงานของ GPU ของแอปพลิเคชันได้เช่นกัน ซึ่งจะทำให้ประสิทธิภาพลดลง เพิ่มเติม
เมื่อเรียกใช้ชื่อที่จัดส่งบน Pixel 4XL เราพบว่า SurfaceFlinger (งานที่มีลำดับความสำคัญสูงกว่าซึ่งขับเคลื่อน Android Compositor) มีลักษณะดังนี้
ขัดจังหวะการทำงานของแอปพลิเคชันเป็นประจำ ทำให้เกิดการเข้าชม 1-3 มิลลิวินาที ในเวลาเฟรม และ
เพิ่มแรงกดดันต่อหน่วยความจำของจุดยอด/พื้นผิวของ GPU เนื่องจาก Compositor ต้องอ่านทั้ง Framebuffer เพื่อทำงานการคอมโพสิต
การจัดการการวางแนวอย่างเหมาะสมจะหยุดการขัดจังหวะ GPU โดย SurfaceFlinger เกือบทั้งหมด ในขณะที่ความถี่ของ GPU จะลดลง 40% เนื่องจากไม่จำเป็นต้องใช้ความถี่ที่เพิ่มขึ้นซึ่งใช้โดยตัวจัดองค์ประกอบของ Android อีกต่อไป
คุณควรใช้วิธีที่ 3 เพื่อให้มั่นใจว่าการหมุนพื้นผิวได้รับการจัดการอย่างถูกต้องโดยมีค่าใช้จ่ายน้อยที่สุด เท่าที่จะเป็นไปได้ ดังที่เห็นในกรณีข้างต้น ซึ่งเรียกว่าการหมุนเวียนล่วงหน้า ซึ่งจะบอกระบบปฏิบัติการ Android ว่าแอปของคุณ จัดการการหมุนพื้นผิว คุณทำได้โดยส่งแฟล็กการเปลี่ยนรูปแบบพื้นผิว ที่ระบุการวางแนวในระหว่างการสร้าง Swapchain ซึ่งจะหยุดไม่ให้ Compositor ของ Android ทำการหมุนด้วยตัวเอง
การทราบวิธีตั้งค่าแฟล็กการแปลงพื้นผิวเป็นสิ่งสำคัญสำหรับแอปพลิเคชัน 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 เพียงครั้งเดียวเมื่อ
แอปพลิเคชันเริ่มต้น/กลับมาทำงานต่อ
รับความละเอียดของหน้าจอและพารามิเตอร์กล้องของ Identity
จากนั้นตรวจหาความละเอียดหน้าจอของอุปกรณ์ที่เชื่อมโยงกับค่า 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
ที่ส่งคืนกับฟิลด์ของ Surface
transformation ที่จัดเก็บอยู่ในปัจจุบัน (ในตัวอย่างโค้ดนี้จัดเก็บไว้ใน 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()
เพื่อเรียกฟังก์ชันที่ตั้งค่า
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 ใหม่
เช่น การเปลี่ยนจากแนวนอนเป็นแนวนอนกลับด้านจะไม่ทริกเกอร์การหมุนดังกล่าว ซึ่งทำให้ Compositor ของ Android ต้องพลิกแอปพลิเคชันของคุณ
การจัดการการเปลี่ยนแปลงการวางแนว
หากต้องการจัดการการเปลี่ยนแปลงการวางแนว ให้เรียกใช้กิจวัตรการเปลี่ยนแปลงการวางแนวที่ด้านบนของลูปการแสดงผลหลักเมื่อตั้งค่าorientationChanged
ตัวแปรเป็นจริง เช่น
bool VulkanDrawFrame() {
if (orientationChanged) {
OnOrientationChange();
}
คุณต้องทำงานทั้งหมดที่จำเป็นเพื่อสร้าง Swapchain ใหม่ภายในฟังก์ชัน OnOrientationChange()
ซึ่งหมายความว่าคุณ
ทำลายอินสแตนซ์ที่มีอยู่ของ
Framebuffer
และImageView
สร้าง Swapchain ใหม่ขณะทำลาย Swapchain เก่า (ซึ่งจะกล่าวถึงในส่วนถัดไป) และ
สร้าง Framebuffer ใหม่ด้วย DisplayImage ของ 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;
}
และที่ส่วนท้ายของฟังก์ชัน คุณจะรีเซ็ตแฟล็ก 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 ที่อัปเดตนี้ ไปยัง Vertex 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 และกรรไกรที่ไม่ใช่แบบเต็มหน้าจอ
หากแอปพลิเคชันของคุณใช้ Viewport/กรรไกรตัดที่ไม่ใช่แบบเต็มหน้าจอ คุณจะต้องอัปเดตตามการวางแนวของอุปกรณ์ ซึ่งกำหนดให้คุณต้องเปิดใช้ตัวเลือก 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);
การคำนวณขอบเขตของวิวพอร์ตจริงในระหว่างการบันทึก Command Buffer จะมีลักษณะดังนี้
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);
การพิจารณา - อนุพันธ์ของ Fragment Shader
หากแอปพลิเคชันใช้การคำนวณอนุพันธ์ เช่น dFdx
และ dFdy
อาจต้องมีการแปลงเพิ่มเติมเพื่อพิจารณาระบบ
พิกัดที่หมุน เนื่องจากระบบจะดำเนินการคำนวณเหล่านี้ในพื้นที่พิกเซล ซึ่งกำหนดให้แอป
ส่งข้อบ่งชี้บางอย่างของ preTransform ไปยัง Fragment Shader (เช่น
จำนวนเต็มที่แสดงการวางแนวอุปกรณ์ปัจจุบัน) และใช้ข้อบ่งชี้นั้นเพื่อแมป
การคำนวณอนุพันธ์อย่างเหมาะสม
- สำหรับเฟรมที่หมุนไว้ล่วงหน้า90 องศา
- ต้องแมป dFdx กับ dFdy
- ต้องแมป dFdy กับ -dFdx
- สำหรับเฟรมที่หมุนล่วงหน้า270 องศา
- ต้องแมป dFdx กับ -dFdy
- ต้องแมป dFdy กับ dFdx
- สำหรับเฟรมที่หมุนไว้ล่วงหน้า180 องศา
- ต้องแมป dFdx กับ -dFdx
- ต้องแมป dFdy กับ -dFdy
บทสรุป
หากต้องการให้แอปพลิเคชันใช้ Vulkan บน Android ได้อย่างเต็มประสิทธิภาพ คุณต้องใช้การหมุนก่อน ประเด็นสำคัญที่สุดจากบทความนี้มีดังนี้
- ตรวจสอบว่าในระหว่างการสร้างหรือสร้าง Swapchain ใหม่ ให้ ตั้งค่าแฟล็ก pretransform ให้ตรงกับแฟล็กที่ระบบปฏิบัติการ Android ส่งคืน ซึ่งจะช่วยหลีกเลี่ยง ค่าใช้จ่ายของ Compositor
- กำหนดขนาด Swapchain ให้คงที่ตามความละเอียดของหน้าต่าง พื้นผิวของแอปในการวางแนวปกติของจอแสดงผล
- หมุนเมทริกซ์ MVP ในพื้นที่คลิปเพื่อพิจารณาการวางแนวของอุปกรณ์ เนื่องจากความละเอียด/ขอบเขตของ Swapchain จะไม่อัปเดตตามการวางแนว ของจอแสดงผลอีกต่อไป
- อัปเดตสี่เหลี่ยมผืนผ้าของ Viewport และ Scissor ตามที่แอปพลิเคชันของคุณต้องการ
แอปตัวอย่าง: การหมุนล่วงหน้าของ Android ที่มีขนาดเล็กที่สุด