// linux_fb_display.cpp — Linux framebuffer HAL implementation. #include "linux_fb_display.h" #include #include #include #include #include #include #include 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( 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(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(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((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(y) * line_len_ + static_cast(x) / 8; uint8_t bit = static_cast(x) & 7u; if (color) *p |= (1u << bit); else *p &= ~(1u << bit); return; } uint32_t offset = static_cast(y) * line_len_ + static_cast(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(y) * line_len_; uint32_t x0 = static_cast(x); uint32_t x1 = x0 + static_cast(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(y) * line_len_ + static_cast(x) * bytes_pp; if (bpp_ == 32) { // For XRGB8888, use 32-bit fill. auto* p = reinterpret_cast(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(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( (r_ch * 77 + g_ch * 150 + b_ch * 29) >> 8); std::memset(row, gray, static_cast(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(x) & 7u; uint8_t* p = shadow_ + static_cast(y) * line_len_ + static_cast(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(y) * line_len_ + static_cast(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(x); uint32_t x1 = x0 + static_cast(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(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(w) * bytes_pp; uint8_t* first_row = shadow_ + static_cast(y) * line_len_ + static_cast(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( (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