Question

I'm porting Windows Phone 7 game to Windows Phone 8 using MonoGame. My game uses Texture2D.FromStream to load pics from Internet, and as soon as it is not implemented in MonoGame yet, I'm trying suggested workaround: loading pic with BitmapImage in UI thread.

Here is the code:

            Texture2D result = null;
            EventWaitHandle Wait = new AutoResetEvent(false);
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                BitmapImage image = new BitmapImage();
                image.SetSource(stream);
                WriteableBitmap writeImage = new WriteableBitmap(image);

                var index = 0;
                var pixels = new byte[writeImage.PixelWidth * writeImage.PixelHeight * 4];
                for (var h = 0; h < writeImage.PixelHeight; h++)
                    for (var w = 0; w < writeImage.PixelWidth; w++)
                    {
                        var pixel = writeImage.Pixels[index++];
                        var wIndex = w * 4 + h * writeImage.PixelWidth * 4;

                        pixels[wIndex++] = (byte)(pixel >> 16 & 0xFF);
                        pixels[wIndex++] = (byte)(pixel >> 8 & 0xFF);
                        pixels[wIndex++] = (byte)(pixel >> 0 & 0xFF);
                        pixels[wIndex++] = (byte)(pixel >> 24 & 0xFF);
                    }

                result = new Texture2D(graphicsDevice, writeImage.PixelWidth, writeImage.PixelHeight);
                result.SetData(pixels);
                Wait.Set();
            });

Theoretically everything is ok. But somehow action code is never called. I call BeginInvoke in non-ui worker thread, Deployment.Current.Dispatcher is not null and provides ui-thread dispatcher. Game is WP8 project made from MonoGame template for VS2012. I tried to get and save to static variables dispatcher and SynchronizationContext in main thread (Game constructor) to be sure they belong exactly to ui thread, but this doesn't help.

Also I tried such code:

SynchronizationContext uiThread = Hub.SyncContext;//set in main thread (Game constructor)
var waitHandle = new object();
lock (waitHandle)
{
    uiThread.Post(_ => 
    {
        lock (waitHandle)
        {

            BitmapImage image = new BitmapImage();
            image.SetSource(stream);
            WriteableBitmap writeImage = new WriteableBitmap(image);

            var index = 0;
            var pixels = new byte[writeImage.PixelWidth * writeImage.PixelHeight * 4];
            for (var h = 0; h < writeImage.PixelHeight; h++)
                for (var w = 0; w < writeImage.PixelWidth; w++)
                {
                    var pixel = writeImage.Pixels[index++];
                    var wIndex = w * 4 + h * writeImage.PixelWidth * 4;

                    pixels[wIndex++] = (byte)(pixel >> 16 & 0xFF);
                    pixels[wIndex++] = (byte)(pixel >> 8 & 0xFF);
                    pixels[wIndex++] = (byte)(pixel >> 0 & 0xFF);
                    pixels[wIndex++] = (byte)(pixel >> 24 & 0xFF);
                }

            result = new Texture2D(graphicsDevice, writeImage.PixelWidth, writeImage.PixelHeight);
            result.SetData(pixels);

           Monitor.Pulse(waitHandle);
        }

    }, null);

    Monitor.Wait(waitHandle);
}

But again no luck: execution stops on wait and breakpoint in action code is never fired.

Update: I've just added simple code with just one log to sample MonoGame project. If I wait for AutoResetEvent I never see any logs. When I remove are.WaitOne() action began to work. I've replaced it with are.WaitOne(1000) and start to work as expected (why?). But still no luck with image loading. It looks like something prevents main thread running while I'm waiting in are.WaitOne().

Was it helpful?

Solution

I solved problem by using this library: https://github.com/Taggersoft/ImageTools this fork is for WP8 exactly, and if you delete all Contract.Requires like statements it works very good. Here's my Texture2d.FromStream replacement:

        public static Texture2D TextureFromStream(GraphicsDevice graphicsDevice, Stream stream, String dbg_nm)
    {           
        #if WP8
        byte [] b = new byte[4];
        stream.Read(b, 0, 4);
        stream.Seek(0, SeekOrigin.Begin);
        int stream_type = getStreamType(b, 0);
        Texture2D result = null;

        if (stream_type == 1 || stream_type == 0)
        {
            var image = new ExtendedImage();
            if (stream_type == 0)
                new JpegDecoderNano().Decode(image, stream);
            else
            {
                try
                {
                    new PngDecoder().Decode(image, stream);
                }
                catch (System.Exception ex)
                {
                    Debug.WriteLine("Error reading " + dbg_nm + ":" + ex.Message);
                }                   
            }

            var Colors = new Color[image.Pixels.Length / 4];
            for (var i = 0; i < image.Pixels.Length / 4; i++)
            {
                Color unpackedColor = new Color();
                unpackedColor.R = (byte)(image.Pixels[i * 4 + 0]);
                unpackedColor.G = (byte)(image.Pixels[i * 4 + 1]);
                unpackedColor.B = (byte)(image.Pixels[i * 4 + 2]);
                unpackedColor.A = (byte)(image.Pixels[i * 4 + 3]);
                Colors[i] = unpackedColor;
            }
            Texture2D texture2D = new Texture2D(graphicsDevice, image.PixelWidth, image.PixelHeight);
            texture2D.SetData(Colors);
            result = texture2D;
        }

        return result;

        #else
        return Texture2D.FromStream(graphicsDevice,  stream);
        #endif
    }

And in game constructor:

    #if WP8
    Decoders.AddDecoder<PngDecoder>();
    Decoders.AddDecoder<JpegDecoder>();
    #endif

It's actualy workaround. But in my case this way is much better than usign MS BitmapImage from uithread.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top