Add stereo support for DirectX12 and Vulkan backends

Change-Id: Id12723d6c392e25935ccb265c58af91aff968984
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
Ilya Doroshenko 2023-08-13 19:11:29 +02:00
parent 91dcc76fc1
commit 1eb15adee3
8 changed files with 210 additions and 16 deletions

View File

@ -7228,10 +7228,9 @@ QRhiResource::Type QRhiSwapChain::resourceType() const
is backed by two color buffers, one for each eye, instead of just one.
When stereoscopic rendering is not supported, the return value will be
the default target. For the time being the only backends and 3D API where traditional
stereoscopic rendering is supported are OpenGL (excluding OpenGL ES) and Direct3D 11, in
the default target. It is supported by all hardware backends except for Metal, in
combination with \l QSurfaceFormat::StereoBuffers, assuming it is supported
by the graphics and display driver stack at run time. All other backends
by the graphics and display driver stack at run time. Metal and Null backends
are going to return the default render target from this overload.
\note the value must not be cached and reused between frames

View File

@ -1497,6 +1497,16 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
swapChainD->rtWrapper.d.dsv = swapChainD->ds ? swapChainD->ds->dsv.cpuHandle
: D3D12_CPU_DESCRIPTOR_HANDLE { 0 };
if (swapChainD->stereo) {
swapChainD->rtWrapperRight.d.rtv[0] = swapChainD->sampleDesc.Count > 1
? swapChainD->msaaRtvs[swapChainD->currentBackBufferIndex].cpuHandle
: swapChainD->rtvsRight[swapChainD->currentBackBufferIndex].cpuHandle;
swapChainD->rtWrapperRight.d.dsv =
swapChainD->ds ? swapChainD->ds->dsv.cpuHandle : D3D12_CPU_DESCRIPTOR_HANDLE{ 0 };
}
// Time to release things that are marked for currentFrameSlot since due to
// the wait above we know that the previous commands on the GPU for this
// slot must have finished already.
@ -5970,6 +5980,7 @@ int QD3D12SwapChainRenderTarget::sampleCount() const
QD3D12SwapChain::QD3D12SwapChain(QRhiImplementation *rhi)
: QRhiSwapChain(rhi),
rtWrapper(rhi, this),
rtWrapperRight(rhi, this),
cbWrapper(rhi)
{
}
@ -6026,6 +6037,8 @@ void QD3D12SwapChain::releaseBuffers()
for (UINT i = 0; i < BUFFER_COUNT; ++i) {
rhiD->resourcePool.remove(colorBuffers[i]);
rhiD->rtvPool.release(rtvs[i], 1);
if (stereo)
rhiD->rtvPool.release(rtvsRight[i], 1);
if (!msaaBuffers[i].isNull())
rhiD->resourcePool.remove(msaaBuffers[i]);
if (msaaRtvs[i].isValid())
@ -6060,6 +6073,11 @@ QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget()
return &rtWrapper;
}
QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
{
return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
}
QSize QD3D12SwapChain::surfacePixelSize()
{
Q_ASSERT(m_window);
@ -6191,6 +6209,7 @@ bool QD3D12SwapChain::createOrResize()
HWND hwnd = reinterpret_cast<HWND>(window->winId());
HRESULT hr;
QRHI_RES_RHI(QRhiD3D12);
stereo = m_window->format().stereo() && rhiD->dxgiFactory->IsWindowedStereoEnabled();
if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
if (rhiD->ensureDirectCompositionDevice()) {
@ -6233,6 +6252,7 @@ bool QD3D12SwapChain::createOrResize()
desc.Flags = swapChainFlags;
desc.Scaling = DXGI_SCALING_NONE;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
desc.Stereo = stereo;
if (dcompVisual) {
// With DirectComposition setting AlphaMode to STRAIGHT fails the
@ -6342,6 +6362,16 @@ bool QD3D12SwapChain::createOrResize()
rtvDesc.Format = srgbAdjustedColorFormat;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvs[i].cpuHandle);
if (stereo) {
rtvsRight[i] = rhiD->rtvPool.allocate(1);
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
rtvDesc.Format = srgbAdjustedColorFormat;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.ArraySize = 1;
rtvDesc.Texture2DArray.FirstArraySlice = 1;
rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvsRight[i].cpuHandle);
}
}
if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
@ -6414,6 +6444,15 @@ bool QD3D12SwapChain::createOrResize()
rtD->d.colorAttCount = 1;
rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
rtWrapperRight.setRenderPassDescriptor(m_renderPassDesc);
QD3D12SwapChainRenderTarget *rtDr = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapperRight);
rtDr->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
rtDr->d.pixelSize = pixelSize;
rtDr->d.dpr = float(window->devicePixelRatio());
rtDr->d.sampleCount = int(sampleDesc.Count);
rtDr->d.colorAttCount = 1;
rtDr->d.dsAttCount = m_depthStencil ? 1 : 0;
if (needsRegistration) {
rhiD->swapchains.insert(this);
rhiD->registerResource(this);

View File

@ -972,6 +972,7 @@ struct QD3D12SwapChain : public QRhiSwapChain
QRhiCommandBuffer *currentFrameCommandBuffer() override;
QRhiRenderTarget *currentFrameRenderTarget() override;
QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
QSize surfacePixelSize() override;
bool isFormatSupported(Format f) override;
@ -991,6 +992,7 @@ struct QD3D12SwapChain : public QRhiSwapChain
QSize pixelSize;
UINT swapInterval = 1;
UINT swapChainFlags = 0;
BOOL stereo = false;
DXGI_FORMAT colorFormat;
DXGI_FORMAT srgbAdjustedColorFormat;
DXGI_COLOR_SPACE_TYPE hdrColorSpace;
@ -999,12 +1001,14 @@ struct QD3D12SwapChain : public QRhiSwapChain
static const UINT BUFFER_COUNT = 3;
QD3D12ObjectHandle colorBuffers[BUFFER_COUNT];
QD3D12Descriptor rtvs[BUFFER_COUNT];
QD3D12Descriptor rtvsRight[BUFFER_COUNT];
DXGI_SAMPLE_DESC sampleDesc;
QD3D12ObjectHandle msaaBuffers[BUFFER_COUNT];
QD3D12Descriptor msaaRtvs[BUFFER_COUNT];
QD3D12RenderBuffer *ds = nullptr;
UINT currentBackBufferIndex = 0;
QD3D12SwapChainRenderTarget rtWrapper;
QD3D12SwapChainRenderTarget rtWrapperRight;
QD3D12CommandBuffer cbWrapper;
struct FrameResources {

View File

@ -1592,9 +1592,16 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
const bool stereo = bool(swapChainD->m_window) && (swapChainD->m_window->format().stereo())
&& surfaceCaps.maxImageArrayLayers > 1;
swapChainD->stereo = stereo;
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) {
if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR))
// Stereo has a weird bug, when using VK_PRESENT_MODE_MAILBOX_KHR,
// black screen is shown, but there is no validation error.
// Detected on Windows, with NVidia RTX A series (at least 4000 and 6000) driver 535.98
if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR) && !stereo)
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
@ -1618,7 +1625,7 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
swapChainInfo.imageFormat = swapChainD->colorFormat;
swapChainInfo.imageColorSpace = swapChainD->colorSpace;
swapChainInfo.imageExtent = VkExtent2D { uint32_t(swapChainD->pixelSize.width()), uint32_t(swapChainD->pixelSize.height()) };
swapChainInfo.imageArrayLayers = 1;
swapChainInfo.imageArrayLayers = stereo ? 2u : 1u;
swapChainInfo.imageUsage = usage;
swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapChainInfo.preTransform = preTransform;
@ -1680,7 +1687,9 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
swapChainD->imageRes.resize(swapChainD->bufferCount);
// Double up for stereo
swapChainD->imageRes.resize(swapChainD->bufferCount * (stereo ? 2u : 1u));
for (int i = 0; i < swapChainD->bufferCount; ++i) {
QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
image.image = swapChainImages[i];
@ -1708,6 +1717,36 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone;
}
if (stereo) {
for (int i = 0; i < swapChainD->bufferCount; ++i) {
QVkSwapChain::ImageResources &image(swapChainD->imageRes[i + swapChainD->bufferCount]);
image.image = swapChainImages[i];
if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
image.msaaImage = msaaImages[i];
image.msaaImageView = msaaViews[i];
}
VkImageViewCreateInfo imgViewInfo = {};
imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imgViewInfo.image = swapChainImages[i];
imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imgViewInfo.format = swapChainD->colorFormat;
imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imgViewInfo.subresourceRange.baseArrayLayer = 1;
imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
if (err != VK_SUCCESS) {
qWarning("Failed to create swapchain image view %d: %d", i, err);
return false;
}
image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone;
}
}
swapChainD->currentImageIndex = 0;
@ -1775,7 +1814,7 @@ void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
}
}
for (int i = 0; i < swapChainD->bufferCount; ++i) {
for (int i = 0; i < swapChainD->bufferCount * (swapChainD->stereo ? 2 : 1); ++i) {
QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
if (image.fb) {
df->vkDestroyFramebuffer(dev, image.fb, nullptr);
@ -1907,6 +1946,12 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin
QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
swapChainD->rtWrapper.d.fb = image.fb;
if (swapChainD->stereo) {
QVkSwapChain::ImageResources &image(
swapChainD->imageRes[swapChainD->currentImageIndex + swapChainD->bufferCount]);
swapChainD->rtWrapperRight.d.fb = image.fb;
}
prepareNewFrame(&swapChainD->cbWrapper);
// Read the timestamps for the previous frame for this slot.
@ -7443,6 +7488,7 @@ const QRhiNativeHandles *QVkCommandBuffer::nativeHandles()
QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
: QRhiSwapChain(rhi),
rtWrapper(rhi, this),
rtWrapperRight(rhi, this),
cbWrapper(rhi)
{
}
@ -7485,6 +7531,11 @@ QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget()
return &rtWrapper;
}
QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
{
return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
}
QSize QVkSwapChain::surfacePixelSize()
{
if (!ensureSurface())
@ -7735,6 +7786,55 @@ bool QVkSwapChain::createOrResize()
}
}
if (stereo) {
rtWrapperRight.setRenderPassDescriptor(
m_renderPassDesc); // for the public getter in QRhiRenderTarget
rtWrapperRight.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
Q_ASSERT(rtWrapperRight.d.rp && rtWrapperRight.d.rp->rp);
rtWrapperRight.d.pixelSize = pixelSize;
rtWrapperRight.d.dpr = float(window->devicePixelRatio());
rtWrapperRight.d.sampleCount = samples;
rtWrapperRight.d.colorAttCount = 1;
if (m_depthStencil) {
rtWrapperRight.d.dsAttCount = 1;
ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
} else {
rtWrapperRight.d.dsAttCount = 0;
ds = nullptr;
}
if (samples > VK_SAMPLE_COUNT_1_BIT)
rtWrapperRight.d.resolveAttCount = 1;
else
rtWrapperRight.d.resolveAttCount = 0;
for (int i = 0; i < bufferCount; ++i) {
QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]);
VkImageView views[3] = {
// color, ds, resolve
samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView,
ds ? ds->imageView : VK_NULL_HANDLE,
samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE
};
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = rtWrapperRight.d.rp->rp;
fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount
+ rtWrapperRight.d.resolveAttCount);
fbInfo.pAttachments = views;
fbInfo.width = uint32_t(pixelSize.width());
fbInfo.height = uint32_t(pixelSize.height());
fbInfo.layers = 1;
VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
if (err != VK_SUCCESS) {
qWarning("Failed to create framebuffer: %d", err);
return false;
}
}
}
frameCount = 0;
if (needsRegistration)

View File

@ -572,6 +572,7 @@ struct QVkSwapChain : public QRhiSwapChain
QRhiCommandBuffer *currentFrameCommandBuffer() override;
QRhiRenderTarget *currentFrameRenderTarget() override;
QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
QSize surfacePixelSize() override;
bool isFormatSupported(Format f) override;
@ -586,6 +587,7 @@ struct QVkSwapChain : public QRhiSwapChain
QWindow *window = nullptr;
QSize pixelSize;
bool supportsReadback = false;
bool stereo = false;
VkSwapchainKHR sc = VK_NULL_HANDLE;
int bufferCount = 0;
VkSurfaceKHR surface = VK_NULL_HANDLE;
@ -597,6 +599,7 @@ struct QVkSwapChain : public QRhiSwapChain
QVarLengthArray<VkPresentModeKHR, 8> supportedPresentationModes;
VkDeviceMemory msaaImageMem = VK_NULL_HANDLE;
QVkSwapChainRenderTarget rtWrapper;
QVkSwapChainRenderTarget rtWrapperRight;
QVkCommandBuffer cbWrapper;
struct ImageResources {

View File

@ -22,7 +22,7 @@ int main(int argc, char **argv)
QSurfaceFormat::setDefaultFormat(fmt);
Window w;
Window w{QRhi::Vulkan};
w.resize(1280, 720);
w.setTitle(QCoreApplication::applicationName());
w.show();

View File

@ -8,9 +8,25 @@
#include <rhi/qshader.h>
#include "../shared/cube.h"
Window::Window()
Window::Window(QRhi::Implementation graphicsApi)
:m_graphicsApi(graphicsApi)
{
setSurfaceType(OpenGLSurface);
switch (graphicsApi) {
default:
case QRhi::OpenGLES2:
setSurfaceType(OpenGLSurface);
break;
case QRhi::Vulkan:
instance.setLayers({ "VK_LAYER_KHRONOS_validation" });
instance.create();
setVulkanInstance(&instance);
setSurfaceType(VulkanSurface);
break;
case QRhi::D3D11:
case QRhi::D3D12:
setSurfaceType(Direct3DSurface);
break;
}
}
void Window::exposeEvent(QExposeEvent *)
@ -57,11 +73,42 @@ void Window::init()
{
QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
QRhiGles2InitParams params;
params.fallbackSurface = m_fallbackSurface.get();
params.window = this;
m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params, rhiFlags));
switch (m_graphicsApi) {
case QRhi::Vulkan:
{
QRhiVulkanInitParams params;
params.window = this;
params.inst = vulkanInstance();
m_rhi.reset(QRhi::create(QRhi::Vulkan, &params, rhiFlags));
break;
}
case QRhi::Null:
case QRhi::Metal:
case QRhi::OpenGLES2:
{
m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
QRhiGles2InitParams params;
params.fallbackSurface = m_fallbackSurface.get();
params.window = this;
m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params, rhiFlags));
break;
}
case QRhi::D3D11:
{
QRhiD3D11InitParams params;
m_rhi.reset(QRhi::create(QRhi::D3D11, &params, rhiFlags));
break;
}
case QRhi::D3D12:
{
QRhiD3D12InitParams params;
params.enableDebugLayer = true;
m_rhi.reset(QRhi::create(QRhi::D3D12, &params, rhiFlags));
break;
}
default:
break;
}
m_sc.reset(m_rhi->newSwapChain());
m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,

View File

@ -11,17 +11,19 @@
class Window : public QWindow
{
public:
Window();
Window(QRhi::Implementation graphicsApi);
void releaseSwapChain();
protected:
QVulkanInstance instance;
std::unique_ptr<QOffscreenSurface> m_fallbackSurface;
std::unique_ptr<QRhi> m_rhi;
std::unique_ptr<QRhiSwapChain> m_sc;
std::unique_ptr<QRhiRenderBuffer> m_ds;
std::unique_ptr<QRhiRenderPassDescriptor> m_rp;
QRhi::Implementation m_graphicsApi;
bool m_hasSwapChain = false;
QMatrix4x4 m_proj;