| // Copyright 2017 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/machina/framebuffer_scanout.h" |
| |
| #include <fcntl.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <lib/framebuffer/framebuffer.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/images/cpp/images.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| |
| static constexpr fuchsia::images::PixelFormat kTargetPixelFormat = |
| fuchsia::images::PixelFormat::BGRA_8; |
| static const size_t kTargetPixelFormatSize = |
| images::BitsPerPixel(kTargetPixelFormat) / CHAR_BIT; |
| |
| // NOTE(abdulla): These functions are lightly modified versions of the same |
| // functions in the Zircon GFX library. |
| |
| static void argb8888_to_rgb565(uint8_t* dst, const uint8_t* src, size_t size) { |
| for (size_t i = 0; i < size; i += 4, dst += 2, src += 4) { |
| uint32_t in = *reinterpret_cast<const uint32_t*>(src); |
| uint16_t out = (in >> 3) & 0x1f; // b |
| out |= ((in >> 10) & 0x3f) << 5; // g |
| out |= ((in >> 19) & 0x1f) << 11; // r |
| *reinterpret_cast<uint16_t*>(dst) = out; |
| } |
| } |
| |
| static void argb8888_to_rgb332(uint8_t* dst, const uint8_t* src, size_t size) { |
| for (size_t i = 0; i < size; i += 4, dst += 1, src += 4) { |
| uint32_t in = *reinterpret_cast<const uint32_t*>(src); |
| uint8_t out = (in >> 6) & 0x3; // b |
| out |= ((in >> 13) & 0x7) << 2; // g |
| out |= ((in >> 21) & 0x7) << 5; // r |
| *dst = out; |
| } |
| } |
| |
| static void argb8888_to_rgb2220(uint8_t* dst, const uint8_t* src, size_t size) { |
| for (size_t i = 0; i < size; i += 4, dst += 1, src += 4) { |
| uint32_t in = *reinterpret_cast<const uint32_t*>(src); |
| uint8_t out = ((in >> 6) & 0x3) << 2; // b |
| out |= ((in >> 14) & 0x3) << 4; // g |
| out |= ((in >> 22) & 0x3) << 6; // r |
| *dst = out; |
| } |
| } |
| |
| static void argb8888_to_luma(uint8_t* dst, const uint8_t* src, size_t size) { |
| for (size_t i = 0; i < size; i += 4, dst += 1, src += 4) { |
| uint32_t in = *reinterpret_cast<const uint32_t*>(src); |
| uint32_t b = (in & 0xff) * 74; |
| uint32_t g = ((in >> 8) & 0xff) * 732; |
| uint32_t r = ((in >> 16) & 0xff) * 218; |
| uint32_t intensity = r + b + g; |
| *dst = (intensity >> 10) & 0xff; |
| } |
| } |
| |
| static void copy(uint8_t* dst, const uint8_t* src, size_t size, |
| zx_pixel_format_t format) { |
| switch (format) { |
| case ZX_PIXEL_FORMAT_ARGB_8888: |
| case ZX_PIXEL_FORMAT_RGB_x888: |
| return static_cast<void>(memcpy(dst, src, size)); |
| case ZX_PIXEL_FORMAT_RGB_565: |
| return argb8888_to_rgb565(dst, src, size); |
| case ZX_PIXEL_FORMAT_RGB_332: |
| return argb8888_to_rgb332(dst, src, size); |
| case ZX_PIXEL_FORMAT_RGB_2220: |
| return argb8888_to_rgb2220(dst, src, size); |
| case ZX_PIXEL_FORMAT_GRAY_8: |
| return argb8888_to_luma(dst, src, size); |
| default: |
| ZX_DEBUG_ASSERT(false); |
| } |
| } |
| |
| namespace machina { |
| |
| // Create a scanout that owns a zircon framebuffer device. |
| zx_status_t FramebufferScanout::Create( |
| GpuScanout* scanout, std::unique_ptr<FramebufferScanout>* out) { |
| *out = nullptr; |
| |
| const char* err; |
| zx_status_t status = fb_bind(true, &err); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to bind to framebuffer: " << err << "\n"; |
| return status; |
| } |
| uint32_t width; |
| uint32_t height; |
| uint32_t stride; |
| zx_pixel_format_t format; |
| |
| fb_get_config(&width, &height, &stride, &format); |
| |
| // Map framebuffer VMO. |
| uintptr_t fbo; |
| size_t size = stride * ZX_PIXEL_FORMAT_BYTES(format) * height; |
| status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, |
| 0, fb_get_single_buffer(), 0, size, &fbo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| auto buf = reinterpret_cast<uint8_t*>(fbo); |
| memset(buf, 0, size); |
| zx_cache_flush(buf, size, ZX_CACHE_FLUSH_DATA); |
| |
| // Create a compatible buffer if necessary. |
| zx::vmo compatible_vmo; |
| size_t compatible_vmo_size = 0; |
| uintptr_t compatible_vmo_data = 0; |
| bool direct_rendering_ok = |
| format == ZX_PIXEL_FORMAT_ARGB_8888 || format == ZX_PIXEL_FORMAT_RGB_x888; |
| if (direct_rendering_ok) { |
| FXL_LOG(INFO) << "Framebuffer pixel format compatible with scanout - " |
| "direct rendering will occur"; |
| } else { |
| FXL_LOG(WARNING) << "Framebuffer pixel format not compatible with scanout " |
| "- conversion will occur"; |
| compatible_vmo_size = width * height * kTargetPixelFormatSize; |
| zx_status_t status = |
| zx::vmo::create(compatible_vmo_size, 0, &compatible_vmo); |
| FXL_CHECK(status == ZX_OK) |
| << "Failed to create guest-compatible VMO " << status; |
| status = zx::vmar::root_self()->map( |
| 0, compatible_vmo, 0, compatible_vmo_size, |
| ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE, &compatible_vmo_data); |
| FXL_CHECK(status == ZX_OK) |
| << "Failed to map guest-compatible VMO " << status; |
| } |
| |
| std::unique_ptr<FramebufferScanout> fbscanout(new FramebufferScanout()); |
| fbscanout->scanout_ = scanout; |
| fbscanout->framebuffer_width_ = width; |
| fbscanout->framebuffer_height_ = height; |
| fbscanout->framebuffer_linear_stride_px_ = stride; |
| fbscanout->framebuffer_format_ = format; |
| fbscanout->buf_ = buf; |
| fbscanout->direct_rendering_ok_ = direct_rendering_ok; |
| fbscanout->compatible_vmo_ = std::move(compatible_vmo); |
| fbscanout->compatible_vmo_size_ = compatible_vmo_size; |
| fbscanout->compatible_vmo_data_ = compatible_vmo_data; |
| if (direct_rendering_ok) { |
| scanout->SetFlushTarget(zx::vmo(fb_get_single_buffer()), size, width, |
| height, stride * kTargetPixelFormatSize); |
| } else { |
| zx::vmo scanout_vmo; |
| fbscanout->compatible_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &scanout_vmo); |
| scanout->SetFlushTarget(std::move(scanout_vmo), compatible_vmo_size, width, |
| height, width * kTargetPixelFormatSize); |
| } |
| scanout->SetFlushHandler( |
| fit::bind_member(fbscanout.get(), &FramebufferScanout::OnFlush)); |
| |
| *out = std::move(fbscanout); |
| return ZX_OK; |
| } |
| |
| FramebufferScanout::~FramebufferScanout() { fb_release(); } |
| |
| void FramebufferScanout::OnFlush(virtio_gpu_rect_t rect) { |
| if (!direct_rendering_ok_) { |
| for (uint32_t row = rect.y; row < rect.y + rect.height; ++row) { |
| auto copy_dest = buf_ + (row * framebuffer_linear_stride_px_ + rect.x) * |
| ZX_PIXEL_FORMAT_BYTES(framebuffer_format_); |
| auto copy_source = |
| reinterpret_cast<uint8_t*>(compatible_vmo_data_) + |
| (row * framebuffer_width_ + rect.x) * kTargetPixelFormatSize; |
| size_t copy_source_size = rect.width * kTargetPixelFormatSize; |
| copy(copy_dest, copy_source, copy_source_size, framebuffer_format_); |
| } |
| } else { |
| for (uint32_t row = rect.y; row < rect.y + rect.height; ++row) { |
| uint8_t* ptr = buf_ + (row * framebuffer_linear_stride_px_ + rect.x) * |
| ZX_PIXEL_FORMAT_BYTES(framebuffer_format_); |
| zx_cache_flush(ptr, |
| rect.width * ZX_PIXEL_FORMAT_BYTES(framebuffer_format_), |
| ZX_CACHE_FLUSH_DATA); |
| } |
| } |
| } |
| |
| } // namespace machina |