ENIGMA Forums

Contributing to ENIGMA => Function Peer Review => Topic started by: TheExDeus on September 25, 2010, 11:05:31 am

Title: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: TheExDeus on September 25, 2010, 11:05:31 am
Notice: These functions work with any sprite size, but the functionality in _ext differs from GM one. The difference is with scaling. x and y arguments, according to manual, is "(x,y) is the place where one of the sprites is drawn". In GM these arguments are modified with scaling, so, for example, when you have a 100x100 sprite, you tile it with 0.5 scale, and put 25 in x and y position, then you won't have a sprite drawn in 25,25 as manual suggests, but you will have one drawn at 12.5,12.5. In this code this it will be drawn in 25,25. This can be changed with two parenthesis, but I think this is how the function needed to work in the first place.

Function: draw_sprite_tiled(int spr,int subimg,double x,double y);

GSsprite.h:
Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y);
GSsprite.cpp:
Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }


  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
    glColor4f(1,1,1,1);

    const float tbx=spr2d->texbordx,tby=spr2d->texbordy,
        xoff=spr2d->xoffset+x,
        yoff=spr2d->yoffset+y;
    const int hortil= int (ceil(room_width/(spr2d->width*tbx))),
        vertil= int (ceil(room_height/(spr2d->height*tby)));
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++){
        for (int c=0; c<vertil; c++){
      glTexCoord2f(0,0);
        glVertex2f(i*spr2d->width-xoff,c*spr2d->height-yoff);
      glTexCoord2f(tbx,0);
        glVertex2f((i+1)*spr2d->width-xoff,c*spr2d->height-yoff);
      glTexCoord2f(tbx,tby);
        glVertex2f((i+1)*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
      glTexCoord2f(0,tby);
        glVertex2f(i*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
        }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

Function:draw_sprite_tiled_ext(int spr,int subimg,double x,double y,double xscale,double yscale,int color,double alpha);

GSsprite.h:
Code: [Select]
int draw_sprite_tiled_ext(int spr,int subimg,double x,double y,double xscale,double yscale,int color,double alpha);
GSsprite.cpp:
Code: [Select]
int draw_sprite_tiled_ext(int spr,int subimg,double x,double y, double xscale,double yscale,int color,double alpha)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
  glColor4ub(__GETR(color),__GETG(color),__GETB(color),char(alpha*255));

    const float tbx=spr2d->texbordx,tby=spr2d->texbordy,
        xoff=spr2d->xoffset*xscale+x,
        yoff=spr2d->yoffset*yscale+y;
    const int hortil= int (ceil(room_width/(spr2d->width*tbx*xscale))),
        vertil= int (ceil(room_height/(spr2d->height*tby*yscale)));
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++){
        for (int c=0; c<vertil; c++){
      glTexCoord2f(0,0);
        glVertex2f(i*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
      glTexCoord2f(tbx,0);
        glVertex2f((i+1)*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
      glTexCoord2f(tbx,tby);
        glVertex2f((i+1)*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
      glTexCoord2f(0,tby);
        glVertex2f(i*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
        }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

In attachments you can see two images.
First one is: draw_sprite_tiled(spr_0,0,50,50);
Other one is: draw_sprite_tiled_ext(spr_0,0,25,25,0.5,0.5,c_yellow,0.5);
Title: Re: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: Josh @ Dreamland on October 16, 2010, 01:55:48 pm
Your choice to use room_width and height disregards views. Other than that, I guess I don't find fault with this.
I should also mention that I appreciate your single use of GL_QUADS, as opposed to one each for() iteration.
Title: Re: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: TheExDeus on October 16, 2010, 05:04:42 pm
Quote
Your choice to use room_width and height disregards views.
Yes, I did thought of using views. With one view it would be easy and it would increase the functions speed, but I figured that it would be quite hard with several views. Then you would need to title in every view, which could get slower. And if views overlapped then it would create visual artifacts (especially if the tiled sprite has alpha). So I think this is the best way. Just tile in all of the room. This functions isn't used in 10kx10k rooms anyway. At least it shouldn't. For one view and large room user can use draw_sprite_tiled_area.
Quote
I should also mention that I appreciate your single use of GL_QUADS, as opposed to one each for() iteration.
Well I am not that bad at optimizing. :D
Title: Re: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: Josh @ Dreamland on October 16, 2010, 09:55:24 pm
Oh, don't worry about that. I replaced your use of room_width and height with a simple ternary expression. Views are inefficient as dog in GM anyway.

This is the code I ended up including, which I will assume to work until further notice:

Code: [Select]
int draw_sprite_tiled(int spr,int subimg,double x,double y)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;
 
  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }
 
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
 
  glPushAttrib(GL_CURRENT_BIT);
    glColor4f(1,1,1,1);
   
    const float
      tbx  = spr2d->texbordx,  tby  = spr2d->texbordy,
      xoff = spr2d->xoffset+x, yoff = spr2d->yoffset+y;
    const int
      hortil = int (ceil(   (view_enabled ? view_xview[view_current] + view_wview[view_current] : room_width)    / (spr2d->width*tbx))),
      vertil = int (ceil(   (view_enabled ? view_yview[view_current] + view_hview[view_current] : room_height)   / (spr2d->height*tby)));
   
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++)
    {
      for (int c=0; c<vertil; c++)
      {
        glTexCoord2f(0,0);
          glVertex2f(i*spr2d->width-xoff,c*spr2d->height-yoff);
        glTexCoord2f(tbx,0);
          glVertex2f((i+1)*spr2d->width-xoff,c*spr2d->height-yoff);
        glTexCoord2f(tbx,tby);
          glVertex2f((i+1)*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
        glTexCoord2f(0,tby);
          glVertex2f(i*spr2d->width-xoff,(c+1)*spr2d->height-yoff);
      }
    }
    glEnd();
  glPopAttrib();
  return 0;
}

int draw_sprite_tiled_ext(int spr,int subimg,double x,double y, double xscale,double yscale,int color,double alpha)
{
  enigma::sprite *spr2d = enigma::spritestructarray[spr];
  if (!spr2d)
    return -1;

  if (enigma::cur_bou_tha_noo_sho_eve_cha_eve != spr2d->texturearray[subimg % spr2d->subcount])
  {
    glBindTexture(GL_TEXTURE_2D,spr2d->texturearray[subimg % spr2d->subcount]);
    enigma::cur_bou_tha_noo_sho_eve_cha_eve = spr2d->texturearray[subimg % spr2d->subcount];
  }

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);


  glPushAttrib(GL_CURRENT_BIT);
  glColor4ub(__GETR(color),__GETG(color),__GETB(color),char(alpha*255));

    const float
      tbx  = spr2d->texbordx,         tby  = spr2d->texbordy,
      xoff = spr2d->xoffset*xscale+x, yoff = spr2d->yoffset*yscale+y;
    const int
      hortil= int (ceil(   (view_enabled ? view_xview[view_current] + view_wview[view_current] : room_width)    / (spr2d->width*tbx*xscale))),
      vertil= int (ceil(   (view_enabled ? view_yview[view_current] + view_hview[view_current] : room_height)   / (spr2d->height*tby*yscale)));
   
    glBegin(GL_QUADS);
    for (int i=0; i<hortil; i++)
    {
      for (int c=0; c<vertil; c++)
      {
        glTexCoord2f(0,0);
          glVertex2f(i*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
        glTexCoord2f(tbx,0);
          glVertex2f((i+1)*spr2d->width*xscale-xoff,c*spr2d->height*yscale-yoff);
        glTexCoord2f(tbx,tby);
          glVertex2f((i+1)*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
        glTexCoord2f(0,tby);
          glVertex2f(i*spr2d->width*xscale-xoff,(c+1)*spr2d->height*yscale-yoff);
      }
    }
    glEnd();

    glPopAttrib();
    return 0;
}

I have one bone left to pick with it, being that it does not account for a special-case optimization in which the bind mode can be set to GL_WRAP and the sprite can just be drawn large. This can be checked for simply by testing that both spr->texbordx and texbordy are 1. In this case, the entirety of the allocated space is used, and there is no need to loop anything (The GPU is, of course, much more proficient at menial looping than the CPU).

My recommendation, as I will soon implement if no one else does, is a pattern like so:

if (spr->texbordx == 1)
{
  if (spr->texbordy == 1)
  {
     Draw single quad with appropriate texture bounds. Negative and extremely large bounds are both valid.
  }
  else
    for (all Y coordinates)
     draw a large horizontal quad with appropriate x bounds, but with texbordy as the y bound.
}
else
  if (spr->texbordy == 1)
    for (all X coordinates)
     draw a large horizontal quad with appropriate Y bounds, but with texbordy as the Y bound.
  else
   Exactly what you do now.

Granted, that's a "fucking large function," and with two branches, no less. The average time saving becomes a question, and we have to wonder if it was worth it for the extra checks and the extra memory. (Both of which are relatively negligible; it's all a matter of weight).



And this is the shit I mull over all day and night.
Title: Re: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: TheExDeus on October 17, 2010, 04:51:30 am
Nevermind..
Title: Re: GML: draw_sprite_tiled + draw_sprite_tiled_ext
Post by: Josh @ Dreamland on October 17, 2010, 08:15:30 am
What am I never minding?