SFML community forums

Bindings - other languages => DotNet => Topic started by: karposhark on September 10, 2017, 09:05:11 pm

Title: Updating texture of sprite.
Post by: karposhark on September 10, 2017, 09:05:11 pm
Hi

I am trying to update the texture of a sprite by updating the current texture with a new image instead of assigning another texture to the sprite. See code below if it seems a bit vague. To bad for me, it does not seem to work. :( (Though maybe it is just because I am doing something wrong...)

In the official tutorials I have found the following quote (https://www.sfml-dev.org/tutorials/2.4/graphics-sprite.php (https://www.sfml-dev.org/tutorials/2.4/graphics-sprite.php)) :
Quote
When you set the texture of a sprite, all it does internally is store a pointer to the texture instance. Therefore, if the texture is destroyed or moves elsewhere in memory, the sprite ends up with an invalid texture pointer.
   
As the quote states, a sprite holds a pointer to the texture it uses, so my idea would be that when the texture changes, the appearance of the sprite also changes. Of course this quote is targeting the C++-version, but i sort of expected that it would be the same for the .NET binding.

I do know that there a several ways around this problem, like creating an image that holds both the first and the second image so i just need to adjust the TextureRect, but I would like to know if there is any way to reach my goal as I am currently trying.

Thanks
     
class MyClass {
        private Image mImage;
        private Texture mTexture;

        public TextureHandlerClass() {
                mImage = new Image( /* image path */ );
                mTexture = new Texture(mImage);
                Sprite a = new Sprite(mTexture);
                Sprite b = new Sprite(mTexture);
                Sprite c = new Sprite(mTexture);
                Sprite d = new Sprite(mTexture);
        }

        private void ColorUpdate(object sender, EventArgs e) {
                mImage = new Image( /* other image path */ );
                mTexture.Update(mImage);
        }
}
 
Title: Re: Updating texture of sprite.
Post by: eXpl0it3r on September 10, 2017, 09:10:39 pm
"Doesn't seem to work" isn't really a problem description. ;)

How did you determine that it doesn't work?
What's the result you're expecting and what result are you getting?
Title: Re: Updating texture of sprite.
Post by: karposhark on September 10, 2017, 10:05:03 pm
I'll try to make it more clear ;).

Suppose I have an image of a cat and an image of a dog. First I load the image of the cat and assign it to the Texture 'my_texture'. Next, I create 10 instances of Sprite with this 'my_texture'.

At a certain moment in time, all the cats are at the exact same moment replaced by dogs. I load the image of the dog and use 'my_texture.Update(dog_image)' to update 'my_texture'. Because all of the 10 instances of Sprite have been created with the same Texture, and 'my_texture' has been updated with the dog image, I expected that all the Sprites would now show a dog, instead of a cat. However, the Sprites are still displaying the image of the cat.

So to wrap it up: I guessed that, because each Sprite keeps a pointer to its Texture, changing the Texture would change the image displayed by the Sprite. This does not seem to be the case. Should I invoke a method from Sprite to let it know the texture has changed?
Title: Re: Updating texture of sprite.
Post by: eXpl0it3r on September 10, 2017, 10:23:16 pm
However, the Sprites are still displaying the image of the cat.
That's all that was missing from your initial post. ;)

This shouldn't happen. Keep in mind the comment in the documentation for the update function:

Quote
Update the texture from an image.

Although the source image can be smaller than the texture, this function is usually used for updating the whole texture. The other overload, which has (x, y) additional arguments, is more convenient for updating a sub-area of the texture.

No additional check is performed on the size of the image, passing an image bigger than the texture will lead to an undefined behavior.

This function does nothing if the texture was not previously created.
Title: Re: Updating texture of sprite.
Post by: karposhark on September 10, 2017, 10:54:06 pm
I double checked it and I can confirm the images are both the same size. ;)

This is exactly what I do. While debugging I can clearly see that the if-test succeeds.
If I uncomment the last line, I works for a single Sprite, but I am looking for updating multiple Sprites at once (and this approach would be quite some overhead).

public MyClass : Sprite {
        private Image mImg;

        public MyClass() {
                mImg = new Image("ArrowRed.png");
               
                this.Texture = new Texture(mImg);
                this.TextureRect = new IntRect(0, 0, 32, 32);
        }

        public void OnAction() {
                Image newImg = new Image("ArrowGreen.png");

                if (mImg.Size == newImg.Size) {
                        this.Texture.Update(newImg);
                        //this.Texture = new Texture(newImg); (This one works obviously)
                }
        }
}
 
Title: Re: Updating texture of sprite.
Post by: Laurent on September 11, 2017, 08:08:51 am
This could be a bug in the .Net binding. It would be great if you could write a complete and minimal code that tests just this feature, outside your current projet (ie. load a texture, assign it to a sprite, update it and draw the sprite).
Title: Re: Updating texture of sprite.
Post by: karposhark on September 11, 2017, 09:49:45 am
I just wrote the code to test this (see attachments). After 2 seconds, each blue 'A' image should change to the yellow 'B' image.

Note the commented line (r33). You can uncomment it to see the expected result.

I am using SFML.Net 2.2.0.0 64-bit.
Title: Re: Updating texture of sprite.
Post by: Laurent on September 11, 2017, 12:54:13 pm
Thanks. Could you try the same code, but with the other update functions (from a pixel array and/or from a window)?

A useful test would be the same thing with CSFML directly, but that's probably too much to ask ;D
Title: Re: Updating texture of sprite.
Post by: karposhark on September 14, 2017, 12:38:56 pm
Hi

I just tried the other update methods, but the result stayed the same.
However I did notice that when using the MouseButtonPressed-event instead of the Timer-event, it all seemed to work.

So now I am wondering: is it possible that the update-method from Texture fails when it is called from a thread that is not the main-thread? I can't recall having read about it in the documentation...
Title: Re: Updating texture of sprite.
Post by: eXpl0it3r on September 14, 2017, 12:44:15 pm
Can you show your modified code?

I just created an example with CSFML and either I did something wrong, or it does really seem like there's no update happening.
Edit: I messed up, see below.

#include <SFML/Graphics.h>

int main()
{
    sfVideoMode mode = {800, 600, 32};
    sfRenderWindow* window;
    sfTexture* texture;
    sfSprite* sprite;
    sfImage* image;
    sfEvent event;

    window = sfRenderWindow_create(mode, "SFML window", sfResize | sfClose, NULL);
    if (!window)
        return -1;

    texture = sfTexture_createFromFile("A.png", NULL);
    if (!texture)
        return -1;
    sprite = sfSprite_create();
    sfSprite_setTexture(sprite, texture, sfTrue);

    image = sfImage_createFromFile("B.png");

    while (sfRenderWindow_isOpen(window))
    {
        while (sfRenderWindow_pollEvent(window, &event))
        {
            if (event.type == sfEvtClosed)
                sfRenderWindow_close(window);
            else if (event.type == sfEvtMouseButtonPressed)
            {
                printf("MouseButton Pressed - updateFromImage\n");
                sfTexture_updateFromImage(texture, image, 64, 64);
            }
            else if (event.type == sfEvtKeyPressed)
            {
                printf("Key Pressed - updateFromPixels\n");
                sfTexture_updateFromPixels(texture, sfImage_getPixelsPtr(image), 64, 64, 64, 64);
            }
        }

        sfRenderWindow_clear(window, sfBlack);
        sfRenderWindow_drawSprite(window, sprite, NULL);
        sfRenderWindow_display(window);
    }

    sfImage_destroy(image);
    sfSprite_destroy(sprite);
    sfTexture_destroy(texture);
    sfRenderWindow_destroy(window);
    return 0;
}
 
Title: Re: Updating texture of sprite.
Post by: eXpl0it3r on September 14, 2017, 01:44:15 pm
Nevermind, it was a misunderstanding of the API. The x and y values on the update functions describe the offsets and not the size or similar. As such they need to be 0.
Changing my code to the follow makes it run without issues.

#include <SFML/Graphics.h>

int main()
{
    sfVideoMode mode = {800, 600, 32};
    sfRenderWindow* window;
    sfTexture* texture;
    sfSprite* sprite;
    sfImage* image;
    sfEvent event;

    window = sfRenderWindow_create(mode, "SFML window", sfResize | sfClose, NULL);
    if (!window)
        return -1;

    texture = sfTexture_createFromFile("A.png", NULL);
    if (!texture)
        return -1;
    sprite = sfSprite_create();
    sfSprite_setTexture(sprite, texture, sfTrue);

    image = sfImage_createFromFile("B.png");
    if (!image)
        return -1;

    while (sfRenderWindow_isOpen(window))
    {
        while (sfRenderWindow_pollEvent(window, &event))
        {
            if (event.type == sfEvtClosed)
                sfRenderWindow_close(window);
            else if (event.type == sfEvtMouseButtonPressed)
            {
                printf("MouseButton Pressed - updateFromImage\n");
                sfTexture_updateFromImage(texture, image, 0, 0);
            }
            else if (event.type == sfEvtKeyPressed)
            {
                printf("Key Pressed - updateFromPixels\n");
                sfTexture_updateFromPixels(texture, sfImage_getPixelsPtr(image), 64, 64, 0, 0);
            }
        }

        sfRenderWindow_clear(window, sfBlack);
        sfRenderWindow_drawSprite(window, sprite, NULL);
        sfRenderWindow_display(window);
    }

    sfImage_destroy(image);
    sfSprite_destroy(sprite);
    sfTexture_destroy(texture);
    sfRenderWindow_destroy(window);
    return 0;
}
 

So it's not an issue with CSFML as far as I can tell.

So now I am wondering: is it possible that the update-method from Texture fails when it is called from a thread that is not the main-thread? I can't recall having read about it in the documentation...
The update will call OpenGL functions, with them no being run in the same thread, it's very well possible that issues can arise. You'd have to activate the thread's context before using anything in that thread and since OpenGL isn't multi-threaded, the OpenGL commands will be put into the queue and executed maybe slightly later.

For animation timing, you may want to go the non timer event route and use some clock to track the passed time etc.
It will most likely also be more precise as you're not relying on OS callbacks/interrupts which need to go through the event pump and precise timers (but I might also be wrong on this one).
Title: Re: Updating texture of sprite.
Post by: karposhark on September 14, 2017, 02:51:38 pm
I can confirm that the problem was related to calling the update-method in a separate thread. I adjusted my code as follows (full code in attachment):
Thread thread = new Thread(delegate () {
        Monitor.Enter(w);
        w.SetActive(true);

        tex.Update(imgB);

        w.SetActive(false);
        Monitor.Exit(w);
});
thread.Start();

while (w.IsOpen) {
        w.DispatchEvents();

        Monitor.Enter(w);
        w.SetActive(true);

        w.Clear(Color.White);
        w.Draw(spr1);
        w.Draw(spr2);
        w.Draw(spr3);
        w.Draw(spr4);

        w.SetActive(false);
        Monitor.Exit(w);

        w.Display();
}
 

Without the Monitor I got the 'Failed to activate the window's context'-message. I don't know whether this is the most elegant solution, but it seems to work without errors.
Title: Re: Updating texture of sprite.
Post by: Laurent on September 14, 2017, 02:59:21 pm
You should never have to explicitely call w.SetActive, SFML is supposed to handle context switches in threads automatically (in this use case, at least).
Title: Re: Updating texture of sprite.
Post by: karposhark on September 14, 2017, 03:05:24 pm
Do you mean that this should work?

Thread thread = new Thread(delegate () {
        Monitor.Enter(w);
        //w.SetActive(true);

        tex.Update(imgB);

        //w.SetActive(false);
        Monitor.Exit(w);
});
thread.Start();

while (w.IsOpen) {
        w.DispatchEvents();

        Monitor.Enter(w);
        //w.SetActive(true);

        w.Clear(Color.White);
        w.Draw(spr1);
        w.Draw(spr2);
        w.Draw(spr3);
        w.Draw(spr4);

        //w.SetActive(false);
        Monitor.Exit(w);

        w.Display();
}    

If I run the program with the code like this, the textures won't update.
Title: Re: Updating texture of sprite.
Post by: Laurent on September 14, 2017, 03:23:23 pm
Yes, it should. In C++, I'm pretty sure it would work. Maybe .Net threads are not standard system threads?
Title: Re: Updating texture of sprite.
Post by: eXpl0it3r on September 14, 2017, 03:34:37 pm
You should never have to explicitely call w.SetActive, SFML is supposed to handle context switches in threads automatically (in this use case, at least).
I don't think this is the case anymore.
We can't have both worlds, where everything happens magically, but everyone can still control things.

Since multi-threaded rendering is a more advanced topic, we've basically opted for giving the user more control and afaik binary1248 refactored it, so that you need to handle context individually on each thread. Nothing is "assumed".
Title: Re: Updating texture of sprite.
Post by: Laurent on September 14, 2017, 07:07:50 pm
From the current source code, it really seems that we still activate a default context if none is active in the current thread.

See:
https://github.com/SFML/SFML/blob/master/src/SFML/Graphics/Texture.cpp#L418
https://github.com/SFML/SFML/blob/master/src/SFML/Window/GlContext.cpp#L170

Moreover, an OpenGL error would be printed to the console if there were no active OpenGL context for Texture::update.
Title: Re: Updating texture of sprite.
Post by: binary1248 on September 14, 2017, 10:27:37 pm
SFML resources still "take care of themselves". Any SFML resource requiring an OpenGL context to be active will activate some implementation defined context and deactivate it once the operation is done. RenderTargets on the other hand need a bit more manual work to be done when multi-threading. Because SFML can't assume which thread is currently drawing to a RenderTarget the user will have to explicitly make sure it is deactivated on all other threads before activating it in a specific thread. Only the user can know when it is safe to do so so this is something SFML can't automatically do.

When it comes to raw OpenGL, the situation has changed from before the context changes. Where one could assume that SFML would leave a residual context active after any operation, one can't assume this is the case now. The "a context is always active" invariant was never formally defined anywhere before anyway and users just figured that part out on their own. This is the reason why some were surprised when this undocumented behaviour was changed.