对于我一直在研究的项目,使用渐变绘制线条的能力(I.E.它们在绘制的区间内改变颜色)将非常有用。我有一个算法,因为我将在下面粘贴,但事实证明它是DREADFULLY慢。我使用Bresenham算法找到每个点,但我担心我已经达到了软件渲染的极限。到目前为止,我一直在使用SDL2,我的线条绘制算法比SDL_RenderDrawLine
慢200倍。这是一个估计,并通过比较两个函数来收集。抽取10,000行的时间。我的功能需要接近500毫秒,SDL_RenderDrawLine
在我的机器上完成2-3分钟。我甚至用水平线测试了这些函数,以确保它不仅仅是一个拙劣的Bresenham算法,并且类似的慢速孵化。不幸的是,SDL没有用于绘制具有渐变的线条的API(或者如果它是,我是盲目的)。我知道任何软件渲染都会比硬件慢很多,但是速度慢的剪切让我感到意外。有没有一种方法可以加快速度?我是否只是将绘图系统搞得一团糟?我考虑过保存一系列想要绘制的像素,然后一次性将它们推到屏幕上,但我不知道如何用SDL2做到这一点,我似乎无法做到在wiki中找到API或允许这样做的文档。那会更快吗?
感谢您的考虑!
void DRW_LineGradient(SDL_Renderer* rend, SDL_Color c1, int x1, int y1, SDL_Color c2, int x2, int y2){
Uint8 tmpr, tmpg, tmpb, tmpa;
SDL_GetRenderDrawColor(rend, &tmpr, &tmpg, &tmpb, &tmpa);
int dy = y2 - y1;
int dx = x2 - x1;
/* Use doubles for a simple gradient */
double d = (abs(x1 - x2) > abs(y1 - y2) ? abs(x1 - x2) : abs(y1 - y2));
double dr = (c2.r - c1.r) / d;
double dg = (c2.g - c1.g) / d;
double db = (c2.b - c1.b) / d;
double da = (c2.a - c1.a) / d;
double r = c1.r, g = c1.g, b = c1.b, a = c1.a;
/* The line is vertical */
if (dx == 0) {
int y;
if (y2 >= y1) {
for (y = y1; y <= y2; y++) {
SDL_SetRenderDrawColor(rend, r, g, b, a);
SDL_RenderDrawPoint(rend, x1, y);
r += dr;
g += dg;
b += db;
a += da;
}
return;
}
else{
for (y = y1; y >= y2; y--) {
SDL_SetRenderDrawColor(rend, r, g, b, a);
SDL_RenderDrawPoint(rend, x1, y);
r += dr;
g += dg;
b += db;
a += da;
}
return;
}
}
/* The line is horizontal */
if (dy == 0) {
int x;
if (x2 >= x1) {
for (x = x1; x <= x2; x++) {
SDL_SetRenderDrawColor(rend, r, g, b, a);
SDL_RenderDrawPoint(rend, x, y1);
r += dr;
g += dg;
b += db;
a += da;
}
return;
}
else{
for (x = x1; x >= x2; x--) {
SDL_SetRenderDrawColor(rend, r, g, b, a);
SDL_RenderDrawPoint(rend, x, y1);
r += dr;
g += dg;
b += db;
a += da;
}
return;
}
}
/* The line has a slope of 1 or -1 */
if (abs(dy) == abs(dx)) {
int xmult = 1, ymult = 1;
if (dx < 0) {
xmult = -1;
}
if (dy < 0) {
ymult = -1;
}
int x = x1, y = y1;
do {
SDL_SetRenderDrawColor(rend, r, g, b, a);
SDL_RenderDrawPoint(rend, x, y);
x += xmult;
y += ymult;
r += dr;
g += dg;
b += db;
a += da;
} while (x != x2);
return;
}
/* Use bresenham's algorithm to render the line */
int checky = dx >> 1;
int octant = findOctant((Line){x1, y1, x2, y2, dx, dy});
dy = abs(dy);
dx = abs(dx);
x2 = abs(x2 - x1) + x1;
y2 = abs(y2 - y1) + y1;
if (octant == 1 || octant == 2 || octant == 5 || octant == 6) {
int tmp = dy;
dy = dx;
dx = tmp;
}
int x, y = 0;
for (x = 0; x <= dx; x++) {
SDL_SetRenderDrawColor(rend, r, g, b, a);
switch (octant) {
case 0:
SDL_RenderDrawPoint(rend, x + x1, y + y1);
break;
case 1:
SDL_RenderDrawPoint(rend, y + x1, x + y1);
break;
case 2:
SDL_RenderDrawPoint(rend, -y + x1, x + y1);
break;
case 3:
SDL_RenderDrawPoint(rend, -x + x1, y + y1);
break;
case 4:
SDL_RenderDrawPoint(rend, -x + x1, -y + y1);
break;
case 5:
SDL_RenderDrawPoint(rend, -y + x1, -x + y1);
break;
case 6:
SDL_RenderDrawPoint(rend, y + x1, -x + y1);
break;
case 7:
SDL_RenderDrawPoint(rend, x + x1, -y + y1);
break;
default:
break;
}
checky += dy;
if (checky >= dx) {
checky -= dx;
y++;
}
r += dr;
g += dg;
b += db;
a += da;
}
SDL_SetRenderDrawColor(rend, tmpr, tmpg, tmpb, tmpa);
}
侧面注意:
我不愿意继续使用OpenGL 3.0+(我听说SDL2支持),因为我不知道如何使用它。我发现的大多数教程都解释了使用SDL设置上下文然后将屏幕着色为纯色的过程,但在解释如何绘制形状等之前停止。如果有人可以提供一个开始学习这个的好地方,那也非常有帮助。
答案 0 :(得分:3)
您的函数的大量开销是重复调用SDL_RenderDrawPoint
。这(很可能)是一个泛型函数,需要执行以下操作:
x
和y
是否在当前表面的范围内; y
与surface->pitch
和x
与surface->format->BytesPerPixel
相乘,计算表面内的位置; SDL_PixelFormat
; 必须对每个像素执行以上所有操作。此外,调用函数本身就是开销 - 虽然可能很小,但仍需要为每个单独的像素完成,即使它不可见。
你可以:
x
和y
范围检查,如果您确定行起点和终点始终可见; BytesPerPixel
和pitch
来更新它运动; line
例程中设置单个像素来省略函数调用。另一个 - 较小 - 问题:你称自己的例行程序为“Bresenham ......但事实并非如此.Bresenham的优化实际上是它完全避免了double
计算(其最强点在于它仍然存在给出了数学上正确的输出;在使用double
变量时,我不会指望它......)。
以下例程不检查范围,颜色模型,颜色值或(确实)如果表面应该锁定。理想情况下,所有这些操作都应在紧密拉伸循环之外完成。实际上,它采用24位RGB彩色屏幕,首先是红色字节。 [*]
我为当前的SDL环境编写了这段代码,它仍然是SDL-1.0,但它也适用于较新的版本。
也可以使用Bresenham对delta-Red,delta-Green和delta-Blue值的计算,但我在这里故意省略它们:)
它们会添加许多额外的变量 - 在一个猜测,每个颜色通道三个 - ,额外的检查,并且,尤其是,并不是真正明显更好的质量。红色的两个连续值(例如127和128)之间的差异通常太小而无法在单个像素宽线中注意到。此外,只有当您的线条长度至少为256像素并且覆盖渐变中0到255的整个红色范围时,才会出现这个小步骤。
[*]如果您100%确定使用自己的程序定位特定的屏幕模型,则可以使用此功能(当然,针对特定的屏幕模型进行了调整)。定位几个不同的屏幕模型也是可行的;为每个编写一个自定义例程,并使用函数指针来调用正确的例程
最有可能的是SDL_RenderDrawLine
能够挤出每毫秒的性能。非常值得为库编写所有代码(将用于各种各样的屏幕设置),但很可能不适用于像您这样的单个程序。注意我注释了一个范围检查,如有必要,它会回退到一个普通的line
例程。您可以对异常或意外的屏幕设置执行相同的操作,在这种情况下,只需调用您自己的,较慢的绘图例程。 (您的例程更加健壮,因为它使用SDL的本机例程。)
下面的原始line
例程是在十多年前从互联网上复制过来的,因为我已经将它用于年龄。我很乐意将它归于某人;如果有人认出这些评论(它们大部分都出现在原始代码中),做发表评论。
void gradient_line (int x1,int y1,int x2,int y2,
int r1,int g1, int b1,
int r2,int g2, int b2)
{
int d; /* Decision variable */
int dx,dy; /* Dx and Dy values for the line */
int Eincr,NEincr; /* Decision variable increments */
int t; /* Counters etc. */
unsigned char *ScrPos;
int LineIncr;
int rd,gd,bd;
if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 ||
x1 >= SCREEN_WIDE || x2 >= SCREEN_WIDE ||
y1 >= SCREEN_HIGH || y2 >= SCREEN_HIGH)
{
line (x1,y1, x2,y2, (r1<<16)+(g1<<8)+b1);
return;
}
rd = (r2-r1)<<8;
gd = (g2-g1)<<8;
bd = (b2-b1)<<8;
dx = x2 - x1;
if (dx < 0)
dx = -dx;
dy = y2 - y1;
if (dy < 0)
dy = -dy;
if (dy <= dx)
{
/* We have a line with a slope between -1 and 1
*
* Ensure that we are always scan converting the line from left to
* right to ensure that we produce the same line from P1 to P0 as the
* line from P0 to P1.
*/
if (x2 < x1)
{
t = x2; x2 = x1; x1 = t; /* Swap X coordinates */
t = y2; y2 = y1; y1 = t; /* Swap Y coordinates */
/* Swap colors */
r1 = r2;
g1 = g2;
b1 = b2;
rd = -rd;
gd = -gd;
bd = -bd;
}
r1 <<= 8;
g1 <<= 8;
b1 <<= 8;
if (y2 > y1)
{
LineIncr = screen->pitch;
} else
{
LineIncr = -screen->pitch;
}
d = 2*dy - dx; /* Initial decision variable value */
Eincr = 2*dy; /* Increment to move to E pixel */
NEincr = 2*(dy - dx); /* Increment to move to NE pixel */
ScrPos = (unsigned char *)(screen->pixels+screen->pitch*y1+x1*screen->format->BytesPerPixel);
rd /= dx;
gd /= dx;
bd /= dx;
/* Draw the first point at (x1,y1) */
ScrPos[0] = r1 >> 8;
ScrPos[1] = g1 >> 8;
ScrPos[2] = b1 >> 8;
r1 += rd;
g1 += gd;
b1 += bd;
/* Incrementally determine the positions of the remaining pixels */
for (x1++; x1 <= x2; x1++)
{
if (d < 0)
{
d += Eincr; /* Choose the Eastern Pixel */
} else
{
d += NEincr; /* Choose the North Eastern Pixel */
ScrPos += LineIncr;
}
ScrPos[0] = r1>>8;
ScrPos[1] = g1>>8;
ScrPos[2] = b1>>8;
ScrPos += screen->format->BytesPerPixel;
r1 += rd;
g1 += gd;
b1 += bd;
}
} else
{
/* We have a line with a slope between -1 and 1 (ie: includes
* vertical lines). We must swap our x and y coordinates for this.
*
* Ensure that we are always scan converting the line from left to
* right to ensure that we produce the same line from P1 to P0 as the
* line from P0 to P1.
*/
if (y2 < y1)
{
t = x2; x2 = x1; x1 = t; /* Swap X coordinates */
t = y2; y2 = y1; y1 = t; /* Swap Y coordinates */
/* Swap colors */
r1 = r2;
g1 = g2;
b1 = b2;
rd = -rd;
gd = -gd;
bd = -bd;
}
r1 <<= 8;
g1 <<= 8;
b1 <<= 8;
if (x2 > x1)
{
LineIncr = screen->format->BytesPerPixel;
} else
{
LineIncr = -screen->format->BytesPerPixel;
}
d = 2*dx - dy; /* Initial decision variable value */
Eincr = 2*dx; /* Increment to move to E pixel */
NEincr = 2*(dx - dy); /* Increment to move to NE pixel */
rd /= dy;
gd /= dy;
bd /= dy;
/* Draw the first point at (x1,y1) */
ScrPos = (unsigned char *)(screen->pixels+screen->pitch*y1+x1*screen->format->BytesPerPixel);
ScrPos[0] = r1 >> 8;
ScrPos[1] = g1 >> 8;
ScrPos[2] = b1 >> 8;
r1 += rd;
g1 += gd;
b1 += bd;
/* Incrementally determine the positions of the remaining pixels
*/
for (y1++; y1 <= y2; y1++)
{
ScrPos += screen->pitch;
if (d < 0)
{
d += Eincr; /* Choose the Eastern Pixel */
} else
{
d += NEincr; /* Choose the North Eastern Pixel */
ScrPos += LineIncr; /* (or SE pixel for dx/dy < 0!) */
}
ScrPos[0] = r1 >> 8;
ScrPos[1] = g1 >> 8;
ScrPos[2] = b1 >> 8;
r1 += rd;
g1 += gd;
b1 += bd;
}
}
}
..这是一系列随机颜色的随机颜色,右边是特写:
我没有时间区分“原生”SDL线条画,你天真的方法,纯色的Bresenham的实现和这个;再说一遍,这不应该比SDL本机线慢得多 。