บทความนี้อธิบายวิธีจัดการการหมุนอุปกรณ์อย่างมีประสิทธิภาพในแอปพลิเคชัน Vulkan โดยใช้การหมุนล่วงหน้า
Vulkan ช่วยให้คุณระบุข้อมูลเกี่ยวกับสถานะการแสดงผลได้มากกว่า OpenGL เมื่อใช้ Vulkan คุณต้องใช้สิ่งที่ไดรเวอร์จัดการใน OpenGL อย่างชัดเจน เช่น การวางแนวอุปกรณ์และความสัมพันธ์กับการวางแนวพื้นผิวการแสดงผล Android สามารถจัดการการปรับพื้นผิวการแสดงผลของอุปกรณ์ให้สอดคล้องกับการวางแนวอุปกรณ์ได้ 3 วิธีดังนี้
- ระบบปฏิบัติการ Android สามารถใช้หน่วยประมวลผลการแสดงผล (DPU) ของอุปกรณ์ ซึ่งสามารถจัดการการหมุนพื้นผิวในฮาร์ดแวร์ได้อย่างมีประสิทธิภาพ ใช้ได้ในอุปกรณ์ที่รองรับเท่านั้น
- ระบบปฏิบัติการ Android สามารถจัดการการหมุนพื้นผิวได้โดยการเพิ่ม Compositor Pass การดำเนินการนี้จะมีค่าใช้จ่ายด้านประสิทธิภาพ โดยขึ้นอยู่กับวิธีที่คอมโพสิเตอร์ต้องจัดการกับการหมุนรูปภาพเอาต์พุต
- แอปพลิเคชันสามารถจัดการการหมุนพื้นผิวได้ด้วยการแสดงผลรูปภาพที่หมุนแล้วบนพื้นผิวการแสดงผลที่ตรงกับการวางแนวปัจจุบันของจอแสดงผล
คุณควรใช้วิธีใดต่อไปนี้
ปัจจุบันแอปพลิเคชันไม่มีวิธีทราบว่าการหมุนพื้นผิวที่จัดการนอกแอปพลิเคชันจะไม่มีค่าใช้จ่ายหรือไม่ แม้ว่าจะมี DPU ที่จะดูแลเรื่องนี้ให้คุณ แต่ก็ยังอาจมีผลกระทบต่อประสิทธิภาพที่วัดได้ซึ่งคุณต้องรับผิดชอบ หากแอปพลิเคชันของคุณใช้ CPU เป็นหลัก ปัญหานี้จะกลายเป็นปัญหาด้านพลังงานเนื่องจากการใช้งาน GPU ที่เพิ่มขึ้นโดย Android Compositor ซึ่งมักจะทำงานที่ความถี่ที่เพิ่มขึ้น หากแอปพลิเคชันของคุณเชื่อมโยงกับ GPU แล้ว Android Compositor ก็สามารถแย่งงาน GPU ของแอปพลิเคชันได้เช่นกัน ซึ่งจะทำให้ประสิทธิภาพลดลงไปอีก
เมื่อเรียกใช้เกมที่เผยแพร่แล้วใน Pixel 4XL เราพบว่า SurfaceFlinger (งานที่มีลำดับความสำคัญสูงกว่าซึ่งขับเคลื่อน Android Compositor) มีลักษณะดังนี้
ขัดจังหวะการทำงานของแอปพลิเคชันเป็นประจำ ทำให้เวลาในการแสดงผลเฟรมเพิ่มขึ้น 1-3 มิลลิวินาที และ
เพิ่มแรงกดดันให้กับหน่วยความจำเวอร์เท็กซ์/พื้นผิวของ GPU เนื่องจาก Compositor ต้องอ่านเฟรมบัฟเฟอร์ทั้งหมดเพื่อทำงานคอมโพสิชัน
การจัดการการวางแนวอย่างเหมาะสมจะหยุดการแย่งชิง GPU โดย SurfaceFlinger เกือบทั้งหมด ขณะที่ความถี่ GPU ลดลง 40% เนื่องจากไม่จำเป็นต้องใช้ความถี่ที่เพิ่มขึ้นซึ่งใช้โดย Android Compositor อีกต่อไป
เพื่อให้แน่ใจว่าการหมุนพื้นผิวได้รับการจัดการอย่างเหมาะสมโดยมีค่าใช้จ่ายเพิ่มเติม (Overhead) น้อยที่สุดเท่าที่จะเป็นไปได้ ดังที่เห็นในกรณีก่อนหน้านี้ คุณควรใช้วิธีที่ 3 ซึ่งเรียกว่าการหมุนล่วงหน้า ซึ่งจะบอกระบบปฏิบัติการ Android ว่าแอปของคุณจะจัดการการหมุนพื้นผิว ซึ่งทำได้โดยการส่ง Flag การเปลี่ยนรูปแบบพื้นผิวที่ระบุการวางแนวระหว่างการสร้าง Swapchain ซึ่งจะหยุดไม่ให้ Android Compositor หมุนเอง
การรู้วิธีตั้งค่า 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 เพียงครั้งเดียวเมื่อเริ่มต้น/กลับมาทำงานของแอปพลิเคชัน
ดูความละเอียดของหน้าจอและพารามิเตอร์กล้องของ 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 ที่แสดงผลกับช่องของการเปลี่ยนรูปแบบพื้นผิวที่จัดเก็บไว้ในปัจจุบัน (ในตัวอย่างโค้ดนี้จัดเก็บไว้ใน 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 มิลลิวินาที
การใช้ Callback
ตัวเลือกที่ 2 สำหรับอุปกรณ์ที่ใช้ Android เวอร์ชันต่ำกว่า 10 คือการลงทะเบียน Callback 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 ใหม่
ตัวอย่างเช่น การเปลี่ยนจากแนวนอนเป็นแนวนอนกลับด้านจะไม่ทริกเกอร์การหมุนหน้าจอ ซึ่งกำหนดให้ Android Compositor ทำการพลิกหน้าจอสำหรับแอปพลิเคชันของคุณ
การจัดการการเปลี่ยนแปลงการวางแนว
หากต้องการจัดการการเปลี่ยนแปลงการวางแนว ให้เรียกใช้รูทีนการเปลี่ยนแปลงการวางแนวที่ด้านบนของลูปการแสดงผลหลักเมื่อตั้งค่าตัวแปร 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 เป็นเท็จเพื่อแสดงว่าคุณได้จัดการการเปลี่ยนแปลงการวางแนวแล้ว
การสร้าง 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;
ข้อควรพิจารณา - วิวพอร์ตและกรรไกรแบบไม่เต็มหน้าจอ
หากแอปพลิเคชันของคุณใช้พอร์ตการมองเห็น/ขอบเขตการตัดที่ไม่ใช่แบบเต็มหน้าจอ จะต้องอัปเดตพอร์ตการมองเห็น/ขอบเขตการตัดดังกล่าวตามการวางแนวของอุปกรณ์ ซึ่งกำหนดให้คุณต้องเปิดใช้ตัวเลือกวิวพอร์ตและกรรไกรแบบไดนามิกระหว่างการสร้างไปป์ไลน์ของ 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);
ข้อควรพิจารณา - อนุพันธ์ของ 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 แสดงผล วิธีนี้จะช่วยหลีกเลี่ยงโอเวอร์เฮดของคอมโพสิเตอร์
- กำหนดขนาด Swapchain ให้คงที่ตามความละเอียดเอกลักษณ์ของพื้นผิวหน้าต่างของแอปในการวางแนวตามธรรมชาติของจอแสดงผล
- หมุนเมทริกซ์ MVP ในพื้นที่คลิปเพื่อพิจารณาการวางแนวของอุปกรณ์ เนื่องจากความละเอียด/ขอบเขตของ Swapchain จะไม่อัปเดตตามการวางแนวของจอแสดงผลอีกต่อไป
- อัปเดตวิวพอร์ตและสี่เหลี่ยมผืนผ้าแบบกรรไกรตามที่แอปพลิเคชันต้องการ
แอปตัวอย่าง: การหมุนล่วงหน้าขั้นต่ำของ Android