Repro Project - MonoGame destroying sampler texture

Hello MonoGame People.

I filed an issue for this problem - https://github.com/mono/MonoGame/issues/2913

I'm encountering an issue with MonoGame unexpectedly destroying a Texture2D image that is being sent to an effect as a sampler.

Here is a screenshot.
http://tagenigma.com/monogame/ repro1.png (can't add image links yet)

  1. The red texture is the source texture2d.

  2. The dotted texture is a mask texture.

  3. The other red texture is supposed to be the red texture with the dotted texture subtracted.

Unfortunately when I pass the texture as the sampler in the effect parameters, it's destroying the second texture setting it to all black.

Use above link image repro 2. (I can't post images yet)

One way to set the sampler texture that reproduces the issue.
GraphicsDevice.Textures[1] = _light; // some how the _light Texture2D is being disposed when this is set

Another way to set the sample that reproduces the issue.
lightingEffect.Parameters["LightSampler"].SetValue(light); // this also causes the _light texture2D to be disposed

I've also included a repro project that reproduces the issues.
(link in the issue tracker)

Just uncomment the two different ways to set the sampler and you'll see the texture being destroyed.

The expected result here should be setting the sampler should definitely not affect texture #2.

The other expected result is setting the sampler should cause the second red texture to do *something.

I'm running this test on the OUYA.

Feedback is definitely appreciated.

Thanks,

~Tim Graupmann

Is _light actually disposed or is it returning an OpenGL error when trying to bind the texture?

1 Like

I don't see any errors in the log.

I don't think the Texture2D instance is actually "disposed" but the source pixels are drawing as black.

Loading content simply loads the effect and textures.

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            font = Content.Load<SpriteFont>("spriteFont1");

            _light = Content.Load<Texture2D>("light");
            _source = Content.Load<Texture2D>("source");

            try
            {
                Android.Util.Log.Info(TAG, "...Loading Lighting Effect");
                _lightingEffect = LoadEffect(@"LightingEffectOuya.zip");
                _lightingAlpha = _lightingEffect.Parameters["Alpha"];
                _lightingAlpha.SetValue(0.2f);
            }
            catch (Exception ex)
            {
                Android.Util.Log.Error(TAG, string.Format("Exception loading lighting: {0}", ex));
            }

This is the basic drawing loop.

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            spriteBatch.DrawString(font, "Hello from MonoGame!", new Vector2(16, 16), Color.White);
            spriteBatch.Draw(_source, new Vector2(200, 200), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
            spriteBatch.Draw(_light, new Vector2(600, 200), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
            spriteBatch.End();

            // PROBLEM: ***
            GraphicsDevice.Textures[1] = _light; // some how the _light Texture2D is being disposed when this is set
            //_lightingEffect.Parameters["LightSampler"].SetValue(_light); // this also causes the _light texture2D to be disposed

            spriteBatch.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone, _lightingEffect, Matrix.Identity);
            spriteBatch.Draw(_source, new Vector2(1000, 600), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
            spriteBatch.End();

            base.Draw(gameTime);
        }

I don't see any errors in the android logcat.

The texture isn't being disposed. The data is just being set to black on a Texture2D that was passed to the sampler.

When passing a Texture2D to a sampler, nothing should happen to the source texture.

But maybe deep down on Android, something got corrupted.

I don't see any bind errors, but since the source texture is being corrupted the downstream effects are probably related to the original problem.

I appreciate your feedback.

I'm on a deadline and would like to fix this issue.

Another data point.

The first render frame renders the _light texture properly.

But immediately after setting the effect parameter, in the next frame _light turns black.

Debugging, I verified it's not something simple like Texture2D.SetData is not being called.

I'll look into something related to SpriteBatchItem...

Okay I found the issue.

SamplerStateCollection.cs

I explicitly bind the texture. So apparently the texture was failing to bind (resulting in black). And now that the texture has been bound the effect sampler works correctly.

#elif OPENGL

            for (var i = 0; i < _samplers.Length; i++)
            {
                var sampler = _samplers[i];
                var texture = device.Textures[i];

                if (sampler != null && texture != null && sampler != texture.glLastSamplerState)
                {
                    // TODO: Avoid doing this redundantly (see TextureCollection.SetTextures())
                    // However, I suspect that rendering from the same texture with different sampling modes
                    // is a relatively rare occurrence...
                    GL.ActiveTexture(TextureUnit.Texture0 + i);
                    GraphicsExtensions.CheckGLError();

                    // NOTE: We don't have to bind the texture here because it is already bound in
                    // TextureCollection.SetTextures(). This, of course, assumes that SetTextures() is called
                    // before this method is called. If that ever changes this code will misbehave.
                    GL.BindTexture(texture.glTarget, texture.glTexture);
                    GraphicsExtensions.CheckGLError();

                    //sampler.Activate(texture.glTarget, texture.LevelCount > 1);
                    texture.glLastSamplerState = sampler;
                }

The developer in this area appears to know something was weird here and it needs to be revisited.

I was able to work around my issue by adding a static hack that I can toggle in these situations for now.

SamplerStateCollection.UseBindHack = true;

// do the effect

SamplerStateCollection.UseBindHack = false;

// render gui etc

I'm going to investigate this now. Glad to see you found a work-around. I'll see if I can work out a proper fix.

This is still an issue for MonoGame 3.4.

My hack doesn't appear to work in 3.4 for SamplerStateCollection.OpenGL.

// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
//
// Author: Kenneth James Pouncey

#if MONOMAC
using MonoMac.OpenGL;
#elif DESKTOPGL
using OpenTK.Graphics.OpenGL;
#elif GLES
using OpenTK.Graphics.ES20;
#endif

namespace Microsoft.Xna.Framework.Graphics
{
    public sealed partial class SamplerStateCollection
    {
        public static bool UseBindHack = false;

        private void PlatformSetSamplerState(int index)
        {
        }

        private void PlatformClear()
        {
        }

        private void PlatformDirty()
        {
        }

        internal void PlatformSetSamplers(GraphicsDevice device)
        {
            for (var i = 0; i < _actualSamplers.Length; i++)
            {
                var sampler = _actualSamplers[i];
                var texture = device.Textures[i];

                if (sampler != null && texture != null && sampler != texture.glLastSamplerState)
                {
                    // TODO: Avoid doing this redundantly (see TextureCollection.SetTextures())
                    // However, I suspect that rendering from the same texture with different sampling modes
                    // is a relatively rare occurrence...
                    GL.ActiveTexture(TextureUnit.Texture0 + i);
                    GraphicsExtensions.CheckGLError();

                    // NOTE: We don't have to bind the texture here because it is already bound in
                    // TextureCollection.SetTextures(). This, of course, assumes that SetTextures() is called
                    // before this method is called. If that ever changes this code will misbehave.
                    if (UseBindHack)
                    {
                        GL.BindTexture(texture.glTarget, texture.glTexture);
                        GraphicsExtensions.CheckGLError();
                    }
                    sampler.Activate(device, texture.glTarget, texture.LevelCount > 1);
                    texture.glLastSamplerState = sampler;
                }
            }
        }
    }
}

I was able to work around the issue with this SamplerStateCollection.OpenGL.

// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
//
// Author: Kenneth James Pouncey

#if MONOMAC
using MonoMac.OpenGL;
#elif DESKTOPGL
using OpenTK.Graphics.OpenGL;
#elif GLES
using OpenTK.Graphics.ES20;
#endif

namespace Microsoft.Xna.Framework.Graphics
{
    public sealed partial class SamplerStateCollection
    {
        private void PlatformSetSamplerState(int index)
        {
        }

        private void PlatformClear()
        {
        }

        private void PlatformDirty()
        {
        }

        internal void PlatformSetSamplers(GraphicsDevice device)
        {
            for (var i = 0; i < _actualSamplers.Length; i++)
            {
                var sampler = _actualSamplers[i];
                var texture = device.Textures[i];

                if (null != texture)
                {
                    // TODO: Avoid doing this redundantly (see TextureCollection.SetTextures())
                    // However, I suspect that rendering from the same texture with different sampling modes
                    // is a relatively rare occurrence...
                    GL.ActiveTexture(TextureUnit.Texture0 + i);
                    GraphicsExtensions.CheckGLError();
                }

                if (sampler != null && texture != null && sampler != texture.glLastSamplerState)
                {
                    // NOTE: We don't have to bind the texture here because it is already bound in
                    // TextureCollection.SetTextures(). This, of course, assumes that SetTextures() is called
                    // before this method is called. If that ever changes this code will misbehave.
                    // GL.BindTexture(texture.glTarget, texture.glTexture);
                    // GraphicsExtensions.CheckGLError();

                    sampler.Activate(device, texture.glTarget, texture.LevelCount > 1);
                    texture.glLastSamplerState = sampler;
                }
            }
        }
    }
}

I upgraded to MonoGame 3.4 and updated the repro project. The issue still reproduces on Forge TV.
http://tagenigma.com/monogame/ExampleScreenEffect_3_4.zip

After making a change to SamplerStateCollection.OpenGL.cs it works as expected on Forge TV.

GL.ActiveTexture needed to be called whether there was a sampler available or not.

GL.ActiveTexture(TextureUnit.Texture0 + i);