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.

We have a client/server architecture for our game with the client being the XNA Game, and the game server is separate and only references XNA. They both use a shared DLL for networking. The game server is windows forms for just starting/restarting and some stats output, and does no drawing or anything.

The problem is I need to load Texture2D's on the game server, to get width/height information, and for the server to be able to do per pixel collision detection for its simulation. However, it seems a Texture2D requires a GraphicsDevice to instantiate. And it appears a GraphicsDevice requires an XNA Game?

Is there some way I can load a Texture2D into memory on the server without a graphics device, or some way to instantiate a GraphicsDevice to do this (even though nothing is going to be drawn)?

share|improve this question
    
You're better off not using XNA at all and instead extracting this information. Are your files guaranteed to be of a specific file type? –  Vaughan Hilts Feb 3 '13 at 19:09
    
What is keeping you from opening the image on the server side? –  Luis Estrada Feb 3 '13 at 21:34
add comment

3 Answers

Texture2D is a very specific class that is used to load an image into something the GPU can easily consume and use as-is. You wouldn't want all this kind of data and the like inside of a server program. I'm going to give some advice here on what I think you want, feel free to comment if I don't meet your requirements.

First of all: I don't think you need per pixel collision checking on the server.

I don't know your game requirements or what kind of game you're trying to make but you should know that if you're hosting any more than a few moving clients at once that per-pixel collision for every movement for a LOT of clients is going to be expensive, and this probably is not what you want. You should look into a way of optimizing this. With this in mind, you may decide this is not the best route after all or decide this is necessary. Give this some thought, we'll return to it soon.

Next, are the height and width still necessary?

If your answer is yes, continue on.

The answer is to not read the entire file on the server and just extract what you need. The obvious thing may be to use a Bitmap object of some sort and just load like that - but the faster and more elegant approach is to just read the file headers and grab the information yourself. This StackOverflow post outlines a few method(s). For reference, I've included my favorite here:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;

namespace ImageDimensions
{
    public static class ImageHelper
    {
        const string errorMessage = "Could not recognise image format.";

        private static Dictionary<byte[], Func<BinaryReader, Size>> imageFormatDecoders = new Dictionary<byte[], Func<BinaryReader, Size>>()
        {
            { new byte[]{ 0x42, 0x4D }, DecodeBitmap},
            { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
            { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif },
            { new byte[]{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng },
            { new byte[]{ 0xff, 0xd8 }, DecodeJfif },
        };

        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>
        public static Size GetDimensions(string path)
        {
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>    
        public static Size GetDimensions(BinaryReader binaryReader)
        {
            int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;

            byte[] magicBytes = new byte[maxMagicBytesLength];

            for (int i = 0; i < maxMagicBytesLength; i += 1)
            {
                magicBytes[i] = binaryReader.ReadByte();

                foreach(var kvPair in imageFormatDecoders)
                {
                    if (magicBytes.StartsWith(kvPair.Key))
                    {
                        return kvPair.Value(binaryReader);
                    }
                }
            }

            throw new ArgumentException(errorMessage, "binaryReader");
        }

        private static bool StartsWith(this byte[] thisBytes, byte[] thatBytes)
        {
            for(int i = 0; i < thatBytes.Length; i+= 1)
            {
                if (thisBytes[i] != thatBytes[i])
                {
                    return false;
                }
            }
            return true;
        }

        private static short ReadLittleEndianInt16(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];
            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt16(bytes, 0);
        }

        private static int ReadLittleEndianInt32(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt32(bytes, 0);
        }

        private static Size DecodeBitmap(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(16);
            int width = binaryReader.ReadInt32();
            int height = binaryReader.ReadInt32();
            return new Size(width, height);
        }

        private static Size DecodeGif(BinaryReader binaryReader)
        {
            int width = binaryReader.ReadInt16();
            int height = binaryReader.ReadInt16();
            return new Size(width, height);
        }

        private static Size DecodePng(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(8);
            int width = binaryReader.ReadLittleEndianInt32();
            int height = binaryReader.ReadLittleEndianInt32();
            return new Size(width, height);
        }

        private static Size DecodeJfif(BinaryReader binaryReader)
        {
            while (binaryReader.ReadByte() == 0xff)
            {
                byte marker = binaryReader.ReadByte();
                short chunkLength = binaryReader.ReadLittleEndianInt16();

                if (marker == 0xc0)
                {
                    binaryReader.ReadByte();

                    int height = binaryReader.ReadLittleEndianInt16();
                    int width = binaryReader.ReadLittleEndianInt16();
                    return new Size(width, height);
                }

                binaryReader.ReadBytes(chunkLength - 2);
            }

            throw new ArgumentException(errorMessage);
        }
    }
}

And remember: Try keeping the graphical and client-like code off the server where it's possible. If you STILL Have the requirement in which you need to do per-pixel-collision, keep on reading.

If you're doing this because of cheating, you probably don't need to. Simple checks to make sure a player isn't blatantly cheating is always important - but for some things it just isn't worth it. If you're worried about players no-clipping, do generalized checks - not a pixel-perfect one. You can even use smaller bounding boxes, and it'll still be a lot cheaper.

You might already know all about masks, but I'm going to include a quick link here for reference. The main thing you really need to know here is that you should generate a mask for your objects if possible and use rectangles for a quick approximation check. This will significantly reduce load on your server where possible - and I think you'll need it!

In the end, it'll depend on your game requirements. Good luck!

A sample mask image

share|improve this answer
    
Thank you for the time you put into this comment. All valid points, and indeed we currently do not use per pixel collision at all, but only bounding boxes, I only added that bit in case we wanted to. However, shared code uses the textures for width/height information, and the textures are loaded from the xna compiled .xnb files. I could rewrite things to work and not have to load the texture on the server, but I dont think the meager saving of resources and it being more correct is worth the time it would take. I did find a way to do this I will post below. –  Joshjje Feb 4 '13 at 1:02
    
Glad you liked it. Feel free to hit "Upvote" if you found it helpful. –  Vaughan Hilts Feb 4 '13 at 1:03
    
Ha you read and commented in the middle of my edit, I hit enter to make a paragraph and it submitted it lol. –  Joshjje Feb 4 '13 at 1:04
1  
While "keeping the graphical and client-like code off the server where it's possible" is generally good advice - keeping collision detection off the server (or reducing server-side collision fidelity) is a very much performance optimisation and needs to come with all the usual warnings about avoiding premature optimisation. If you can keep the client and server collision code the same, you will save yourself a lot of headaches. It's absolutely not worth it just to avoid loading up some assets on the server! –  Andrew Russell Feb 4 '13 at 3:57
add comment

Texture2D is more or less a class that sets how a particular graphics device draws a particular texture, with mip-maps and whatnot. If there is no graphics device...what is it doing exactly?

This said, you will need to save and store the textures in a slightly more manual way. Fortunately, .NET has libraries that allow for per-pixel manipulations through their own classes and structures. You should take a look at Microsoft's information on BitmapImage and BitmapSource. You still have access to individual pixels, so you can do calculations through CopyPixels().

share|improve this answer
    
Yeah, it doesnt seem to make much sense to use these on the server, but code library that we have that is shared between server and client uses them. I could re-write things, but I dont think it would be worth it. I did find a way to create a graphics device on server I will post below. Thank you. –  Joshjje Feb 4 '13 at 1:01
add comment

You can create a GraphicsDevice and ContentManager without having a Game. I've already written about this in this answer - I'll reproduce the code below, it uses some classes from the XNA WinForms sample. Basically all you need is a window handle (Form.Handle) to attach the graphics device to.

Form form = new Form(); // Dummy form for creating a graphics device
GraphicsDeviceService gds = GraphicsDeviceService.AddRef(form.Handle,
        form.ClientSize.Width, form.ClientSize.Height);

ServiceContainer services = new ServiceContainer();
services.AddService<IGraphicsDeviceService>(gds);
content = new ContentManager(services, "Content");

An alternative to this, which is probably way too much effort, is to load the texture yourself. The documentation for the XNB file format is here.

The biggest challenge in using this method is if your assets are built with XNB compression. You'd then either need a separate asset build for your server, without compression. Or you'd need to mess around with reflection to get access to the required decompression methods in XNA.

This method is preferred if you'd like your server to be able to run without a graphics device, as there are many circumstances - often the kind of scenarios where you'd like to run a server - where it's not possible to create a graphics device (or you "lose" the device during execution).

share|improve this answer
add comment

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.