Files
GfxCanvas/linux_fb_display.cpp

308 lines
10 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;
}
shadow_ = new uint8_t[line_len_ * static_cast<uint32_t>(rawHeight())]();
return true;
}
void LinuxFBDisplay::close() {
delete[] shadow_;
shadow_ = nullptr;
if (buf_) {
munmap(buf_, map_size_);
buf_ = nullptr;
}
if (fd_ >= 0) {
::close(fd_);
fd_ = -1;
}
}
// ---------------------------------------------------------------------------
// Update — flush shadow buffer to the mmap'd framebuffer
// ---------------------------------------------------------------------------
void LinuxFBDisplay::update() {
if (!buf_ || !shadow_) return;
std::memcpy(buf_, shadow_,
line_len_ * static_cast<uint32_t>(rawHeight()));
fsync(fd_);
}
// ---------------------------------------------------------------------------
// 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;
if (bpp_ == 1) {
uint8_t* p = shadow_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) / 8;
uint8_t bit = static_cast<uint32_t>(x) & 7u;
if (color) *p |= (1u << bit);
else *p &= ~(1u << bit);
return;
}
uint32_t offset = static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) * (bpp_ / 8);
writePixel(shadow_ + 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;
if (bpp_ == 1) {
uint8_t* row = shadow_ + static_cast<uint32_t>(y) * line_len_;
uint32_t x0 = static_cast<uint32_t>(x);
uint32_t x1 = x0 + static_cast<uint32_t>(w) - 1;
uint32_t b0 = x0 >> 3;
uint32_t b1 = x1 >> 3;
// LSB-first: bit mask for pixel x within its byte = 0x01 << (x & 7)
// A run from x0 to end-of-byte b0 → 0xFF << (x0 & 7)
// A run from start-of-byte b1 to x1 → 0xFF >> (7 - (x1 & 7))
if (b0 == b1) {
uint8_t mask = (0xFFu << (x0 & 7u)) & (0xFFu >> (7u - (x1 & 7u)));
if (color) row[b0] |= mask;
else row[b0] &= ~mask;
} else {
uint8_t lead = 0xFFu << (x0 & 7u);
uint8_t trail = 0xFFu >> (7u - (x1 & 7u));
if (color) {
row[b0] |= lead;
if (b1 > b0 + 1) std::memset(row + b0 + 1, 0xFF, b1 - b0 - 1);
row[b1] |= trail;
} else {
row[b0] &= ~lead;
if (b1 > b0 + 1) std::memset(row + b0 + 1, 0x00, b1 - b0 - 1);
row[b1] &= ~trail;
}
}
return;
}
uint32_t bytes_pp = bpp_ / 8;
uint8_t* row = shadow_ + 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;
if (bpp_ == 1) {
uint8_t bit = static_cast<uint32_t>(x) & 7u;
uint8_t* p = shadow_ + static_cast<uint32_t>(y) * line_len_
+ static_cast<uint32_t>(x) / 8;
for (int16_t i = 0; i < h; ++i) {
if (color) *p |= (1u << bit);
else *p &= ~(1u << bit);
p += line_len_;
}
return;
}
uint32_t bytes_pp = bpp_ / 8;
uint8_t* p = shadow_ + 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;
if (bpp_ == 1) {
uint32_t x0 = static_cast<uint32_t>(x);
uint32_t x1 = x0 + static_cast<uint32_t>(w) - 1;
uint32_t b0 = x0 >> 3;
uint32_t b1 = x1 >> 3;
// Fill the first row (handles partial edge bytes correctly).
drawHLineImpl(x, y, w, color);
if (h == 1) return;
uint8_t* src = shadow_ + static_cast<uint32_t>(y) * line_len_ + b0;
uint32_t span = b1 - b0 + 1;
if ((x0 & 7u) == 0 && (x1 & 7u) == 7u) {
// Byte-aligned: safe to memcpy the full span to every subsequent row.
uint8_t* dst = src + line_len_;
for (int16_t r = 1; r < h; ++r) {
std::memcpy(dst, src, span);
dst += line_len_;
}
} else {
// Unaligned: edge bytes are shared with neighboring pixels,
// so each row must be drawn individually.
for (int16_t r = 1; r < h; ++r)
drawHLineImpl(x, y + r, w, color);
}
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 = shadow_ + 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