3
votes

I am building a game editor for an XNA game that I'm building that would accept JPEG/PNG, and output a XNB file that has Texture2D content in it.

I have my custom serializers for my custom etc and I'm dug everything down into invoking ContentCompiler using Reflection etc, gone into the real dirty bits, and I CAN actually get that part working for my own formats. But I now need to serialize to the regular Texture2D format that XNA know how to compile (in Visual Studio when building project) and load (in the game with the most used Content.Load method. I don't want to make my own image file format for this obviously. When I try to compile a Texture2D, it doesn't give me an error, it creates the XNB file, but the file contents are not image data, it's just some 3-4 KBs of overhead (which I think are the other, non-image properties of a Texture2D object).

Here is the code I use for compiling:

    protected void InitializeWriter()
    {
        Type compilerType = typeof(ContentCompiler);
        cc = compilerType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0].Invoke(null) as ContentCompiler;
        compileMethod = compilerType.GetMethod("Compile", BindingFlags.NonPublic | BindingFlags.Instance);
    }

    internal void Write(string objectName, string path, object objectToWrite) 
    {
        if (basePath == null)
        {
            throw new InvalidOperationException("The base path to write has not been set.");
        }
        if (cc == null) { InitializeWriter(); }
        string fullPath = Path.Combine(basePath, path, objectName + ".xnb");
        using (FileStream fs = File.Create(fullPath))
        {
            compileMethod.Invoke(cc, new object[]{
            fs, objectToWrite, TargetPlatform.Windows, GraphicsProfile.Reach, false/*true*/, fullPath, fullPath
        });
        }
    }

I have my own content processors and this code works with them flawlessly. This just calls the ContentCompiler's Compile method, but using Reflection (as it's an internal class).

I don't understand why Texture2D serializes, but without the actual bitmap data.

EDIT Sorry, it does NOT compile Texture2D (it complains about cyclic reference of my GraphicsDevice), if I try to compile a Bitmap object, it compiles with no errors but does not serialize the actual bitmap data)

1

1 Answers

1
votes

Ok, after some digging and hours of research, I've solved my problem.

Here is what to do:

You can't serialize Texture2D and load it back. Although loading is obviously done with no problem (as we load Texture2D using content manager in any 2D XNA game), we cannot save it for loading it from the game. After further investigation of the issue, I've found a Texture2DContent class (in the graphics pipeline) which I thought would serialize to Texture2D (which turns out to be perfectly correct).

So I just hooked up a new instance of Texture2DContent class, but found no way to set its data to my bitmap. Searched through all the DLLs and base/derived classes but no way. Then I've realized a TextureImporter class which has an import method. It takes a filename and a ContentImporterContext, which was a complicated class which had constructors from other deep-inner-working classes as well, and I just knew I wasn't able to initialize that one correctly. So I tried to pass null as my parameter to the import method, stepping in the code to see my NullPointerException, but, weirdly, it worked, and outputted my file. I checked the XNB and it was just a valid texture. Here is my code:

    TextureImporter importer = new TextureImporter();
    Texture2DContent tc = importer.Import(item, null) as Texture2DContent;
    AssetWriter.Write(item.Substring(item.Replace('\\', '/').LastIndexOf('/') + 1), tc);

where item is just a filename and AssetWriter does this practically (some methods in there but I've pasted them together below):

   Type compilerType = typeof(ContentCompiler);
   cc = compilerType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0].Invoke(null) as ContentCompiler;
   compileMethod = compilerType.GetMethod("Compile", BindingFlags.NonPublic | BindingFlags.Instance);
   string fullPath = Path.Combine(basePath, path, objectName + ".xnb");
   using (FileStream fs = File.Create(fullPath))
   {
      compileMethod.Invoke(cc, new object[]{
      fs, objectToWrite, TargetPlatform.Windows, GraphicsProfile.Reach, false/*true*/, fullPath, fullPath
      });
   }

It just works this way. The program passes everything needed to the content compiler, and the compiles does the job. It handles Texture2DContent correctly and maps it to a XNB output of Texture2D which can then be loaded using contentManager's load method.