Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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)?

share|improve this question
    
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. –  Seth Battin Sep 1 '12 at 4:02
    
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. –  Dialock Sep 1 '12 at 6:18
    
@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. –  Andrew Russell Sep 1 '12 at 8:52

2 Answers 2

up vote 4 down vote accepted

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);
share|improve this answer
    
Arguments about render targets aside, this is the right answer. –  Seth Battin Sep 1 '12 at 17:39

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.

share|improve this answer
    
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. –  Seth Battin Sep 1 '12 at 3:59
    
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). –  Andrew Russell Sep 1 '12 at 9:01
    
@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. –  Seth Battin Sep 1 '12 at 17:38
    
@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. –  Andrew Russell Sep 2 '12 at 7:29
    
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. –  A-Type Sep 4 '12 at 17:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.