Page 1 of 1

Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 5:39 pm
by Neyder
Hey !
I'm sorry if the question has already been asked, but almost everything I find is related to VBE which calls bios interrupts for switching between modes, etc.

I have a problem when drawing into the framebuffer provided by uefi, after ExitBootServices().
On VMWare, everything goes fine, I can fill the screen with a color an print text. But on real hardware, and Parallel Desktops, the image is broken.

This is how I fill the entire screen with a color, let say 0xAA0000.
The FB pointer and color are uint32

Code: Select all

addr = fb->base_addr;
sbytes = fb->hrez * fb->vrez;

while (sbytes--)
{
     *addr++ = bg_color;
}
On VMware, the entire screen is filled with the right color, but on Parallel Desktop, I got a strange image with individuals RGB pixels and the text is unaligned.
This is a screenshot of Hello World on AA0000 background
Image

If we look closer, we see RGB pixels
Image

Also, new row is not lfb_start + hrez, but something like hrez/2 + hrez/4 which equals VREZ

The only way to fill the background, is using the same byte for every color, even the alpha one, like 0xAAAAAAAA
When I try to plot a single pixel on a random place, I got 2 pixels on screen. On with the right color, an the other with a random one.
Image

If I fill the background 3px by 3 px using this template :

Code: Select all

*addr++ = 0xAA0000;
*addr++ = 0x00AA00;
*addr++ = 0x0000AA;
I got almost all screen filled with the right color, but with black lines every 3 pixels.
Image

When using 32bit color, like 0xFFAA0000, the "fouth" pixel is rendered, but colors correspond to nothing expected.

The Graphics Mode is set to 1024x768x24 (3 bytes per pixels),
Pixel format is 2 (Bit Mask)
and the bit mask is R= 0xFF0000, G = 0xFF00, B = 0xFF
FB Start = 3221225472, FB Size = 268435456
Scan line = 1024

Any ideas ?

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 6:56 pm
by Nable
Neyder wrote: If I fill the background 3px by 3 px using this template :

Code: Select all

*addr++ = 0xAA0000;
*addr++ = 0x00AA00;
*addr++ = 0x0000AA;
What is the exact type of "addr" variable? And why are you sure that current mode is 24bpp (it may be 15/16bpp)?

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 7:05 pm
by Neyder

Code: Select all

typedef unsigned int uint32_t;

uint32_t *addr;
And I guess its 24bits because UEFI is saying that Bytes Per Pixel = 3.
I'm not very sure how to test depth, I used ConfigurePixelBitMaskFormat() function from FrameBufferBltLib.c

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 7:22 pm
by kzinti
It looks like you are writing 32 bits 'pixels' to a 24 bpp frame buffer. This can't work. You will need to write 3 bytes per pixels, not 4.

The layout of a 32 bits frame buffer is typically

xx RR GG BB xx RR GG BB xx RR GG BB ...

The layout of a 24 bits frame buffer is typically

RR GG BB RR GG BB RR GG BB ...

I say typically, because the red and blue bytes are often swapped, especially in 24 bits mode.

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 7:24 pm
by Nable
Neyder wrote:addr is uint32_t pointer.
Then it ("black lines every 3 pixels") is obvious if you try to reproduce these writes by hand in hexedit.
Try this:

Code: Select all

*addr++ = 0x00AA0000;
*addr++ = 0x0000AA00;
*addr++ = 0xAA0000AA;
Without the last 0xAA you get the following pattern:

Code: Select all

              | addr[0] |  | addr[1] |  | addr[2] |
              v         v  v         v  v         v
0x00000000:   00 00 AA 00  00 AA 00 00  AA 00 00 00
              ^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^
              pixel[0] pixel[1]  pixel[2]  pixel[3] - oops!


Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 7:56 pm
by Neyder
Dude ! It works =D
Thx a lot ! I understand better with your example.
I was assuming that one address = one pixel, because I was using 0xRRGGBB format, but I have 32 bit variable...

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 8:30 pm
by gerryg400
Why did it work on Vmware ?

Re: Frame Buffer problem (UEFI)

Posted: Mon Feb 02, 2015 8:40 pm
by Neyder
Because vmware mode was 32 bit depth, here we have 24 bits (3bpp), and we don't have 24bit variable type...
So an address (32bit) is one pixel + 1/3 of the next pixel, instead of one pixel like in 32bit depth (4bytes per pixel)

We need to draw 4 pixels at time using 3 addresses.
This code draw 4 pixels of the same color.

Code: Select all

     *addr++ = bg_color;
     *addr++ = bg_color >> 8;
     *addr++ = bg_color << 8 | bg_color >> 16;

Re: Frame Buffer problem (UEFI)

Posted: Tue Feb 03, 2015 4:55 am
by Nable
Neyder wrote: This code draw 4 pixels of the same color.

Code: Select all

     *addr++ = bg_color;
     *addr++ = bg_color >> 8;
     *addr++ = bg_color << 8 | bg_color >> 16;
No, color won't be the same in most cases. You should look at my "picture" once more.

Re: Frame Buffer problem (UEFI)

Posted: Tue Feb 03, 2015 10:19 am
by MDenham
Nable wrote:
Neyder wrote: This code draw 4 pixels of the same color.

Code: Select all

     *addr++ = bg_color;
     *addr++ = bg_color >> 8;
     *addr++ = bg_color << 8 | bg_color >> 16;
No, color won't be the same in most cases. You should look at my "picture" once more.
The suggested code was for clearing the background, not for general-purpose drawing. (It's also wrong; it leaves part of the second dword unset.)

For general-purpose drawing, assuming you want to work internally with 32-bit colors, it becomes somewhat of a mess (especially if you implement alpha at the same time). I suspect you'd be better off making your framebuffer pointer a char*/uint8* so that you can handle each color individually in this situation as that way your code ends up more readable.

Re: Frame Buffer problem (UEFI)

Posted: Tue Feb 03, 2015 1:27 pm
by Neyder
Well done, thx everybody =)

Code: Select all

// For clearing the background

pixels = fb->width * fb->height;

...

else if (fb->depth == 24)
{
    uint8_t* addr = (uint8_t*)fb->start;
    while (pixels--)
    {
        if (fb->format == PIXEL_BGR)
        {
            *addr++ = (bg_color >> 16) & 255;
            *addr++ = (bg_color >> 8) & 255;
            *addr++ = bg_color & 255;
        }
        else // RGB
        {
            *addr++ = bg_color & 255;
            *addr++ = (bg_color >> 8) & 255;
            *addr++ = (bg_color >> 16) & 255;
        }
    }
}
else if (fb->depth == 32)
{
    uint32_t* addr = fb->start;
    while (pixels--)
    {
        *addr++ = bg_color;
    }
}
But I think I'm gonna use something similar to the EFI BLT Pixel format

Code: Select all

color = { 0xRR, 0xGG, 0xBB, 0xAA }
It will be easier to use with different depths

Re: Frame Buffer problem (UEFI)

Posted: Tue Feb 03, 2015 5:45 pm
by Brendan
Hi,

Just a quick note...

To get decent performance for 24-bpp, you want to work on 4 pixels at a time and pack them so that you can write three aligned dwords.

For clearing the background (all 4 pixels the same), this means something more like:

Code: Select all

   uint32_t v1 = red << 24 + green << 16 + blue << 8 + red;
   uint32_t v2 = v1 << 8 + green;
   uint32_t v3 = v2 << 8 + blue;
   uint32_t *dest = (uint32_t *)addr;

    while (pixels)
    {
        *dest++ = v1;
        *dest++ = v2;
        *dest++ = v3;
        pixels -= 4;
    }
Of course for 64-bit code you could go one step further and do 8 pixels with three 64-bit writes (or even further and use SSE to do 16 pixels as three 128-bit writes).


Cheers,

Brendan

Re: Frame Buffer problem (UEFI)

Posted: Wed Feb 04, 2015 3:17 am
by Neyder
Nice !

This will fill the background 8 pixels at once in 24 bits depth

Code: Select all

uint64_t* addr = fb->start;
pixels = (fb->width * fb->height) / 8;

// Draw Passes
const uint64_t pass1 = ((uint64_t)green << 56) + ((uint64_t)blue << 48) +
                       ((uint64_t)red << 40) + ((uint64_t)green << 32) +
                       (blue << 24) + (red << 16) + (green << 8) + blue;
const uint64_t pass2 = (pass1 << 8) + red;
const uint64_t pass3 = (pass2 << 8) + green;

while (pixels--)
{
    *addr++ = pass1;
    *addr++ = pass2;
    *addr++ = pass3;
}
And 2 pixels at once in 32 bit depth

Code: Select all

uint64_t* addr = (uint64_t*)fb->start;
pixels = (fb->width * fb->height) / 2;

const uint64_t color = (bg_color << 32) + bg_color;
while (pixels--) *addr++ = color;
I'm checking the pixel format earlier, before checking the color depth, so blue can be red, and red can be blue. code don't need to change here.
I will try SSE for the GUI =D

Re: Frame Buffer problem (UEFI)

Posted: Wed Feb 04, 2015 12:56 pm
by jnc100
Out of interest, you may want to support the 32-bit blue-green-red-reserved pixel format as your default, as new machines which pass the Microsoft Hardware Compatibility Kit testing guidelines are guaranteed to support this mode (reference) and is thus the closest thing you'll find to a standard in the UEFI world. This removes the calculations you need to do to to support 24 bpp.

You'd typically iterate through the available modes (using the QueryMode and Mode->MaxMode members) and find the one which most closely fits your needs.

Regards,
John.

Re: Frame Buffer problem (UEFI)

Posted: Wed Feb 04, 2015 2:52 pm
by Neyder
There is something strange with Parallels Desktop
Even if EFI is reporting that the selected mode pixel format is 1 (32bit BGR Reserved), the framebuffer seems to be 24bpp
My 32bpp code works on VMWare and my PC, but on Parallels, it will works only if I force 24bpp drawing.

When the mode pixel format is 2 (BitMask) I can check for the depth with ConfigurePixelBitMaskFormat() from FrameBufferBltLib.c
I'm not sure if it really works, but it successfully reported 3bpp

Is there a better way, other than Info->PixelFormat to check for color depth ?
I'm using gnu-efi

The only solution I found for now, is using the mode provided at boot time and to not change it.
It can be a bug in the Parallels firmware, maybe I should ignore it...
I'll try with other VM

Edit : Works well with VirtualBox too...