ReneHollander / node-canvas

Node canvas is a Cairo backed Canvas implementation for NodeJS.
www.automattic.com
3 stars 1 forks source link

Allow to define the framebuffer device #2

Closed piranna closed 9 years ago

piranna commented 9 years ago

On my Ubuntu machine (the one where FBDev doesn't work) I've found the next:

 > cat /proc/fb 
0 inteldrmfb
1 nouveaufb
 > ls /dev/fb*
/dev/fb0  /dev/fb1

I have two framebuffer devices and it's hardcoded to use /dev/fb0, so probably the problem is that I'm using the high-end NVidia one, and that's the reason why nothing it's shown in the screen. Could be?

piranna commented 9 years ago

I've modified the simple_fbdev.js script to allow to set the framebuffer device from command line and yes, the problem of not seeing anything on the screen was due to having two framebuffers and two graphic cards on my laptop :-P

20150705_160317

The green lines seems to be related to the hardcoded framebuffer configuration, the same way of the gray squares on QEmu :-)

(Now I'm thinking about a Javascript-only backend capable of combine several FBDev devices and create and extended desktop like Xinerama... :-P)

ReneHollander commented 9 years ago

The configuration should be correct. It seems that the stride is messed up. I also had the issue at some point, but somehow managed to fix it on my pc.

piranna commented 9 years ago

The .png files are correct, both for fb0 and fb1. Seems it's related with the screen output only.

ReneHollander commented 9 years ago

Please try 78fffe268b0c7b2b1ac7754eb9a08b5f4fdc3e36, maybe it fixes it.

piranna commented 9 years ago

I have tried it and result is the same :-( Also I've tried to test it on NodeOS on QEmu and it failed due to lack of X11 headers. I'll try to see if I can add a gyp variable to disable it.

ReneHollander commented 9 years ago

Hmm...

I will fix the problem with the headers in https://github.com/ReneHollander/node-canvas/issues/6 in the next days.

piranna commented 9 years ago

Ok, if you need help/beta-testing send me an email ;-) El 05/07/2015 22:27, "Rene Hollander" notifications@github.com escribió:

Hmm...

I will fix the problem with the headers in #6 https://github.com/ReneHollander/node-canvas/issues/6 in the next days.

— Reply to this email directly or view it on GitHub https://github.com/ReneHollander/node-canvas/issues/2#issuecomment-118665456 .

piranna commented 9 years ago

I've changed simple_fbdev.js to paint it at 0,0 and seems like it's eating 24 pixels for each line, since it start to paint the second line at the end of the first one. Also there's a lot more variables, also offsets that maybe should be taken in account...

ReneHollander commented 9 years ago

I will look into the issue, but first have to find a device that produces the error. That will be fun...

piranna commented 9 years ago

01:00.0 VGA compatible controller: NVIDIA Corporation GT216M [GeForce GT 330M](rev a2)

It's a MacBook Pro 6.2 (circa 2012). Hope this helps... :-)

piranna commented 9 years ago

Anyway I'm looking for it myself, too :-)

piranna commented 9 years ago

Confirmed: first line is right, but after that, each one is printed 24 fixels to left, and at the end of the previous row if they move too much to the left. There's a gap of 48 pixels between each row, thought, but I don't believe this would be related to hsync or any other similar low-level thing.... Maybe it's needed to configure it with some kind of offset? Seems there are values both for the real and the visible screen region...

piranna commented 9 years ago

I've been reviewing the code and the FBDev data structures... Shouldn't be used the next one instead of being calculated?

struct fb_fix_screeninfo {
...
        unsigned long smem_start;       /* Start of frame buffer mem */
                                        /* (physical address) */
        __u32 smem_len;                 /* Length of frame buffer mem */
...
        unsigned long mmio_start;       /* Start of Memory Mapped I/O   */
                                        /* (physical address) */
        __u32 mmio_len;                 /* Length of Memory Mapped I/O  */
...
};

I think mmio_len is specially useful to define the size of the mmap...

ReneHollander commented 9 years ago

I am now using smem_len on the branch fixing_fbdev

piranna commented 9 years ago

Cool :+1:

I have been looking info for my problem, according to http://www.ummon.eu/Linux/API/Devices/framebuffer.html seems the line_length entry would be bigger than my real screen size (1680x1050) but data is copied directly from Cairo on the framebuffer memory region. Maybe Cairo surface width should be set to the line_length value? In any case, when painting out of the screen in the gap between rows, the generated file gets a lot of garbage that don't happen when painting in the correct place...

piranna commented 9 years ago

According to http://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-create-for-data, "Note that the stride may be larger than width*bytes_per_pixel to provide proper alignment for each pixel and row. This alignment is required to allow high-performance rendering within cairo. The correct way to obtain a legal stride value is to call cairo_format_stride_for_width() with the desired format and maximum image width value, and then use the resulting stride value to allocate the data and to create the image surface. See cairo_format_stride_for_width() for example code", I think that my problem could be related to a bad format that makes stride to be 48 pixels smaller...

ReneHollander commented 9 years ago

Can you check the output of fbset --show again? Mine is

mode "1920x1080"
    geometry 1920 1080 1920 1080 32 <-- This should be the bbp
    timings 0 0 0 0 0 0 0
    rgba 8/16,8/8,8/0,8/24
endmode

If it's not 32 bbp, try to set it with fbset -depth 32. Maybe it helps somehow...

piranna commented 9 years ago

I have just tested on another machine and got a similar result:

mode "1366x768"
    geometry 1366 768 1920 1080 32
    timings 0 0 0 0 0 0 0
    accel true
    rgba 8/16,8/8,8/0,0/0
endmode

In fact, in this machine the gap between rows seems to be bigger, so probably it's something related to the differences between real visible size vs virtual size (yours are the same).

ReneHollander commented 9 years ago

Please try the following code in a tty:

gcc -o fbdev fbdev.c
./fbdev
#include <linux/fb.h>
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

inline uint32_t pixel_color(uint8_t r, uint8_t g, uint8_t b, struct fb_var_screeninfo *vinfo)
{
    return (r<<vinfo->red.offset) | (g<<vinfo->green.offset) | (b<<vinfo->blue.offset);
}

int main()
{
    struct fb_fix_screeninfo finfo;
    struct fb_var_screeninfo vinfo;

    int fb_fd = open("/dev/fb0",O_RDWR);

    //Get variable screen information
    ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
    vinfo.grayscale=0;
    vinfo.bits_per_pixel=32;
    ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vinfo);
    ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);

    ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo);

    long screensize = vinfo.yres_virtual * finfo.line_length;

    uint8_t *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, (off_t)0);

    int x,y;

    for (x = 50; x < 50 + 50; x++)
        for (y = 50; y < 50 + 50; y++)
        {
            long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length;

            *((uint32_t*)(fbp + location)) = pixel_color(0xFF,0x00,0xFF, &vinfo);
        }

    return 0;
}
piranna commented 9 years ago

Please try the following code in a tty:

It paints me a pink square :-) The machine is a Lenovo Z500 with an Intel graphic card (although it has a sticker about a NVidia GeForce, so maybe it's a dual graphic card machine too but with the secondary one disabled...).

I'll test the code on the MacBook later.

ReneHollander commented 9 years ago

Thats good news! I implemented the changes into the fbdev backend. Maybe f348fb4ca0f4007cbd9874148bd8e071e537ba9a does the job...

piranna commented 9 years ago

I've just check your fixing_fbdev branch on the Lenovo and I'm still getting the same result... :-/

piranna commented 9 years ago

I've checked the stride value, it's 5464 bytes, that at 32bpp it's 1366 pixels wide, that's the wide of my screen without the gap... :-/ I don't know where could it be comming... :-(

piranna commented 9 years ago

Ok, by using this->fb_vinfo.xres_virtual instead of this->fb_vinfo.xres not only it worked but also it gave me an stride of 7680, that means 1920 pixels, the width of Full HD screen, so seems this is the value that's need to be used. I'll send you a pull-request.

ReneHollander commented 9 years ago

Is this completely fixed now?

piranna commented 9 years ago

I've only checked it on the Lenovo laptop, need to test it too on MacBook Pro and on QEmu, but seems it's the correct way to do it. Could you be able to test it on your machine? The more the merrier... :-)

ReneHollander commented 9 years ago

I tested it before I merged :)

piranna commented 9 years ago

Ah cool, then I'll try to test it later or tomorrow.

piranna commented 9 years ago

I need to fix the FbDev support on my MacBook by using directly fb_finfo.line_length instead of calculating it, now it works there and also here on the Lenovo, but QEmu throw an error about "bad stride value" and crash, but seems this is the correct way. We would need to inspect the other values to find one valid for FbDev on QEmu, but seems the framebuffer line length is always fixed and the same independtly of the resolution and color depth being used.

piranna commented 9 years ago

Curiously, the error is being thrown on the PNGStream, so maybe the error is not directly related to the FbDev itself?

piranna commented 9 years ago

I've improved X11 support :-) Only thing createPNGStream() throw an error about "invalid surface for operation" that maybe is somewhat related to the problem of FbDev on QEmu. I've commented the line about take an screenshoot to a file and it works, only that closes to quickly. After putting a setTimeout() of 10 seconds I can see the window is correctly created, but nothing is drawn. Maybe it needs a command to flush the operations to the screen on X11, like swapBuffers() or something?

piranna commented 9 years ago

Another thing: it creates a Window, but maybe it would be more useful to allow it work as a widget embeded on other X11 apps, for example using node-x11 module...

ReneHollander commented 9 years ago

I initially added the X11 backend to be able to test dynamic rendering without the need to switch to a tty. Everythink I implemented worked on my side. I also needed a setTimeout to be able to look at the X11 window for longer than a split second.

piranna commented 9 years ago

Everythink I implemented worked on my side.

I've just tested the "animated clock" and it works, only thing that when clossing the window the app doesn't end, but seems easy to fix (just listen to WM_DESTROY_MESSAGE). I was testing before was a copy of simple_fbdev.js but using X11 instead, I'll review it.

piranna commented 9 years ago

I have been checking the animated clock example and stripped it down, and seems in X11 it's doing all the paint steps inside the clock sphere. They can be painted outside it, but the clock sphere needs to be painted first and in fact it's shown on top of the rectangles... :-/ Also I needed to add an stroke() operation at the end that's not necesary on FbDev:

#!/usr/bin/env node

var fs = require('fs');
var Canvas = require('..');

var backend = new Canvas.backends.X11Backend(800, 600);
var canvas = new Canvas(backend);
var ctx = canvas.getContext('2d');

const squareSize = 100

// Clock sphere, if I remove or change any of this lines, nothing is showed
ctx.lineWidth = 14;
ctx.strokeStyle = '#325FA2';
ctx.arc(0, 0, 142, 0, Math.PI * 2);
ctx.stroke();
ctx.fill();

var offsetX = canvas.width-squareSize
var offsetY = canvas.height-squareSize

ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, squareSize, squareSize);

ctx.fillStyle = "#00FF00";
ctx.fillRect(offsetX, 0, squareSize, squareSize);

ctx.fillStyle = "#0000FF";
ctx.fillRect(0, offsetY, squareSize, squareSize);

ctx.fillStyle = "#FFFFFF";
ctx.fillRect(offsetX, offsetY, squareSize, squareSize);

// This is not necesary on FbDev
ctx.stroke();

console.log("Width: " + canvas.width + ", Height: " + canvas.height);

setInterval(function() {
  console.log('tick')
}, 1000);

//var outPath = __dirname + '/rectangle.png';
//canvas.createPNGStream().pipe(fs.createWriteStream(outPath));