Commit e488d8b8 by Ian Elliott Committed by Commit Bot

Vulkan: Implement Android pre-rotation

As an Android GLES driver on top of Vulkan, ANGLE must pre-rotate rendering on behalf of the application. This involves modifying the vertex shader to multiply gl_Position with a mat2 "rotation matrix". Not doing so means that SurfaceFlinger (SF) will perform a costly rotation blit before presenting every image. Setting WindowSurfaceVk::mPreTransform to mCurrentTransform tells SF to not do the blit. When the surface is rotated 90 or 270 degrees, the width and height must be swapped for: - The swapchain images, and for any depth, stencil, and/or multisample attachments used with the swapchain image. - The viewport, scissor, and render area. Because the Metal back-end shares the TranslatorVulkan, it will define the same preRotation (mat2) DriverUniform that is used for Vulkan. Bug: angleproject:3502 Change-Id: I968dbe8869ba0f50de18dd41f1195e847c06b545 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2038272 Commit-Queue: Ian Elliott <ianelliott@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent ba8ef68c
......@@ -244,6 +244,14 @@ struct FeaturesVk : FeatureSetBase
"allocate_non_zero_memory", FeatureCategory::VulkanFeatures,
"Fill new allocations with non-zero values to flush out errors.", &members,
"http://anglebug.com/4384"};
// Android needs to pre-rotate surfaces that are not oriented per the native device's
// orientation (e.g. a landscape application on a Pixel phone). This feature works for
// full-screen applications. http://anglebug.com/3502
Feature enablePreRotateSurfaces = {"enable_pre_rotation_surfaces",
FeatureCategory::VulkanFeatures,
"Enable Android pre-rotation for landscape applications",
&members, "http://anglebug.com/3502"};
};
inline FeaturesVk::FeaturesVk() = default;
......
......@@ -180,11 +180,12 @@ constexpr const char kXfbVerticesPerDraw[] = "xfbVerticesPerDraw";
constexpr const char kXfbBufferOffsets[] = "xfbBufferOffsets";
constexpr const char kAcbBufferOffsets[] = "acbBufferOffsets";
constexpr const char kDepthRange[] = "depthRange";
constexpr const char kPreRotation[] = "preRotation";
constexpr size_t kNumGraphicsDriverUniforms = 9;
constexpr size_t kNumGraphicsDriverUniforms = 10;
constexpr std::array<const char *, kNumGraphicsDriverUniforms> kGraphicsDriverUniformNames = {
{kViewport, kHalfRenderAreaHeight, kViewportYScale, kNegViewportYScale, kXfbActiveUnpaused,
kXfbVerticesPerDraw, kXfbBufferOffsets, kAcbBufferOffsets, kDepthRange}};
kXfbVerticesPerDraw, kXfbBufferOffsets, kAcbBufferOffsets, kDepthRange, kPreRotation}};
constexpr size_t kNumComputeDriverUniforms = 1;
constexpr std::array<const char *, kNumComputeDriverUniforms> kComputeDriverUniformNames = {
......@@ -332,6 +333,35 @@ ANGLE_NO_DISCARD bool AppendVertexShaderDepthCorrectionToMain(TCompiler *compile
return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}
// This operation performs Android pre-rotation and y-flip. For Android (and potentially other
// platforms), the device may rotate, such that the orientation of the application is rotated
// relative to the native orientation of the device. This is corrected in part by multiplying
// gl_Position by a mat2.
// The equations reduce to an expression:
//
// gl_Position.xy = gl_Position.xy * preRotation
ANGLE_NO_DISCARD bool AppendPreRotation(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
const TVariable *driverUniforms)
{
TIntermBinary *preRotationRef = CreateDriverUniformRef(driverUniforms, kPreRotation);
TIntermSymbol *glPos = new TIntermSymbol(BuiltInVariable::gl_Position());
TVector<int> swizzleOffsetXY = {0, 1};
TIntermSwizzle *glPosXY = new TIntermSwizzle(glPos, swizzleOffsetXY);
// Create the expression "(gl_Position.xy * preRotation)"
TIntermBinary *zRotated =
new TIntermBinary(EOpMatrixTimesVector, preRotationRef->deepCopy(), glPosXY->deepCopy());
// Create the assignment "gl_Position.xy = (gl_Position.xy * preRotation)"
TIntermBinary *assignment =
new TIntermBinary(TOperator::EOpAssign, glPosXY->deepCopy(), zRotated);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}
ANGLE_NO_DISCARD bool AppendVertexShaderTransformFeedbackOutputToMain(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable)
......@@ -390,6 +420,7 @@ const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root, TSymbolTa
new TType(EbtInt, 4),
new TType(EbtUInt, 4),
emulatedDepthRangeType,
new TType(EbtFloat, 2, 2),
}};
for (size_t uniformIndex = 0; uniformIndex < kNumGraphicsDriverUniforms; ++uniformIndex)
......@@ -976,6 +1007,10 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root,
{
return false;
}
if (!AppendPreRotation(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
}
else if (getShaderType() == GL_GEOMETRY_SHADER)
{
......
......@@ -393,6 +393,10 @@ class ContextMtl : public ContextImpl, public mtl::Context
// We'll use x, y, z, w for near / far / diff / zscale respectively.
float depthRange[4];
// Used to pre-rotate gl_Position for Vulkan swapchain images on Android (a mat2, which is
// padded to the size of two vec4's).
float preRotation[8];
};
struct DefaultAttribute
......
......@@ -1607,6 +1607,16 @@ angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context)
mDriverUniforms.depthRange[2] = depthRangeDiff;
mDriverUniforms.depthRange[3] = NeedToInvertDepthRange(depthRangeNear, depthRangeFar) ? -1 : 1;
// Fill in a mat2 identity matrix, plus padding
mDriverUniforms.preRotation[0] = 1.0f;
mDriverUniforms.preRotation[1] = 0.0f;
mDriverUniforms.preRotation[2] = 0.0f;
mDriverUniforms.preRotation[3] = 0.0f;
mDriverUniforms.preRotation[4] = 0.0f;
mDriverUniforms.preRotation[5] = 1.0f;
mDriverUniforms.preRotation[6] = 0.0f;
mDriverUniforms.preRotation[7] = 0.0f;
ASSERT(mRenderEncoder.valid());
mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
......
......@@ -29,6 +29,23 @@ namespace rx
class RendererVk;
class WindowSurfaceVk;
// The possible rotations of the surface/draw framebuffer, used for pre-rotating gl_Position
// in the vertex shader.
enum class SurfaceRotationType
{
Identity,
Rotated90Degrees,
Rotated180Degrees,
Rotated270Degrees,
FlippedIdentity,
FlippedRotated90Degrees,
FlippedRotated180Degrees,
FlippedRotated270Degrees,
InvalidEnum,
EnumCount = InvalidEnum,
};
struct CommandBatch final : angle::NonCopyable
{
CommandBatch();
......@@ -345,6 +362,11 @@ class ContextVk : public ContextImpl, public vk::Context
bool isViewportFlipEnabledForDrawFBO() const;
bool isViewportFlipEnabledForReadFBO() const;
// When the device/surface is rotated such that the surface's aspect ratio is different than
// the native device (e.g. 90 degrees), the width and height of the viewport, scissor, and
// render area must be swapped.
bool isRotatedAspectRatioForDrawFBO() const;
bool isRotatedAspectRatioForReadFBO() const;
// State sync with dirty bits.
angle::Result syncState(const gl::Context *context,
......@@ -775,6 +797,8 @@ class ContextVk : public ContextImpl, public vk::Context
void updateDepthRange(float nearPlane, float farPlane);
void updateFlipViewportDrawFramebuffer(const gl::State &glState);
void updateFlipViewportReadFramebuffer(const gl::State &glState);
void updateSurfaceRotationDrawFramebuffer(const gl::State &glState);
void updateSurfaceRotationReadFramebuffer(const gl::State &glState);
angle::Result updateActiveTextures(const gl::Context *context);
angle::Result updateActiveImages(const gl::Context *context,
......@@ -895,6 +919,10 @@ class ContextVk : public ContextImpl, public vk::Context
gl::PrimitiveMode mCurrentDrawMode;
WindowSurfaceVk *mCurrentWindowSurface;
// Records the current rotation of the surface (draw/read) framebuffer, derived from
// mCurrentWindowSurface->getPreTransform().
SurfaceRotationType mCurrentRotationDrawFramebuffer;
SurfaceRotationType mCurrentRotationReadFramebuffer;
// Keep a cached pipeline description structure that can be used to query the pipeline cache.
// Kept in a pointer so allocations can be aligned, and structs can be portably packed.
......
......@@ -1646,6 +1646,9 @@ void RendererVk::initFeatures(DisplayVk *displayVk, const ExtensionNameList &dev
(&mFeatures), supportsExternalMemoryHost,
ExtensionFound(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, deviceExtensionNames));
// Pre-rotation support is not fully ready to be enabled.
ANGLE_FEATURE_CONDITION((&mFeatures), enablePreRotateSurfaces, false);
angle::PlatformMethods *platform = ANGLEPlatformCurrent();
platform->overrideFeaturesVk(platform, &mFeatures);
......
......@@ -95,6 +95,19 @@ constexpr VkImageUsageFlags kSurfaceVKColorImageUsageFlags =
constexpr VkImageUsageFlags kSurfaceVKDepthStencilImageUsageFlags =
kSurfaceVKImageUsageFlags | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// If the device is rotated with any of the following transform flags, the swapchain width and
// height must be swapped (e.g. make a landscape window portrait). This must also be done for all
// attachments used with the swapchain (i.e. depth, stencil, and multisample buffers).
constexpr VkSurfaceTransformFlagsKHR k90DegreeRotationVariants =
VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR |
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR |
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR;
bool Is90DegreeRotation(VkSurfaceTransformFlagsKHR transform)
{
return ((transform & k90DegreeRotationVariants) != 0);
}
} // namespace
SurfaceVk::SurfaceVk(const egl::SurfaceState &surfaceState) : SurfaceImpl(surfaceState) {}
......@@ -554,6 +567,27 @@ angle::Result WindowSurfaceVk::initializeImpl(DisplayVk *displayVk)
gl::Extents extents(static_cast<int>(width), static_cast<int>(height), 1);
if (renderer->getFeatures().enablePreRotateSurfaces.enabled)
{
// Use the surface's transform. For many platforms, this will always be identity (ANGLE
// does not need to do any pre-rotation). However, when mSurfaceCaps.currentTransform is
// not identity, the device has been rotated away from its natural orientation. In such a
// case, ANGLE must rotate all rendering in order to avoid the compositor
// (e.g. SurfaceFlinger on Android) performing an additional rotation blit. In addition,
// ANGLE must create the swapchain with VkSwapchainCreateInfoKHR::preTransform set to the
// value of mSurfaceCaps.currentTransform.
mPreTransform = mSurfaceCaps.currentTransform;
}
else
{
// Default to identity transform.
mPreTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if ((mSurfaceCaps.supportedTransforms & mPreTransform) == 0)
{
mPreTransform = mSurfaceCaps.currentTransform;
}
}
uint32_t presentModeCount = 0;
ANGLE_VK_TRY(displayVk, vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, mSurface,
&presentModeCount, nullptr));
......@@ -567,13 +601,6 @@ angle::Result WindowSurfaceVk::initializeImpl(DisplayVk *displayVk)
// will get clamped to the min/max values specified at display creation time.
setSwapInterval(renderer->getFeatures().disableFifoPresentMode.enabled ? 0 : 1);
// Default to identity transform.
mPreTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if ((mSurfaceCaps.supportedTransforms & mPreTransform) == 0)
{
mPreTransform = mSurfaceCaps.currentTransform;
}
uint32_t surfaceFormatCount = 0;
ANGLE_VK_TRY(displayVk, vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, mSurface,
&surfaceFormatCount, nullptr));
......@@ -793,6 +820,19 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
const vk::Format &format = renderer->getFormat(mState.config->renderTargetFormat);
VkFormat nativeFormat = format.vkImageFormat;
gl::Extents rotatedExtents = extents;
if (Is90DegreeRotation(mPreTransform))
{
// The Surface is oriented such that its aspect ratio no longer matches that of the
// device. In this case, the width and height of the swapchain images must be swapped to
// match the device's native orientation. This must also be done for other attachments
// used with the swapchain (e.g. depth buffer). The width and height of the viewport,
// scissor, and render-pass render area must also be swapped. Then, when ANGLE rotates
// gl_Position in the vertex shader, the rendering will look the same as if no
// pre-rotation had been done.
std::swap(rotatedExtents.width, rotatedExtents.height);
}
// We need transfer src for reading back from the backbuffer.
VkImageUsageFlags imageUsageFlags = kSurfaceVKColorImageUsageFlags;
......@@ -813,8 +853,8 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
swapchainInfo.imageColorSpace = MapEglColorSpaceToVkColorSpace(
static_cast<EGLenum>(mState.attributes.get(EGL_GL_COLORSPACE, EGL_NONE)));
// Note: Vulkan doesn't allow 0-width/height swapchains.
swapchainInfo.imageExtent.width = std::max(extents.width, 1);
swapchainInfo.imageExtent.height = std::max(extents.height, 1);
swapchainInfo.imageExtent.width = std::max(rotatedExtents.width, 1);
swapchainInfo.imageExtent.height = std::max(rotatedExtents.height, 1);
swapchainInfo.imageArrayLayers = 1;
swapchainInfo.imageUsage = imageUsageFlags;
swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
......@@ -845,7 +885,7 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
ANGLE_VK_CHECK(context, samples > 0, VK_ERROR_INITIALIZATION_FAILED);
VkExtent3D vkExtents;
gl_vk::GetExtent(extents, &vkExtents);
gl_vk::GetExtent(rotatedExtents, &vkExtents);
if (samples > 1)
{
......@@ -926,12 +966,17 @@ angle::Result WindowSurfaceVk::checkForOutOfDateSwapchain(ContextVk *contextVk,
// If window size has changed, check with surface capabilities. It has been observed on
// Android that `getCurrentWindowSize()` returns 1920x1080 for example, while surface
// capabilities returns the size the surface was created with.
if (currentExtents != swapchainExtents)
const VkPhysicalDevice &physicalDevice = contextVk->getRenderer()->getPhysicalDevice();
ANGLE_VK_TRY(contextVk, vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface,
&mSurfaceCaps));
if (contextVk->getFeatures().enablePreRotateSurfaces.enabled)
{
const VkPhysicalDevice &physicalDevice = contextVk->getRenderer()->getPhysicalDevice();
ANGLE_VK_TRY(contextVk, vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
physicalDevice, mSurface, &mSurfaceCaps));
// Update the surface's transform, which can change even if the window size does not.
mPreTransform = mSurfaceCaps.currentTransform;
}
if (currentExtents != swapchainExtents)
{
uint32_t width = mSurfaceCaps.currentExtent.width;
uint32_t height = mSurfaceCaps.currentExtent.height;
......@@ -1179,13 +1224,24 @@ angle::Result WindowSurfaceVk::present(ContextVk *contextVk,
// If OUT_OF_DATE is returned, it's ok, we just need to recreate the swapchain before
// continuing.
// If VK_SUBOPTIMAL_KHR is returned it's because the device orientation changed and we should
// recreate the swapchain with a new window orientation. We aren't quite ready for that so just
// ignore for now.
// TODO: Check for preRotation: http://anglebug.com/3502
*presentOutOfDate = result == VK_ERROR_OUT_OF_DATE_KHR;
if (!*presentOutOfDate && result != VK_SUBOPTIMAL_KHR)
// recreate the swapchain with a new window orientation.
if (contextVk->getFeatures().enablePreRotateSurfaces.enabled)
{
ANGLE_VK_TRY(contextVk, result);
// Also check for VK_SUBOPTIMAL_KHR.
*presentOutOfDate = ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR));
if (!*presentOutOfDate)
{
ANGLE_VK_TRY(contextVk, result);
}
}
else
{
// We aren't quite ready for that so just ignore for now.
*presentOutOfDate = result == VK_ERROR_OUT_OF_DATE_KHR;
if (!*presentOutOfDate && result != VK_SUBOPTIMAL_KHR)
{
ANGLE_VK_TRY(contextVk, result);
}
}
return angle::Result::Continue;
......@@ -1399,7 +1455,11 @@ angle::Result WindowSurfaceVk::getCurrentFramebuffer(ContextVk *contextVk,
framebufferInfo.pAttachments = imageViews.data();
framebufferInfo.width = static_cast<uint32_t>(extents.width);
framebufferInfo.height = static_cast<uint32_t>(extents.height);
framebufferInfo.layers = 1;
if (Is90DegreeRotation(mPreTransform))
{
std::swap(framebufferInfo.width, framebufferInfo.height);
}
framebufferInfo.layers = 1;
if (isMultiSampled())
{
......
......@@ -224,6 +224,8 @@ class WindowSurfaceVk : public SurfaceVk
vk::Semaphore getAcquireImageSemaphore();
VkSurfaceTransformFlagBitsKHR getPreTransform() { return mPreTransform; }
protected:
angle::Result swapImpl(const gl::Context *context,
EGLint *rects,
......
......@@ -1162,6 +1162,12 @@ angle::Result UtilsVk::startRenderPass(ContextVk *contextVk,
framebufferInfo.width = renderArea.x + renderArea.width;
framebufferInfo.height = renderArea.y + renderArea.height;
framebufferInfo.layers = 1;
if (contextVk->isRotatedAspectRatioForDrawFBO())
{
// The surface is rotated 90/270 degrees. This changes the aspect ratio of
// the surface. Swap the width and height of the framebuffer.
std::swap(framebufferInfo.width, framebufferInfo.height);
}
vk::Framebuffer framebuffer;
ANGLE_VK_TRY(contextVk, framebuffer.init(contextVk->getDevice(), framebufferInfo));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment