Home / Desarrollo Android / Gestión eficiente de la orientación del dispositivo en Vulkan con prerrotación

Gestión eficiente de la orientación del dispositivo en Vulkan con prerrotación

  Ilustración de la reserva

Por Omar El Sheikh, ingeniero de Android

Francesco Carucci Advocate Developer

Vulkan proporciona a los desarrolladores el poder de especificar mucha más información sobre dispositivos en renderizado de estado en comparación con OpenGL. Con ese poder, sin embargo, vienen algunas nuevas responsabilidades; los desarrolladores deben implementar explícitamente cosas que han sido manejadas por el controlador en OpenGL. Una de estas cosas es la orientación del dispositivo y su relación para hacer la orientación de la superficie. Actualmente, hay 3 formas en que Android puede gestionar la conciliación de la superficie de representación del dispositivo con la orientación del dispositivo:

  1. El dispositivo tiene una unidad de procesamiento de pantalla (DPU) capaz de administrar eficientemente la rotación de la superficie en el hardware para adaptarse a la orientación del dispositivo (requiere un dispositivo que lo admita)
  2. El sistema operativo Android puede administrar la rotación de la superficie agregando un pasaje del compositor que tendrá un costo en términos de rendimiento dependiendo de cómo el compositor debe administrar la rotación de la imagen de salida
  3. La aplicación en sí misma puede gestionar la rotación de la superficie renderizando una imagen girada en una superficie de representación que corresponde a la orientación actual de la pantalla

¿Qué significa esto para sus aplicaciones?

Por el momento no hay forma de que una aplicación sepa si la rotación de la superficie administrada fuera de la aplicación será libre. Aunque habrá una DPU que se encargará de esto por nosotros, es probable que haya una multa medible para los beneficios que se pagarán. Si la aplicación está conectada a la CPU, esto se convierte en un gran problema de energía debido al mayor uso de la GPU por parte de Android Compositor, que generalmente funciona incluso con una frecuencia mejorada; y si su aplicación está relacionada con la GPU, también se convierte en un problema de rendimiento potencialmente grande, ya que Android Compositor evitará que la GPU de su aplicación funcione, lo que provocará una caída de la velocidad de fotogramas.

En Pixel 4XL, hemos visto en los títulos de envío que SurfaceFlinger (la actividad de mayor prioridad que impulsa el Compositor de Android) impide regularmente que la aplicación funcione causando impactos de 1 a 3 ms al tiempo de fotogramas, además de ejerce más presión sobre las GPU de memoria de vértices / cuadros, ya que el Compositor debe leer el cuadro para hacer su trabajo de composición.

La orientación de gestión interrumpe correctamente la preferencia de la GPU por SurfaceFlinger casi por completo, y ve que la frecuencia de la GPU disminuye en un 40% ya que la frecuencia mejorada utilizada por Android Compositor ya no es necesaria.

Para garantizar que las rotaciones de la superficie se manejen correctamente con la menor sobrecarga posible (como se ve en el caso anterior), recomendamos implementar el método 3, esto se conoce como rotación previa . El mecanismo principal con el que funciona es decirle al sistema operativo Android que estamos administrando la rotación de la superficie al especificar la orientación durante la creación de swapchains a través de las banderas de transformación de superficie que pasaron, lo que impide que Android Compositor se ejecute La rotación misma.

Saber cómo configurar el indicador de transformación de superficie es importante para cada aplicación Vulkan, ya que las aplicaciones tienden a admitir múltiples orientaciones o una única orientación donde su superficie de representación está en una orientación diferente que el dispositivo considera su orientación de identidad; Por ejemplo, una aplicación solo horizontal en un teléfono con identidad vertical o una aplicación solo vertical en una tableta con identidad horizontal.

En esta publicación describiremos en detalle cómo implementar la rotación previa y administrar la rotación del dispositivo en su aplicación Vulkan.

Modificar AndroidManifest.xml

Para administrar la rotación del dispositivo en su aplicación, modifique AndroidManifest.xml de la aplicación para comunicar a Android que la aplicación administrará la orientación y los cambios en las dimensiones la pantalla Esto evita que Android destruya y vuelva a crear la actividad de Android y llame a la función onDestroy () en la superficie de la ventana existente cuando se produce un cambio de orientación. Esto se hace agregando los atributos de orientación (para admitir el nivel API <13) y screenSize a la sección configChanges de la actividad:

Si la aplicación corrige la orientación de la pantalla utilizando el atributo screenOrientation, no es necesario hacerlo. Además, si la aplicación utiliza una orientación fija, será necesario configurar la cadena de intercambio solo una vez al inicio / reinicio de la aplicación.

Obtenga la resolución de pantalla de identidad y los parámetros de la cámara

Lo siguiente que debe hacerse es detectar la resolución de pantalla del dispositivo asociada con VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR ya que esta resolución es la de Swapchain tendrá que siempre se establece en. La forma más confiable de obtenerlo es hacer una llamada a vkGetPhysicalDeviceSurfaceCapabilitiesKHR () al iniciar la aplicación y almacenar la extensión devuelta, intercambiando el ancho y la altura según a la Transformación actual que también se devuelve para asegurarse de que estamos almacenando la resolución de la pantalla de identidad:











      VkSurfaceCapabilitiesKHR funcionalidad;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR (dispositivo físico, superficie y funcionalidad);

uint32_t ancho = capacity.currentExtent.width;
uint32_t height = skills.currentExtent.height;
if (skill.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
skills.currentTransform & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
// Cambia para obtener el ancho y el alto de la identidad
skills.currentExtent.height = ancho;
skills.currentExtent.width = height;
}

displaySizeIdentity = skills.currentExtent; 

displaySizeIdentity es un VkExtent2D que utilizamos para almacenar la resolución de identidad de la superficie de la ventana de la aplicación en la orientación natural de la pantalla.

Detectar cambios en la orientación del dispositivo (Android 10+)

Para saber cuándo la aplicación encontró un cambio en la orientación, la forma más confiable de rastrear este cambio es verifique el valor de retorno de vkQueuePresentKHR () y vea si devuelve VK_SUBOPTIMAL_KHR


    auto res = vkQueuePresentKHR (queue_, & present_info);
if (res == VK_SUBOPTIMAL_KHR) {
orienteadoCambiado = verdadero;
} 

Una cosa a tener en cuenta sobre esta solución es que solo funciona en dispositivos con Android Q y versiones posteriores, ya que es cuando Android comenzó a regresar VK_SUBOPTIMAL_KHR desde vkQueuePresentKHR ()

orienteadoChanged es un valor booleano almacenado en una ubicación accesible por el bucle de representación de la aplicación principal

Seguimiento de cambios de orientación del dispositivo (anterior a Android 10)

Para los dispositivos que ejecutan versiones anteriores de Android que la 10, se necesita una implementación diferente ya que no tenemos acceso a [19659018] VK_SUBOPTIMAL_KHR.

Uso de sondeo

En dispositivos anteriores a 10 podemos sondear la transformación del dispositivo actual en cada marco pollingInterval donde pollingInterval es una granularidad decidida por el programador. La forma en que hacemos esto es llamando vkGetPhysicalDeviceSurfaceCapabilitiesKHR () y luego comparando el campo devuelto currentTransform con el de la transformación de la superficie actualmente almacenada (en este ejemplo de código almacenado en pretransF ) 19659032])








    currFrameCount ++;
if (currFrameCount> = pollInterval) {
VkSurfaceCapabilitiesKHR funcionalidad;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR (dispositivo físico, superficie y funcionalidad);

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

En un Pixel 4 con Android Q, el sondeo vkGetPhysicalDeviceSurfaceCapabilitiesKHR () tomó entre .120-.250ms y en un Pixel 1XL con Android O, el sondeo requirió .110-.350ms.

Uso de devoluciones de llamada

Una segunda opción para dispositivos con Android 10 es registrar una devolución de llamada enNativeWindowResized () para llamar a una función que establece el indicador orientaciónCambiado para informar al aplicación se produjo un cambio de orientación:


  void android_main (estructura android_app * aplicación) {
...
app-> activity-> callbacks-> onNativeWindowResized = ResizeCallback;
} 

Donde ResizeCallback se define como:

  void ResizeCallback (actividad ANativeActivity *, ventana ANativeWindow *) {
orienteadoCambiado = verdadero;
} 

La desventaja de esta solución es que onNativeWindowResized () siempre se llama solo en cambios de orientación de 90 grados (yendo de horizontal a vertical o viceversa), por lo tanto, por ejemplo, un cambio de orientación de horizontal un paisaje inverso no activará la recreación de la cadena de intercambio, lo que requerirá que el compositor de Android voltee la aplicación.

Gestión del cambio de orientación

Para gestionar eficazmente el cambio de orientación, primero debemos comprobar en la parte superior del ciclo de representación principal si la variable orientaciónCambiada se ha establecido en cierto, y en este caso entraremos en la rutina de cambio de orientación:


  bool VulkanDrawFrame () {
if (orientaciónCambiado) {
OnOrientationChange ();
} 

Y dentro de la función OnOrientationChange () haremos todo el trabajo necesario para recrear la cadena de intercambio. Esto da como resultado la destrucción de cualquier Framebuffer e ImageView existente; recrear la cadena de intercambio mientras destruye la antigua cadena de intercambio (que se discutirá más adelante); y luego recrear los framebuffers con las nuevas DisplayImages de la cadena de intercambio. Tenga en cuenta que las imágenes de archivos adjuntos (por ejemplo, imágenes de profundidad / plantilla) generalmente no necesitan ser recreadas, ya que dependen de la resolución de identidad de las imágenes de cadena de intercambio previamente rotadas.














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);
orienteadoChanged = falso;
}

Y al final de la función restablecemos el indicador de orientación a falso para mostrar que logramos el cambio de orientación.

Recreación de la cadena de intercambio

En la sección anterior mencionamos la necesidad de recrear la cadena de intercambio. Los primeros pasos para hacer esto se refieren a obtener las nuevas características de la superficie de renderizado:


  void createSwapChain (VkSwapchainKHR oldSwapchain) {
VkSurfaceCapabilitiesKHR funcionalidad;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR (dispositivo físico, superficie y funcionalidad);
pretransformFlag = skills.currentTransform; 

Con la estructura VkSurfaceCapabilities poblada con la nueva información, ahora podemos verificar si se ha producido un cambio de orientación al verificar el campo currentTransform y almacenarlo para más adelante en el campo pretransformFlag ya que lo necesitaremos para más adelante cuando hagamos ajustes en la matriz MVP.

Para hacer esto, debemos asegurarnos de especificar correctamente algunos atributos dentro de la estructura VkSwapchainCreateInfo :











  VkSwapchainCreateInfoKHR swapchainCreateInfo {
...
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.imageExtent = displaySizeIdentity,
.preTransform = pretransformFlag,
.oldSwapchain = oldSwapchain,
};

vkCreateSwapchainKHR (dispositivo_, & swapchainCreateInfo, nullptr, & swapchain_));

if (oldSwapchain! = VK_NULL_HANDLE) {
vkDestroySwapchainKHR (device_, oldSwapchain, nullptr);
} 

El campo imageExtent se completará con la extensión displaySizeIdentity que presentamos al inicio de la aplicación. El campo preTransform se completará con nuestra variable pretransformFlag (que se establece en el campo currentTransform de SurfaceCapabilities ). También configuramos el campo oldSwapchain en la cadena de intercambio que estamos a punto de destruir.

Es importante que el campo surfaceCapabilities.currentTransform y el campo swapchainCreateInfo.preTransform coincidan porque esto permite que el sistema operativo Android sepa que nos estamos administrando a nosotros mismos orientación, evitando así el Compositor de Android.

Ajuste de matriz MVP

Lo último que hay que hacer es aplicar la pre-transformación. Esto se realiza aplicando una matriz de rotación a la matriz MVP. Lo que esencialmente hace es aplicar rotación en el espacio del clip para que la imagen resultante se rote según la orientación actual del dispositivo. Luego puede simplemente pasar esta matriz MVP actualizada a su sombreador de vértices y usarla normalmente sin la necesidad de editar sus sombreadores.














  glm :: mat4 pre_rotate_mat = glm :: mat4 (1.0f);
glm :: vec3 rotacion_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 :: radianes (90.0f), rotación_axis);
}

más si (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
pre_rotate_mat = glm :: rotate (pre_rotate_mat, glm :: radianes (270.0f), rotación_axis);
}

más si (pretransformFlag & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
pre_rotate_mat = glm :: rotate (pre_rotate_mat, glm :: radianes (180.0f), rotación_axis);
}

MVP = pre_rotate_mat * MVP; 

Consideración: vista de ventana y tijera sin pantalla completa

Si la aplicación usa una ventana / región de tijera sin pantalla completa, deberán actualizarse según la orientación del dispositivo. Esto requiere que habilitemos las opciones dinámicas de Viewport y Scissor al crear la tubería 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 (dispositivo, VK_NULL_HANDLE, 1 y pipelineCreateInfo, nullptr y mPipeline);

El cálculo real de la extensión de la ventana gráfica al registrar el búfer de comandos es similar al siguiente:


























  int x = 0, y = 0, w = 500, h = 400;
glm :: vec4 viewportData;

switch (dispositivo-> GetPretransformFlag ()) {
caso VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
viewportData = {bufferWidth - h - y, x, h, w};
romper;
caso VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
viewportData = {bufferWidth - w - x, bufferHeight - h - y, w, h};
romper;
caso VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
viewportData = {y, bufferHeight - w - x, h, w};
romper;
por defecto:
viewportData = {x, y, w, h};
romper;
}

const VkViewport viewport = {
.x = viewportData.x,
.y = viewportData.y,
.width = viewportData.z,
.height = viewportData.w,
.minDepth = 0.0F,
.maxDepth = 1.0F,
};

vkCmdSetViewport (renderizador-> GetCurrentCommandBuffer (), 0, 1 y viewport);

About AndroidEditor

Check Also

El nuevo Unity 2019.3 ofrece características nuevas y potentes para desarrolladores de juegos

Unity 2019.3, la última versión del famoso motor de juegos multiplataforma, ya está disponible para …

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *