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