Fix surface snapshot timing (#4339)

This commit is contained in:
Adrian Cojocaru
2026-06-23 18:41:35 +03:00
committed by GitHub
parent 55175e1d9d
commit 51b142b309
7 changed files with 171 additions and 93 deletions

View File

@@ -149,6 +149,7 @@ public:
const vk::UniquePipelineLayout& getPushConstantPipelineLayout();
uint8_t getCurrentFrameResourceIndex() const { return frameResourceIndex; }
vk::UniqueCommandBuffer& getCommandBuffer() { return frameResources[frameResourceIndex].commandBuffer; }
void enqueueDeletion(DeletionTask&& function);
void submitOneTimeCommand(const std::function<void(const vk::UniqueCommandBuffer&)>& function);
void submitOneTimeCommand(const vk::UniqueCommandBuffer& buffer);

View File

@@ -47,6 +47,7 @@ protected:
void setColorFormat(vk::Format format);
void setDepthFormat(vk::Format format);
void copySurfaceToReadTexture();
void swap() override;
public:
@@ -77,7 +78,7 @@ public:
void init(uint32_t w, uint32_t h);
void recreateSwapchain();
void enableSurfaceRead(bool value = true);
void queueSurfaceRead();
std::shared_ptr<PremultipliedImage> readImage();
protected:
@@ -104,6 +105,7 @@ protected:
int32_t surfaceTransformPollingInterval{-1};
bool surfaceRead{false};
std::unique_ptr<Texture2D> readTexture{nullptr};
};
class Renderable : public gfx::Renderable {

View File

@@ -87,7 +87,17 @@ public:
const vk::Sampler& getVulkanSampler();
void copyImage(vk::Image image, Size imageSize, uint16_t xOffset = 0, uint16_t yOffset = 0);
void copyImage(vk::Image image,
Size imageSize,
uint16_t xOffset,
uint16_t yOffset,
const vk::UniqueCommandBuffer& commandBuffer);
void blitImage(vk::Image image, Size imageSize, uint16_t xOffset = 0, uint16_t yOffset = 0);
void blitImage(vk::Image image,
Size imageSize,
uint16_t xOffset,
uint16_t yOffset,
const vk::UniqueCommandBuffer& commandBuffer);
std::shared_ptr<PremultipliedImage> readImage();
private:

View File

@@ -83,11 +83,23 @@ void AndroidVulkanRendererBackend::resizeFramebuffer(int, int) {
}
}
void AndroidVulkanRendererBackend::enableFramebufferRead(bool value) {
getResource<AndroidVulkanRenderableResource>().enableSurfaceRead();
void AndroidVulkanRendererBackend::enableFramebufferRead(bool) {
MBGL_VERIFY_THREAD(tid);
if (!hasResource()) {
return;
}
getResource<AndroidVulkanRenderableResource>().queueSurfaceRead();
}
PremultipliedImage AndroidVulkanRendererBackend::readFramebuffer() {
MBGL_VERIFY_THREAD(tid);
if (!hasResource()) {
return PremultipliedImage();
}
return std::move(*getResource<AndroidVulkanRenderableResource>().readImage());
}

View File

@@ -41,6 +41,8 @@ protected:
protected:
ANativeWindow* window;
MBGL_STORE_THREAD(tid);
};
} // namespace android

View File

@@ -27,6 +27,8 @@ SurfaceRenderableResource::~SurfaceRenderableResource() {
depthAllocation.reset();
colorAllocations.clear();
readTexture.reset();
}
void SurfaceRenderableResource::initColor(uint32_t w, uint32_t h) {
@@ -346,7 +348,17 @@ void SurfaceRenderableResource::setDepthFormat(vk::Format format) {
void SurfaceRenderableResource::swap() {
auto& context = static_cast<Context&>(backend.getContext());
if (surfaceRead) {
copySurfaceToReadTexture();
}
context.submitFrame();
if (surfaceRead) {
context.waitFrame();
surfaceRead = false;
}
}
const vk::Image SurfaceRenderableResource::getAcquiredImage() const {
@@ -460,89 +472,106 @@ void SurfaceRenderableResource::recreateSwapchain() {
acquireSemaphores.clear();
presentSemaphores.clear();
readTexture.reset();
init(extent.width, extent.height);
}
void SurfaceRenderableResource::enableSurfaceRead(bool value) {
if (surfaceRead == value) {
void SurfaceRenderableResource::queueSurfaceRead() {
if (surfaceRead) {
return;
}
surfaceRead = value;
surfaceRead = true;
backend.getContext<Context>().requestSurfaceUpdate(false);
}
std::shared_ptr<PremultipliedImage> SurfaceRenderableResource::readImage() {
void SurfaceRenderableResource::copySurfaceToReadTexture() {
auto& contextImpl = backend.getContext<Context>();
const auto& dispatcher = backend.getDispatcher();
const auto& physicalDevice = backend.getPhysicalDevice();
auto texture = std::make_unique<Texture2D>(contextImpl);
texture->setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte);
texture->setUsage(Texture2DUsage::Read);
texture->setSize({extent.width, extent.height});
if (!readTexture) {
readTexture = std::make_unique<Texture2D>(contextImpl);
readTexture->setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte);
readTexture->setUsage(Texture2DUsage::Read);
}
contextImpl.waitFrame();
readTexture->setSize({extent.width, extent.height});
const auto swapchainImage = getAcquiredImage();
auto& commandBuffer = contextImpl.getCommandBuffer();
if (surface) {
contextImpl.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& buffer) {
const auto barrier = vk::ImageMemoryBarrier()
.setImage(swapchainImage)
.setOldLayout(vk::ImageLayout::ePresentSrcKHR)
.setNewLayout(vk::ImageLayout::eTransferSrcOptimal)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eTransferWrite)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
const auto barrier = vk::ImageMemoryBarrier()
.setImage(swapchainImage)
.setOldLayout(vk::ImageLayout::ePresentSrcKHR)
.setNewLayout(vk::ImageLayout::eTransferSrcOptimal)
.setSrcAccessMask({})
.setDstAccessMask(vk::AccessFlagBits::eTransferWrite)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer,
{},
nullptr,
nullptr,
barrier,
dispatcher);
});
commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer,
{},
nullptr,
nullptr,
barrier,
dispatcher);
}
bool useBlit = surface &&
(physicalDevice.getFormatProperties(colorFormat, dispatcher).optimalTilingFeatures &
vk::FormatFeatureFlagBits::eBlitSrc &&
physicalDevice.getFormatProperties(texture->getVulkanFormat(), dispatcher).linearTilingFeatures &
vk::FormatFeatureFlagBits::eBlitDst);
bool useBlit =
surface &&
(physicalDevice.getFormatProperties(colorFormat, dispatcher).optimalTilingFeatures &
vk::FormatFeatureFlagBits::eBlitSrc &&
physicalDevice.getFormatProperties(readTexture->getVulkanFormat(), dispatcher).linearTilingFeatures &
vk::FormatFeatureFlagBits::eBlitDst);
if (useBlit) {
texture->blitImage(swapchainImage, texture->getSize());
readTexture->blitImage(swapchainImage, readTexture->getSize(), 0, 0, commandBuffer);
} else {
texture->copyImage(swapchainImage, texture->getSize());
readTexture->copyImage(swapchainImage, readTexture->getSize(), 0, 0, commandBuffer);
}
if (surface) {
contextImpl.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& buffer) {
const auto barrier = vk::ImageMemoryBarrier()
.setImage(swapchainImage)
.setOldLayout(vk::ImageLayout::eTransferSrcOptimal)
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
.setSrcAccessMask(vk::AccessFlagBits::eMemoryRead)
.setDstAccessMask(vk::AccessFlagBits::eTransferWrite)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
const auto barrier = vk::ImageMemoryBarrier()
.setImage(swapchainImage)
.setOldLayout(vk::ImageLayout::eTransferSrcOptimal)
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
.setSrcAccessMask(vk::AccessFlagBits::eMemoryRead)
.setDstAccessMask(vk::AccessFlagBits::eTransferWrite)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer,
{},
nullptr,
nullptr,
barrier,
dispatcher);
});
commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer,
{},
nullptr,
nullptr,
barrier,
dispatcher);
}
}
std::shared_ptr<PremultipliedImage> SurfaceRenderableResource::readImage() {
if (!readTexture) {
return nullptr;
}
const auto image = texture->readImage();
const auto& dispatcher = backend.getDispatcher();
const auto& physicalDevice = backend.getPhysicalDevice();
bool useBlit =
surface &&
(physicalDevice.getFormatProperties(colorFormat, dispatcher).optimalTilingFeatures &
vk::FormatFeatureFlagBits::eBlitSrc &&
physicalDevice.getFormatProperties(readTexture->getVulkanFormat(), dispatcher).linearTilingFeatures &
vk::FormatFeatureFlagBits::eBlitDst);
const auto image = readTexture->readImage();
// swizzle pixels to RGBA
if (!useBlit && colorFormat == vk::Format::eB8G8R8A8Unorm) {

View File

@@ -558,55 +558,77 @@ const vk::Sampler& Texture2D::getVulkanSampler() {
}
void Texture2D::copyImage(vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset) {
if (!image) return;
create();
if (!image) {
return;
}
context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) {
const auto copyInfo = vk::ImageCopy()
.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setExtent({imageSize.width, imageSize.height, 1})
.setDstOffset({xOffset, yOffset, 0});
transitionToTransferWriteLayout(commandBuffer);
commandBuffer->copyImage(image,
vk::ImageLayout::eTransferSrcOptimal,
imageAllocation->image,
imageLayout,
copyInfo,
context.getBackend().getDispatcher());
transitionToGeneralLayout(commandBuffer);
copyImage(image, imageSize, xOffset, yOffset, commandBuffer);
});
}
void Texture2D::blitImage(vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset) {
if (!image) return;
void Texture2D::copyImage(
vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset, const vk::UniqueCommandBuffer& commandBuffer) {
if (!image) {
return;
}
create();
context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) {
const std::array<vk::Offset3D, 2> offsets = {
vk::Offset3D{xOffset, yOffset, 0},
{static_cast<int32_t>(imageSize.width), static_cast<int32_t>(imageSize.height), 1}};
const auto blitInfo = vk::ImageBlit()
.setSrcOffsets(offsets)
.setDstOffsets(offsets)
.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1});
const auto copyInfo = vk::ImageCopy()
.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setExtent({imageSize.width, imageSize.height, 1})
.setDstOffset({xOffset, yOffset, 0});
transitionToTransferWriteLayout(commandBuffer);
commandBuffer->blitImage(image,
vk::ImageLayout::eTransferSrcOptimal,
imageAllocation->image,
imageLayout,
blitInfo,
vk::Filter::eNearest,
context.getBackend().getDispatcher());
transitionToGeneralLayout(commandBuffer);
transitionToTransferWriteLayout(commandBuffer);
commandBuffer->copyImage(image,
vk::ImageLayout::eTransferSrcOptimal,
imageAllocation->image,
imageLayout,
copyInfo,
context.getBackend().getDispatcher());
transitionToGeneralLayout(commandBuffer);
}
void Texture2D::blitImage(vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset) {
if (!image) {
return;
}
context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) {
blitImage(image, imageSize, xOffset, yOffset, commandBuffer);
});
}
void Texture2D::blitImage(
vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset, const vk::UniqueCommandBuffer& commandBuffer) {
if (!image) {
return;
}
create();
const std::array<vk::Offset3D, 2> offsets = {
vk::Offset3D{xOffset, yOffset, 0},
{static_cast<int32_t>(imageSize.width), static_cast<int32_t>(imageSize.height), 1}};
const auto blitInfo = vk::ImageBlit()
.setSrcOffsets(offsets)
.setDstOffsets(offsets)
.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1});
transitionToTransferWriteLayout(commandBuffer);
commandBuffer->blitImage(image,
vk::ImageLayout::eTransferSrcOptimal,
imageAllocation->image,
imageLayout,
blitInfo,
vk::Filter::eNearest,
context.getBackend().getDispatcher());
transitionToGeneralLayout(commandBuffer);
}
std::shared_ptr<PremultipliedImage> Texture2D::readImage() {
if (!imageData) {
imageData = std::make_shared<PremultipliedImage>();