Implement Vulkan surface snapshot (#4314)

This commit is contained in:
Adrian Cojocaru
2026-06-06 02:06:40 +03:00
committed by GitHub
parent 8a393c5b2c
commit 9fa80b37d2
9 changed files with 147 additions and 8 deletions

View File

@@ -55,7 +55,8 @@ public:
const vk::UniqueSurfaceKHR& getPlatformSurface() const { return surface; }
const vk::UniqueSwapchainKHR& getSwapchain() const { return swapchain; }
const vk::UniqueFramebuffer& getFramebuffer() const override;
const vk::UniqueFramebuffer& getFramebuffer() const override { return swapchainFramebuffers[acquiredImageIndex]; }
vk::Format getColorFormat() const { return colorFormat; }
uint32_t getImageCount() const { return static_cast<uint32_t>(swapchainFramebuffers.size()); };
uint32_t getAcquiredImageIndex() const { return acquiredImageIndex; };
@@ -76,6 +77,9 @@ public:
void init(uint32_t w, uint32_t h);
void recreateSwapchain();
void enableSurfaceRead(bool value = true);
std::shared_ptr<PremultipliedImage> readImage();
protected:
vk::UniqueSurfaceKHR surface;
vk::UniqueSwapchainKHR swapchain;
@@ -99,6 +103,7 @@ protected:
vk::Format depthFormat{vk::Format::eUndefined};
int32_t surfaceTransformPollingInterval{-1};
bool surfaceRead{false};
};
class Renderable : public gfx::Renderable {

View File

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

View File

@@ -31,6 +31,7 @@ public:
virtual void destroySurface() {}
virtual void resizeFramebuffer(int width, int height);
virtual void enableFramebufferRead(bool value) {}
virtual PremultipliedImage readFramebuffer();
gfx::Renderable::SwapBehaviour getSwapBehavior() const { return swapBehaviour; }

View File

@@ -83,9 +83,12 @@ void AndroidVulkanRendererBackend::resizeFramebuffer(int, int) {
}
}
void AndroidVulkanRendererBackend::enableFramebufferRead(bool value) {
getResource<AndroidVulkanRenderableResource>().enableSurfaceRead();
}
PremultipliedImage AndroidVulkanRendererBackend::readFramebuffer() {
// TODO not implemented
return PremultipliedImage(Size(2, 2));
return std::move(*getResource<AndroidVulkanRenderableResource>().readImage());
}
} // namespace android

View File

@@ -24,6 +24,7 @@ public:
std::vector<const char*> getInstanceExtensions() override;
void resizeFramebuffer(int width, int height) override;
void enableFramebufferRead(bool value) override;
PremultipliedImage readFramebuffer() override;
// mbgl::gfx::RendererBackend implementation

View File

@@ -207,6 +207,12 @@ void MapRenderer::resetRenderer() {
void MapRenderer::scheduleSnapshot(std::unique_ptr<SnapshotCallback> callback) {
snapshotCallback = std::move(callback);
if (backend) {
gfx::BackendScope backendGuard{backend->getImpl()};
backend->enableFramebufferRead(true);
}
requestRender();
}

View File

@@ -977,7 +977,7 @@ void GLFWView::makeSnapshot(bool withOverlay) {
oss << "Made snapshot './snapshot.png' with size w:" << image.size.width << "px h:" << image.size.height
<< "px";
mbgl::Log::Info(mbgl::Event::General, oss.str());
std::ofstream file("./snapshot.png");
std::ofstream file("./snapshot.png", std::ios::out | std::ios::binary);
file << mbgl::encodePNG(image);
} else {
mbgl::Log::Error(mbgl::Event::General, "Failed to make a snapshot!");

View File

@@ -92,9 +92,17 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) {
}
}
// pick surface size
vk::ImageUsageFlags imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.get(), dispatcher);
// when reading from the swapchain disable pre-rotation and enable source transfer usage
if (surfaceRead) {
capabilities.currentTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity;
imageUsage |= vk::ImageUsageFlagBits::eTransferSrc;
}
// pick surface size
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
extent = capabilities.currentExtent;
} else {
@@ -124,7 +132,7 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) {
.setPresentMode(presentMode)
.setImageExtent(extent)
.setImageArrayLayers(1)
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment);
.setImageUsage(imageUsage);
int32_t graphicsQueueIndex = backend.getGraphicsQueueIndex();
int32_t presentQueueIndex = backend.getPresentQueueIndex();
@@ -449,8 +457,95 @@ void SurfaceRenderableResource::recreateSwapchain() {
init(extent.width, extent.height);
}
const vk::UniqueFramebuffer& SurfaceRenderableResource::getFramebuffer() const {
return swapchainFramebuffers[acquiredImageIndex];
void SurfaceRenderableResource::enableSurfaceRead(bool value) {
if (surfaceRead == value) {
return;
}
surfaceRead = value;
backend.getContext<Context>().requestSurfaceUpdate(false);
}
std::shared_ptr<PremultipliedImage> SurfaceRenderableResource::readImage() {
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});
contextImpl.waitFrame();
const auto swapchainImage = getAcquiredImage();
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});
buffer->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);
if (useBlit) {
texture->blitImage(swapchainImage, texture->getSize());
} else {
texture->copyImage(swapchainImage, texture->getSize());
}
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});
buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTransfer,
{},
nullptr,
nullptr,
barrier,
dispatcher);
});
}
const auto image = texture->readImage();
// swizzle pixels to RGBA
if (!useBlit && colorFormat == vk::Format::eB8G8R8A8Unorm) {
for (size_t i = 0, size = image->size.area(); i < size; ++i) {
std::swap(image->data[i * image->channels + 0], image->data[i * image->channels + 2]);
}
}
return image;
}
} // namespace vulkan

View File

@@ -580,6 +580,33 @@ void Texture2D::copyImage(vk::Image image, Size imageSize, uint16_t xOffset, uin
});
}
void Texture2D::blitImage(vk::Image image, Size imageSize, uint16_t xOffset, uint16_t yOffset) {
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});
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>();