Vulkan: Apply the desired pretransform when drawing to the backbuffer.

This should save a lot of memory bandwidth on mobile devices that can't
rotate images natively in the display engine. Fixes #12099.
This commit is contained in:
Henrik Rydgård 2019-06-21 00:53:51 +02:00
parent 9def42d800
commit e943724905
7 changed files with 129 additions and 47 deletions

View File

@ -929,6 +929,7 @@ bool VulkanContext::InitSwapchain() {
if (physicalDeviceProperties_[physical_device_].properties.vendorID == VULKAN_VENDOR_IMGTEC) {
// Swap chain width hack to avoid issue #11743 (PowerVR driver bug).
// TODO: Check if still broken if pretransform is used
swapChainExtent_.width &= ~31;
}
@ -975,17 +976,46 @@ bool VulkanContext::InitSwapchain() {
// Application must settle for fewer images than desired:
desiredNumberOfSwapChainImages = surfCapabilities_.maxImageCount;
}
// We mostly follow the practices from
// https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/surface_rotation/surface_rotation_tutorial.html
//
VkSurfaceTransformFlagBitsKHR preTransform;
std::string supportedTransforms = surface_transforms_to_string(surfCapabilities_.supportedTransforms);
std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform);
ILOG("Supported transforms: %s", supportedTransforms.c_str());
ILOG("Current transform: %s", currentTransform.c_str());
if (surfCapabilities_.supportedTransforms & (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) {
g_display_rotation = DisplayRotation::ROTATE_0;
g_display_rot_matrix.setIdentity();
bool swapChainExtentSwap = false;
if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) {
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
} else if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)) {
// Normal, sensible rotations. Let's handle it.
preTransform = surfCapabilities_.currentTransform;
g_display_rot_matrix.setIdentity();
switch (surfCapabilities_.currentTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
g_display_rotation = DisplayRotation::ROTATE_90;
g_display_rot_matrix.setRotationZ90();
std::swap(swapChainExtent_.width, swapChainExtent_.height);
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
g_display_rotation = DisplayRotation::ROTATE_180;
g_display_rot_matrix.setRotationZ180();
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
g_display_rotation = DisplayRotation::ROTATE_270;
g_display_rot_matrix.setRotationZ270();
std::swap(swapChainExtent_.width, swapChainExtent_.height);
break;
}
} else {
// Let the OS rotate the image (potentially slow on many Android devices)
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
std::string preTransformStr = surface_transforms_to_string(preTransform);
ILOG("Chosen pretransform transform: %s", preTransformStr.c_str());
VkSwapchainCreateInfoKHR swap_chain_info{ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
swap_chain_info.surface = surface_;

View File

@ -20,6 +20,7 @@
#include "profiler/profiler.h"
#include "base/display.h"
#include "base/timeutil.h"
#include "math/lin/matrix4x4.h"
#include "math/dataconv.h"
@ -302,6 +303,16 @@ void FramebufferManagerVulkan::DrawActiveTexture(float x, float y, float w, floa
vtx[i].y = vtx[i].y * invDestH - 1.0f;
}
if (g_display_rotation != DisplayRotation::ROTATE_0) {
for (int i = 0; i < 4; i++) {
// backwards notation, should fix that...
Vec3 v(vtx[i].x, vtx[i].y, 0.0f);
v = v * g_display_rot_matrix;
vtx[i].x = v.x;
vtx[i].y = v.y;
}
}
draw_->FlushState();
// TODO: Should probably use draw_ directly and not go low level

View File

@ -111,6 +111,34 @@ public:
zz = 1.0f;
ww = 1.0f;
}
// Exact angles to avoid any artifacts.
void setRotationZ90() {
empty();
float c = 0.0f;
float s = 1.0f;
xx = c; xy = s;
yx = -s; yy = c;
zz = 1.0f;
ww = 1.0f;
}
void setRotationZ180() {
empty();
float c = -1.0f;
float s = 0.0f;
xx = c; xy = s;
yx = -s; yy = c;
zz = 1.0f;
ww = 1.0f;
}
void setRotationZ270() {
empty();
float c = 0.0f;
float s = -1.0f;
xx = c; xy = s;
yx = -s; yy = c;
zz = 1.0f;
ww = 1.0f;
}
void setRotationAxisAngle(const Vec3 &axis, float angle);

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include "base/logging.h"
#include "base/display.h"
#include "thin3d/thin3d.h"
#include "Common/Log.h"
#include "Common/ColorConv.h"
@ -352,6 +353,35 @@ DrawContext::~DrawContext() {
DestroyPresets();
}
void DrawContext::RotateRectToDisplay(FRect &rect, float curRTWidth, float curRTHeight) {
if (g_display_rotation == DisplayRotation::ROTATE_0)
return;
switch (g_display_rotation) {
case DisplayRotation::ROTATE_180:
rect.x = curRTWidth - rect.w - rect.x;
rect.y = curRTHeight - rect.h - rect.y;
break;
case DisplayRotation::ROTATE_90: {
// Note that curRTWidth_ and curRTHeight_ are "swapped"!
float origX = rect.x;
float origY = rect.y;
float rtw = curRTHeight;
float rth = curRTWidth;
rect.x = rth - rect.h - origY;
rect.y = origX;
std::swap(rect.w, rect.h);
break;
}
case DisplayRotation::ROTATE_270: {
float origX = rect.x;
float origY = rect.y;
// TODO
std::swap(rect.w, rect.h);
break;
}
}
}
// TODO: SSE/NEON
// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :)
void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) {

View File

@ -661,6 +661,11 @@ public:
virtual void FlushState() {}
protected:
struct FRect {
float x, y, w, h;
};
void RotateRectToDisplay(FRect &rect, float curRTWidth, float curRTHeight);
ShaderModule *vsPresets_[VS_MAX_PRESET];
ShaderModule *fsPresets_[FS_MAX_PRESET];

View File

@ -159,12 +159,7 @@ public:
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
private:
struct FRect {
float x, y, w, h;
};
void ApplyCurrentState();
void RotateRectToDisplay(FRect &rect);
HWND hWnd_;
ID3D11Device *device_;
@ -362,42 +357,12 @@ void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1
}
}
void D3D11DrawContext::RotateRectToDisplay(FRect &rect) {
if (g_display_rotation == DisplayRotation::ROTATE_0)
return;
if (curRenderTargetView_ != bbRenderTargetView_)
return; // Only the backbuffer is actually rotated wrong!
switch (g_display_rotation) {
case DisplayRotation::ROTATE_180:
rect.x = curRTWidth_ - rect.w - rect.x;
rect.y = curRTHeight_ - rect.h - rect.y;
break;
case DisplayRotation::ROTATE_90: {
// Note that curRTWidth_ and curRTHeight_ are "swapped"!
float origX = rect.x;
float origY = rect.y;
float rtw = curRTHeight_;
float rth = curRTWidth_;
rect.x = rth - rect.h - origY;
rect.y = origX;
std::swap(rect.w, rect.h);
break;
}
case DisplayRotation::ROTATE_270: {
float origX = rect.x;
float origY = rect.y;
// TODO
std::swap(rect.w, rect.h);
break;
}
}
}
void D3D11DrawContext::SetViewports(int count, Viewport *viewports) {
D3D11_VIEWPORT vp[4];
for (int i = 0; i < count; i++) {
FRect rc{ viewports[i].TopLeftX , viewports[i].TopLeftY, viewports[i].Width, viewports[i].Height };
RotateRectToDisplay(rc);
if (curRenderTargetView_ == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong!
RotateRectToDisplay(rc, curRTWidth_, curRTHeight_);
vp[i].TopLeftX = rc.x;
vp[i].TopLeftY = rc.y;
vp[i].Width = rc.w;
@ -410,8 +375,8 @@ void D3D11DrawContext::SetViewports(int count, Viewport *viewports) {
void D3D11DrawContext::SetScissorRect(int left, int top, int width, int height) {
FRect frc{ (float)left, (float)top, (float)width, (float)height };
RotateRectToDisplay(frc);
if (curRenderTargetView_ == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong!
RotateRectToDisplay(frc, curRTWidth_, curRTHeight_);
D3D11_RECT rc{};
rc.left = (INT)frc.x;
rc.top = (INT)frc.y;

View File

@ -1059,17 +1059,30 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) {
}
void VKContext::SetScissorRect(int left, int top, int width, int height) {
VkRect2D scissor{ {left, top}, {(uint32_t)width, (uint32_t)height} };
FRect rc{ (float)left, (float)top, (float)width, (float)height };
if (curFramebuffer_ == nullptr) { // Only the backbuffer is actually rotated wrong!
int curRTWidth, curRTHeight;
GetFramebufferDimensions((Framebuffer *)curFramebuffer_, &curRTWidth, &curRTHeight);
RotateRectToDisplay(rc, (float)curRTWidth, (float)curRTHeight);
}
VkRect2D scissor{ {(int32_t)rc.x, (int32_t)rc.y}, {(uint32_t)rc.w, (uint32_t)rc.h} };
renderManager_.SetScissor(scissor);
}
void VKContext::SetViewports(int count, Viewport *viewports) {
if (count > 0) {
// Ignore viewports more than the first.
VkViewport viewport;
viewport.x = viewports[0].TopLeftX;
viewport.y = viewports[0].TopLeftY;
viewport.width = viewports[0].Width;
viewport.height = viewports[0].Height;
FRect rc{ viewports[0].TopLeftX , viewports[0].TopLeftY, viewports[0].Width, viewports[0].Height };
if (curFramebuffer_ == nullptr) { // Only the backbuffer is actually rotated wrong!
int curRTWidth, curRTHeight;
GetFramebufferDimensions((Framebuffer *)curFramebuffer_, &curRTWidth, &curRTHeight);
RotateRectToDisplay(rc, (float)curRTWidth, (float)curRTHeight);
}
viewport.x = rc.x;
viewport.y = rc.y;
viewport.width = rc.w;
viewport.height = rc.h;
viewport.minDepth = viewports[0].MinDepth;
viewport.maxDepth = viewports[0].MaxDepth;
renderManager_.SetViewport(viewport);