In diesem Artikel wird beschrieben, wie die Gerätedrehung effizient gehandhabt wird. in Ihrer Vulkan-Anwendung durch Implementieren der Vor-Rotation.
Vulkan bietet Ihnen folgende Möglichkeiten: mehr Informationen zum Rendering-Status angeben als mit OpenGL. Bei Vulkan müssen Sie Dinge explizit implementieren, die der Treiber OpenGL, z. B. die Geräteausrichtung und ihre Beziehung zu Ausrichtung der Rendering-Oberfläche. Es gibt drei Möglichkeiten, wie Android den Abgleich der Renderoberfläche des Geräts mit der Geräteausrichtung:
- Das Android-Betriebssystem kann die Display Processing Unit (DPU) des Geräts verwenden, das die Oberflächenrotation der Hardware effizient bewältigen kann. Verfügbar auf unterstützten Geräten.
- Das Android-Betriebssystem kann die Oberflächenrotation übernehmen, indem eine zusammengesetzte Karte bzw. ein zusammengesetztes Ticket hinzugefügt wird. Dieses haben Leistungskosten, die davon abhängen, wie der Compositor damit umgehen muss Drehen des Ausgabebilds.
- Die Anwendung selbst kann die Oberflächenrotation verarbeiten, indem ein gedrehtes Bild auf eine Rendering-Fläche, die der aktuellen Ausrichtung des Display.
Welche dieser Methoden sollten Sie verwenden?
Derzeit kann für eine Anwendung nicht festgestellt werden, ob die Oberflächenrotation die außerhalb der Anwendung abgewickelt werden, kostenlos. Selbst wenn eine Datenschutzaufsichtsbehörde Wenn Sie dies tun, wird die Leistung Ihrer Anzeigen zu bezahlen. Wenn Ihre Anwendung CPU-gebunden ist, wird dies zu einem Energieproblem. die erhöhte GPU-Nutzung durch den Android Compositor, der in der Regel erhöhte Frequenz. Wenn Ihre Anwendung GPU-gebunden ist, kann auch die GPU-Arbeit Ihrer Anwendung vorzeitig beenden, was zu zusätzlicher Leistung führt. Verlust.
Beim Versand von Titeln auf Pixel 4 XL konnten wir feststellen, dass SurfaceFlinger (die übergeordnete Aufgabe, die das Android-Betriebssystem Compositor):
Beendet die Arbeit der Anwendung regelmäßig, was 1–3 ms verursacht Treffer bis Frametimes
Erhöht die Druckstärke auf die GPU Vertex-/Texturspeicher, da der Compositor den gesamten Framebuffer für die Komposition.
Die korrekte Verarbeitung der Ausrichtung unterbricht das vorzeitige Beenden der GPU durch SurfaceFlinger fast während die GPU-Frequenz um 40% sinkt, da die vom Android Compositor wird nicht mehr benötigt.
Um sicherzustellen, dass Oberflächendrehungen mit so wenig Aufwand wie möglich Wie im vorherigen Fall gezeigt, sollten Sie Methode 3 implementieren. Dies wird als Vorrotation bezeichnet. Dadurch wird dem Android-Betriebssystem mitgeteilt, dass Ihre App die Rotation der Oberfläche übernimmt. Dazu übergeben Sie die Flags zur Oberflächentransformation. die die Ausrichtung während der Swapchain-Erstellung angeben. Dadurch wird der Vorgang angehalten Android Compositor daran, die Rotation selbst auszuführen.
Zu wissen, wie das Flag zur Oberflächentransformation festgelegt wird, ist für jeden Vulkan wichtig . Apps unterstützen entweder mehrere Ausrichtungen oder eine einzelne Ausrichtung unterstützen, bei der sich die Renderingfläche in einer anderen Ausrichtung auf das, was das Gerät als Identitätsorientierung betrachtet. Beispiel: eine Anwendung im Querformat auf einem Smartphone im Hochformat oder ein Smartphone im Hochformat auf einem Tablet im Querformat.
AndroidManifest.xml modifizieren
Damit die Gerätedrehung in Ihrer App gehandhabt wird, ändern Sie zuerst
AndroidManifest.xml
, um Android mitzuteilen, dass deine App die Ausrichtung verarbeitet
und Bildschirmgrößen ändern. Dadurch wird verhindert, dass Android Daten löscht und neu erstellt
das Android-Activity
und das Aufrufen der
onDestroy()
-Funktion in der
vorhandene Fensteroberfläche, wenn sich die Ausrichtung ändert. Dies geschieht durch
Hinzufügen der Attribute orientation
(zur Unterstützung von API-Level <13) und screenSize
auf die
Abschnitt configChanges
:
<activity android:name="android.app.NativeActivity"
android:configChanges="orientation|screenSize">
Wenn die Bildschirmausrichtung in Ihrer App mithilfe der screenOrientation
korrigiert wird
müssen Sie dies nicht tun. Wenn Ihre Anwendung eine feste
Ausrichtung muss die Swapchain nur einmal eingerichtet werden.
Start/Fortsetzen von Anwendungen.
Auflösung des Identitätsbildschirms und Kameraparameter abrufen
Bildschirmauflösung des Geräts ermitteln
mit dem Wert VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
verknüpft ist. Dieses
hängt von der Identitätsausrichtung des Geräts ab
also diejenige, auf die
die Auslagerungschain immer eingestellt sein muss. Die meisten
können Sie dies zuverlässig erreichen, indem Sie
vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
beim Start der Anwendung und
den zurückgegebenen Ausmaß zu speichern. Vertauschen Sie Breite und Höhe basierend auf dem
currentTransform
zurückgegeben, um sicherzustellen, dass
Auflösung des Identitätsbildschirms:
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 ist eine VkExtent2D
-Struktur, die wir zum Speichern der genannten Identität verwenden.
Auflösung der Fensteroberfläche der App in der natürlichen Ausrichtung des Bildschirms.
Änderungen der Geräteausrichtung erkennen (Android 10 und höher)
Die zuverlässigste Methode zum Erkennen einer Ausrichtungsänderung in Ihrer Anwendung ist
um zu prüfen, ob die Funktion vkQueuePresentKHR()
VK_SUBOPTIMAL_KHR
. Beispiel:
auto res = vkQueuePresentKHR(queue_, &present_info);
if (res == VK_SUBOPTIMAL_KHR){
orientationChanged = true;
}
Hinweis:Diese Lösung funktioniert nur auf Geräten, auf denen
Android 10 oder höher. Diese Android-Versionen
VK_SUBOPTIMAL_KHR
von vkQueuePresentKHR()
. Wir speichern das Ergebnis dieser
orientationChanged
überprüfen, eine boolean
, die über die
Anwendungen eine Haupt-Rendering-Schleife.
Änderungen der Geräteausrichtung erkennen (vor Android 10)
Für Geräte mit Android 10 oder älter wird eine andere
Implementierung ist erforderlich, da VK_SUBOPTIMAL_KHR
nicht unterstützt wird.
Abfragen verwenden
Auf Geräten mit älteren Versionen als Android 10 können Sie die aktuelle Gerätetransformation alle abfragen.
pollingInterval
Frames, wobei pollingInterval
ein Detaillierungsgrad ist, der
durch den Programmierer. Rufen Sie hierzu
vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
und vergleicht dann die zurückgegebenen Werte
Feld currentTransform
mit dem Wert der aktuell gespeicherten Oberfläche
Transformation (in diesem in pretransformFlag
gespeicherten Codebeispiel).
currFrameCount++;
if (currFrameCount >= pollInterval){
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
if (pretransformFlag != capabilities.currentTransform) {
window_resized = true;
}
currFrameCount = 0;
}
Auf einem Pixel 4 mit Android 10 werden die
vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
dauerte zwischen 0,120 und 0,250 ms und auf einer
Bei einem Pixel 1 XL mit Android 8 dauerte die Abfrage 0,110 bis 0,350 ms.
Rückrufe verwenden
Eine zweite Möglichkeit für Geräte mit einer älteren Version unter Android 10 ist die Registrierung eines
onNativeWindowResized()
-Callback, um eine Funktion aufzurufen, die den Parameter
orientationChanged
-Flag, das der Anwendung eine Ausrichtungsänderung signalisiert
ist aufgetreten:
void android_main(struct android_app *app) {
...
app->activity->callbacks->onNativeWindowResized = ResizeCallback;
}
Dabei ist ResizeCallback so definiert:
void ResizeCallback(ANativeActivity *activity, ANativeWindow *window){
orientationChanged = true;
}
Das Problem bei dieser Lösung ist, dass onNativeWindowResized()
erfordert eine 90-Grad-Ausrichtungsänderung, zum Beispiel das Wechseln von Quer- ins Hochformat oder
und umgekehrt. Andere Ausrichtungsänderungen lösen die Neuerstellung der Auslagerungschain nicht aus.
Beim Wechsel vom Querformat zum umgekehrten Querformat
nicht auslösen, sodass der Android-Compositor das Umdrehen für Ihr
.
Umgang mit der Ausrichtungsänderung
Um die Änderung der Ausrichtung zu verarbeiten, rufen Sie die Routine zum Ändern der Ausrichtung am
oberhalb der Haupt-Renderingschleife, wenn orientationChanged
auf "true" gesetzt ist. Beispiel:
bool VulkanDrawFrame() {
if (orientationChanged) {
OnOrientationChange();
}
Sie erledigen alles, was nötig ist, um die Swapchain in
die Funktion OnOrientationChange()
. Das bedeutet Folgendes:
Löschen Sie alle vorhandenen Instanzen von
Framebuffer
undImageView
.Baue die Auslagerungskette neu auf der alten Swapchain (wird als Nächstes besprochen)
Erstelle die Framebuffers mit DisplayImages der neuen Swapchain neu. Hinweis: Bilder im Anhang (z. B. Tiefenbilder oder Schablonenbilder) haben in der Regel müssen neu erstellt werden, basieren auf der Identitätsauflösung der vorgedrehten Swapchain-Images.
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;
}
Am Ende der Funktion setzen Sie das Flag orientationChanged
auf „false“ zurück.
um zu zeigen, dass Sie die Ausrichtungsänderung vorgenommen haben.
Swapchain-Freizeit
Im vorherigen Abschnitt haben wir erwähnt, dass die Swapchain neu erstellt werden muss. Zunächst müssen die neuen Eigenschaften Rendering-Oberfläche:
void createSwapChain(VkSwapchainKHR oldSwapchain) {
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevice, surface, &capabilities);
pretransformFlag = capabilities.currentTransform;
Wenn die Struktur VkSurfaceCapabilities
mit den neuen Informationen gefüllt ist,
können Sie jetzt überprüfen, ob eine Änderung der Ausrichtung vorgenommen wurde.
currentTransform
. Du speicherst ihn für später im pretransformFlag
da Sie diese später benötigen, wenn Sie Anpassungen
MVP-Matrix.
Geben Sie dazu die folgenden Attribute an:
in der Struktur 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);
}
Das Feld imageExtent
wird mit dem displaySizeIdentity
-Wert gefüllt, der
die Sie beim Start der Anwendung gespeichert haben. Das Feld preTransform
wird automatisch
durch die Variable pretransformFlag
(die auf das Feld "currentTransform" festgelegt ist
des surfaceCapabilities
). Außerdem setzen Sie das Feld oldSwapchain
auf den
die gelöscht wird.
MVP-Matrixanpassung
Als Letztes müssen Sie die Transformation vor der Transformation indem Sie eine Rotationsmatrix auf Ihre MVP-Matrix anwenden. Im Wesentlichen bedeutet dies, die Drehung im Clipbereich anwenden, sodass das Bild gedreht wird mit der aktuellen Ausrichtung des Geräts. Sie können diese aktualisierte MVP-Matrix in Ihren Vertex-Shader einfügen und ihn wie gewohnt verwenden, ohne 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;
Kaufbereitschaft – Darstellungsbereich und Schere (nicht Vollbild)
Wenn Ihre Anwendung einen nicht bildschirmfüllenden Darstellungsbereich/Scherenbereich verwendet, werden muss entsprechend der Ausrichtung des Geräts aktualisiert werden. Dieses müssen Sie die Optionen "Dynamischer Darstellungsbereich" und "Schere" während der Pipelineerstellung:
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);
Die tatsächliche Berechnung des Darstellungsbereichs während der Aufnahme des Befehlspuffers sieht so aus:
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);
Die Variablen x
und y
definieren die Koordinaten der oberen linken Ecke des
Darstellungsbereich, während w
und h
die Breite bzw. Höhe des Darstellungsbereichs definieren.
Dieselbe Berechnung kann auch für den Scherentest verwendet werden.
hier der Vollständigkeit halber:
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);
Kaufbereitschaft – Ableitungen von Fragment-Shadern
Wenn Ihre Anwendung abgeleitete Berechnungen wie dFdx
und dFdy
verwendet,
Möglicherweise sind weitere Transformationen erforderlich, um die gedrehte Koordinate zu berücksichtigen.
da diese Berechnungen im Pixelbereich ausgeführt werden. Dazu ist die App
um eine Angabe der preTransform an den Fragment-Shader zu übergeben (z. B.
für die aktuelle Ausrichtung des Geräts. Verwenden Sie diese Ganzzahl, um die
Ableitungsberechnungen
korrekt ausführen:
- Für einen um 90 Grad gedrehten Rahmen
<ph type="x-smartling-placeholder">
- </ph>
- dFdx muss dFdy zugeordnet sein.
- dFdy muss -dFdx zugeordnet sein.
- Für einen um 270 Grad gedrehten Rahmen
<ph type="x-smartling-placeholder">
- </ph>
- dFdx muss -dFdy zugeordnet sein.
- dFdy muss dFdx zugeordnet sein.
- Für einen um 180 Grad gedrehten Rahmen
<ph type="x-smartling-placeholder">
- </ph>
- dFdx muss -dFdx zugeordnet sein.
- dFdy muss -dFdy zugeordnet sein.
Fazit
Damit Sie Vulkan auf Android optimal nutzen können, ist die Implementierung der Vor-Rotation ein Muss. Die wichtigsten Erkenntnisse aus dieser Artikel:
- Achten Sie darauf, dass beim Erstellen oder Wiederherstellen der Swapchain das Pretransform-Flag so eingestellt, dass sie mit dem Flag übereinstimmt, das vom Android-Betriebssystem zurückgegeben wird. Dadurch werden für den Compositor-Overhead.
- Die Größe der Swapchain muss an die Identitätsauflösung des App-Fensters angepasst bleiben in der natürlichen Ausrichtung des Bildschirms.
- Drehen Sie die MVP-Matrix im Clipbereich, um die Ausrichtung des Geräts zu berücksichtigen. da die Auflösung/Erweiterung der Swapchain nicht mehr mit der Ausrichtung aktualisiert wird. des Bildschirms.
- Aktualisieren Sie den Darstellungsbereich und die Scherenrechtecke nach Bedarf für Ihre Anwendung.
Beispiel-App:Minimale Android-Vorrotation