PDA

View Full Version : WILL's Custom sdlutils.pas Modification



WILL
12-09-2006, 04:38 AM
Savage requested that I post this so here is the entire sdlutils.pas file. It's based off the 1.0 Beta release of JEDI-SDL.

New things added:

- Precalculated Commonly used trig values
- Precalculated Trig Tables (negative and possitive values)
- New Draw Functions (SDL_RotateDeg_Alpha, SDL_DrawCircle, SDL_DrawLine_Alpha, SDL_PutPixel_Alpha, SDL_DrawRect, SDL_DrawRect_Alpha, SDL_DrawFillRect_Alpha)
- A few minor optimizations, tweeks & enhancements

sdlutils_will01.zip (http://www.pascalgamedevelopment.com/files/188/sdlutils_will01.zip) (18 KB)

tanffn
12-09-2006, 08:59 AM
Neat, thanks WILL :)
It would be nice if it was Ellipse and not Circle, and even harder add thickens to the Lines/Circle/Etc.
I wrote it a while back but I was very disappointed with it.. (there were gaps)

WILL
12-09-2006, 04:51 PM
All my modifications are shown within "{Added/Modified by WILL}" comments. I believe all the "Jason" stuff is added by the other guy. ;)

Ah... well the circle is obviously a much easier to draw in a speedy way. (I'm not sure if I'm using the best method here though.)

Why not contribute your own Ellipse function? I'm sure that there is a ton of docs out there on how to do it.

tanffn
13-09-2006, 10:21 AM
I tried to make a set with line “thickens”, I wasn’t able to make it satisfactory :(
The make it look full/good I overdrawn, making it slow and without it there were pixels missing.

WILL
12-10-2006, 08:39 AM
I figured that this would be the best place to post this as it relates to my own sdlutils.pas added draw functions.

In my Cyber-Crisis post I was trying to do an explosion effect and needed a function that would allow me to take a texture and rotate, ADD, scale and fade all at once. It seems that even with my added functions, JEDI-SDL just doesn't cut the mustard. :?

So I went looking for the best way I could at least work my way to such a function. I came to my own (as yet un-added) SDL_RotateDeg_Alpha() function. It was the only one to allow mor than one effect at once. Rotate and Alpha. I figured I'd start with changing simple Alpha to Add with the ability to adjust the level for fading.

This is the result:
procedure SDL_RotateDeg_AddAlpha(DstSurface, SrcSurface: PSDL_Surface; SrcRect: PSDL_Rect;
DestX, DestY, OffsetX, OffsetY: Integer; Angle: Integer;
Alpha: UInt8);
var
aSin, aCos: Single;
MX, MY, DX, DY, NX, NY, SX, SY, OX, OY, Width, Height, TX, TY, RX, RY, ROX, ROY: Integer;
SrcColor, DstColor: UInt32;
srcRR, srcGG, srcBB, dstRR, dstGG, dstBB: UInt8;
TempTransparentColour: UInt32;
MAXX, MAXY: Integer;
begin
// Rotate the surface to the target surface.
TempTransparentColour := SrcSurface.format.colorkey;

maxx := DstSurface.w;
maxy := DstSurface.h;
aCos := degCOS[Angle];
aSin := degSIN[Angle];

Width := round(abs(srcrect.h * acos) + abs(srcrect.w * asin));
Height := round(abs(srcrect.h * asin) + abs(srcrect.w * acos));

OX := Width shr 1;
OY := Height shr 1;
MX := (srcRect.x + (srcRect.x + srcRect.w)) shr 1;
MY := (srcRect.y + (srcRect.y + srcRect.h)) shr 1;
ROX := (-(srcRect.w shr 1)) + Offsetx;
ROY := (-(srcRect.h shr 1)) + OffsetY;
Tx := ox + round(ROX * aSin - ROY * aCos);
Ty := oy + round(ROY * aSin + ROX * aCos);
SX := 0;
for DX := DestX - TX to DestX - TX + (width) do
begin
inc(SX);
SY := 0;
for DY := DestY - TY to DestY - TY + (Height) do
begin
if ((DX > 0) and (DX < MAXX)) and
((DY > 0) and (DY < MAXY)) then
begin
RX := SX - OX;
RY := SY - OY;
NX := mx + Round(RX * aSin + RY * aCos); //
NY := my + Round(RY * aSin - RX * aCos); //

if (NX >= srcRect.x) and (NX <= srcRect.x + srcRect.w) then
begin
if (NY >= srcRect.y) and (NY <= srcRect.y + srcRect.h) then
begin
SrcColor := SDL_GetPixel(SrcSurface, NX, NY);
if (SrcColor <> TempTransparentColour) then
begin
SDL_GetRGB(SrcColor, SrcSurface.format, @srcRR, @srcGG, @srcBB);


SDL_AddPixel(DstSurface, DX, DY,
SDL_MapRGB(DstSurface.format,
(Alpha * srcRR) shr 8,
(Alpha * srcGG) shr 8,
(Alpha * srcBB) shr 8));
end;
end;
end;
end;
inc(SY);
end;
end;
end;

Works fairly well and the effect looks nice too. But there are two things that really bother me about both of these functions still.

1) It's slow! Stick try to use this function in your game loop and you'll notice the FPS drop.

2) That darn border around where the texture's edge is.


I'd like to find a way to improve (1) and remove (2) for both these functions. Anyone capable willing to lend some time to this?

savage
12-10-2006, 09:20 AM
If the games is 2D you may want to pre-render the rotations ( maybe 8 or 12 frames ) and maybe even the scaling as well. Then at run-time all you would need to do is ADD and FADE the textures.

Doing 2 for loops and some SDL_AddPixel or any other Pixel level operation will get slower the bigger the texture gets.

Pre-rendering may take up more memory, but should give you a few extra clock cycles, I think. Or you could switch to using OpenGL for your 2D game where Rotating, Adding, Fading and Scaling are almost free.

WILL
12-10-2006, 10:03 AM
You know whats funny though? The function that uses Add instead of Alpha is actually a bit quicker. I believe that has a great deal to do with the fact that there is less calculations and only 1 GetPixel command called.

We'll I'd be glad to toss in some OpenGL stuff to do all of the messy stuff in hardware, but I'd probably need a few pointers on how I'd go about that.

Also, I think I can take off a bit more wasted ticks off of this function. At least enough to make this function a bit more manageable for use in the library it's self.

alexione
12-10-2006, 11:17 AM
Hi!

I've only had a look at SDL, and I've never really used it, but here are few ideas how could speed-up your function:


1) If SDL had someting like SDL_GetPixels(source_surface, source_rect, memory_buffer_1), then you could do all transformations in memory and after that you call SDL_AddPixels(dest_surface, dest_rect, memory_buffer_2).


2) Try avoiding floating operations. For example, you can do something like:

aSin, aCos: Longint

aCos := Round (degCOS[Angle] * 1024);
aSin := (degSIN[Angle] * 1024);

And then you do

NX := mx + (RX * aSin + RY * aCos) shr 10;
NY := my + (RY * aSin - RX * aCos) shr 10;


3) You should put

if ((DX > 0) and (DX < MAXX)) and
((DY > 0) and (DY < MAXY)) then

outside the loop - you first find limits for DX and DY, and then you loop between them.


4) Little trick: Change

if (NX >= srcRect.x) and (NX <= srcRect.x + srcRect.w) then

to

if (UInt32(NX - srcRect.x) <= UInt32(srcRect.w)) then

Also do the same with NY.


5) You can completely avoid SX and SY from your calculations:
SX := 0 -> RX := -OX
Inc(SX) -> Inc(RX)
SY := 0 -> RY := -OY
Inc(SY) -> Inc(RY)


Well, those are few thoughts that come to my mind. Hope they will help :)


Best regards
Alexa

jasonf
12-10-2006, 12:08 PM
I make extensive use of the AddBlit, Subtraction Blit, Add Rotate, Subtract Rotate functions in JEDI-SDL.. any speed increases in these would be greatly appreciated.

'm using some 386asm versions too.. from a while back. Do they still exist in JEDI-SDL?

I get fairly good performance with small sprites, but I could always use optimisations to these commonly called functions as things do slow down when there are lots of spinning particles on screen.

WILL
13-10-2006, 01:25 AM
alexione: Thanks a ton! :) I've managed to incorporate a few of these already.

Jason F.: I'll try. ;) Whats the version of JEDI-SDL files you are currently using? I'm using sort of a hybrid of my 0.5 modifications (sdlutils.pas file only) with JEDI-SDL 1.0 Beta


After working with these a bit, here are the functions as they exist right now.

SDL_RotateDeg_AddAlpha();
procedure SDL_RotateDeg_AddAlpha(DstSurface, SrcSurface: PSDL_Surface; SrcRect: PSDL_Rect;
DestX, DestY, OffsetX, OffsetY: Integer; Angle: Integer;
Alpha: UInt8);
var
aSin, aCos: Single;
MX, MY, DX, DY, NX, NY, OX, OY, Width, Height, TX, TY, RX, RY, ROX, ROY: Integer;
startDX, endDX, startDY, endDY: Integer;
SrcColor, DstColor: UInt32;
srcRR, srcGG, srcBB: UInt8;
TempTransparentColour: UInt32;
MAXX, MAXY: Integer;
begin
// Rotate the surface to the target surface.
TempTransparentColour := SrcSurface.format.colorkey;

maxx := DstSurface.w;
maxy := DstSurface.h;
aCos := degCOS[Angle];
aSin := degSIN[Angle];

Width := Round(abs(srcrect.h * aCos) + abs(srcrect.w * aSin));
Height := Round(abs(srcrect.h * aSin) + abs(srcrect.w * aCos));

OX := Width shr 1;
OY := Height shr 1;
MX := (srcRect.x + (srcRect.x + srcRect.w)) shr 1;
MY := (srcRect.y + (srcRect.y + srcRect.h)) shr 1;
ROX := (-(srcRect.w shr 1)) + Offsetx;
ROY := (-(srcRect.h shr 1)) + OffsetY;
Tx := ox + round(ROX * aSin - ROY * aCos);
Ty := oy + round(ROY * aSin + ROX * aCos);
RX := -OX;

startDX := DestX - TX;
if (startDX <= 0) then
begin
RX := RX + 1 - startDX;
startDX := 1;
end;

endDX := DestX - TX + Width;
if (endDX >= MAXX) then endDX := MAXX - 1;

for DX := startDX to endDX do
begin
inc(RX);
RY := -OY;

startDY := DestY - TY;
if (startDY <= 0) then
begin
RY := RY + 1 - startDY;
startDY := 1;
end;

endDY := DestY - TY + Height;
if (endDY >= MAXY) then
endDY := MAXY - 1;

for DY := startDY to endDY do
begin
NX := mx + Round(RX * aSin + RY * aCos); //
NY := my + Round(RY * aSin - RX * aCos); //

if (UInt32(NX - srcRect.x) <= UInt32(srcRect.w)) then
begin
if (UInt32(NY - srcRect.y) <= UInt32(srcRect.h)) then
begin
SrcColor := SDL_GetPixel(SrcSurface, NX, NY);
if (SrcColor <> TempTransparentColour) then
begin
SDL_GetRGB(SrcColor, SrcSurface.format, @srcRR, @srcGG, @srcBB);

SDL_AddPixel(DstSurface, DX, DY,
SDL_MapRGB(DstSurface.format,
(Alpha * srcRR) shr 8,
(Alpha * srcGG) shr 8,
(Alpha * srcBB) shr 8));
end;
end;
end;
inc(RY);
end;
end;
end;

SDL_RotateDeg_Alpha();
procedure SDL_RotateDeg_Alpha(DstSurface, SrcSurface: PSDL_Surface; SrcRect: PSDL_Rect;
DestX, DestY, OffsetX, OffsetY: Integer; Angle: Integer;
Alpha: UInt8);
var
aSin, aCos: Single;
MX, MY, DX, DY, NX, NY, OX, OY, Width, Height, TX, TY, RX, RY, ROX, ROY: Integer;
startDX, endDX, startDY, endDY: Integer;
SrcColor, DstColor: UInt32;
srcRR, srcGG, srcBB, dstRR, dstGG, dstBB: UInt8;
TempTransparentColour: UInt32;
MAXX, MAXY: Integer;
begin
// Rotate the surface to the target surface.
TempTransparentColour := SrcSurface.format.colorkey;

maxx := DstSurface.w;
maxy := DstSurface.h;
aCos := degCOS[Angle];
aSin := degSIN[Angle];

Width := Round(abs(srcrect.h * aCos) + abs(srcrect.w * aSin));
Height := Round(abs(srcrect.h * aSin) + abs(srcrect.w * aCos));

OX := Width shr 1;
OY := Height shr 1;
MX := (srcRect.x + (srcRect.x + srcRect.w)) shr 1;
MY := (srcRect.y + (srcRect.y + srcRect.h)) shr 1;
ROX := (-(srcRect.w shr 1)) + Offsetx;
ROY := (-(srcRect.h shr 1)) + OffsetY;
Tx := ox + round(ROX * aSin - ROY * aCos);
Ty := oy + round(ROY * aSin + ROX * aCos);
RX := -OX;

startDX := DestX - TX;
if (startDX <= 0) then
begin
RX := RX + 1 - startDX;
startDX := 1;
end;

endDX := DestX - TX + Width;
if (endDX >= MAXX) then endDX := MAXX - 1;

for DX := startDX to endDX do
begin
inc(RX);
RY := -OY;

startDY := DestY - TY;
if (startDY <= 0) then
begin
RY := RY + 1 - startDY;
startDY := 1;
end;

endDY := DestY - TY + Height;
if (endDY >= MAXY) then
endDY := MAXY - 1;

for DY := startDY to endDY do
begin
NX := mx + Round(RX * aSin + RY * aCos); //
NY := my + Round(RY * aSin - RX * aCos); //

if (UInt32(NX - srcRect.x) <= UInt32(srcRect.w)) then
begin
if (UInt32(NY - srcRect.y) <= UInt32(srcRect.h)) then
begin
SrcColor := SDL_GetPixel(SrcSurface, NX, NY);
if (SrcColor <> TempTransparentColour) then
begin
SDL_GetRGB(SrcColor, SrcSurface.format, @srcRR, @srcGG, @srcBB);

DstColor := SDL_GetPixel(DstSurface, DX, DY);
SDL_GetRGB(DstColor, DstSurface.format, @dstRR, @dstGG, @dstBB);

SDL_PutPixel(DstSurface, DX, DY,
SDL_MapRGB(DstSurface.format,
(Alpha * (srcRR - dstRR)) shr 8 + dstRR,
(Alpha * (srcGG - dstGG)) shr 8 + dstGG,
(Alpha * (srcBB - dstBB)) shr 8 + dstBB));
end;
end;
end;
inc(RY);
end;
end;
end;


Notes on alexione's Suggestions:

I was able to incorporate (4) + (5) without any trouble at all. (3) caused a small issue with the offset of the image's dst orientation until I factored in RX and RY into it. Works fine now. (1) I didn't fully understand... I think I need it explained to me in detail. And (2) I understand, but haven't been able to properly incorporate it without major issues (the function stops working all together so far)

Overall you actually notice a small bit or an improvement. Testing with about 11 60x60 textures you get about 3-5 frames difference. (on top of whats already in my game engine: background+2 tile layers+2 sprites+3 lines of rastered font text) But I think with (2) implimented I can squeeze a bit more.


There is one thing I'd like to try... and thats to replace SDL_AddPixel in form SDL_RotateDeg_AddAlpha() to try and optimize the calculation of the RGB values. Something I noticed was that the most intensive part of both of these functions is the calculation of the new color. And here I'm trying to do 2 sets of calculations instead of just one.


Here is the existing SDL_AddPixel(); function:
procedure SDL_AddPixel( DstSurface : PSDL_Surface; x : cardinal; y : cardinal; Color :
cardinal );
var
SrcColor : cardinal;
Addr : cardinal;
R, G, B : cardinal;
begin
if Color = 0 then
exit;
with DstSurface^ do
begin
Addr := cardinal( Pixels ) + y * Pitch + x * format.BytesPerPixel;
SrcColor := PUInt32( Addr )^;
case format.BitsPerPixel of
8 :
begin
R := SrcColor and $E0 + Color and $E0;
G := SrcColor and $1C + Color and $1C;
B := SrcColor and $03 + Color and $03;
if R > $E0 then
R := $E0;
if G > $1C then
G := $1C;
if B > $03 then
B := $03;
PUInt8( Addr )^ := R or G or B;
end;
15 :
begin
R := SrcColor and $7C00 + Color and $7C00;
G := SrcColor and $03E0 + Color and $03E0;
B := SrcColor and $001F + Color and $001F;
if R > $7C00 then
R := $7C00;
if G > $03E0 then
G := $03E0;
if B > $001F then
B := $001F;
PUInt16( Addr )^ := R or G or B;
end;
16 :
begin
R := SrcColor and $F800 + Color and $F800;
G := SrcColor and $07C0 + Color and $07C0;
B := SrcColor and $001F + Color and $001F;
if R > $F800 then
R := $F800;
if G > $07C0 then
G := $07C0;
if B > $001F then
B := $001F;
PUInt16( Addr )^ := R or G or B;
end;
24 :
begin
R := SrcColor and $00FF0000 + Color and $00FF0000;
G := SrcColor and $0000FF00 + Color and $0000FF00;
B := SrcColor and $000000FF + Color and $000000FF;
if R > $FF0000 then
R := $FF0000;
if G > $00FF00 then
G := $00FF00;
if B > $0000FF then
B := $0000FF;
PUInt32( Addr )^ := SrcColor and $FF000000 or R or G or B;
end;
32 :
begin
R := SrcColor and $00FF0000 + Color and $00FF0000;
G := SrcColor and $0000FF00 + Color and $0000FF00;
B := SrcColor and $000000FF + Color and $000000FF;
if R > $FF0000 then
R := $FF0000;
if G > $00FF00 then
G := $00FF00;
if B > $0000FF then
B := $0000FF;
PUInt32( Addr )^ := R or G or B;
end;
end;
end;
end;

alexione
13-10-2006, 09:06 AM
I was able to incorporate (4) + (5) without any trouble at all. (3) caused a small issue with the offset of the image's dst orientation until I factored in RX and RY into it. Works fine now.

Great!


(1) I didn't fully understand... I think I need it explained to me in detail.

This is closely connected to your notice that you need to speed-up SDL_AddPixel (more about this later). My idea was to call SDL_GetPixels, so you get all pixels of specified rectangle as DWORDs and you do your calculations in 32bit format. After that, you call SDL_AddPixels, and it transforms pixels back from 32bit to whatever format you use.


And (2) I understand, but haven't been able to properly incorporate it without major issues (the function stops working all together so far)

Hm, try using more bits for precision. Here's more refactored version:

const
PRECISION_BITS = 10;
PRECISION_ONE = 1 shl PRECISION_BITS;
PRECISION_HALF = 1 shl (PRECISION_BITS - 1);

aCos := Round (degCOS[Angle] * PRECISION_ONE);
aSin := Round (degSIN[Angle] * PRECISION_ONE);

NX := mx + (RX * aSin + RY * aCos + PRECISION_HALF) shr PRECISION_BITS;
NY := my + (RY * aSin - RX * aCos + PRECISION_HALF) shr PRECISION_BITS;

Now, try using 15 or 16 bits, but you should keep in mind that all of your calculations should stay inside 32 bits.


There is one thing I'd like to try... and thats to replace SDL_AddPixel in form SDL_RotateDeg_AddAlpha() to try and optimize the calculation of the RGB values. Something I noticed was that the most intensive part of both of these functions is the calculation of the new color. And here I'm trying to do 2 sets of calculations instead of just one.

One thing you should definitely do is to remove case inside your SDL_AddPixel. One way to do this is to have

type TAddPixelProc = procedure( DstSurface : PSDL_Surface; x : cardinal; y : cardinal; Color : cardinal );

and inside format structure, you have

AddPixelProc: TAddPixelProc;

So, instead of calling SDL_AddPixel(...), you would call

DstSurface.format.AddPixelProc(...)

This way, you will eliminate format-check performed for every pixel. Of course, you'll need specific versions SDL_AddPixel8, SDL_AddPixel15, ...

Hope this will help a bit :)