9
\$\begingroup\$

My game uses the Content Pipeline to load the spriteSheet at runtime. The artist for the game sends me the modified spritesheet and I do a build in my machine and send him an updated project. So I'm looking for a way to generate the xnb files in his machine (this is the output of the content pipeline) without him having to install the full XNA Game studio.

  1. I don't want my artist to install VS + Xna (I know there is a free version of VS but this won't scale once we add more people to the team).
  2. I'm not interested in running this editor/tool in Xbox so a Windows only solution works.
  3. I'm aware of MSBuild options but they require full XNA

I researched Shawn's blog and found the option of using Msbuild Sample or a new option in XNA 4.0 that looked promising here but seems like it has the same restriction: Need to install full XNA GS because the ContentPipeline is not part of the XNA redist.

So has anyone found a workaround for this?

\$\endgroup\$

4 Answers 4

12
\$\begingroup\$

Seems like the right answer to this is to skip the ContentPipeline and use Texture2D.FromStream to load the textures at runtime. This method works fine in a PC and even though there will be a small performance hit this is something that I can optimize once I'm closer to the release date. For now, having the ability of dynamically modifying the content for both the editor and game are exactly what I need. Once the content is frozen I can optimize this by going back to the ContentPipeline.

Since you've chosen this route I must warn you that it's actually not as simple as just using Texture2D.FromStream for two reasons:

Problem #1 - Lack of pre-multiplied alpha support

XNA4 now handles textures with colors in premultiplied alpha format by default. When you load a texture through the content pipeline, that processing is done for you automatically. Unfortunately Texture2D.FromStream doesn't do the same thing, so any textures that require some degree of transparency will be loaded and rendered incorrectly. Below is a screenshot to illustrate the problem:

enter image description here

So in order to get the correct results, you need to do the processing yourself. The method I'll show uses the GPU to do the processing so it's pretty fast. It was based on this great article. Of course you could also instruct SpriteBatch to render in the old NonPremultiplyAlpha mode but I don't really recommend doing that.

Problem #2 - Unsupported formats

The content pipeline supports more formats than Texture2D.FromStream. In particular, Texture2D.FromStream only supports png, jpg and gif. On the other hand, the content pipeline supports bmp, dds, dib, hdr, jpg, pfm, png, ppm, and tga. If you try to load an usuported format through Texture2D.FromStream you'll get an InvalidOperationException with little additional information.

I really needed bmp support on my engine so for that particular case I found a workaround that seems to work okay. I don't know about any of the other formats though. The catch with my method is that you need to add a reference to the System.Drawing assembly to your project, because it makes use of GDI's Image.FromStream which supports more formats than Texture2D.FromStream.

If you don't care about supporting bmp you can easily drop that part of my solution and just do the pre-multiplied alpha processing.

Solution - Simple Version (Slower)

First of all, here's the simplest solution if you don't care about supporting bmps. In this example the processing stage is done entirely on the CPU. It's a bit slower than the alternative I'll show below (I did benchmark both solutions) but easier to understand:

public static Texture2D FromStream(GraphicsDevice graphicsDevice, Stream stream)
{
    Texture2D texture = Texture2D.FromStream(graphicsDevice, stream);
    Color[] data = new Color[texture.Width * texture.Height];
    texture.GetData(data);
    for (int i = 0; i != data.Length; ++i)
        data[i] = Color.FromNonPremultiplied(data[i].ToVector4());
    texture.SetData(data);
    return texture;
}

If you care about bmps then the thing you need to do is load the image with GDI first and then convert into PNG internally before passing it to Texture2D.FromStream. Here's the code that does that:

// Load image using GDI because Texture2D.FromStream doesn't support BMP
using (Image image = Image.FromStream(stream))
{
    // Now create a MemoryStream which will be passed to Texture2D after converting to PNG internally
    using (MemoryStream ms = new MemoryStream())
    {
        image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
        ms.Seek(0, SeekOrigin.Begin);
        texture = Texture2D.FromStream(_graphicsDevice, ms);
    }
}

Solution - Complex Version (Faster)

Finally, the approach I use on my projects is to use the GPU to do the processing instead. In this method you need to create a render target, set up some blend states properly, and draw the image twice with a SpriteBatch. At the end I go over the entire RenderTarget2D and clone the contents into a separate Texture2D object because the RenderTarget2D is volatile and won't survive things like changing the backbuffer size so it's safer to make a copy.

The funny thing is that even with all of this, on my tests this approach performed about 3 times faster than the CPU approach. So it's definitively faster than going over each pixel and calculating the color yourself. The code is a bit long so I placed it in a pastebin:

http://pastie.org/3651642

Just add that class to your project, and using it as simple as:

TextureLoader textureLoader = new TextureLoader(GraphicsDevice);
Texture2D texture = textureLoader.FromFile("Content/texture.png");

Note: You only need to create one TextureLoader instance for the entire game. Also I'm using the BMP fix but you can take it out if you don't need and gain a bunch of performance, or just leave the needsBmp parameter as false.

\$\endgroup\$
2
  • \$\begingroup\$ wow, this is great! It will help me a lot :) Thanks a lot David, I appreciate it. \$\endgroup\$
    – krolth
    Mar 23, 2012 at 15:17
  • \$\begingroup\$ +1 Actually, I was using FromStream with a memory stream containing a 32bit bitmap (saved to png), just like your other example, but this method didn't create a premultiplied texture. Explicitly premultiplying each color did the trick, thanks. \$\endgroup\$
    – vgru
    Apr 25, 2015 at 19:42
3
\$\begingroup\$

I think most teams would commit the xnb changes (well, all changes really, xnb includud) to their svn server (that can be set up for free) and allow others (the artist, etc.) to update their own working copies.

In fact, that would be a good mechanism for the artist to version control the original (pre- xnb) art. He would commit changes to that, you would update your working copy of it, build it (making it an xnb in the process), commit your changes, he updates his working copy of your work and and everybody has all changes. (You have latest raw artwork, he has the xnb(s).

This scales very well too.

\$\endgroup\$
3
  • \$\begingroup\$ So you are saying you don't think it can be done? Your suggestion is adding version control to the XNB and sprites, we are already doing that. But I don't like it because I become the bottleneck for them. I already wrote a tool for them to edit the animations and they can try them in the game. But if they make changes to the spritesheet they need to wait until I build it before they can see it. As you can imagine if they make a mistake they need to do it again. \$\endgroup\$
    – krolth
    Mar 19, 2012 at 18:13
  • \$\begingroup\$ @krolth Is it that big a deal to have your artists get VS Express and XNA as part of getting them setup to work on a project? I think at this point the tradeoff of having to write up a guide and help people through it will far outweigh the productivity you're losing now since artists can't see their work in-engine. To streamline the process, give them a .BAT file they can double-click on to recompile everything without having to open the IDE. And if they are only running OS X, well, tough shit. Welcome to game dev. They can commit their sprites and wait for the next committed XNBs. \$\endgroup\$ Mar 19, 2012 at 20:36
  • \$\begingroup\$ it's not such a big deal, just a pain. But I guess it will have to do. Thanks everybody for your answers/comments! \$\endgroup\$
    – krolth
    Mar 20, 2012 at 14:54
1
\$\begingroup\$

I kept investigating this and will post this for benefit of someone that has the same question.

Seems like the right answer to this is to skip the ContentPipeline and use Texture2D.FromStream to load the textures at runtime. This method works fine in a PC and even though there will be a small performance hit this is something that I can optimize once I'm closer to the release date.

For now, having the ability of dynamically modifying the content for both the editor and game are exactly what I need. Once the content is frozen I can optimize this by going back to the ContentPipeline.

\$\endgroup\$
2
  • \$\begingroup\$ Have you tested this correctly? From my experience Texture2D.FromStream by itself is not enough. The reason for that is that since version 4 XNA works with premultiplied alpha textures, and while the content pipeline takes care of this processing automatically for you, Texture2D.FromStream doesn't so you will probably run into problems when drawing sprites with transparency. I can post a working solution if you'd like though. \$\endgroup\$ Mar 22, 2012 at 21:20
  • \$\begingroup\$ Also, Texture2D.FromStream doesn't support loading .BMP files while the content pipeline does. This is something that would probably throw you off if you used any .BMP assets previously and then switched to Texture2D.FromStream. I also have a workaround for that limitation. I'll just go ahead and post it. \$\endgroup\$ Mar 22, 2012 at 21:27
0
\$\begingroup\$

Check this project out.

It will allow your artist to create XNB out of just about anything the default XNA content pipeline supports. The XNA framework redistributable is still necessary though your artist doesn't need Visual Studio.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the pointer. Looking at the code it seems like a modification to the Microsoft sample that I made reference to. It depends on having the full XNA Game Studio installed (see ContentBuilder.cs). Why do you think it doesn't? \$\endgroup\$
    – krolth
    Mar 19, 2012 at 2:29
  • \$\begingroup\$ I don't think it doesn't. If you want to use the content pipeline then you have to have the full game studio. However the project does prevent your artists from having to use Visual Studio. The only other alternatives it to rewrite the content pipeline. \$\endgroup\$ Mar 19, 2012 at 4:56

You must log in to answer this question.

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