Sign in to follow this  
codymanix

XNA Generate Mip Maps with Texture.FromStream()

Recommended Posts

codymanix    132

Hi!

 

Can I generate Mip Maps in XNA without using the content pipeline? When I use Texture.FromStream there is no parameter which can be used for that.

 

Is it even recommended using XNA without the content pipeline? I know that it provides faster content loading and its content importers save your quite a bit coding..

 

But the thing with the content pipeline is that everybody who wants/needs to change game content (graphics designer, sound artist or even a hobbyist modder) needs to have an installed copy of visual studio. That is quite bad, so I want to avoid using the content pipeline.

 

Share this post


Link to post
Share on other sites
PhillipHamlyn    579
// Input Image is a byte[] from your Png
System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(this.InputImage);
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
Texture2D intermediateTexture = Texture2D.FromStream(deviceAccessor.GetCurrentGraphicsDevice(), memoryStream);

Texture2D texture = null;
RenderTarget2D renderTarget = new RenderTarget2D(deviceAccessor.GetCurrentGraphicsDevice(), InputImageSize.Width, InputImageSize.Height, mipMap: true, preferredFormat: surfaceFormat, preferredDepthFormat: DepthFormat.None);

BlendState blendState = BlendState.Opaque;

currentGraphicsDevice.SetRenderTarget(renderTarget);
using (SpriteBatch sprite = new SpriteBatch(currentGraphicsDevice))
{
sprite.Begin(SpriteSortMode.Immediate, blendState, samplerState, DepthStencilState.None, RasterizerState.CullNone,
effect: null);
sprite.Draw(this.IntermediateTexture, new Vector2(0, 0), Color.White);
sprite.End();
}

texture = (Texture2D)renderTarget;
currentGraphicsDevice.SetRenderTarget(null);
intermediateTexture.Dispose();
return texture; Edited by PhillipHamlyn

Share this post


Link to post
Share on other sites
codymanix    132

Thank you for the reply!

 

I also found out that I could use new Texture2D(device,w,h,true,format) and then call SetData() on it. Would this be possible? Will it automatically create a valid mip map too?

 

I believe that there is a performance penalty on writeable textures (RenderTarget2D). Is this true?

Share this post


Link to post
Share on other sites
PhillipHamlyn    579

I suspect the data you are setting using SetData must already have the mipmap set into it - but I'm not sure.

 

I dont believe writable textures are bad for performance, so long as you create them and dont then lock and update - in my case, the texture once written stays static.

Share this post


Link to post
Share on other sites
phil_t    8084

SetData provides a parameter that indicates with mip level you are supplying the data for. You'll need to calculate the mipmaps yourself if you do it this way.

 

Also note that using Dxt-compressed textures (which can provide space savings, and significant performance improvements if you're bandwidth-limited) becomes much harder if you're not using the content pipeline. It's not possible if you're using a RenderTarget2D as shown above. And if you're using Texture2D.SetData, you'll need to supply your own code to compress the texture data.

Share this post


Link to post
Share on other sites
PhillipHamlyn    579
SetData provides a parameter that indicates with mip level you are supplying the data for. You'll need to calculate the mipmaps yourself if you do it this way.

 

Also note that using Dxt-compressed textures (which can provide space savings, and significant performance improvements if you're bandwidth-limited) becomes much harder if you're not using the content pipeline. It's not possible if you're using a RenderTarget2D as shown above. And if you're using Texture2D.SetData, you'll need to supply your own code to compress the texture data.

 

Phil_t - the constructor for the RenderTarget2D allows you to specify your SurfaceFormat, which I set to DXT1 - so the original code post does support compression, however I would agree that the codemanix should avoid SetData if they are wanting mipmap support.

Share this post


Link to post
Share on other sites
phil_t    8084

[quote name='PhillipHamlyn' timestamp='1357990413' post='5020660']
Phil_t - the constructor for the RenderTarget2D allows you to specify your SurfaceFormat, which I set to DXT1 - so the original code post does support compression, however I would agree that the codemanix should avoid SetData if they are wanting mipmap support.
[/quote]

 

It allows you to specify your SurfaceFormat, but the compressed surface formats aren't supported as rendertargets.

Share this post


Link to post
Share on other sites
PhillipHamlyn    579

Phil_t - the constructor for the RenderTarget2D allows you to specify your SurfaceFormat, which I set to DXT1 - so the original code post does support compression, however I would agree that the codemanix should avoid SetData if they are wanting mipmap support.

 

 

It allows you to specify your SurfaceFormat, but the compressed surface formats aren't supported as rendertargets.

 

Ok, I see that now - its interesting that XNA doesn't complain when I request DXT1 but just gives me back a Color surface. I hadn't noticed it ignored my preferred format before now. Thanks for the tip.

Share this post


Link to post
Share on other sites
codymanix    132

// Input Image is a byte[] from your Png
System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(this.InputImage);
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
Texture2D intermediateTexture = Texture2D.FromStream(deviceAccessor.GetCurrentGraphicsDevice(), memoryStream);

Texture2D texture = null;
RenderTarget2D renderTarget = new RenderTarget2D(deviceAccessor.GetCurrentGraphicsDevice(), InputImageSize.Width, InputImageSize.Height, mipMap: true, preferredFormat: surfaceFormat, preferredDepthFormat: DepthFormat.None);

BlendState blendState = BlendState.Opaque;

currentGraphicsDevice.SetRenderTarget(renderTarget);
using (SpriteBatch sprite = new SpriteBatch(currentGraphicsDevice))
{
sprite.Begin(SpriteSortMode.Immediate, blendState, samplerState, DepthStencilState.None, RasterizerState.CullNone,
effect: null);
sprite.Draw(this.IntermediateTexture, new Vector2(0, 0), Color.White);
sprite.End();
}

texture = (Texture2D)renderTarget;
currentGraphicsDevice.SetRenderTarget(null);
intermediateTexture.Dispose();
return texture;

 

Thank you for the Code. It first seemed to work, it creates textures which also look mip mapped.

But now if I switch during the running game to fullscreen or activate multisampling (Anything that needs

 

GraphicsDeviceManager.ApplyChanges()
 

 

to be called), then things go mad.

My textures seem to be exchanged and sometimes I see nothing at all. When I use the original texture loading mechanism through content pipeline, it works again.

 

My first thought was passing

 

RenderTargetUsage.PreserveContents
 

 

when creating the rendertarget, but this didn't help.

 

Do you have an idea what the problem could be?

Share this post


Link to post
Share on other sites
codymanix    132

I found the problem: Rendertagets loose their contents on backbuffer change and normally need to be recreated, so needed to copy the data to a new texture.

 

I managed it to create a normal mip mapped Texture2D from a file with the following code.

It uses 3 Steps:

 

1. Load file with Texture2D.FromStream

2. Create mipmapped RenderTarget2D and render texture as sprite on it

3. Copy rendertarget texture to new Texture2D using GetData(mipLevel, ..)/SetData(mipLevel, ...)

 

It is unbelievable unefficient but I fear there is no better way to do it in XNA:

      MemoryStream ms = new MemoryStream();
                s.CopyTo(ms, 1024);
                ms.Seek(0, SeekOrigin.Begin);

                // load texture from file
                using (Texture2D intermediateTexture = Texture2D.FromStream(Graphics, ms))
                {
                    // create mip mapped texture
                    using (RenderTarget2D renderTarget = new RenderTarget2D(
                        Graphics,
                        intermediateTexture.Width,
                        intermediateTexture.Height,
                        mipMap: true,
                        preferredFormat: SurfaceFormat.Color,
                        preferredDepthFormat: DepthFormat.None,
                        preferredMultiSampleCount: 0,
                        usage: RenderTargetUsage.PreserveContents))
                    {
                        SamplerState oldSS = Graphics.SamplerStates[0];
                        RasterizerState oldrs = Graphics.RasterizerState;

                        SamplerState newss = SamplerState.LinearClamp; // todo which is best?

                        Graphics.SetRenderTarget(renderTarget);

                        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, newss, DepthStencilState.None, RasterizerState.CullNone, effect: null);
                        spriteBatch.Draw(intermediateTexture, new Vector2(0, 0), Color.White);
                        spriteBatch.End();

                        Graphics.SetRenderTarget(null);
                        Graphics.DepthStencilState = DepthStencilState.Default;
                        Graphics.BlendState = BlendState.Opaque;
                        Graphics.SamplerStates[0] = oldSS;
                        Graphics.RasterizerState = oldrs;

                        // since rendertarget textures are volatile (contents get lost on device) we have to copy data in new texture
                        Texture2D mergedTexture = new Texture2D(Graphics, intermediateTexture.Width, intermediateTexture.Height, true, SurfaceFormat.Color);
                        Color[] content = new Color[intermediateTexture.Width * intermediateTexture.Height];

                        for (int i = 0; i < renderTarget.LevelCount; i++)
                        {
                            int n = renderTarget.Width * renderTarget.Height / ((1 << i) * (1 << i));
                            renderTarget.GetData<Color>(i, null, content, 0, n);
                            mergedTexture.SetData<Color>(i, null, content, 0, n);
                        }

                        t = mergedTexture;
                    }                   
                }
 

Share this post


Link to post
Share on other sites
phil_t    8084
Render targets (and dynamic vertex buffers, and a few other things) lose their data when the graphics device is reset. So another option is to listen for the GraphicsDevice.DeviceReset event and just regenerate your render target then.

(Note: you don't need RenderTargetUsage.PreserveContents in the code you have above).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this