Renderpass multisampling resolve
According to the Vulkan spec:
"If pResolveAttachments is not NULL, each of its elements
corresponds to a color attachment (the element in
pColorAttachments at the same index), and a multisample
resolve operation is defined for each attachment. At the
end of each subpass, multisample resolve operations read
the subpass’s color attachments, and resolve the samples
for each pixel to the same pixel location in the
corresponding resolve attachments, unless the resolve
attachment index is VK_ATTACHMENT_UNUSED."
Note: This cl adds support for multisampling, but
requires syncing before performing the resolve
operation. The intent is for the next cl to move
the resolve to Renderer::finishRendering(), in
order to avoid having to sync when it's not
necessary.
Bug b/119620965
Change-Id: Id4fae41347e354b822d089fb5b6d4e36592c146b
Tests: dEQP-VK.pipeline.multisample.raster_samples.*
Tests: dEQP-VK.pipeline.multisample.raster_samples_consistency.*
Tests: dEQP-VK.pipeline.multisample.sample_mask.*
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/27650
Tested-by: Alexis Hétu <sugoi@google.com>
Presubmit-Ready: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Device/Blitter.cpp b/src/Device/Blitter.cpp
index 9f59d88..be209a3 100644
--- a/src/Device/Blitter.cpp
+++ b/src/Device/Blitter.cpp
@@ -47,7 +47,7 @@
return;
}
- State state(format, dest->getFormat(aspect), dest->getSampleCountFlagBits(), { 0xF });
+ State state(format, dest->getFormat(aspect), 1, dest->getSampleCountFlagBits(), { 0xF });
Routine *blitRoutine = getRoutine(state);
if(!blitRoutine)
{
@@ -89,6 +89,7 @@
format.bytes(), // sPitchB
dest->rowPitchBytes(aspect, subresLayers.mipLevel), // dPitchB
+ 0, // sSliceB (unused in clear operations)
dest->slicePitchBytes(aspect, subresLayers.mipLevel), // dSliceB
0.5f, 0.5f, 0.0f, 0.0f, // x0, y0, w, h
@@ -1413,6 +1414,21 @@
{
return nullptr;
}
+
+ if(state.srcSamples > 1) // Resolve multisampled source
+ {
+ Float4 accum = color;
+ for(int i = 1; i < state.srcSamples; i++)
+ {
+ s += *Pointer<Int>(blit + OFFSET(BlitData, sSliceB));
+ if(!read(color, s, state))
+ {
+ return nullptr;
+ }
+ accum += color;
+ }
+ color = accum * Float4(1.0f / static_cast<float>(state.srcSamples));
+ }
}
else // Bilinear filtering
{
@@ -1551,7 +1567,7 @@
float y0 = region.srcOffsets[0].y + (0.5f - region.dstOffsets[0].y) * heightRatio;
bool doFilter = (filter != VK_FILTER_NEAREST);
- State state(src->getFormat(srcAspect), dst->getFormat(dstAspect), dst->getSampleCountFlagBits(),
+ State state(src->getFormat(srcAspect), dst->getFormat(dstAspect), src->getSampleCountFlagBits(), dst->getSampleCountFlagBits(),
{ doFilter, srcAspect == VK_IMAGE_ASPECT_STENCIL_BIT, doFilter });
state.clampToEdge = (region.srcOffsets[0].x < 0) ||
(region.srcOffsets[0].y < 0) ||
@@ -1573,6 +1589,7 @@
nullptr, // dest
src->rowPitchBytes(srcAspect, region.srcSubresource.mipLevel), // sPitchB
dst->rowPitchBytes(dstAspect, region.dstSubresource.mipLevel), // dPitchB
+ src->slicePitchBytes(srcAspect, region.srcSubresource.mipLevel), // sSliceB
dst->slicePitchBytes(dstAspect, region.dstSubresource.mipLevel), // dSliceB
x0,
diff --git a/src/Device/Blitter.hpp b/src/Device/Blitter.hpp
index f842081..bc4a41d 100644
--- a/src/Device/Blitter.hpp
+++ b/src/Device/Blitter.hpp
@@ -63,8 +63,8 @@
{
State() = default;
State(const Options &options) : Options(options) {}
- State(vk::Format sourceFormat, vk::Format destFormat, int destSamples, const Options &options) :
- Options(options), sourceFormat(sourceFormat), destFormat(destFormat), destSamples(destSamples) {}
+ State(vk::Format sourceFormat, vk::Format destFormat, int srcSamples, int destSamples, const Options &options) :
+ Options(options), sourceFormat(sourceFormat), destFormat(destFormat), srcSamples(srcSamples), destSamples(destSamples) {}
bool operator==(const State &state) const
{
@@ -73,6 +73,7 @@
vk::Format sourceFormat;
vk::Format destFormat;
+ int srcSamples = 0;
int destSamples = 0;
};
@@ -82,6 +83,7 @@
void *dest;
int sPitchB;
int dPitchB;
+ int sSliceB;
int dSliceB;
float x0;
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index cbf9287..30763d5 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -60,7 +60,7 @@
executionState.renderPass = renderPass;
executionState.renderPassFramebuffer = framebuffer;
renderPass->begin();
- framebuffer->clear(clearValueCount, clearValues, renderArea);
+ framebuffer->clear(executionState.renderPass, clearValueCount, clearValues, renderArea);
}
private:
@@ -81,6 +81,16 @@
protected:
void play(CommandBuffer::ExecutionState& executionState) override
{
+ bool hasResolveAttachments = (executionState.renderPass->getCurrentSubpass().pResolveAttachments != nullptr);
+ if(hasResolveAttachments)
+ {
+ // FIXME(sugoi): remove the following lines and resolve in Renderer::finishRendering()
+ // for a Draw command or after the last command of the current subpass
+ // which modifies pixels.
+ executionState.renderer->synchronize();
+ executionState.renderPassFramebuffer->resolve(executionState.renderPass);
+ }
+
executionState.renderPass->nextSubpass();
}
@@ -97,13 +107,18 @@
protected:
void play(CommandBuffer::ExecutionState& executionState) override
{
- executionState.renderPass->end();
- executionState.renderPass = nullptr;
- executionState.renderPassFramebuffer = nullptr;
-
// Execute (implicit or explicit) VkSubpassDependency to VK_SUBPASS_EXTERNAL
// This is somewhat heavier than the actual ordering required.
executionState.renderer->synchronize();
+
+ // FIXME(sugoi): remove the following line and resolve in Renderer::finishRendering()
+ // for a Draw command or after the last command of the current subpass
+ // which modifies pixels.
+ executionState.renderPassFramebuffer->resolve(executionState.renderPass);
+
+ executionState.renderPass->end();
+ executionState.renderPass = nullptr;
+ executionState.renderPassFramebuffer = nullptr;
}
private:
@@ -506,7 +521,7 @@
void play(CommandBuffer::ExecutionState& executionState) override
{
- executionState.renderPassFramebuffer->clear(attachment, rect);
+ executionState.renderPassFramebuffer->clear(executionState.renderPass, attachment, rect);
}
private:
diff --git a/src/Vulkan/VkFramebuffer.cpp b/src/Vulkan/VkFramebuffer.cpp
index 2b525e7..c5bdfde 100644
--- a/src/Vulkan/VkFramebuffer.cpp
+++ b/src/Vulkan/VkFramebuffer.cpp
@@ -22,7 +22,6 @@
{
Framebuffer::Framebuffer(const VkFramebufferCreateInfo* pCreateInfo, void* mem) :
- renderPass(Cast(pCreateInfo->renderPass)),
attachmentCount(pCreateInfo->attachmentCount),
attachments(reinterpret_cast<ImageView**>(mem))
{
@@ -37,7 +36,7 @@
vk::deallocate(attachments, pAllocator);
}
-void Framebuffer::clear(uint32_t clearValueCount, const VkClearValue* pClearValues, const VkRect2D& renderArea)
+void Framebuffer::clear(const RenderPass* renderPass, uint32_t clearValueCount, const VkClearValue* pClearValues, const VkRect2D& renderArea)
{
ASSERT(attachmentCount == renderPass->getAttachmentCount());
@@ -69,7 +68,7 @@
}
}
-void Framebuffer::clear(const VkClearAttachment& attachment, const VkClearRect& rect)
+void Framebuffer::clear(const RenderPass* renderPass, const VkClearAttachment& attachment, const VkClearRect& rect)
{
if(attachment.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT)
{
@@ -99,6 +98,22 @@
return attachments[index];
}
+void Framebuffer::resolve(const RenderPass* renderPass)
+{
+ VkSubpassDescription subpass = renderPass->getCurrentSubpass();
+ if(subpass.pResolveAttachments)
+ {
+ for(uint32_t i = 0; i < subpass.colorAttachmentCount; i++)
+ {
+ uint32_t resolveAttachment = subpass.pResolveAttachments[i].attachment;
+ if(resolveAttachment != VK_ATTACHMENT_UNUSED)
+ {
+ attachments[subpass.pColorAttachments[i].attachment]->resolve(attachments[resolveAttachment]);
+ }
+ }
+ }
+}
+
size_t Framebuffer::ComputeRequiredAllocationSize(const VkFramebufferCreateInfo* pCreateInfo)
{
return pCreateInfo->attachmentCount * sizeof(void*);
diff --git a/src/Vulkan/VkFramebuffer.hpp b/src/Vulkan/VkFramebuffer.hpp
index c615763..8830bb7 100644
--- a/src/Vulkan/VkFramebuffer.hpp
+++ b/src/Vulkan/VkFramebuffer.hpp
@@ -30,14 +30,14 @@
~Framebuffer() = delete;
void destroy(const VkAllocationCallbacks* pAllocator);
- void clear(uint32_t clearValueCount, const VkClearValue* pClearValues, const VkRect2D& renderArea);
- void clear(const VkClearAttachment& attachment, const VkClearRect& rect);
+ void clear(const RenderPass* renderPass, uint32_t clearValueCount, const VkClearValue* pClearValues, const VkRect2D& renderArea);
+ void clear(const RenderPass* renderPass, const VkClearAttachment& attachment, const VkClearRect& rect);
static size_t ComputeRequiredAllocationSize(const VkFramebufferCreateInfo* pCreateInfo);
ImageView *getAttachment(uint32_t index) const;
+ void resolve(const RenderPass* renderPass);
private:
- RenderPass* renderPass;
uint32_t attachmentCount = 0;
ImageView** attachments = nullptr;
};
diff --git a/src/Vulkan/VkImage.cpp b/src/Vulkan/VkImage.cpp
index 8ac5bf3..0abc1c5 100644
--- a/src/Vulkan/VkImage.cpp
+++ b/src/Vulkan/VkImage.cpp
@@ -52,10 +52,6 @@
samples(pCreateInfo->pCreateInfo->samples),
tiling(pCreateInfo->pCreateInfo->tiling)
{
- if (samples != VK_SAMPLE_COUNT_1_BIT)
- {
- UNIMPLEMENTED("Multisample images not yet supported");
- }
}
void Image::destroy(const VkAllocationCallbacks* pAllocator)
@@ -93,7 +89,7 @@
}
auto aspect = static_cast<VkImageAspectFlagBits>(pSubresource->aspectMask);
pLayout->offset = getMemoryOffset(aspect, pSubresource->mipLevel, pSubresource->arrayLayer);
- pLayout->size = getMipLevelSize(aspect, pSubresource->mipLevel);
+ pLayout->size = getMultiSampledLevelSize(aspect, pSubresource->mipLevel);
pLayout->rowPitch = rowPitchBytes(aspect, pSubresource->mipLevel);
pLayout->depthPitch = slicePitchBytes(aspect, pSubresource->mipLevel);
pLayout->arrayPitch = getLayerSize(aspect);
@@ -123,6 +119,25 @@
UNIMPLEMENTED("dstSubresource");
}
+ if((samples > VK_SAMPLE_COUNT_1_BIT) && (imageType == VK_IMAGE_TYPE_2D) && !format.isNonNormalizedInteger())
+ {
+ // Requires multisampling resolve
+ VkImageBlit region;
+ region.srcSubresource = pRegion.srcSubresource;
+ region.srcOffsets[0] = pRegion.srcOffset;
+ region.srcOffsets[1].x = region.srcOffsets[0].x + pRegion.extent.width;
+ region.srcOffsets[1].y = region.srcOffsets[0].y + pRegion.extent.height;
+ region.srcOffsets[1].z = region.srcOffsets[0].z + pRegion.extent.depth;
+
+ region.dstSubresource = pRegion.dstSubresource;
+ region.dstOffsets[0] = pRegion.dstOffset;
+ region.dstOffsets[1].x = region.dstOffsets[0].x + pRegion.extent.width;
+ region.dstOffsets[1].y = region.dstOffsets[0].y + pRegion.extent.height;
+ region.dstOffsets[1].z = region.dstOffsets[0].z + pRegion.extent.depth;
+
+ return device->getBlitter()->blit(this, dst, region, VK_FILTER_NEAREST);
+ }
+
VkImageAspectFlagBits srcAspect = static_cast<VkImageAspectFlagBits>(pRegion.srcSubresource.aspectMask);
VkImageAspectFlagBits dstAspect = static_cast<VkImageAspectFlagBits>(pRegion.dstSubresource.aspectMask);
@@ -239,10 +254,7 @@
Buffer* buffer = Cast(buf);
uint8_t* bufferMemory = static_cast<uint8_t*>(buffer->getOffsetPointer(region.bufferOffset));
- uint8_t* imageMemory = static_cast<uint8_t*>(deviceMemory->getOffsetPointer(
- getMemoryOffset(aspect, region.imageSubresource.mipLevel,
- region.imageSubresource.baseArrayLayer) +
- texelOffsetBytesInStorage(region.imageOffset, region.imageSubresource)));
+ uint8_t* imageMemory = static_cast<uint8_t*>(getTexelPointer(region.imageOffset, region.imageSubresource));
uint8_t* srcMemory = bufferIsSource ? bufferMemory : imageMemory;
uint8_t* dstMemory = bufferIsSource ? imageMemory : bufferMemory;
@@ -467,7 +479,7 @@
VkDeviceSize offset = getMemoryOffset(aspect);
for(uint32_t i = 0; i < mipLevel; ++i)
{
- offset += getMipLevelSize(aspect, i);
+ offset += getMultiSampledLevelSize(aspect, i);
}
return offset;
}
@@ -482,13 +494,18 @@
return getMipLevelExtent(mipLevel).depth * slicePitchBytes(aspect, mipLevel);
}
+VkDeviceSize Image::getMultiSampledLevelSize(VkImageAspectFlagBits aspect, uint32_t mipLevel) const
+{
+ return getMipLevelSize(aspect, mipLevel) * samples;
+}
+
VkDeviceSize Image::getLayerSize(VkImageAspectFlagBits aspect) const
{
VkDeviceSize layerSize = 0;
for(uint32_t mipLevel = 0; mipLevel < mipLevels; ++mipLevel)
{
- layerSize += getMipLevelSize(aspect, mipLevel);
+ layerSize += getMultiSampledLevelSize(aspect, mipLevel);
}
return layerSize;
diff --git a/src/Vulkan/VkImage.hpp b/src/Vulkan/VkImage.hpp
index 07f284b..cee6b8e 100644
--- a/src/Vulkan/VkImage.hpp
+++ b/src/Vulkan/VkImage.hpp
@@ -70,6 +70,7 @@
void copy(VkBuffer buffer, const VkBufferImageCopy& region, bool bufferIsSource);
VkDeviceSize getStorageSize(VkImageAspectFlags flags) const;
VkDeviceSize getMipLevelSize(VkImageAspectFlagBits aspect, uint32_t mipLevel) const;
+ VkDeviceSize getMultiSampledLevelSize(VkImageAspectFlagBits aspect, uint32_t mipLevel) const;
VkDeviceSize getLayerSize(VkImageAspectFlagBits aspect) const;
VkDeviceSize getMemoryOffset(VkImageAspectFlagBits aspect, uint32_t mipLevel) const;
VkDeviceSize getMemoryOffset(VkImageAspectFlagBits aspect, uint32_t mipLevel, uint32_t layer) const;
diff --git a/src/Vulkan/VkImageView.cpp b/src/Vulkan/VkImageView.cpp
index ca73f5d..1d7bcab 100644
--- a/src/Vulkan/VkImageView.cpp
+++ b/src/Vulkan/VkImageView.cpp
@@ -121,6 +121,35 @@
image->clear(clearValue, renderArea.rect, sr);
}
+void ImageView::resolve(ImageView* resolveAttachment)
+{
+ if((subresourceRange.levelCount != 1) || (resolveAttachment->subresourceRange.levelCount != 1))
+ {
+ UNIMPLEMENTED("levelCount");
+ }
+
+ VkImageCopy region;
+ region.srcSubresource =
+ {
+ subresourceRange.aspectMask,
+ subresourceRange.baseMipLevel,
+ subresourceRange.baseArrayLayer,
+ subresourceRange.layerCount
+ };
+ region.srcOffset = { 0, 0, 0 };
+ region.dstSubresource =
+ {
+ resolveAttachment->subresourceRange.aspectMask,
+ resolveAttachment->subresourceRange.baseMipLevel,
+ resolveAttachment->subresourceRange.baseArrayLayer,
+ resolveAttachment->subresourceRange.layerCount
+ };
+ region.dstOffset = { 0, 0, 0 };
+ region.extent = image->getMipLevelExtent(subresourceRange.baseMipLevel);
+
+ image->copyTo(*(resolveAttachment->image), region);
+}
+
void *ImageView::getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect) const
{
VkImageSubresourceLayers imageSubresourceLayers =
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index 1b58179..5466d1e 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -34,6 +34,7 @@
void clear(const VkClearValue& clearValues, VkImageAspectFlags aspectMask, const VkRect2D& renderArea);
void clear(const VkClearValue& clearValue, VkImageAspectFlags aspectMask, const VkClearRect& renderArea);
+ void resolve(ImageView* resolveAttachment);
Format getFormat() const { return format; }
int getSampleCount() const { return image->getSampleCountFlagBits(); }