5
\$\begingroup\$

I am having trouble with the basic logic of this solution: Xna: Splitting one large texture into an array of smaller textures in respect to my specific problem (specifically, I'm looking at the second answer.)

How can I use my source rectangle that I already use for drawing to create a new Texture2D?

spriteBatch.Draw(CurrentMap.CollisionSet, currentMap.CellScreenRectangle(x, y),
      CurrentMap.TileSourceRectangle(currentMap.MapCells[x, y].TileDepths[4]),
      Color.FromNonPremultiplied(0,0,0, 45), 0.0f, Vector2.Zero, SpriteEffects.None, 0.91f);

I know I want a method that I started so:

//In Update Method of say the player's character.

Texture2D CollisionTexture = ExtractTexture(MapManager.CurrentMap.CollisionSet, MapManager.TileWidth, MapManager.TileHeight);

// In MapManager Class who knows everything about tiles that make up a level. 
    public Texture2D ExtractTexture(Texture2D original, int partWidth, int partheight, MapTile mapCell)
            {
                var dataPerPart = partWidth * partheight;
                Color[] originalPixelData = new Color[original.Width * original.Height];
                original.GetData<Color>(originalPixelData);
                Color[] newTextureData = new Color[dataPerPart];
                original.GetData<Color>(0, CurrentMap.TileSourceRectangle(mapCell.TileDepths[4]), originalPixelData,  0, originalPixelData.Count());
                Texture2D outTexture = new Texture2D(original.GraphicsDevice, partWidth, partheight);
            }

I think the problem is I'm just not understanding the overload of Texture2D.GetData<>

Part of my concern is creating an array of the whole texture in the first place. Can I target the original texture and create an array of colors for copying based on what I already get from the method TileSourceRecatangle(int)?

\$\endgroup\$
3
  • \$\begingroup\$ So, you have the old texture in memory, you have a way to draw a subset of it. Why do you need to create a new object to hold a copy? If you are doing this every frame, the new objects will burn through memory very quickly. \$\endgroup\$ Sep 1, 2012 at 4:02
  • \$\begingroup\$ I implemented a secondary tile set that consists of black or transparent pixels. I don't need to draw them, but the system needs to know about them so that it can check if the pixel in this tile is opaque while trying to move into it then stop moving. \$\endgroup\$
    – Dialock
    Sep 1, 2012 at 6:18
  • \$\begingroup\$ @Dialock In that case it would probably be best - if you are not going to render them anyway - to keep your extracted texture data on the CPU as an array of Color - rather than making a new Texture2D. \$\endgroup\$ Sep 1, 2012 at 8:52

2 Answers 2

7
\$\begingroup\$

Here's the code you need:

Texture2D originalTexture = Content.Load<Texture2D>("myTexture");
Rectangle sourceRectangle = new Rectangle(10, 10, originalTexture.Width - 20, originalTexture.Height - 20);

Texture2D cropTexture = new Texture2D(GraphicsDevice, sourceRectangle.Width, sourceRectangle.Height);
Color[] data = new Color[sourceRectangle.Width * sourceRectangle.Height];
originalTexture.GetData(0, sourceRectangle, data, 0, data.Length);
cropTexture.SetData(data);
\$\endgroup\$
1
  • \$\begingroup\$ Arguments about render targets aside, this is the right answer. \$\endgroup\$ Sep 1, 2012 at 17:39
0
\$\begingroup\$

You'll want to use RenderTarget2D, which are convertible with Texture2Ds (that's probably not the proper way of saying it). This uses the graphics device to create your new texture instead of the more expensive pixel copy, and is quite simple.

You'll want to

  1. Create

    RenderTarget2D newTarget = new RenderTarget2D(GraphicsDevice, partWidth, partHeight)
    
  2. Set

    GraphicsDevice.SetRenderTarget(newTarget);
    
  3. Draw

    SpriteBatch.Draw(original, Vector2.Zero, partSourceRectangle, <... other options you want ...>);
    
  4. Reset

    GraphicsDevice.SetRenderTarget(null);
    

    (this gets you back to drawing to the backbuffer instead of your temporary target)

  5. Convert

    Texture2D newTexture = (Texture2D)newTarget;
    

    (I believe the conversion is necessary so that the target is not automatically disposed of; render targets are meant to be temporary)

To summarize what I did: I set the draw target of the GraphicsDevice to a custom target which I created with the dimensions of the new texture. I then drew the new texture onto that target. Then I reset the GraphicsDevice so we are back to drawing to the screen. I then converted the render target into a more permanent texture.

Note that this will interrupt any drawing you are in the process of doing, so I wouldn't call this in the middle of a SpriteBatch run.

Also, I don't have VS open so I can't verify any of this, I'm going by memory. If anyone else has comments on the technique or syntax, feel free to edit or correct in comments.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Using a render target to generate assets is not less expensive than a 'pixel copy'. If the asset goes back into the cpu's hands as a new Texture2D anyway, you haven't saved any processing. You HAVE used a very expensive draw call to accomplish it. Copying an array of data is one of the fastest things a CPU can do. There's no need to throw in extra CPU-GPU interaction to make it happen. \$\endgroup\$ Sep 1, 2012 at 3:59
  • \$\begingroup\$ It's worth pointing out that your step #5 isn't quite correct. a RenderTarget2D is a Texture2D - so casting it to one doesn't actually do anything. It is still a render target and susceptible to ContentLost. If you want a "proper" Texture2D, you have to copy it to one with GetData and SetData. This, in turn, means that @Seth is also wrong - because it doesn't actually get copied back to the CPU - it stays on the GPU (which is why it can be lost). \$\endgroup\$ Sep 1, 2012 at 9:01
  • \$\begingroup\$ @AndrewRussell GetData() does move texture data out of the video memory, but only when used on a render target. The XNA devs have cited that as the reason its performance is so poor, i.e. too slow to happen every frame. It performs reasonably if you are drawing your data from another source, like a Content.Load texture2D. Generating a Color[] (in your code) also uses new non-video memory as well. I was only trying to explain that creating a new object from a render target is the improper way to go about it. \$\endgroup\$ Sep 1, 2012 at 17:38
  • \$\begingroup\$ @Seth It sounds like we're in agreement then: A-Type's code would be slow (at draw time) if it did what he said it does (copied the Texture2D). But because it doesn't - it's actually relatively fast. \$\endgroup\$ Sep 2, 2012 at 7:29
  • 1
    \$\begingroup\$ Well after coming back from a weekend away it appears I was quite wrong! Thanks for the feedback, this has certainly helped me learn a bit more about the subject. I'll try to do more research before I post 'answers' in the future. \$\endgroup\$
    – A-Type
    Sep 4, 2012 at 17:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .