blob: 72aad0f59d6084facb67852da0564f9d8b2d634c [file] [log] [blame]
// 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