| // 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 <lib/zx/time.h> |
| #include <trace/event.h> |
| |
| #include <functional> |
| #include <utility> |
| #include <vector> |
| |
| #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/fsl/vmo/sized_vmo.h" |
| #include "lib/fsl/vmo/vector.h" |
| #include "src/lib/files/file.h" |
| #include "src/ui/lib/escher/impl/command_buffer.h" |
| #include "src/ui/lib/escher/impl/command_buffer_pool.h" |
| #include "src/ui/lib/escher/impl/image_cache.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| namespace { |
| |
| // HACK(SCN-1253): The FIDL requires a valid VMO (even in failure cases). |
| fuchsia::ui::scenic::ScreenshotData EmptyScreenshot() { |
| fuchsia::ui::scenic::ScreenshotData screenshot; |
| // TODO(SCN-1253): If we can't create an empty VMO, bail because otherwise the |
| // caller will hang indefinitely. |
| FXL_CHECK(zx::vmo::create(0, 0u, &screenshot.data.vmo) == ZX_OK); |
| return screenshot; |
| } |
| |
| // Function to rotate the array of pixel data depending on the rotation |
| // input given. Values should be multiples of 90. Returns a vector of |
| // the rotated pixels. The width and height are passed by reference and |
| // updated to reflect the new orientation in the event of rotation by |
| // 90 or 270 degrees. All rotation is counterclockwise. |
| // |
| // This may potentially cause some unnecessary bottlenecking since |
| // Scenic is currently single-threaded. In the future we might want to |
| // move this to the root presenter, which runs on a separate process, |
| // or when Scenic eventually becomes multi-threaded, we keep it here and |
| // and run the rotation on a background thread. |
| std::vector<uint8_t> rotate_img_vec(const std::vector<uint8_t>& imgvec, |
| uint32_t& width, uint32_t& height, |
| uint32_t bytes_per_pixel, |
| uint32_t rotation) { |
| // Trace performance. |
| TRACE_DURATION("gfx", "Screenshotter rotate_img_vec"); |
| |
| // Rotation should always be a multiple of 90 degrees. |
| FXL_CHECK(rotation % 90 == 0 && rotation < 360); |
| |
| // Rotation determines which of the width and height |
| // are the inner and outer loop. |
| uint32_t outer = (rotation == 180) ? height : width; |
| uint32_t inner = (rotation == 180) ? width : height; |
| |
| // Code for rotation of 90 degrees, 180 or 270 degrees. |
| std::vector<uint8_t> result; |
| result.reserve(width * height * bytes_per_pixel); |
| |
| for (uint32_t i = 0; i < outer; i++) { |
| for (uint32_t j = 0; j < inner; j++) { |
| // Determine which loop represents x or y. |
| uint32_t x = (rotation == 180) ? j : i; |
| uint32_t y = (rotation == 180) ? i : j; |
| |
| // Take inverse y for 180 or 270 degrees. |
| uint32_t new_y = (rotation == 90) ? y : (height - y - 1); |
| |
| for (uint32_t b = 0; b < bytes_per_pixel; b++) { |
| result.push_back(imgvec[(x + new_y * width) * bytes_per_pixel + b]); |
| } |
| } |
| } |
| |
| // Must reverse width and height of image. |
| if (rotation == 90 || rotation == 270) { |
| std::swap(width, height); |
| } |
| return result; |
| } |
| |
| constexpr vk::Format kImageFormat = vk::Format::eB8G8R8A8Unorm; |
| |
| constexpr uint32_t kBytesPerPixel = 4u; |
| |
| }; // namespace |
| |
| // static |
| void Screenshotter::OnCommandBufferDone( |
| const escher::BufferPtr& buffer, uint32_t width, uint32_t height, |
| uint32_t rotation, |
| fuchsia::ui::scenic::Scenic::TakeScreenshotCallback done_callback) { |
| TRACE_DURATION("gfx", "Screenshotter::OnCommandBufferDone"); |
| |
| std::vector<uint8_t> imgvec; |
| const size_t kImgVecElementSize = sizeof(decltype(imgvec)::value_type); |
| imgvec.resize(kBytesPerPixel * width * height); |
| |
| const uint8_t* row = buffer->host_ptr(); |
| FXL_CHECK(row != nullptr); |
| uint32_t num_bytes = width * height * kBytesPerPixel; |
| FXL_DCHECK(num_bytes <= kImgVecElementSize * imgvec.size()); |
| memcpy(imgvec.data(), row, num_bytes); |
| |
| // Apply rotation of 90, 180 or 270 degrees counterclockwise. |
| if (rotation > 0) { |
| imgvec = rotate_img_vec(imgvec, width, height, kBytesPerPixel, rotation); |
| } |
| |
| fsl::SizedVmo sized_vmo; |
| if (!fsl::VmoFromVector(imgvec, &sized_vmo)) { |
| done_callback(EmptyScreenshot(), 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(); |
| const CompositorWeakPtr& compositor = |
| engine->scene_graph()->first_compositor(); |
| |
| if (!compositor || compositor->GetNumDrawableLayers() == 0) { |
| FXL_LOG(WARNING) |
| << "TakeScreenshot: No drawable layers; returning empty screenshot."; |
| done_callback(EmptyScreenshot(), false); |
| return; |
| } |
| uint32_t width; |
| uint32_t height; |
| std::tie(width, height) = compositor->GetBottomLayerSize(); |
| |
| uint32_t rotation = compositor->layout_rotation(); |
| escher::ImageInfo image_info; |
| image_info.format = kImageFormat; |
| image_info.width = width; |
| image_info.height = height; |
| image_info.usage = vk::ImageUsageFlagBits::eColorAttachment | |
| vk::ImageUsageFlagBits::eTransferSrc; |
| |
| // 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(); |
| |
| escher::BufferPtr buffer = escher->buffer_cache()->NewHostBuffer( |
| width * height * kBytesPerPixel); |
| |
| vk::BufferImageCopy region; |
| region.bufferRowLength = width; |
| region.bufferImageHeight = height; |
| region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; |
| region.imageSubresource.layerCount = 1; |
| region.imageExtent.width = width; |
| region.imageExtent.height = height; |
| region.imageExtent.depth = 1; |
| command_buffer->TransitionImageLayout( |
| image, vk::ImageLayout::eColorAttachmentOptimal, |
| vk::ImageLayout::eTransferSrcOptimal); |
| command_buffer->vk().copyImageToBuffer(image->vk(), |
| vk::ImageLayout::eTransferSrcOptimal, |
| buffer->vk(), 1, ®ion); |
| command_buffer->TransitionImageLayout( |
| image, vk::ImageLayout::eUndefined, |
| vk::ImageLayout::eColorAttachmentOptimal); |
| command_buffer->KeepAlive(image); |
| |
| command_buffer->Submit( |
| queue, [buffer, width, height, rotation, |
| done_callback = std::move(done_callback)]() mutable { |
| OnCommandBufferDone(buffer, width, height, rotation, |
| 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 |