martes , diciembre 1 2020
Home / Desarrollo Android / La rotación previa maneja efectivamente la orientación del dispositivo en Vulkan

La rotación previa maneja efectivamente la orientación del dispositivo en Vulkan

 ilustraciones pre-rotadas

por Omar El Sheikh, ingeniero de Android

Francesco Carucci Promotor del desarrollador

Vulkan ofrece a los desarrolladores la capacidad de especificar más información sobre el estado de representación del dispositivo, En comparación con OpenGL. Este poder trae algunas nuevas responsabilidades. Los desarrolladores deben implementar explícitamente las operaciones manejadas por el controlador en OpenGL. Uno de ellos es la orientación del dispositivo y su relación con la orientación de la superficie de representación. Actualmente, Android puede coordinar la superficie de representación del dispositivo y la orientación del dispositivo de tres maneras:

  1. El dispositivo tiene una unidad de procesamiento de pantalla (DPU) que puede manejar eficientemente la rotación de la superficie en el hardware para que coincida con la orientación del dispositivo (requiere un dispositivo que admita esta función)
  2. El sistema operativo Android puede manejar la rotación de la superficie agregando un canal de sintetizador, cuyo costo de ejecución depende de cómo el sintetizador debe manejar la imagen de salida girada
  3. La ​​aplicación en sí puede manejar la rotación de la superficie al representar la imagen girada en una superficie de representación que coincida con la orientación actual de la pantalla

¿Qué significa esto para su aplicación?

Actualmente se desconoce en la aplicación si las rotaciones de superficie procesadas fuera de la aplicación son gratuitas. Incluso si hay una DPU para resolver este problema para nosotros, aún es posible pagar una pérdida de rendimiento medible. Si su aplicación está vinculada a la CPU, esto se convertirá en un gran problema de energía debido al mayor uso de GPU del sintetizador de Android (que generalmente también se ejecuta a una mayor frecuencia). Y si su aplicación está limitada por la GPU, esto se convertirá en un problema de rendimiento potencialmente grande porque el Compositor de Android se adelantará al trabajo de la GPU de su aplicación, haciendo que la aplicación reduzca su velocidad de fotogramas.

En el Pixel 4XL, ya hemos visto que SurfaceFlinger (la tarea de mayor prioridad que maneja el Compositor de Android) evitará el trabajo de la aplicación de forma regular en el momento del envío, lo que resulta en 1-3ms de tiempo de fotogramas y presión de GPU La memoria de vértices / texturas también se está haciendo más grande porque el compositor tiene que leer el marco para completar su trabajo de composición.

La dirección de procesamiento detuvo casi por completo la preferencia de la GPU de SurfaceFlinger, y vio que la frecuencia de la GPU se redujo en un 40% porque ya no se necesita la frecuencia de refuerzo utilizada por Android Compositor.

Para garantizar que las rotaciones de la superficie se manejen correctamente con la menor sobrecarga posible (como se muestra arriba), recomendamos implementar el Método 3: esto se llama prerotación . El mecanismo principal de este trabajo es informar al sistema operativo Android que manejamos la rotación de la superficie especificando la dirección durante la creación de la cadena de intercambio pasando la bandera de transformación de superficie, lo que evitará que el propio Compositor de Android gire.

Para cada aplicación Vulkan, es importante saber cómo configurar el indicador de transformación de superficie, porque la aplicación tiende a admitir múltiples direcciones, o una dirección, es decir, su superficie de representación y el dispositivo se consideran como su identificación Las instrucciones son diferentes; por ejemplo, una aplicación solo horizontal en un teléfono vertical o una aplicación solo vertical en una tableta vertical.

En este artículo, describiremos en detalle cómo implementar la rotación de dispositivos de prerotación y procesamiento en aplicaciones Vulkan.

Modificar AndroidManifest.xml

Para manejar la rotación del dispositivo en la aplicación, cambie AndroidManifest.xml de la aplicación para indicarle a Android que su aplicación cambiará la orientación y el tamaño de la pantalla. Esto evita que Android rompa y vuelva a crear la actividad de Android y llame a la función onDestroy () en la superficie de una ventana existente cuando se produce un cambio de dirección. Esto se hace agregando la orientación (para admitir <13 API) y los atributos screenSize a la sección configChanges de la actividad:

Si su aplicación utiliza la propiedad screenOrientation para corregir la orientación de la pantalla, no necesita hacer esto. Del mismo modo, si su aplicación utiliza una orientación fija, solo necesita configurar la cadena de intercambio una vez cuando la aplicación se inicia / reanuda.

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

El siguiente paso es detectar la resolución de pantalla del dispositivo asociado con VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR porque esta resolución es la resolución en la cadena de intercambio. Siempre es necesario configurarlo. La forma más confiable de lograr esto es llamar a vkGetPhysicalDeviceSurfaceCapabilitiesKHR () cuando se inicia la aplicación, y almacenar el rango devuelto, cambiar el ancho y la altura según el currentTransform devuelto para garantizar que almacenamos la resolución de la pantalla de identidad:

      Función VkSurfaceCapabilitiesKHR;
VkGetPhysicalDeviceSurfaceCapabilitiesKHR (physDevice, surface, & Capacidades);

Uint32_t width = functions.currentExtent.width;
Uint32_t height = skills.currentExtent.height;
If (Capacidades.Transformación actual y VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
Abilities.currentTransform y VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
/// intercambio para obtener el ancho y alto de la identidad
Abilities.currentExtent.height = ancho;
Abilities.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 dirección natural de la pantalla.

Detecta el cambio de orientación del dispositivo (Android 10+)

Para saber cuándo una aplicación encuentra un cambio de dirección, la forma más confiable de rastrear este cambio es verificar el valor de retorno de vkQueuePresentKHR () y ver Ya sea para regresar VK_SUBOPTIMAL_KHR

    auto res = vkQueuePresentKHR (queue_, & present_info);
If (res == VK_SUBOPTIMAL_KHR) {
OrientationChanged = true;

Una cosa a tener en cuenta sobre esta solución es que solo funciona en dispositivos con Android Q y superior, porque cuando Android se inicia desde vkQueuePresentKHR () devuelve VK_SUBOPTIMAL_KHR

orientaciónCambiado es un valor booleano almacenado en una ubicación a la que se puede acceder de forma iterativa desde la representación principal de la aplicación

Detectar cambio de orientación del dispositivo (anterior a Android 10)

Para dispositivos que ejecutan versiones anteriores de Android inferiores a 10, ya que no podemos acceder a VK_SUBOPTIMAL_KHR, se necesitan diferentes implementaciones.

Uso de sondeo

En los dispositivos anteriores a 10, podemos sondear la conversión del dispositivo actual cada pollingInterval cuadros, donde pollingInterval es la granularidad determinada por el programador. La forma en que hacemos esto es llamar a vkGetPhysicalDeviceSurfaceCapabilitiesKHR () y luego comparar el campo devuelto currentTransform con el campo de transformación de superficie actualmente almacenado (almacenado en en este ejemplo de código pretransformFlag [19659032])

    currFrameCount ++;
If (currFrameCount> = pollInterval) {
Función VkSurfaceCapabilitiesKHR;
VkGetPhysicalDeviceSurfaceCapabilitiesKHR (physDevice, surface, & Capacidades);

If (pretransformFlag! = Capability.currentTransform) {
Window_resize = true;
}
CurrFrameCount = 0;

En Pixel 4 con Android Q, sondeo vkGetPhysicalDeviceSurfaceCapabilitiesKHR () tomó 0.120-.250ms, y tomó 0.110-.350ms en Pixel 1XL con Android O

Usar devolución de llamada

Para dispositivos que se ejecutan por debajo de Android 10, la segunda opción es registrar enNativeWindowResized () devolución de llamada para llamar a una función que establece la bandera orientaciónCambiada como señal La aplicación ha cambiado de dirección:

  void android_main (estructura android_app * aplicación) {
...
Aplicación-> actividad-> devoluciones de llamada-> onNativeWindowResized = ResizeCallback;
} 

donde ResizeCallback se define como:

  void ResizeCallback (actividad ANativeActivity *, ventana ANativeWindow *) {
OrientationChanged = true;
} 

La desventaja de esta solución es que onNativeWindowResized () solo puede llamar a un cambio de dirección de 90 grados (de paisaje a retrato, y viceversa), por ejemplo, cambiar el paisaje de paisaje a reverso no cambia Activar una recreación de la cadena de intercambio requiere un sintetizador de Android para voltear su aplicación.

Manejo de cambios de orientación

Para manejar realmente los cambios de orientación, primero verificamos en la parte superior del bucle de representación principal si la variable orienteChanged está establecida en verdadero, y si es así, ingresaremos a la rutina de cambio de orientación:

  bool VulkanDrawFrame () {
Si (orientación cambiada) {
OnOrientationChange ();

En la función OnOrientationChange () haremos todo el trabajo necesario para recrear la cadena de intercambio. Esto implica destruir cualquier Framebuffers e ImageViews existentes. Recree la cadena de intercambio mientras destruye la antigua cadena de intercambio (que se discute a continuación); luego use las nuevas DisplayImages de intercambio para recrear el búfer de cuadros. Tenga en cuenta que, por lo general, no es necesario volver a crear imágenes de archivos adjuntos (por ejemplo, imágenes de profundidad / plantilla) porque se basan en la resolución de identidad de las imágenes de cadena de intercambio rotadas previamente.

  void OnOrientationChange () {
VkDeviceWaitIdle (getDevice ());

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

y luego restablece el indicador orienteChanged a falso al final de la función para indicar que hemos procesado el cambio de orientación.

Ocio de la cadena de intercambio

En la sección anterior, mencionamos que debemos recrear la cadena de intercambio. El primer paso para hacer esto implica obtener nuevas características para la superficie de renderizado:

  void createSwapChain (VkSwapchainKHR oldSwapchain) {
Función VkSurfaceCapabilitiesKHR;
VkGetPhysicalDeviceSurfaceCapabilitiesKHR (physDevice, surface, & Capacidades);
pretransformFlag = Capacitys.currentTransform; 

VkSurfaceCapabilities estructura llena de información nueva, ahora podemos verificar si el cambio de dirección se ha producido comprobando el campo currentTransform y el almacenamiento posterior en [19659017] pretransformFlag ya que lo necesitaremos más adelante cuando realicemos ajustes en la matriz MVP.

Para este fin, debemos asegurarnos de que algunas propiedades se especifiquen correctamente en la estructura VkSwapchainCreateInfo :

  VkSwapchainCreateInfoKHR swapchainCreateInfo {
...
.SType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
ImageExtent = displaySizeIdentity,
Prepreform = pretransformFlag,
Old.oldSwapchain = oldSwapchain,
};

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

If (oldSwapchain! = VK_NULL_HANDLE) {
VkDestroySwapchainKHR (device_, oldSwapchain, nullptr);
} 

El campo imageExtent llenará el rango displaySizeIdentity que almacenamos cuando se lanzó la aplicación. El campo preTransform rellenará nuestra variable pretransformFlag (esta variable se establece en el campo currentTransform de surfaceCapabilities ). También establecemos el campo oldSwapchain en la cadena de intercambio que se destruirá.

Es importante que el campo surfaceCapabilities.currentTransform y el campo swapchainCreateInfo.preTransform deben coincidir, ya que esto hace que el sistema operativo Android sea consciente de que estamos lidiando con cambios de orientación por nuestra cuenta, así que evite usar Android Compositor

Ajuste de matriz MVP

Lo último que se debe hacer es aplicar una pretransformación. Esto se realiza aplicando una matriz de rotación a la matriz MVP. Esto realmente está aplicando rotación en el espacio del clip para rotar la imagen resultante a la orientación actual del dispositivo. Luego puede simplemente pasar esta matriz MVP actualizada al sombreador de vértices y usarla como de costumbre sin modificar el sombreador.

  glm :: mat4 pre_rotate_mat = glm :: mat4 (1.0f);
glm :: vec3 rotacion_axis = glm :: vec3 (0.0f, 0.0f, 1.0f);

If (pretransformFlag y VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) {
Pre_rotate_mat = glm :: rotate (pre_rotate_mat, glm :: radianes (90.0f), rotacion_axis);
}

De lo contrario, if (pretransformFlag y VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
Pre_rotate_mat = glm :: rotate (pre_rotate_mat, glm :: radianes (270.0f), rotacion_eje);
}

De lo contrario, si (pretransformFlag y VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) {
Pre_rotate_mat = glm :: rotate (pre_rotate_mat, glm :: radianes (180.0f), rotacion_axis);
}

MVP = pre_rotate_mat * MVP; 

Considere las ventanas gráficas y las tijeras sin pantalla completa

Si su aplicación usa ventanas gráficas / áreas con tijeras sin pantalla completa, debe actualizar de acuerdo con la orientación del dispositivo. Esto requiere que habilitemos la ventana dinámica y las opciones de tijeras durante el proceso de creación de la tubería de 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,
Banderas = 0,
DynamicStateCount = 2
P.DynamicStates = estados dinámicos,
};

VkGraphicsPipelineCreateInfo pipeCreateInfo = {
.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 del rango de la ventana gráfica durante la grabación del búfer de comandos es el 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};
Descanso
Caso VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
ViewportData = {bufferWidth-w-x, bufferHeight-h-y, w, h};
Descanso
Caso VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
ViewportData = {y, bufferHeight-w-x, h, w};
Descanso
El valor por defecto:
ViewportData = {x, y, w, h};
Descanso
}

const VkViewport viewport = {
X. ViewportData.x,
Y. ViewportData.y,
Ancho. ViewportData.z,
H.height = viewportData.w,
Profundidad mínima = 0.0F,
Profundidad máxima = 1.0F,
};

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

donde x y y y definen las coordenadas de la esquina superior izquierda de la vista, y ] w y h definen el ancho y la altura de la ventana gráfica, respectivamente.

Los mismos cálculos también se pueden utilizar para configurar una prueba de tijera, que se incluye a continuación para completar:

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

Switch (dispositivo-> GetPretransformFlag ()) {
Caso VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
ScissorData = {bufferWidth-h-y, x, h, w};
Descanso
Caso VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
ScissorData = {bufferWidth-w-x, bufferHeight-h-y, w, h};
Descanso
Caso VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
ScissorData = {y, bufferHeight-w-x, h, w};
Descanso
El valor por defecto:
ScissorData = {x, y, w, h};
Descanso
}

const VkRect2D tijeras = {
Offset.offset =
{{{
X. (Int32_t) viewportData.x,
Y = (int32_t) viewportData.y,
}
Extensión =
{{{
Ancho. (Uint32_t) viewportData.z,
.Height = (uint32_t) viewportData.w,
}
};

vkCmdSetScissor (renderizador-> GetCurrentCommandBuffer (), 0, 1, y tijera); 

Derivados de sombreador de fragmentos de nota

Si su aplicación utiliza cálculos derivados como dFdx y dFdy, pueden ser necesarias otras conversiones Para resolver el problema del sistema de coordenadas de rotación, porque estos cálculos se realizan en espacio de píxeles. Esto requiere que la aplicación pase alguna indicación de la preTransformación al sombreador de fragmentos (como un número entero que representa la orientación actual del dispositivo) y use esa indicación para mapear correctamente el cálculo derivado:

  • Para marcos prerrotados de 90 grados
    • dFdx necesita ser mapeado a dFdy
      Zh
    • dFdy debe asignarse a dFdx
  • Marco prerrotatorio para 270 grados
    • dFdx debe asignarse a -dFdy
      Zh
    • dFdy debe asignarse a -dFdx
  • Para marcos prerrotados de 180 grados,
    • dFdx debe asignarse a -dFdx
      Zh
    • dFdy debe asignarse a -dFdy

Conclusión

Para que su aplicación aproveche al máximo Vulkan en Android, debe realizar una rotación previa. Las ganancias más importantes de esta publicación de blog son:

  1. Asegúrese de que durante la creación o recreación de la cadena de intercambio, el indicador de pretransformación esté configurado para coincidir con el indicador devuelto por el sistema operativo Android. Esto evitará la sobrecarga del sintetizador
  2. Manteniendo el tamaño de la cadena de intercambio fijo a la resolución de identificación de la superficie de la ventana de la aplicación en la dirección natural
  3. Gire la matriz MVP en el espacio de intercambio para resolver problemas de orientación del dispositivo, ya que la resolución / rango de la cadena de intercambio ya no se actualiza con la orientación de la pantalla
  4. Actualice la ventana gráfica y el rectángulo de tijera según lo necesite la aplicación

Aquí hay un enlace al ejemplo mínimo de rotación previa implementado para una aplicación de Android:

https://github.com/google/vulkan-pre-rotation-demo



Deja un comentario

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