Files
GfxCanvas/linux_fb_display.cpp

215 lines
7.0 KiB
C++

// linux_fb_display.cpp — Linux framebuffer HAL implementation.
#include "linux_fb_display.h"
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
namespace gfx {
// ---------------------------------------------------------------------------
// Construction / destruction
// ---------------------------------------------------------------------------
LinuxFBDisplay::LinuxFBDisplay(int16_t width, int16_t height)
: GfxCanvas(width, height) {}
LinuxFBDisplay::~LinuxFBDisplay() { close(); }
// ---------------------------------------------------------------------------
// Open / close
// ---------------------------------------------------------------------------
bool LinuxFBDisplay::open(const char* device) {
fd_ = ::open(device, O_RDWR);
if (fd_ < 0) {
std::perror("LinuxFBDisplay::open");
return false;
}
if (ioctl(fd_, FBIOGET_VSCREENINFO, &vinfo_) < 0) {
std::perror("FBIOGET_VSCREENINFO");
close();
return false;
}
if (ioctl(fd_, FBIOGET_FSCREENINFO, &finfo_) < 0) {
std::perror("FBIOGET_FSCREENINFO");
close();
return false;
}
bpp_ = vinfo_.bits_per_pixel;
line_len_ = finfo_.line_length;
map_size_ = finfo_.smem_len;
buf_ = static_cast<uint8_t*>(
mmap(nullptr, map_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
if (buf_ == MAP_FAILED) {
std::perror("mmap");
buf_ = nullptr;
close();
return false;
}
return true;
}
void LinuxFBDisplay::close() {
if (buf_) {
munmap(buf_, map_size_);
buf_ = nullptr;
}
if (fd_ >= 0) {
::close(fd_);
fd_ = -1;
}
}
// ---------------------------------------------------------------------------
// Pixel format conversion
// ---------------------------------------------------------------------------
void LinuxFBDisplay::writePixel(uint8_t* dst, Color color) const {
if (bpp_ == 32) {
// XRGB8888 — write directly.
std::memcpy(dst, &color, 4);
} else if (bpp_ == 16) {
// RGB565
uint16_t r = (color >> 19) & 0x1F;
uint16_t g = (color >> 10) & 0x3F;
uint16_t b = (color >> 3) & 0x1F;
uint16_t pixel = (r << 11) | (g << 5) | b;
std::memcpy(dst, &pixel, 2);
} else if (bpp_ == 8) {
// Grayscale — simple luminance approximation.
uint8_t r = (color >> 16) & 0xFF;
uint8_t g = (color >> 8) & 0xFF;
uint8_t b = color & 0xFF;
dst[0] = static_cast<uint8_t>((r * 77 + g * 150 + b * 29) >> 8);
}
}
// ---------------------------------------------------------------------------
// drawPixelImpl
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawPixelImpl(int16_t x, int16_t y, Color color) {
if (!buf_) return;
uint32_t offset = static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * (bpp_ / 8);
writePixel(buf_ + offset, color);
}
// ---------------------------------------------------------------------------
// Accelerated horizontal line — memset for monochrome, loop for color
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawHLineImpl(int16_t x, int16_t y,
int16_t w, Color color) {
if (!buf_ || w <= 0) return;
uint32_t bytes_pp = bpp_ / 8;
uint8_t* row = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
if (bpp_ == 32) {
// For XRGB8888, use 32-bit fill.
auto* p = reinterpret_cast<uint32_t*>(row);
for (int16_t i = 0; i < w; ++i)
p[i] = color;
} else if (bpp_ == 16) {
uint16_t r = (color >> 19) & 0x1F;
uint16_t g = (color >> 10) & 0x3F;
uint16_t b = (color >> 3) & 0x1F;
uint16_t pixel = (r << 11) | (g << 5) | b;
auto* p = reinterpret_cast<uint16_t*>(row);
for (int16_t i = 0; i < w; ++i)
p[i] = pixel;
} else if (bpp_ == 8) {
uint8_t r_ch = (color >> 16) & 0xFF;
uint8_t g_ch = (color >> 8) & 0xFF;
uint8_t b_ch = color & 0xFF;
uint8_t gray = static_cast<uint8_t>(
(r_ch * 77 + g_ch * 150 + b_ch * 29) >> 8);
std::memset(row, gray, static_cast<size_t>(w));
}
}
// ---------------------------------------------------------------------------
// Accelerated vertical line
// ---------------------------------------------------------------------------
void LinuxFBDisplay::drawVLineImpl(int16_t x, int16_t y,
int16_t h, Color color) {
if (!buf_ || h <= 0) return;
uint32_t bytes_pp = bpp_ / 8;
uint8_t* p = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
// Pre-encode the pixel once.
uint8_t pixel_buf[4];
writePixel(pixel_buf, color);
for (int16_t i = 0; i < h; ++i) {
std::memcpy(p, pixel_buf, bytes_pp);
p += line_len_;
}
}
// ---------------------------------------------------------------------------
// Accelerated filled rectangle
// ---------------------------------------------------------------------------
void LinuxFBDisplay::fillRectImpl(int16_t x, int16_t y,
int16_t w, int16_t h, Color color) {
if (!buf_ || w <= 0 || h <= 0) return;
// For 32-bpp with color == black or white, we can use memset for the
// entire rectangle. Otherwise, fill the first row with drawHLineImpl
// and memcpy it to subsequent rows.
uint32_t bytes_pp = bpp_ / 8;
uint32_t row_bytes = static_cast<uint32_t>(w) * bytes_pp;
uint8_t* first_row = buf_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * bytes_pp;
// Optimized path: if all bytes of the pixel are the same value,
// the entire rect can be filled with memset.
bool can_memset = false;
uint8_t fill_byte = 0;
if (bpp_ == 32) {
// 0x00000000 → all bytes 0x00. 0x00FFFFFF → not uniform.
// Only pure black (all 0x00) qualifies.
if (color == 0x00000000) { can_memset = true; fill_byte = 0x00; }
} else if (bpp_ == 8) {
uint8_t r_ch = (color >> 16) & 0xFF;
uint8_t g_ch = (color >> 8) & 0xFF;
uint8_t b_ch = color & 0xFF;
fill_byte = static_cast<uint8_t>(
(r_ch * 77 + g_ch * 150 + b_ch * 29) >> 8);
can_memset = true;
}
if (can_memset) {
uint8_t* row = first_row;
for (int16_t r = 0; r < h; ++r) {
std::memset(row, fill_byte, row_bytes);
row += line_len_;
}
return;
}
// General path: fill first row with pixel values, then memcpy.
drawHLineImpl(x, y, w, color);
uint8_t* src = first_row;
uint8_t* dst = first_row + line_len_;
for (int16_t r = 1; r < h; ++r) {
std::memcpy(dst, src, row_bytes);
dst += line_len_;
}
}
} // namespace gfx