Vulkan ön döndürme özelliğiyle cihaz yönünü yönetin

Bu makalede, cihaz rotasyonunun verimli bir şekilde nasıl yönetileceği açıklanmaktadır. Vulkan uygulamanızda görebilirsiniz.

Vulkan ile şunları yapabilirsiniz: oluşturma durumu hakkında, OpenGL'de oluşturabileceğinizden çok daha fazla bilgi belirtme. Vulkan'da sürücü tarafından işlenen öğeleri (ör. cihaz yönü ve oluşturma yüzey yönünü. Android iki şekilde cihazın oluşturma yüzeyinin cihaz yönü ile aynı hizada olmasını sağlayın:

  1. Android OS, cihazın ekran işleme birimini (DPU) kullanabilir. verimli bir şekilde işleyebilen, otomatik olarak entegre edilmiş bir cihaz modelidir. İzleyebileceğiniz yerler yalnızca desteklenen cihazlarda kullanılabilir.
  2. Android OS, birleştirici geçişi ekleyerek yüzey dönüşünü işleyebilir. Bu bir performans maliyeti, birleştiren kişinin çıktı resmi döndürün.
  3. Uygulamanın kendisi, bir resmin mevcut yönüyle eşleşen bir oluşturma yüzeyine döndürüldü görebilirsiniz.

Bu yöntemlerden hangisini kullanmalısınız?

Şu anda, bir uygulamanın yüzey dönüşünün münferit olup olmadığını bilmesi mümkün değildir. hiçbir ücret ödemezsiniz. Alınacak bir DPU olsa bile önem taşırsanız, ölçülebilir bir performans cezası daha fazla bilgi edineceksiniz. Uygulamanız CPU'ya bağlıysa bu durum, genellikle belirli bir hızda çalışan Android Compositor'da daha yüksek frekans. Uygulamanız GPU'ya bağlıysa Android Compositor ayrıca uygulamanızın GPU çalışmasını önceden ayırarak ek performansa neden olabilir kaybetmezsiniz.

Pixel 4XL'de kargo öğelerini yayınlarken SurfaceFlinger (Android'i mobil cihazlarda ve Birleştirici):

  • Uygulamanın çalışmasını düzenli olarak önceden ayırarak 1-3 ms. ve kare süreleri için isabet sayısı

  • GPU'nun çalışma ortamına daha fazla birleştiricinin tüm veri kaynağını okuması gerektiğinden, köşe/doku belleği karebuffer'ı kullanacağım.

Yönün doğru şekilde işlenmesi, SurfaceFlinger tarafından GPU önlemenin yapılmasını neredeyse durduruyor tarafından kullanılan artırılmış frekans olarak GPU frekansı% 40 düşer. Android Compositor artık gerekli değil.

Yüzey dönüşlerinin olabildiğince az ek yük ile düzgün bir şekilde gerçekleştirilmesini sağlamak için olduğu gibi 3. yöntemi uygulamalısınız. Bu işlem, ön rotasyon olarak bilinir. Bu işlem, Android OS'e uygulamanızın yardımcı olabilir. Bunu, yüzey dönüşümü bayraklarını ileterek yapabilirsiniz. değiştirilebilir. Bu işlem Android Birleştirici'nin, döndürmeyi kendi yapmasını engeller.

Her Vulkan için yüzey dönüşüm işaretinin nasıl ayarlanacağını bilmek önemlidir. bir uygulamadır. Uygulamalar birden çok yönü destekler. veya oluşturma yüzeyinin farklı bir yönde olduğu tek bir yönü destekleme cihazın kimlik yönü olarak kabul ettiği yüzeye göre ayarlayabilirsiniz. Örneğin, Dikey kimlik telefonda yalnızca yatay kullanılabilen bir uygulama veya yalnızca dikey özellikli bir uygulama bir uygulamadır.

AndroidManifest.xml dosyasını değiştir

Uygulamanızda cihaz döndürme işlemini yönetmek için öncelikle uygulamanın Android'e, uygulamanızın yönü işleyeceğini bildirmek için AndroidManifest.xml dosyası ve ekran boyutu değişiklikleri yaşar. Bu işlem, Android'in öğeleri kaldırmasını ve yeniden oluşturmasını engeller Android Activity onDestroy() işlevini Yön değişikliği yapıldığında mevcut pencere yüzeyini. Bu, orientation (API seviyesini 13'ü desteklemek için) ve screenSize özelliklerini ekleyerek etkinliğin configChanges bölümü:

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

Uygulamanız, ekran yönünü screenOrientation özelliği kullanıyorsanız bunu yapmanız gerekmez. Ayrıca, uygulamanızda sabit bir düzenlendikten sonra, değişim zincirini yalnızca bir kez uygulama başlatılır/devam ettirilir.

Kimlik ekranı çözünürlüğünü ve kamera parametrelerini alma

Ardından cihazın ekran çözünürlüğünü tespit edin VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR değeriyle ilişkilidir. Bu cihazın kimlik yönü ile ilişkilendirildiğinden, buna göre takas zincirinin her zaman ayarlanması gerekir. En bunu sağlamanın en güvenilir yolu Uygulama başlatılırken vkGetPhysicalDeviceSurfaceCapabilitiesKHR() ve yeterince zaman ayırın. Genişlik ve yüksekliği depoladığınızdan emin olmak için de döndürülen currentTransform kimlik ekran çözünürlüğü:

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, söz konusu kimliği saklamak için kullandığımız bir VkExtent2D yapısıdır. uygulamanın pencere yüzeyinin ekranın doğal yöndeki çözünürlüğü.

Cihaz Yönü Değişikliklerini Algılama (Android 10 ve sonraki sürümler)

Uygulamanızdaki yön değişikliğini algılamanın en güvenilir yolu vkQueuePresentKHR() işlevinin VK_SUBOPTIMAL_KHR. Örnek:

auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
  orientationChanged = true;
}

Not: Bu çözüm yalnızca şu sürümlerin yüklü olduğu cihazlarda çalışır: Android 10 ve sonraki sürümler. Android'in bu sürümleri geri dönüyor VK_SUBOPTIMAL_KHR, kalkış: vkQueuePresentKHR(). Bunun sonucunu saklarız şuradan erişilebilen bir boolean olan orientationChanged check in yapın: uygulamaların ana oluşturma döngüsü.

Cihaz Yönü Değişikliklerini Algılama (Android 10 Öncesi)

Android 10 veya daha eski sürümleri çalıştıran cihazlarda farklı bir VK_SUBOPTIMAL_KHR desteklenmediğinden uygulama gereklidir.

Anket özelliğini kullanma

Android 10 öncesi cihazlarda mevcut cihaz dönüşümünü her zaman sorgulayabilirsiniz. pollingInterval öğesinin belirlenen ayrıntı düzeyi olduğu pollingInterval kareler programcı tarafından belirlenir. Bunu yapmak için vkGetPhysicalDeviceSurfaceCapabilitiesKHR() ve ardından döndürülen Şu anda depolanmış yüzeyinkiyle birlikte currentTransform alanı (bu kod örneğinde, pretransformFlag içinde depolanmaktadır).

currFrameCount++;
if (currFrameCount >= pollInterval){
  VkSurfaceCapabilitiesKHR capabilities;
  vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);

  if (pretransformFlag != capabilities.currentTransform) {
    window_resized = true;
  }
  currFrameCount = 0;
}

Android 10 çalıştıran Pixel 4'te yoklama vkGetPhysicalDeviceSurfaceCapabilitiesKHR() 0,120-0,250 ms .sürdü. Android 8 çalıştıran Pixel 1XL, anket süresi 0,110-0,350 ms sürdü.

Geri Çağırmaları Kullanma

Android 10'dan önceki sürümleri çalıştıran cihazlar için ikinci seçenek, onNativeWindowResized() Uygulamaya yön değişikliğinin sinyalini veren orientationChanged işareti gerçekleşti:

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

Yeniden Boyutlandırma Geri çağırması şu şekilde tanımlanır:

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

Bu çözümdeki sorun, onNativeWindowResized() yalnızca gibi 90 derece yön değişiklikleri gerektirir (yataydan dikey veya ve bu böyle devam eder. Diğer yön değişiklikleri, değişim zincirinin yeniden oluşturulmasını tetiklemez. Örneğin, yataydan ters yatay görünüme geçildiğinde, Android derleyicisinin bir uygulamadır.

Yön Değişikliğini Ele Alma

Yön değişikliğini işlemek için orientationChanged, ana oluşturma döngüsünün üst kısmına değişkeninin doğru değerine ayarlanmasını sağlar. Örnek:

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

Değişim zincirini yeniden oluşturmak için gereken tüm işleri OnOrientationChange() işlevini kullanın. Bunun anlamı şudur:

  1. Mevcut tüm Framebuffer ve ImageView örneklerini yok edin,

  2. Yok etme işlemi sırasında takas zincirini yeniden oluştur (daha sonra ele alınacak olan) eski takas zinciri

  3. Framebuffers'ları yeni takas zincirinin DisplayImages'iyle yeniden oluşturun. Not: Ek resimleri (örneğin, derinlik/şablon resimleri) genellikle aynı şekilde yeniden önceden döndürülmüş takas zinciri resimlerinin kimlik çözünürlüğüne dayanır.

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

İşlevin sonunda ise orientationChanged işaretini "false" (yanlış) olarak sıfırlarsınız. yön değişikliğini ele aldığınızı gösterin.

Değişim Zinciri Oluşturma

Önceki bölümde takas zincirini yeniden oluşturmanız gerektiğinden bahsedeceğiz. Bunu yapmanın ilk adımları, işletmenin yeni özelliklerini oluşturma yüzeyi:

void createSwapChain(VkSwapchainKHR oldSwapchain) {
   VkSurfaceCapabilitiesKHR capabilities;
   vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
   pretransformFlag = capabilities.currentTransform;

VkSurfaceCapabilities yapısı yeni bilgilerle doldurulduğunda, artık yön değişikliği olup olmadığını kontrol etmek için currentTransform alanı. Daha sonra pretransformFlag içinde incelemek üzere saklayacaksınız olacaktır çünkü bu bilgilere daha sonra ihtiyacınız olacak. MVP matrisi.

Bunun için aşağıdaki özellikleri belirtin VkSwapchainCreateInfo yapısında:

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 alanı şu şekilde doldurulur: displaySizeIdentity depoladığınız veri türlerini görürsünüz. preTransform alanı doldurulur (currentTransform alanına ayarlanır) pretransformFlag değişkeniyle surfaceCapabilities). Ayrıca oldSwapchain alanını bir takas zinciri olabilir.

MVP Matris Ayarlaması

Yapmanız gereken son şey, dönüşümden önce bunu MVP matrisinize rotasyon matrisi uygulayarak yapabilirsiniz. Bunun temel işlevi, Sonuçta elde edilen resmin dikey yönde döndürülmesi için klip alanına döndürmeyi geçerli cihaz yönünü görebilirsiniz. Böylece, güncellenmiş bu MVP matrisini içine yerleştirmenize olanak tanıyan, ayarlarınızı değiştirmek zorunda kalmadan gölgelendiriciler.

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;

Üzerinde Düşünme - Tam Ekran Olmayan Görüntü Alanı ve Makas

Uygulamanız tam ekran olmayan bir görüntü alanı/makas bölgesi kullanıyorsa cihazın yönüne göre güncellenmesi gerekir. Bu Vulkan’ın işlemi sırasında dinamik Görünüm ve Makas seçeneklerini ardışık düzen oluşturma:

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

Komut arabelleği kaydı sırasında görüntü alanı kapsamının gerçek hesaplaması şöyle görünür:

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 ve y değişkenleri, grafiğin sol üst köşesinin koordinatlarını görüntü alanının genişliğini ve yüksekliğini tanımlarken w ve h, görüntü alanının genişliğini ve yüksekliğini tanımlar. Aynı hesaplama, makas testini ayarlamak için de kullanılabilir ve teste dahil edilir şu noktaları inceleyin:

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

Üzerinde Düşünme - Parça Gölgelendirici Türevleri

Uygulamanızda dFdx ve dFdy gibi türev hesaplamalar kullanılıyorsa döndürülen koordinatı hesaplamak için ek dönüştürme işlemleri gerekebilir tek bir sisteme entegre edildiğini unutmayın. Bunun için uygulama gerekir preTransform'un bir gösterimini parça gölgelendiriciye (örneğin, tam sayı) oturum açın ve bunu eşlemek için kullanın Türev hesaplamaları düzgün şekilde yapabilirsiniz:

  • 90 derece önceden döndürülmüş kare için
    • dFdx, dFdy ile eşlenmelidir
    • dFdy, -dFdx ile eşlenmelidir
  • 270 derece önceden döndürülmüş kare için
    • dFdx, -dFdy ile eşlenmelidir
    • dFdy, dFdx ile eşlenmelidir
  • 180 derece önceden döndürülmüş bir çerçeve için
    • dFdx, -dFdx ile eşlenmelidir
    • dFdy, -dFdy ile eşlenmelidir

Sonuç

Uygulamanızın Android'de Vulkan'dan en iyi şekilde yararlanması için kaçınılmazdır. Bundan çıkardığımız en önemli ders, şunlardır:

  • Takas zinciri oluşturma veya yeniden oluşturma sırasında önceden dönüşüm işaretinin tarafından döndürülen bayrakla eşleşecek şekilde ayarlanmış olduğundan emin olun. Böylece, birleşimcinin ek yükünü azaltır.
  • Değiştirme zinciri boyutunu, uygulama penceresinin kimlik çözünürlüğüne sabit tutun otomatik olarak ayarlamak üzere oluşturun.
  • Cihazın yönünü hesaba katmak için klip alanında MVP matrisini döndürün. çünkü değişim zinciri çözünürlüğü/uzunluğu artık yön ile güncellenmiyor. görebilirsiniz.
  • Uygulamanızın gerektirdiği şekilde görüntü alanını ve makas dikdörtgenlerini güncelleyin.

Örnek Uygulama: Minimum Android önceden rotasyon