vulkan: improve render pass performance

Before this patch some common patterns would lead to very suboptimal
render passes.
Consider the following code:
```lua
local canvas = love.graphics.newCanvas(...)

function love.draw()
    love.graphics.setCanvas(canvas)
    love.graphics.clear(...)
    love.graphics.draw(...)
    love.graphics.setCanvas()

    love.graphics.draw(canvas)
end
```
This would lead to the following rendering:
1) render pass on main window with loadOp=load, followed by an immediate
    call to vkCmdClearAttachments
2) render pass on canvas with loadOp=load, followed by an immediate call
    to vkCmdClearAttachments
3) render pass on main window with loadOp=load

This patch changes the behaviour to the more performant
(and equivalent) version:
1) render pass on canvas with loadOp=clear
2) render pass on main window with loadOp=clear

This is especially helpful on mobile devices, where creating render
passes is an expensive operation.
This commit is contained in:
niki
2023-02-11 20:06:19 +01:00
parent 9f452474c7
commit 094d6e39d1
2 changed files with 241 additions and 136 deletions
+212 -130
View File
@@ -110,66 +110,10 @@ void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble d
flushBatchedDraws();
if (!renderPassState.active)
startRenderPass();
VkClearAttachment attachment{};
if (color.hasValue)
{
Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
gammaCorrectColor(cf);
attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
}
VkClearAttachment depthStencilAttachment{};
if (stencil.hasValue)
{
depthStencilAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
}
if (depth.hasValue)
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
}
std::array<VkClearAttachment, 2> attachments = {
attachment,
depthStencilAttachment
};
VkClearRect rect{};
rect.layerCount = 1;
rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
vkCmdClearAttachments(
commandBuffers[currentFrame],
static_cast<uint32_t>(attachments.size()), attachments.data(),
1, &rect);
}
void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth)
{
if (colors.empty() && !stencil.hasValue && !depth.hasValue)
return;
flushBatchedDraws();
if (!renderPassState.active)
startRenderPass();
std::vector<VkClearAttachment> attachments;
for (const auto &color : colors)
if (renderPassState.active)
{
VkClearAttachment attachment{};
if (color.hasValue)
{
Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
@@ -181,33 +125,154 @@ void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt sten
attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
}
attachments.push_back(attachment);
VkClearAttachment depthStencilAttachment{};
if (stencil.hasValue)
{
depthStencilAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
}
if (depth.hasValue)
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
}
std::array<VkClearAttachment, 2> attachments = {
attachment,
depthStencilAttachment
};
VkClearRect rect{};
rect.layerCount = 1;
rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
vkCmdClearAttachments(
commandBuffers[currentFrame],
static_cast<uint32_t>(attachments.size()), attachments.data(),
1, &rect);
}
VkClearAttachment depthStencilAttachment{};
if (stencil.hasValue)
else
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
renderPassState.useConfigurations = true;
if (color.hasValue)
{
renderPassState.renderPassConfiguration.colorAttachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
renderPassState.clearColors[0].color.float32[0] = static_cast<float>(color.value.r);
renderPassState.clearColors[0].color.float32[1] = static_cast<float>(color.value.g);
renderPassState.clearColors[0].color.float32[2] = static_cast<float>(color.value.b);
renderPassState.clearColors[0].color.float32[3] = static_cast<float>(color.value.a);
}
if (depth.hasValue)
{
renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
renderPassState.clearColors[1].depthStencil.depth = static_cast<float>(depth.value);
}
if (stencil.hasValue)
{
renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
renderPassState.clearColors[1].depthStencil.stencil = static_cast<uint32_t>(stencil.value);
}
if (renderPassState.isWindow)
{
renderPassState.windowClearRequested = true;
renderPassState.mainWindowClearColorValue = color;
renderPassState.mainWindowClearDepthValue = depth;
renderPassState.mainWindowClearStencilValue = stencil;
}
else
startRenderPass();
}
if (depth.hasValue)
}
void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth)
{
if (colors.empty() && !stencil.hasValue && !depth.hasValue)
return;
flushBatchedDraws();
if (renderPassState.active)
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
std::vector<VkClearAttachment> attachments;
for (const auto &color : colors)
{
VkClearAttachment attachment{};
if (color.hasValue)
{
Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
gammaCorrectColor(cf);
attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
}
attachments.push_back(attachment);
}
VkClearAttachment depthStencilAttachment{};
if (stencil.hasValue)
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
}
if (depth.hasValue)
{
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
}
attachments.push_back(depthStencilAttachment);
VkClearRect rect{};
rect.layerCount = 1;
rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
vkCmdClearAttachments(
commandBuffers[currentFrame],
static_cast<uint32_t>(attachments.size()), attachments.data(),
1, &rect);
}
else
{
renderPassState.useConfigurations = true;
attachments.push_back(depthStencilAttachment);
for (size_t i = 0; i < colors.size(); i++)
{
if (colors[i].hasValue)
{
renderPassState.renderPassConfiguration.colorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
renderPassState.clearColors[i].color.float32[0] = colors[i].value.r;
renderPassState.clearColors[i].color.float32[1] = colors[i].value.g;
renderPassState.clearColors[i].color.float32[2] = colors[i].value.b;
renderPassState.clearColors[i].color.float32[3] = colors[i].value.a;
}
}
VkClearRect rect{};
rect.layerCount = 1;
rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
if (depth.hasValue)
{
renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
renderPassState.clearColors[colors.size()].depthStencil.depth = depth.value;
}
vkCmdClearAttachments(
commandBuffers[currentFrame],
static_cast<uint32_t>(attachments.size()), attachments.data(),
1, &rect);
if (stencil.hasValue)
{
renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
renderPassState.clearColors[colors.size()].depthStencil.stencil = stencil.value;
}
startRenderPass();
}
}
void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
@@ -215,40 +280,19 @@ void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
if (renderPassState.active)
endRenderPass();
if (renderPassState.useConfigurations)
{
auto & renderPassConfiguration = renderPassState.renderPassConfiguration;
renderPassState.useConfigurations = true;
auto & renderPassConfiguration = renderPassState.renderPassConfiguration;
for (size_t i = 0; i < colorbuffers.size(); i++)
renderPassConfiguration.colorAttachments[i].discard = colorbuffers[i];
renderPassConfiguration.staticData.depthAttachment.discard = depthstencil;
for (size_t i = 0; i < colorbuffers.size(); i++)
{
if (colorbuffers[i])
renderPassConfiguration.colorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
else
if (depthstencil)
{
RenderPassConfiguration renderPassConfiguration{};
renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, colorbuffers[0], msaaSamples });
renderPassConfiguration.staticData.depthAttachment = { findDepthFormat(), depthstencil, msaaSamples };
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
renderPassConfiguration.staticData.resolve = false;
else
renderPassConfiguration.staticData.resolve = true;
FramebufferConfiguration framebufferConfiguration{};
framebufferConfiguration.staticData.depthView = depthImageView;
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
{
framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
framebufferConfiguration.staticData.resolveView = VK_NULL_HANDLE;
}
else
{
framebufferConfiguration.colorViews.push_back(colorImageView);
framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
}
renderPassState.useConfigurations = true;
renderPassState.renderPassConfiguration = renderPassConfiguration;
renderPassState.framebufferConfiguration = framebufferConfiguration;
renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
startRenderPass();
@@ -422,6 +466,9 @@ void Graphics::present(void *screenshotCallbackdata)
if (isRenderTargetActive())
throw love::Exception("present cannot be called while a render target is active.");
if (!renderPassState.active && renderPassState.windowClearRequested)
startRenderPass();
deprecations.draw(this);
submitGpuCommands(true, screenshotCallbackdata);
@@ -1938,8 +1985,8 @@ void Graphics::createScreenshotCallbackBuffers()
void Graphics::createDefaultRenderPass()
{
RenderPassConfiguration renderPassConfiguration{};
renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, false, msaaSamples });
renderPassConfiguration.staticData.depthAttachment = { findDepthFormat(), false, msaaSamples };
renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples });
renderPassConfiguration.staticData.depthStencilAttachment = { findDepthFormat(), VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples };
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
renderPassConfiguration.staticData.resolve = false;
else
@@ -2050,10 +2097,7 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
VkAttachmentDescription colorDescription{};
colorDescription.format = colorAttachment.format;
colorDescription.samples = colorAttachment.msaaSamples;
if (colorAttachment.discard)
colorDescription.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
else
colorDescription.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
colorDescription.loadOp = colorAttachment.loadOp;
colorDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
@@ -2066,24 +2110,18 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
subPass.pColorAttachments = colorAttachmentRefs.data();
VkAttachmentReference depthStencilAttachmentRef{};
if (configuration.staticData.depthAttachment.format != VK_FORMAT_UNDEFINED)
if (configuration.staticData.depthStencilAttachment.format != VK_FORMAT_UNDEFINED)
{
depthStencilAttachmentRef.attachment = attachment++;
depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
subPass.pDepthStencilAttachment = &depthStencilAttachmentRef;
VkAttachmentDescription depthStencilAttachment{};
depthStencilAttachment.format = configuration.staticData.depthAttachment.format;
depthStencilAttachment.samples = configuration.staticData.depthAttachment.msaaSamples;
if (configuration.staticData.depthAttachment.discard)
depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
else
depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
depthStencilAttachment.format = configuration.staticData.depthStencilAttachment.format;
depthStencilAttachment.samples = configuration.staticData.depthStencilAttachment.msaaSamples;
depthStencilAttachment.loadOp = configuration.staticData.depthStencilAttachment.depthLoadOp;
depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
if (configuration.staticData.depthAttachment.discard)
depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
else
depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
depthStencilAttachment.stencilLoadOp = configuration.staticData.depthStencilAttachment.stencilLoadOp;
depthStencilAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
depthStencilAttachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
depthStencilAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
@@ -2285,13 +2323,18 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
void Graphics::setDefaultRenderPass()
{
uint32_t numClearValues = 2;
renderPassState.clearColors.resize(numClearValues);
renderPassState.beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassState.beginInfo.renderPass = defaultRenderPass;
renderPassState.beginInfo.framebuffer = defaultFramebuffers[imageIndex];
renderPassState.beginInfo.renderArea.offset = { 0, 0 };
renderPassState.beginInfo.renderArea.extent = swapChainExtent;
renderPassState.beginInfo.clearValueCount = 0;
renderPassState.beginInfo.clearValueCount = numClearValues;
renderPassState.beginInfo.pClearValues = renderPassState.clearColors.data();
renderPassState.isWindow = true;
renderPassState.useConfigurations = false;
renderPassState.pipeline = VK_NULL_HANDLE;
renderPassState.width = static_cast<float>(swapChainExtent.width);
@@ -2299,6 +2342,36 @@ void Graphics::setDefaultRenderPass()
renderPassState.msaa = msaaSamples;
renderPassState.numColorAttachments = 1;
renderPassState.transitionImages.clear();
RenderPassConfiguration renderPassConfiguration{};
renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples });
renderPassConfiguration.staticData.depthStencilAttachment = { findDepthFormat(), VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples };
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
renderPassConfiguration.staticData.resolve = false;
else
renderPassConfiguration.staticData.resolve = true;
FramebufferConfiguration framebufferConfiguration{};
framebufferConfiguration.staticData.depthView = depthImageView;
framebufferConfiguration.staticData.width = swapChainExtent.width;
framebufferConfiguration.staticData.height = swapChainExtent.height;
if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
{
framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
framebufferConfiguration.staticData.resolveView = VK_NULL_HANDLE;
}
else
{
framebufferConfiguration.colorViews.push_back(colorImageView);
framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
}
renderPassState.renderPassConfiguration = renderPassConfiguration;
renderPassState.framebufferConfiguration = framebufferConfiguration;
if (renderPassState.windowClearRequested)
clear(renderPassState.mainWindowClearColorValue, renderPassState.mainWindowClearStencilValue, renderPassState.mainWindowClearDepthValue);
}
void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture)
@@ -2310,12 +2383,13 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
for (const auto &color : rts.colors)
renderPassConfiguration.colorAttachments.push_back({
Vulkan::getTextureFormat(color.texture->getPixelFormat(), isPixelFormatSRGB(color.texture->getPixelFormat())).internalFormat,
false,
VK_ATTACHMENT_LOAD_OP_LOAD,
dynamic_cast<Texture*>(color.texture)->getMsaaSamples() });
if (rts.depthStencil.texture != nullptr)
renderPassConfiguration.staticData.depthAttachment = {
renderPassConfiguration.staticData.depthStencilAttachment = {
Vulkan::getTextureFormat(rts.depthStencil.texture->getPixelFormat(), false).internalFormat,
false,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_LOAD_OP_LOAD,
dynamic_cast<Texture*>(rts.depthStencil.texture)->getMsaaSamples() };
FramebufferConfiguration configuration{};
@@ -2333,14 +2407,19 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
configuration.staticData.width = static_cast<uint32_t>(pixelw);
configuration.staticData.height = static_cast<uint32_t>(pixelh);
uint32_t numClearValues = rts.colors.size() + 1;
renderPassState.clearColors.resize(numClearValues);
renderPassState.beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassState.beginInfo.renderPass = VK_NULL_HANDLE;
renderPassState.beginInfo.framebuffer = VK_NULL_HANDLE;
renderPassState.beginInfo.renderArea.offset = {0, 0};
renderPassState.beginInfo.renderArea.extent.width = static_cast<uint32_t>(pixelw);
renderPassState.beginInfo.renderArea.extent.height = static_cast<uint32_t>(pixelh);
renderPassState.beginInfo.clearValueCount = 0;
renderPassState.beginInfo.clearValueCount = numClearValues;
renderPassState.beginInfo.pClearValues = renderPassState.clearColors.data();
renderPassState.isWindow = false;
renderPassState.useConfigurations = true;
renderPassState.renderPassConfiguration = renderPassConfiguration;
renderPassState.framebufferConfiguration = configuration;
@@ -2356,6 +2435,9 @@ void Graphics::startRenderPass()
{
renderPassState.active = true;
if (renderPassState.isWindow && renderPassState.windowClearRequested)
renderPassState.windowClearRequested = false;
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
+29 -6
View File
@@ -46,27 +46,43 @@ namespace graphics
namespace vulkan
{
struct RenderPassAttachment
struct ColorAttachment
{
VkFormat format = VK_FORMAT_UNDEFINED;
bool discard = true;
VkAttachmentLoadOp loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
bool operator==(const RenderPassAttachment &attachment) const
bool operator==(const ColorAttachment&attachment) const
{
return format == attachment.format &&
discard == attachment.discard &&
loadOp == attachment.loadOp &&
msaaSamples == attachment.msaaSamples;
}
};
struct DepthStencilAttachment
{
VkFormat format = VK_FORMAT_UNDEFINED;
VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
bool operator==(const DepthStencilAttachment &attachment) const
{
return format == attachment.format &&
depthLoadOp == attachment.depthLoadOp &&
stencilLoadOp == attachment.stencilLoadOp &&
msaaSamples == attachment.msaaSamples;
}
};
struct RenderPassConfiguration
{
std::vector<RenderPassAttachment> colorAttachments;
std::vector<ColorAttachment> colorAttachments;
struct StaticRenderPassConfiguration
{
RenderPassAttachment depthAttachment;
DepthStencilAttachment depthStencilAttachment;
bool resolve = false;
} staticData;
@@ -216,6 +232,7 @@ struct RenderpassState
{
bool active = false;
VkRenderPassBeginInfo beginInfo{};
bool isWindow = false;
bool useConfigurations = false;
RenderPassConfiguration renderPassConfiguration{};
FramebufferConfiguration framebufferConfiguration{};
@@ -225,6 +242,12 @@ struct RenderpassState
float width = 0.0f;
float height = 0.0f;
VkSampleCountFlagBits msaa = VK_SAMPLE_COUNT_1_BIT;
std::vector<VkClearValue> clearColors;
bool windowClearRequested = false;
OptionalColorD mainWindowClearColorValue;
OptionalDouble mainWindowClearDepthValue;
OptionalInt mainWindowClearStencilValue;
};
struct ScreenshotReadbackBuffer