UEFI开发学习9 – 高效画图
之前在用UEFI 进行GUI绘图的时候,发现画稍微复杂点的图(如圆形)就会很慢,肉眼都可以看得出这个图像是怎么被画出来的,这样的话就很难做一些复杂的GUI窗口图像绘制。后来研究了一下,发现是写法的问题导致的,看下图,同样画两个圆,所用时间相差非常大。
这是什么原因呢?首先来看看第一个圆的代码:
VOID FillCircle(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput,
INTN x0,
INTN y0,
INTN r,
EFI_GRAPHICS_OUTPUT_BLT_PIXEL c,
UINT8 Transparence
)
{
INTN x, y, xd;
INTN lastx[4] = { 0 };
if ( x0 < 0 ) return;
if ( y0 < 0 ) return;
if ( r <= 0 ) return;
xd = 3 - (r << 1);
x = 0;
y = r;
while ( x <= y )
{
if( y > 0 )
{
if (lastx[0] != x0 - x) {
DrawLine(GraphicsOutput, x0 - x, y0 - y,x0 - x, y0 + y, 1, c, Transparence );
lastx[0] = x0 - x;
}
if (lastx[1] != x0 + x) {
if (x0 + x != x0) {
DrawLine(GraphicsOutput, x0 + x, y0 - y,x0 + x, y0 + y, 1, c, Transparence );
lastx[1] = x0 + x;
}
}
}
if( x > 0 )
{
if (lastx[2] != x0 - y && x0 - y != x0 - x) {
DrawLine(GraphicsOutput, x0 - y, y0 - x,x0 - y, y0 + x, 1, c, Transparence);
lastx[2] = x0 - y;
}
if (lastx[3] != x0 + y && x0 + y != x0 + x) {
DrawLine(GraphicsOutput, x0 + y, y0 - x,x0 + y, y0 + x, 1, c, Transparence);
lastx[3] = x0 + y;
}
}
if ( xd < 0 )
{
xd += (x << 2) + 6;
}
else
{
xd += ((x - y) << 2) + 10;
y--;
}
x++;
}
}
EFI_STATUS DrawLine(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput,
IN UINTN x0, UINTN y0, UINTN x1, UINTN y1,
IN UINTN BorderWidth,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL BorderColor,
IN UINT8 Transparence
){
INT32 dx = abs((int)(x1 - x0));
INT32 sx = x0 < x1 ? 1 : -1;
INT32 dy = abs((int)(y1-y0)), sy = y0 < y1 ? 1 : -1;
INT32 err = ( dx > dy ? dx : -dy) / 2, e2;
for(;;)
{
DrawPoint(GraphicsOutput, x0, y0, BorderWidth, BorderColor, Transparence);
if (x0==x1 && y0==y1) break;
e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
return EFI_SUCCESS;
}
EFI_STATUS DrawPoint(
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput,
IN UINTN x,
IN UINTN y,
IN UINTN Width,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
IN UINT8 Alpha
){
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color;
if (Alpha){
Status = GraphicsOutput->Blt(
GraphicsOutput,
&Color,
EfiBltVideoToBltBuffer,
x, y,
0, 0,
1, 1,
1
);
PixelColor.Red = (PixelColor.Red * (100 - Alpha) + Color.Red * Alpha) / 100 ;
PixelColor.Green = (PixelColor.Green * (100 - Alpha) + Color.Green * Alpha) / 100 ;
PixelColor.Blue = (PixelColor.Blue * (100 - Alpha) + Color.Blue * Alpha) / 100 ;
}
Status = GraphicsOutput->Blt(
GraphicsOutput,
&PixelColor,
EfiBltVideoFill,
0, 0,
x, y,
Width, Width,
0
);
return EFI_SUCCESS;
}
这里画圆的算法称为Bresenham画圆又称中点画圆(具体实现可百度),最终都是通过算法得到的圆的坐标再用EFI_GRAPHICS_OUTPUT_PROTOCOL画一个像素点实现的,也就是说,如果一个圆有1000个点组成,将调用1000次EFI_GRAPHICS_OUTPUT_PROTOCOL这个协议画点,所以这样的效率就非常低了。怎么提高呢?EFI_GRAPHICS_OUTPUT_PROTOCOL.BLT()本身就支持用矩形的方式填充绘制图形,做法是将它的第三个参数设置为EfiBltBufferToVideo,然后传一个图形的buffer到第二个参数即可,详情参考->UEFI开发学习3 -高级GUI编程。所以的画图的时候,可以预先算好整个图形的坐标,然后将其转换到一个buffer,最后再一次性绘制出来就是了。下面看一下改良后的画圆算法:
VOID BufferDrawFillCircle(
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ColorBuf,
INTN x0,
INTN y0,
INTN r,
EFI_GRAPHICS_OUTPUT_BLT_PIXEL c,
UINT8 Transparence
)
{
INTN x,y,xd;
INTN lastx[4] = { 0 };
POSITION_LEFT_TOP Ref;
if ( x0 < 0 ) return;
if ( y0 < 0 ) return;
if ( r <= 0 ) return;
xd = 3 - (r << 1);
x = 0;
y = r;
Ref.x = x0 - r;
Ref.y = y0 - r;
while ( x <= y )
{
if( y > 0 )
{
if (lastx[0] != x0 - x) {
BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 - x, y0 - y, x0 - x, y0 + y, c, Transparence);
lastx[0] = x0 - x;
}
if (lastx[1] != x0 + x) {
if (x0 + x != x0) {
BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 + x, y0 - y,x0 + x, y0 + y, c, Transparence);
lastx[1] = x0 + x;
}
}
}
if( x > 0 )
{
if (lastx[2] != x0 - y && x0 - y != x0 - x) {
BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 - y, y0 - x,x0 - y, y0 + x, c, Transparence);
lastx[2] = x0 - y;
}
if (lastx[3] != x0 + y && x0 + y != x0 + x) {
BufferDrawLine(ColorBuf, Ref, 2 * r, 2 * r, x0 + y, y0 - x,x0 + y, y0 + x, c, Transparence);
lastx[3] = x0 + y;
}
}
if ( xd < 0 )
{
xd += (x << 2) + 6;
}
else
{
xd += ((x - y) << 2) + 10;
y--;
}
x++;
}
}
EFI_STATUS BufferDrawLine(
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ColorBuf,
IN POSITION_LEFT_TOP Ref,
IN UINTN Width,
IN UINTN Height,
IN UINTN x0,
IN UINTN y0,
IN UINTN x1,
IN UINTN y1,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
IN UINT8 Alpha
){
INT32 dx = abs((int)(x1 - x0));
INT32 sx = x0 < x1 ? 1 : -1;
INT32 dy = abs((int)(y1-y0)), sy = y0 < y1 ? 1 : -1;
INT32 err = ( dx > dy ? dx : -dy) / 2, e2;
for(;;)
{
BufferDrawPoint(ColorBuf, Ref, Width, Height, x0, y0, PixelColor, Alpha);
if (x0==x1 && y0==y1) break;
e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
return EFI_SUCCESS;
}
EFI_STATUS BufferDrawPoint(
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ColorBuf,
IN POSITION_LEFT_TOP Ref,
IN UINTN Width,
IN UINTN Height,
IN UINTN x,
IN UINTN y,
IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL PixelColor,
IN UINT8 Alpha
){
// EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Color;
UINTN Position;
if (x > Ref.x + Width || x < Ref.x ||
y > Ref.y + Height || y < Ref.y)
{
return EFI_UNSUPPORTED;
}
Position = Width * (y - Ref.y) + (x - Ref.x);
if (Alpha)
{
gBS->CopyMem(&Color, &ColorBuf[Position], sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
PixelColor.Red = (PixelColor.Red * (100 - Alpha) + Color.Red * Alpha) / 100 ;
PixelColor.Green = (PixelColor.Green * (100 - Alpha) + Color.Green * Alpha) / 100 ;
PixelColor.Blue = (PixelColor.Blue * (100 - Alpha) + Color.Blue * Alpha) / 100 ;
}
gBS->CopyMem(&(ColorBuf[Position]), &PixelColor, sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
return EFI_SUCCESS;
}
这个程序可看到,最终都会把每个点都存到一个buffer上,然后一次性绘制出来。在这里可能会有个疑问,程序虽然减少了Blt()的调用,但是却多了转换的算法,看起来程序跑的次数比原来的还多了,为什么时间减少了?原因是调用Blt()绘制一次所需的时间远大于跑一次其它算法的时间,主要是Blt()绘制图像的时候是需要跟硬件(显示设备)进行通信的,会消耗较多的时间,就好比BIOS的DEBUG版本跟RELEASE版本,DEBUG版本需要打印信息出来,才拖慢了其它程序的运行时间。所以写程序最好能尽量避免大量操作硬件次数,这样才能提高程序运行的效率。
版权声明:
作者:bin
链接:https://ay123.net/mystudy/852/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。

11