XNA Generate Mip Maps with Texture.FromStream()

Started by
9 comments, last by phil_t 11 years, 2 months ago

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.

Advertisement
// 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 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?

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.

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.

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.

[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.

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.

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

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;
                    }                   
                }
 

This topic is closed to new replies.

Advertisement