Slow Drawing, how can I improve it

Started by Dualnames, Sun 10/02/2019 14:26:02

Previous topic - Next topic

Dualnames

This basically creates a circle and saves each frame on a Dynamic Sprite array (size of 20).
I've noticed that this takes 2-4 seconds to execute, and the whole issue stands (i've commented parts of the code etc) inside the two while loops, is there a way I can make this faster/better?



Code: ags


//CODE IS EXECUTED TILL 20 Dynamic Sprite FRAMES ARE CREATED.

  waveFrames[waveFrameC]=DynamicSprite.CreateFromExistingSprite(waveFrames[0].Graphic, true);//waveFrameC
  DrawingSurface*area = waveFrames[waveFrameC].GetDrawingSurface();
  DrawingSurface*refarea = waveFrames[0].GetDrawingSurface();//waveFrameC-1
  
      float Size=6.0;//2.0;
      float IncX=2.6;
      int CircleSize=((wvc+1)-waveFrameC);
      int incBy=waveFrameC*FloatToInt(Size, eRoundNearest);//*6;
      
      int incByx=FloatToInt(IntToFloat(incBy)*IncX, eRoundNearest);
      
      
      int CentralX=cEgo.x;
      int CentralY=cEgo.y-150;
      
      int x=CentralX+(incByx);
      int x2=CentralX-(incByx);
      
      int y=CentralY+(incBy);
      int y2=CentralY-(incBy);//180-(incBy);
      
      int d=x2;
      int d1=0;
      int f=y2;
      int f1=0;
      
      
      float difX;
      float difY;
      float sum;
      float distance;        
      int checkDist;
      
      float gx=IntToFloat(waveFrameC)*Size*IncX;
      int getXv=FloatToInt(gx, eRoundNearest);
      int getX=CentralX-getXv;
      int getY=CentralY-(waveFrameC*6);
      
      while (d < x && CircleSize>0)//while
      {
        f=y2;
        f1=0;
        while (f < y)
        {
          //CALCULATE DISTANCE BETWEEN TWO POINTS
          
          difX = IntToFloat(CentralX) - IntToFloat(d);
          difY = IntToFloat(CentralY) - IntToFloat(f);
          difX = Maths.RaiseToPower(difX, 2.0);
          difY = Maths.RaiseToPower(difY, 2.0);
          sum = difX+difY;
          distance = Maths.Sqrt(sum);        
          checkDist = FloatToInt(distance, eRoundNearest);
          
          if (checkDist<= incBy && checkDist>incBy-CircleSize)
          {            
            area.DrawingColor=refarea.GetPixel(getX+d1, getY + (f1-1));            
            area.DrawPixel(d, f);
          }
          
          f++;
          f1++;
        }
        d1++;
        d++;
      }    
  
  
  area.Release();
  refarea.Release();
  waveFrameC++;
  
  
}
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Snarky

#1
A common trick is to skip the square root and instead compare the squared distance to the square of the threshold(s).

Also, you don't need to do the difX/Y calculations as floats, do you?

Another approach might be to just use DrawingSurface.DrawCircle() and DynamicSprite.CopyTransparencyMask() to handle the masking, instead of drawing it manually. I'm not sure whether it's quicker, but it might be worth a try.

Dualnames

The issue doesn't stand on the calculations, albeit those can be improved, the issue lies on the two nested while loops

Code: ags

while (d < x && CircleSize>0)
{
 f=y2;
        f1=0;
        while (f < y)
        {         
          f++;
          f1++;
        }
        d1++;
        d++;
      }    


Even like this, no drawing whatsoever, the frame rate drops to 10 for a solid 2 seconds.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Snarky

Well, have you calculated or checked how many loops that works out to be? Trying to follow your code, it appears to depend on the player x/y position.

Dualnames

#4
It takes about 3 seconds to finish executing the code, around 120-150 loops. I did your adjustments, but same problem, I'm afraid.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Snarky

Quote from: Dualnames on Sun 10/02/2019 15:06:51
Even like this, no drawing whatsoever, the frame rate drops to 10 for a solid 2 seconds.

Well, you're a very good coder (better than I am, for sure), so you don't need me to tell you that the AGS framerate isn't gonna drop to 10 fps just because you're counting to 150 each game loop.

Which means that something is off in your analysis. Either you're counting wrong and it's way more loops than that (150 inside each loop, maybe?), or the main source of the slowdown is elsewhere.

Dualnames

I've noticed i was drawing a smaller circle than a rectangle, so now frames are at 29. 11 fps droprate.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Jack

Unless it's changed recently, pixel operations are too slow in AGS to be used outside of AGI resolutions.

Crimson Wizard

#8
Quote from: Jack on Sun 10/02/2019 21:23:10
Unless it's changed recently, pixel operations are too slow in AGS to be used outside of AGI resolutions.

I'd say it's definitely not something you'd like to do every game tick.



Quote from: Dualnames on Sun 10/02/2019 15:06:51
The issue doesn't stand on the calculations, albeit those can be improved, the issue lies on the two nested while loops
<...>
Even like this, no drawing whatsoever, the frame rate drops to 10 for a solid 2 seconds.

In the end was this confirmed or something else is causing slowdowns?
Could you post the newer code after changes made by Snarky's advice?


Also, why are you writing your own circle drawing instead of using DrawCircle? It seems like you need to combine certain images?. TBH I am too tired right now to understand what the code is doing without comments...

Snarky

If I understand the code correctly, it draws a sprite that's a copy of some given area, except that in a ring between an outer circle and an inner circle, the coordinates are offset somehow (by a fixed amount, it seems). The size of the circles depends on a formula that uses the position of the player as well as the index of the sprite in an array, but working out the details without seeing it in action is beyond me. The excerpt we're seeing draws a single frame, it presumably exists within a bigger loop.

My interpretation is that it's meant to draw some sort of animated bubbles (or similar).

Dualnames

This is what it's supposed to be doing https://www.youtube.com/watch?v=HUxcmeT3LKs

I altered the code to make it run to 40 fps or so and made the effect a bit more subtle.

https://www.youtube.com/watch?v=vXQCf3zYez4&feature=youtu.be

Ignore the 2 characters, there are still WIP parts of this.
But you can see the frame drop is non existent.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Dualnames

#11
Code: ags


int i=0;
while (i <20)
{
while (d < x && CircleSize>0)
{
 f=y2;
        f1=0;
        while (f < y)
        {         
          f++;
          f1++;
        }
        d1++;
        d++;
      }    
i++;
}



My main thing, is that the issues within AGS's inability to perform complex drawing stuff in real-time, isn't on the drawing functions. A big while like the one above, seems to be the problem in most cases. Like for example doing

Code: ags


int x=0;
while (x < width)//Width being 640 or 320 or whatever
{
int y=0;
while (y < height)//same as above
{
y++;
}
x++;
}



This loop without anything inside the while conditions, just as is, will cause framerate drop. I'm not sure if that's ameniable/fixable in any way, but I think it would solve a lot of issues. I wanted to use LUA for AGS but it's very risky given the state of that plugin. Regardless, I appreciate your work and I love you super hardcore <3.
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)


Gurok

Jack suggested lowering the resolution on IRC. Another way to control the number of iterations would be to draw the radius of the circle at various angles like a bunch of tiny sectors instead of checking all possible x/y values for the ones that lie in the circle.
[img]http://7d4iqnx.gif;rWRLUuw.gi

eri0o

Just wanted to add that this is a very cool effect!

One way I can see it be emulated is creating a circle with the desired alpha for transitions and stuff in a bigger size, then resizing it, creating a new sprite in a square area from the background, copying the prepared circle alpha on this square area you just got from the background, and do another resize at a slightly bigger size. It's a bit tricky but would avoid the while and could possibly run with no frame drops - need to test though.

Kweepa

It looks like you are modifying a ring about 150-200 pixels in diameter, so writing maybe 14000 pixels.
I seem to remember AGS was capable of updating 160x100=16000 pixels at about 20 FPS back in the day, so it should be able to manage 40 FPS now.
To ensure you are looping through the minimum number of iterations, I would work per scan line, and calculate the left and right extent of the circle, then decide if you are in a section with an inner unmodified part, and skip over the unmodified pixels, something like this:

// for a ring with small radius r and large radius R
for (y = (centerY - R) to (centerY + R))
   h = abs(y - centerY)
   halfOuterWidth = sqrt(R*R - h * h)
   if (h < r)
      halfInnerWidth = sqrt(r * r - h * h)
      // modify pixels from centerX-halfOuterWidth to centerX-halfInnerWidth and from centerX+halfInnerWidth to centerX+halfOuterWidth
   else
      // modify pixels from centerX-halfOuterWidth to centerX+halfOuterWidth
Still waiting for Purity of the Surf II

Snarky

Quote from: Dualnames on Sun 10/02/2019 22:50:45
I altered the code to make it run to 40 fps or so

I'd be interested to hear what you did to make that happen.

Quote from: Kweepa on Sat 16/02/2019 00:51:01
// for a ring with small radius r and large radius R
for (y = (centerY - R) to (centerY + R))

You don't need to run through the whole way from the top to bottom of the circle, because of symmetry; only the top half, then do the same operations except with the signs changed.

Kweepa

#17
Quote from: Snarky on Sat 16/02/2019 06:52:01
You don't need to run through the whole way from the top to bottom of the circle, because of symmetry; only the top half, then do the same operations except with the signs changed.
Yeah, but the slow bit is the bit inside the inner loop, so it just complicates things.

This runs at 40 fps on my laptop.
I just move the ring in and out with a sin() for simplicity.

http://www.kweepa.org/step/ags/tech/Screech.zip

Code: ags

// room script file

DynamicSprite *unchangedBg;

float time = 0.0;
 // when I made the ring 20 pixels thick it slowed down
float ringThickness = 15.0;

function room_RepExec()
{
  time += 0.1;
  DynamicSprite *newBg = DynamicSprite.CreateFromExistingSprite(unchangedBg.Graphic);
  float bigR = 60.0 + 39.0 * Maths.Sin(time);
  float weeR = bigR - ringThickness;
  float x;
  float y;
  DrawingSurface *oldSurf = unchangedBg.GetDrawingSurface();
  DrawingSurface *newSurf = newBg.GetDrawingSurface();
  for (y = -bigR; y < bigR; y += 1.0)
  {
    float p = bigR*bigR - y*y;
    if (p > 1.0)
    {
      float how = Maths.Sqrt(p);
      float q = weeR*weeR - y*y;
      if (q > 1.0)
      {
        float hiw = Maths.Sqrt(q);
        for (x = -how; x < -hiw; x+= 1.0)
        {
          float d = 1.0 - 3.0 / Maths.Sqrt(x*x + y*y);
          newSurf.DrawingColor = oldSurf.GetPixel(100 + FloatToInt(x*d), 100 + FloatToInt(y*d));
          newSurf.DrawPixel(100 + FloatToInt(x), 100 + FloatToInt(y));
        }
        for (x = hiw; x < how; x+= 1.0)
        {
          float d = 1.0 - 3.0 / Maths.Sqrt(x*x + y*y);
          newSurf.DrawingColor = oldSurf.GetPixel(100 + FloatToInt(x*d), 100 + FloatToInt(y*d));
          newSurf.DrawPixel(100 + FloatToInt(x), 100 + FloatToInt(y));
        }
      }
      else
      {
        for (x = -how; x < how; x+= 1.0)
        {
          float d = 1.0 - 3.0 / Maths.Sqrt(x*x + y*y);
          newSurf.DrawingColor = oldSurf.GetPixel(100 + FloatToInt(x*d), 100 + FloatToInt(y*d));
          newSurf.DrawPixel(100 + FloatToInt(x), 100 + FloatToInt(y));
        }
      }
    }
  }
  
  DrawingSurface *ds = Room.GetDrawingSurfaceForBackground();
  ds.DrawImage(100, 50, newBg.Graphic);
  newBg.Delete();
}

function room_Load()
{
  Debug(4, 1);
  unchangedBg = DynamicSprite.CreateFromBackground(0, 100, 50, 200, 200);
}
Still waiting for Purity of the Surf II

Dualnames

what a bastard, that's amazing, really nice idea on solving the thing with the circle from your previous post!
Worked on Strangeland, Primordia, Hob's Barrow, The Cat Lady, Mage's Initiation, Until I Have You, Downfall, Hunie Pop, and every game in the Wadjet Eye Games catalogue (porting)

Jack

I was thinking, it might be the nested loops slowing it down. Maybe it will go better if you do one big loop for all pixels and use div and modulus to get the column and row indexes.

SMF spam blocked by CleanTalk