Trying to dynamically render cards in game faster

Started by
15 comments, last by Oconzer 9 years, 2 months ago

For my game that I'm developing I want to have my cards dynamically drawn so if there is any changes done to them it could be drawn on the card itself. I'm currently using a CPU method as I'm not sure if there is a way to do it via GPU as I'm new to drawing and textures.

What I have currently is that the cards are drawn to a RenderTarget at their originally designed size of 2100x1480 px. (I'll probably for game purposes have to scale that down to 1050x740 for its native size, but was designed that way to have 600 dpi for physical versions).

Anyway the way I want to do it is that after drawing everything to the RenderTarget I scale it down and store that scaled down texture to the card's texture (which will be 164 x 115 for the playing size, the 1050 x 740 is the zoomed in size that I want so players can view what the card does and stats). The issue that I'm facing is that the way I currently tried to do it was far too slow taking over 9 seconds to scale it down after drawing the original size to the RenderTarget.

The code that I'm currently using:


protected void DrawCardTest(decimal cardScale)
{
   startTime = DateTime.Now;
   // Set the device to the render target
   graphics.GraphicsDevice.SetRenderTarget(cardRenderTarget);

   graphics.GraphicsDevice.Clear(Color.Transparent);

   spriteBatch.Begin();
   Vector2 pos = Vector2.Zero;
   spriteBatch.Draw(cardDrawables.Texture, pos, cardDrawables.SourceRectangle("Blank Card Front"), Color.White, 0.0F, pos, new Vector2(1.0F, 1.0F), SpriteEffects.None, 0.0F);
   spriteBatch.End();

   // Reset the device to the back buffer so can grab the texture from the rendertarget
   graphics.GraphicsDevice.SetRenderTarget(null);
   colorArrayForTexture = new Color[cardDrawables.SourceRectangle("Blank Card Front").Width * cardDrawables.SourceRectangle("Blank Card Front").Height];
   cardRenderTarget.GetData(colorArrayForTexture);

   cardTest = new Texture2D(graphics.GraphicsDevice, (int)(cardRenderTarget.Width * (decimal)cardScale), (int)(cardRenderTarget.Height * (decimal)cardScale));

   Color[] arrayAfterScale = new Color[(int)(cardRenderTarget.Width * (decimal)cardScale) * (int)(cardRenderTarget.Height * (decimal)cardScale)];

   cardTest.SetData(ScaleTexture(colorArrayForTexture, cardRenderTarget.Width, cardRenderTarget.Height, (decimal)cardScale));
   endTime = DateTime.Now;
}

public Color[] ScaleTextureNew(Color[] textureData, int width, int height, decimal scaleAmount)
{
   #region ScaleTexture Variables and Setup
   decimal scaleX = scaleAmount;
   decimal scaleY = scaleAmount;

   int widthAfterScale = (int)(width * scaleX);
   int heightAfterScale = (int)(height * scaleY);

   //create a pixel array that will hold the scaled texture data
   Color[] scaledTextureData = new Color[widthAfterScale * heightAfterScale];

   ColorStorage colorToAverage = new ColorStorage();

   decimal widthStep = (decimal)width / (decimal)widthAfterScale;
   decimal heightStep = (decimal)height / (decimal)heightAfterScale;

   decimal widthTotal = widthStep;
   decimal heightTotal = heightStep;

   //check to see if width sample total or height sample total will be repeating
   if ((widthStep * widthAfterScale) != width) // this should equal out if not there is a repeating issue
      widthTotal += repeatingFix;
   if ((heightStep * heightAfterScale) != height) // this should equal out if not there is a repeating issue
      heightTotal += repeatingFix;

   decimal sampledWidth = 0.0M;
   decimal sampledHeight = 0.0M;

   decimal widthSampling = 0.0M;
   decimal heightSampling = 0.0M;

   decimal widthSamplingTotal = 0.0M;
   decimal heightSamplingTotal = 0.0M;

   decimal widthIncrement = 0.0M;
   decimal heightIncrement = 0.0M;

   decimal widthRemainder = 0.0M;
   decimal heightRemainder = 0.0M;

   decimal transparentPixelArea = 0.0M;
   decimal areaSampled = 1.0M;
   decimal totalAreaSampled = 0.0M;

   decimal currentHeight = 0.0M;
   decimal currentWidth = 0.0M;

   decimal alpha = 0.0M;

   if (widthStep > 1.0M)
      widthIncrement = 1.0M;
   else if (widthStep < 1.0M)
      widthIncrement = widthStep;

   if (heightStep > 1.0M)
      heightIncrement = 1.0M;
   else if (heightStep < 1.0M)
      heightIncrement = heightStep;

   Index widthIndexUnscaled = new Index(0);
   Index heightIndexUnscaled = new Index(0);

   Index widthIndexScaled = new Index(0);
   Index heightIndexScaled = new Index(0);

   Index widthIndexToUse;
   Index heightIndexToUse;

   int indexIntoArray = 0;

   decimal[] samplingArrayWidth; // use the bigger between width and widthafterscale
   if (width > widthAfterScale)
   {
      samplingArrayWidth = new decimal[width];
      widthIndexToUse = widthIndexUnscaled;
   }
   else
   {
      samplingArrayWidth = new decimal[widthAfterScale];
      widthIndexToUse = widthIndexScaled;
   }
   CalculateSampling(ref samplingArrayWidth, widthStep);

   decimal[] samplingArrayHeight; // use the bigger between width and widthafterscale
   if (height > heightAfterScale)
   {
      samplingArrayHeight = new decimal[height];
      heightIndexToUse = heightIndexUnscaled;
   }
   else
   {
      samplingArrayHeight = new decimal[heightAfterScale];
      heightIndexToUse = heightIndexScaled;
   }
   CalculateSampling(ref samplingArrayHeight, heightStep);

   bool doneScaling = false;
   #endregion

   //will iterate top left to right bottom
   while (!doneScaling)
   {
      while (sampledHeight < heightStep)
      {
         if (heightRemainder == 0.0M)
         {
            heightRemainder = heightStep - samplingArrayHeight[heightIndexToUse.Point];
            heightSampling = heightStep - heightRemainder;
         }
         else if (heightRemainder >= 1.0M)
         {
            heightRemainder -= samplingArrayHeight[heightIndexToUse.Point];
            heightSampling = 1.0M;
         }
         else
            heightSampling = heightRemainder;

         while (sampledWidth < widthStep)
         {
            if (widthRemainder == 0.0M)
            {
               widthRemainder = widthStep - samplingArrayWidth[widthIndexToUse.Point];
               widthSampling = widthStep - widthRemainder;
            }
            else if (widthRemainder >= 1.0M)
            {
               widthRemainder -= samplingArrayWidth[widthIndexToUse.Point];
               widthSampling = 1.0M;
            }
            else
               widthSampling = widthRemainder;

               areaSampled *= heightSampling * widthSampling;

               // check to see if the pixel is blank to correctly sample
               indexIntoArray = width * heightIndexUnscaled.Point + widthIndexUnscaled.Point; // so don't have to calculate index every time
               if (textureData[indexIntoArray].A == 0)
                  transparentPixelArea += areaSampled;

               alpha = textureData[indexIntoArray].A;
               colorToAverage.Alpha += alpha * areaSampled;
               alpha /= 255; //get the oppacity percentage to reverse the multiplyied alpha onto the colors

               if (alpha == 0.0M)
                  alpha = 1.0M; // this is so colors don't get created where there were none.

               // undivide the colors by alpha since its been done and needs to be corrected

               colorToAverage.Red += textureData[indexIntoArray].R * areaSampled / alpha;// *alpha * areaSampled;
               colorToAverage.Green += textureData[indexIntoArray].G * areaSampled / alpha;// *alpha * areaSampled;
               colorToAverage.Blue += textureData[indexIntoArray].B * areaSampled / alpha;// *alpha * areaSampled;

               //add the area sampled to total and reset area to sample
               totalAreaSampled += areaSampled;
               areaSampled = 1.0M;

               sampledWidth += widthSampling;
               widthSamplingTotal += widthSampling;
               widthIndexUnscaled.Point = (int)widthSamplingTotal; // have to do this here since counter has to change to sample properly
            }
            widthSampling = 0.0M;
            widthRemainder = 0.0M;
            sampledWidth = 0.0M;
            widthSamplingTotal = currentWidth;
            widthIndexUnscaled.Point = (int)widthSamplingTotal; // have to do this here since counter has to change to sample properly

            sampledHeight += heightSampling;
            heightSamplingTotal += heightSampling;
            heightIndexUnscaled.Point = (int)heightSamplingTotal; // have to do this here since counter has to change to sample properly
         }
         heightSampling = 0.0M;
         heightRemainder = 0.0M;
         sampledHeight = 0.0M;

         //calculate the scaled texture's value here
         scaledTextureData[heightIndexScaled.Point * widthAfterScale + widthIndexScaled.Point] = ColorAveraged(colorToAverage,
           (totalAreaSampled - transparentPixelArea), totalAreaSampled);

         //reset colorToAverage for next pass
         colorToAverage.Alpha = 0.0M;
         colorToAverage.Red = 0.0M;
         colorToAverage.Blue = 0.0M;
         colorToAverage.Green = 0.0M;

         totalAreaSampled = 0.0M;
         transparentPixelArea = 0.0M;

         if (widthTotal >= width) // at the right of the texture
         {
            if (heightTotal >= height) // at the bottom of the texture
               doneScaling = true;
            else
            {
               //increment height stuff for next line of texture
               currentHeight = heightTotal;
               heightSamplingTotal = currentHeight;
               heightIndexUnscaled.Point = (int)heightSamplingTotal; // have to do this here since counter has to change to sample properly
               heightTotal += heightStep;
               heightIndexScaled.Point++;

               //reset width stuff for next line of texture
               widthTotal = widthStep;

               if ((widthStep * widthAfterScale) != width) // this should equal out if not there is a repeating issue
                  widthTotal += repeatingFix;

               widthIndexScaled.Point = 0;
               widthIndexUnscaled.Point = 0;
            }
         }
         else
         {
            currentWidth = widthTotal;
            widthSamplingTotal = currentWidth;
            widthIndexUnscaled.Point = (int)widthSamplingTotal; // have to do this here since counter has to change to sample properly
            widthTotal += widthStep;
            widthIndexScaled.Point++;
            heightSamplingTotal = currentHeight;
            heightIndexUnscaled.Point = (int)heightSamplingTotal; // have to do this here since counter has to change to sample properly
         }
      }

      decimal widthToBlur;
      if (widthAfterScale > width) // only blur width if its been scaled up
         widthToBlur = ((decimal)widthAfterScale / (decimal)width) / 2.0M;
      else
         widthToBlur = 0.0M;

      decimal heightToBlur;
      if (heightAfterScale > height) // only blur height if its been scaled up
         heightToBlur = ((decimal)heightAfterScale / (decimal)height) / 2.0M;
      else
         heightToBlur = 0.0M;

      //if ((height < heightAfterScale) || (width < widthAfterScale)) //texture was scaled up so need to blur it
      // scaledTextureData = Blur(scaledTextureData, widthAfterScale, heightAfterScale,
      // widthToBlur, heightToBlur);
      return scaledTextureData;
   }

}

What the function is supposed to do. Example1: if scaling down a 4x4 image to a 2x2 image each pixel for the 2x2 image will be a composition of a 2x2 pixel sampling area of the original image.

Example2: if scaling down a 66x66 image to a 10x10 image each pixel for the 10x10 image will be a composition of a 6.6x6.6 pixel sampling area of the original image.

EDIT: Not sure how to do code for this site so it shows up more readable. :S

Advertisement

Yeah, pretty much impossible to read. Scaling with the draw call doesn't work well enough?

The Four Horsemen of Happiness have left.

Is there something that the built-in scaling and rotating code does not handle? Some reason you are trying to do all this yourself?

Seems rather burdensome to render it, copy the image, then attempt to manually scale the earlier rendering. Instead just draw it with the right parameters in the first place.

I want to be able to scale the texture down so it doesn't take up as much memory is the reason that I'm trying to scale the texture down from the render target and storing it in a texture. If I do not the memory taken by each card will be 2100x1480x4/1024/1024 = 11.856 MB per card and when my game is going to have 6 players able to play at once each with upto 100 cards that could not be duplicates, thats now 11.856 MB * 100 * 6 = 7113.6 MB

That is the reason that I need to be able to scale the texture down, not for being drawn but for storage reasons.

Also I don't have any clue how to format the code on this site to show up as code so its easily read.

Also I don't have any clue how to format the code on this site to show up as code so its easily read.

Place 'code' tags around your code, like html tags but using square brackets [] instead of angle brackets <>, e.g.


Like this

Thanks braindigitalis as I couldn't figure out how to get the code to show up as code so it was less obnoxious.

You should just set your render target to the size you want it to be at. There is no difference really in drawing to a higher res render target then scaling it down after.

Then you don't have to do any work on the CPU.

But you say you want the texture at 1050 x 740 and 164 x 115. So you are going to have a minimum of the 1050 x 740 x 100 x 6 players or whatever your numbers were.

If you generate mips maps on those textures it will be a little more memory per card but you can then just render a quad at the 164x115 size with the full res texture to get the look of a small card.

The mips will help with performance since you don't need to read from the full res version to render the small size one.

You can also make sure your texture formats are using a compressed format. Also if you don't need alpha then it can be reduced as well and compressed better.

Not sure what you mean by the texture formats as I'm new to textures and drawing. Do you mean like the file format or something with when I declare the texture that will hold the image?

Yes, a texture doesn't have to be RGBA 32bits. There are other formats that can be used.
http://en.wikipedia.org/wiki/S3_Texture_Compression

Surely not all 600 cards will be displayed all at the same time, which means you don't need to have the textures for all 600 cards loaded at the same time.

Load the textures when the card needs to be displayed. Unload them when the card is removed from play.

if it's some sort of fast paced action card game that needs to maintain 60fps, then you may need to implement some form of streaming pre-load of the next card in each players deck, but I find that unlikely.

This topic is closed to new replies.

Advertisement