| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "garnet/lib/ui/gfx/screenshotter.h" |
| |
| #include <functional> |
| #include <utility> |
| #include <vector> |
| |
| #include <lib/zx/time.h> |
| |
| #include "garnet/lib/ui/gfx/engine/engine_renderer.h" |
| #include "garnet/lib/ui/gfx/resources/compositor/compositor.h" |
| #include "garnet/lib/ui/gfx/resources/compositor/layer.h" |
| #include "garnet/lib/ui/gfx/util/time.h" |
| #include "garnet/lib/ui/scenic/scenic.h" |
| #include "lib/escher/impl/command_buffer.h" |
| #include "lib/escher/impl/command_buffer_pool.h" |
| #include "lib/escher/impl/image_cache.h" |
| #include "lib/fsl/vmo/sized_vmo.h" |
| #include "lib/fsl/vmo/vector.h" |
| #include "lib/fxl/functional/make_copyable.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| // static |
| void Screenshotter::OnCommandBufferDone( |
| const escher::ImagePtr& image, uint32_t width, uint32_t height, |
| vk::Device device, |
| fuchsia::ui::scenic::Scenic::TakeScreenshotCallback done_callback) { |
| // Map the final image so CPU can read it. |
| const vk::ImageSubresource sr(vk::ImageAspectFlagBits::eColor, 0, 0); |
| vk::SubresourceLayout sr_layout; |
| device.getImageSubresourceLayout(image->vk(), &sr, &sr_layout); |
| |
| constexpr uint32_t kBytesPerPixel = 4u; |
| std::vector<uint8_t> imgvec; |
| const size_t kImgVecElementSize = sizeof(decltype(imgvec)::value_type); |
| imgvec.resize(kBytesPerPixel * width * height); |
| |
| const uint8_t* row = image->memory()->mapped_ptr(); |
| FXL_CHECK(row != nullptr); |
| row += sr_layout.offset; |
| if (width == sr_layout.rowPitch) { |
| uint32_t num_bytes = width * height * kBytesPerPixel; |
| FXL_DCHECK(num_bytes <= kImgVecElementSize * imgvec.size()); |
| memcpy(imgvec.data(), row, num_bytes); |
| } else { |
| uint8_t* imgvec_ptr = imgvec.data(); |
| for (uint32_t y = 0; y < height; y++) { |
| uint32_t num_bytes = width * kBytesPerPixel; |
| FXL_DCHECK(num_bytes <= |
| kImgVecElementSize * (1 + &imgvec.back() - imgvec_ptr)); |
| memcpy(imgvec_ptr, row, num_bytes); |
| row += sr_layout.rowPitch; |
| imgvec_ptr += num_bytes; |
| } |
| } |
| |
| fsl::SizedVmo sized_vmo; |
| if (!fsl::VmoFromVector(imgvec, &sized_vmo)) { |
| done_callback(fuchsia::ui::scenic::ScreenshotData{}, false); |
| } |
| |
| fuchsia::ui::scenic::ScreenshotData data; |
| data.data = std::move(sized_vmo).ToTransport(); |
| data.info.width = width; |
| data.info.height = height; |
| data.info.stride = width * kBytesPerPixel; |
| done_callback(std::move(data), true); |
| } |
| |
| void Screenshotter::TakeScreenshot( |
| Engine* engine, |
| fuchsia::ui::scenic::Scenic::TakeScreenshotCallback done_callback) { |
| auto* escher = engine->escher(); |
| Compositor* compositor = engine->GetFirstCompositor(); |
| |
| if (compositor->GetNumDrawableLayers() == 0) { |
| FXL_LOG(ERROR) << "No drawable layers."; |
| done_callback(fuchsia::ui::scenic::ScreenshotData{}, false); |
| return; |
| } |
| uint32_t width; |
| uint32_t height; |
| std::tie(width, height) = compositor->GetBottomLayerSize(); |
| escher::ImageInfo image_info; |
| image_info.format = vk::Format::eB8G8R8A8Unorm; |
| image_info.width = width; |
| image_info.height = height; |
| image_info.usage = vk::ImageUsageFlagBits::eColorAttachment | |
| vk::ImageUsageFlagBits::eSampled; |
| image_info.memory_flags = vk::MemoryPropertyFlagBits::eHostVisible; |
| image_info.tiling = vk::ImageTiling::eLinear; |
| |
| // TODO(ES-7): cache is never trimmed. |
| escher::ImagePtr image = escher->image_cache()->NewImage(image_info); |
| escher::FramePtr frame = escher->NewFrame("Scenic Compositor", 0); |
| |
| std::vector<Layer*> drawable_layers = compositor->GetDrawableLayers(); |
| engine->renderer()->RenderLayers(frame, dispatcher_clock_now(), image, |
| drawable_layers); |
| |
| // TODO(SCN-1096): Nobody signals this semaphore, so there's no point. One |
| // way that it could be used is export it as a zx::event and watch for that to |
| // be signaled instead of adding a completion-callback to the command-buffer. |
| auto frame_done_semaphore = escher::Semaphore::New(escher->vk_device()); |
| frame->EndFrame(frame_done_semaphore, nullptr); |
| |
| // TODO(SCN-1096): instead of submitting another command buffer, this could be |
| // done as part of the same Frame above. |
| vk::Queue queue = escher->command_buffer_pool()->queue(); |
| auto* command_buffer = escher->command_buffer_pool()->GetCommandBuffer(); |
| |
| command_buffer->Submit( |
| queue, |
| fxl::MakeCopyable([image, width, height, device = escher->vk_device(), |
| done_callback = std::move(done_callback)]() mutable { |
| OnCommandBufferDone(image, width, height, device, |
| std::move(done_callback)); |
| })); |
| |
| // Force the command buffer to retire to guarantee that |done_callback| will |
| // be called in a timely fashion. |
| engine->CleanupEscher(); |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |