3

I'm making a video player in C++, which displays the video on the linux framebuffer. The frames aren't visible directly after updating, but only after the tty is updated (e.g. the cursor blinks or I hold down a key).

I don't think the cpu is to slow / does too much stuff, because no core has 100% usage and the program at most uses 30% cpu.

If I run htop, which hides the cursor on the tty, the framebuffer will only update when htop refreshes (every 1.5 seconds).

I'm using arch linux with linux 5.11.11-arch1-1 (now 5.11.14-arch1-1) on a Laptop (acer Swift 3) with a Dual Core Intel Core i3-8145U and integrated graphics (Intel UHD Graphics 620) whith the i915 driver.

Is there any way to tell the framebuffer to update? I tried using fsync, but it didn't do anything.

Printing newlines on the tty makes the tty update, but it seems unnecessary. I tried ioctl(tty1_fd, KDSETMODE, KD_GRAPHICS), but this will freeze the output and I have to log in via ssh and run ioctl(tty1_fd, KDSETMODE, KD_TEXT) to do anything with the laptop or see the updated framebuffer.

This only affects my laptop. Everything works if I run it on a PC with a Dual Core AMD E-350 and (integrated?) Advanced Micro Devices [AMD/ATI] Wrestler [Radeon HD 6310] graphics and linux 5.11.10-gentoo-x86_64.

This framebuffer example has the same problem.

I tried playing a video with mplayer, but it doesn't work either. If I go to tty3 and run it with mplayer -vo fbdev2:/dev/fb0 video.mp4 the video will play normaly without any lag. If I switch to tty4, the video (not the sound) will start stuttering. It looks like it shows the content of the framebuffer only after the tty updates the framebuffer (i.e. the cursor blinks or a new character is written) It works on tty3 because mplayer writes the current time after every screen update, which will make the framebuffer update.

Minimal example:

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <chrono>
#include <thread>
#include <sys/mman.h>

constexpr size_t WIDTH = 1920; // framebuffer width (in pixels)
constexpr size_t HEIGHT = 1080; // framebuffer height (in pixel)
constexpr size_t BYTES_PER_PIXEL = 4; // bytes per pixel (bgra)
constexpr size_t MEM_WIDTH = WIDTH * BYTES_PER_PIXEL; // framebuffer width in memory
constexpr size_t FRAME_SIZE = MEM_WIDTH * HEIGHT; // size of one frame
constexpr size_t FPS = 24; // framerate
static constexpr size_t NSPF = FPS ? std::nano::den / (FPS * std::nano::num) : 0; // nanoseconds per frame
static constexpr std::chrono::nanoseconds SLEEP_DURATION{NSPF};

int fb_fd; // framebuffer filedescriptor
uint8_t *fb_mem; // pointer to mmaped framebuffer

static void set_pixel(size_t x, size_t y, bool black)
{
    auto *pixel = &fb_mem[MEM_WIDTH * y + x * BYTES_PER_PIXEL];
    std::fill_n(pixel, 4, black ? 0 : 255);
}

int main()
{
    fb_fd = open("/dev/fb0", O_RDWR);
    fb_mem = static_cast<uint8_t *>(mmap(nullptr, FRAME_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0));
    size_t x = 0;
    size_t y = 100;
    bool black = false;
    while (true)
    {
        for (size_t _x = x; x < _x + 10; x++)
            set_pixel(x, y, black);
        if (x >= WIDTH)
        {
            x %= WIDTH; // go back to the beginning side of the line
            black = !black;
        }
        std::this_thread::sleep_for(SLEEP_DURATION); // wait for the next frame
    }
}
MaxSilvester
  • 193
  • 1
  • 12
  • 2
    Please show a [mre] – Alan Birtles Apr 15 '21 at 12:04
  • Does it help to use `volatile` (or compile without optimization, or use a compiler fence/memory barrier like `asm("" ::: "memory")`)? I wonder if the compiler is reordering your stores somehow, since you told it to only use a plain pointer type (`uint8_t*`) which means it gets to assume that memory contents aren't a visible side-effect as far as optimization is concerned. Hmm, probably not if it's different with different hardware, unless you also used a different compiler version / options on the other system. – Peter Cordes Apr 16 '21 at 23:59
  • I usually compile it with `clang++ -march=native -flto -Ofast`. I tried using just `clang++`, `clang++ -O0`, `g++` and `g++ -O0`, made the pointer `volatile uint8*` and added `asm("" ::: "memory")` everywhere. I tried compiling for 32bit (with `-m32`), but it didn't do anything either. – MaxSilvester Apr 17 '21 at 10:31
  • 1
    I think it is some power saving feature which makes the framebuffer only update when nececary, because it updates when the blinking cursor turns on/off and holding down any key or printing a newline after every line makes the framebuffer update. – MaxSilvester Apr 17 '21 at 10:40
  • `-O0` would be sufficient to rule out any optimization of stores in this case (it's somewhat similar to making *everything* `volatile`), and probably wouldn't happen across calls to `sleep_for` even with optimization. Was worth trying though, and leaving in an `asm("":::"memory");` after storing the framebuffer (e.g. right before sleep) would be a good idea for long-term robustness / code quality. – Peter Cordes Apr 17 '21 at 11:05

0 Answers0