SFML community forums

General => Feature requests => Topic started by: nitram_cero on December 11, 2008, 07:17:39 pm

Title: "Huge Sprites"
Post by: nitram_cero on December 11, 2008, 07:17:39 pm
Sometimes there is the need to load a really big image, for a background for example.

As sf::Image uses a texture that is graphics-card-dependent... you're limited to the texture limit size there.
(e.g.: ye olde GeForce2ee can't go bigger than 512x512 textures if I'm correct)

So the need to load and slice the image arises, having a bunch of images and sprites to handle.
If this could be transparent to the user it would be really cool.

I'm going to implement this out of need and I'll post the code if you think it would be good to add it to the library.

-Martín
Title: "Huge Sprites"
Post by: dorkfish on December 11, 2008, 08:09:02 pm
I think this is a fantastic idea, go for it. :)
Title: "Huge Sprites"
Post by: nitram_cero on December 11, 2008, 11:20:03 pm
Searching some code I found this:

"if (Adjust && myIsSmooth) " (see below)

Why is image smoothness needed for the half texel adjustment?
And what's that adjustment anyway? :D

--------------------------------------

@ Image.cpp
Code: [Select]

////////////////////////////////////////////////////////////
/// Convert a subrect expressed in pixels, into float
/// texture coordinates
////////////////////////////////////////////////////////////
FloatRect Image::GetTexCoords(const IntRect& Rect, bool Adjust) const
{
    float Width  = static_cast<float>(myTextureWidth);
    float Height = static_cast<float>(myTextureHeight);

    if (Adjust && myIsSmooth)  
    {
        return FloatRect((Rect.Left   + 0.5f) / Width,
                         (Rect.Top    + 0.5f) / Height,
                         (Rect.Right  - 0.5f) / Width,
                         (Rect.Bottom - 0.5f) / Height);
    }
    else
    {
        return FloatRect(Rect.Left   / Width,
                         Rect.Top    / Height,
                         Rect.Right  / Width,
                         Rect.Bottom / Height);
    }
}


Title: "Huge Sprites"
Post by: Wizzard on December 11, 2008, 11:50:38 pm
Here (http://www.sfml-dev.org/forum/viewtopic.php?t=197) is an article on it.
Title: "Huge Sprites"
Post by: nitram_cero on December 12, 2008, 03:17:19 pm
Quote from: "Wizzard"
Here (http://www.sfml-dev.org/forum/viewtopic.php?t=197) is an article on it.


Thanks a lot!
Title: "Huge Sprites"
Post by: nitram_cero on December 13, 2008, 12:20:14 am
While reading the code, I found out that each sf::Image also keeps a local (system memory) copy of the pixels.

I think this is a big overhead for images not modified in-game (which is the most common case in sprites).

Maybe an option to disable that cachéd pixels, also disabling SetPixel, SaveFile, et cetera... or implementing a slow "get from opengl" version (I don't know if thats possible thou, but it should).

I'm implementing this caché-flag in the proof-of-concept "BigImage/BigSprite" classes I'm coding, because the target is a background for a game I'm developing (4000x1000 px RGBA), and that's roughly  15 Megabytes, which is useless to have it on system memory as I'm not modifying it.


Also, just calling "myPixels.clear()" doesn't change the vector's capacity (see vector::capacity()), just the size is set to 0... so memory is not freed at all.



Well, what do you think?
Title: "Huge Sprites"
Post by: Imbue on December 13, 2008, 01:05:03 am
Quote from: "nitram_cero"
While reading the code, I found out that each sf::Image also keeps a local (system memory) copy of the pixels.
I'm not 100% sure, but I don't think you can trust the graphics card to store images for very long (like hitting alt-tab may wipe the memory).
Title: "Huge Sprites"
Post by: nitram_cero on December 13, 2008, 01:15:01 am
Thanks for the reply, I haven't thought of that but I think you're correct.



I was looking around for a solution to vector::clear() and I found that doing a swap for a new, empty vector could exchange the internal array and then as the object dies, it's really freed.

myPixels.swap(vector<Color>());
Title: "Huge Sprites"
Post by: Laurent on December 13, 2008, 11:07:25 am
You're right, storing the pixel data in memory is often useless. However it's a different and complex topic, which would requires more than just a workaround in the code. You should focus on your primary goal, in my opinion.

The swap trick is indeed the only way to free the memory allocated by a vector ;)
Title: "Huge Sprites"
Post by: zarka on December 13, 2008, 01:11:47 pm
Quote from: "Imbue"
Quote from: "nitram_cero"
While reading the code, I found out that each sf::Image also keeps a local (system memory) copy of the pixels.
I'm not 100% sure, but I don't think you can trust the graphics card to store images for very long (like hitting alt-tab may wipe the memory).


I belive that is only a problem on DirectX version <= 9.
OpenGL and DX10 should not have that problem.
Title: "Huge Sprites"
Post by: Ceylo on December 13, 2008, 02:42:29 pm
Quote from: "Imbue"
I'm not 100% sure, but I don't think you can trust the graphics card to store images for very long (like hitting alt-tab may wipe the memory).

If this was to happen, how would SFML notice this ? To my mind, there would be no way.

But anyway, the images are stored in the graphic card memory through an OpenGL context, so until you destroy this context, your images are kept in the graphic card memory. Therefore, as SFML never destroy the main shared OpenGL context, alt-tab can't destroy your images until you explicitely choose to do so.
Title: "Huge Sprites"
Post by: Laurent on December 13, 2008, 02:46:45 pm
Quote
But anyway, the images are stored in the graphic card memory through an OpenGL context, so until you destroy this context, your images are kept in the graphic card memory. Therefore, as SFML never destroy the main shared OpenGL context, alt-tab can't destroy your images until you explicitely choose to do so.

Absolutely :)
Title: "Huge Sprites"
Post by: nitram_cero on December 13, 2008, 06:35:49 pm
Then why keep the pixels inside the image class on system memory besides SetPixel/SaveToFile?
Most games never modify an Image in-game.

Also, I repeat, there is no freeing with "clear()"...
The code should be "myPixels.swap(std::vector<sf::Color>());" and that's it.

Thanks
-Martín
Title: "Huge Sprites"
Post by: Laurent on December 13, 2008, 07:01:44 pm
Quote
Then why keep the pixels inside the image class on system memory besides SetPixel/SaveToFile?

For SetPixel/SaveToFile (and all others).
You're right to say that, but that's just your point of view. SFML is not only made for games using static sprites, it can be used for a lot more purposes, which may include more complex image handling.
Adding an option not to store pixels in memory would require a smarter modification of SFML public interface.

Quote
Also, I repeat, there is no freeing with "clear()"...
The code should be "myPixels.swap(std::vector<sf::Color>());" and that's it.

myPixels.clear() is only called after an error. In such case, keeping the image instance alive obviously means it will be reused, so keeping the memory allocated makes sense. I don't see the benefits of clearing the memory in this function.
Title: "Huge Sprites"
Post by: nitram_cero on December 13, 2008, 07:41:05 pm
I think it's quite easy to add a "DoesCachePixels()/SetCachePixels(bool)" to the image interface, it's trivial (As "IsSmooth()/SetSmooth()")

In the case of not-caching, swap would be the only way to really free (as and only if wanted) that myPixels member after the CreateTexture().

I'm doing a BigImage class, that is multi-texture container similar to sf::Image for images larger than GL's max texture size.
I'm doing it to load three 15 Megabyte (uncompressed) background and making it easy (also implementing a BigSprite derived from sf::Drawable).

Keeping that 45 megs it's not necessary at all.

Quote
You're right to say that, but that's just your point of view.

Well, I know... I said it.
But I think you got me wrong (it's probably really hard to keep up reading line by line each post of the forum).

I know that it's useful to keep the pixels in some contexts. But in others it's not, so forcing it to keep them is not a solution neither.
Maybe I want to have 100 static sprites and 40 modifiable ones (for blood splatter for example, applied directly to the image).

It's ok for small (200x100, 80KB) sprites, but for not-so-big backgrounds (1000x1000, 4MB) it's quite a lot of memory waste.

I don't want to sound as a smart-ass, I'm just trying to contribute... so don't take it personal, man.

--------------------------
In
"void Image::SetSmooth(bool Smooth)"

It could check if "myIsSmooth != Smooth" before doing all the bind-change parameter-etc, to know if it's relevant and save some CPU time.
Even if it's not really important as you wont be calling it a lot, it's a good programming habit.

You only need to add an if clause.

---------------------------
    void EnsureTextureUpdate() const;
Should be public, as I may want to modify images and batch-update them before the first time they get binded (that could hog the CPU in different moments).
Title: "Huge Sprites"
Post by: Laurent on December 13, 2008, 08:46:13 pm
Quote
I think it's quite easy to add a "DoesCachePixels()/SetCachePixels(bool)" to the image interface, it's trivial (As "IsSmooth()/SetSmooth()")

I know it's technically easy to add, but it's not my main concern.
I could add many of these specific small functions, but one day I'll take a global look at SFML and realize it's become bloated and not-so-simple ;)
But again, I'm not saying it's useless, actually it is, and it's even on my roadmap. I'm just saying it's globally not as simple as you may think.

Quote
In the case of not-caching, swap would be the only way to really free (as and only if wanted) that myPixels member after the CreateTexture().

In this case I would directly use a temporary vector instead of the member. No clear needed.

Quote
I don't want to sound as a smart-ass, I'm just trying to contribute... so don't take it personal, man.

It's ok, no problem dude :)
I appreciate this kind of feedback, and any effort to help implementing such important features. I'll be very interested to see what you get.

Quote
In
"void Image::SetSmooth(bool Smooth)"

It could check if "myIsSmooth != Smooth" before doing all the bind-change parameter-etc

That's right, I'll add it. This is probably because I didn't store this state before, and I forgot to add this optimization :)

Quote
void EnsureTextureUpdate() const;
Should be public, as I may want to modify images and batch-update them before the first time they get binded (that could hog the CPU in different moments).

I don't like providing such functions in the public interface. It's really not what I'm aiming at for SFML. What about calling Bind() in your case ? It may sound like a workaround, but your situation is a very specific optimization and I don't want to change the public interface for such specific code.
Title: "Huge Sprites"
Post by: nitram_cero on December 15, 2008, 01:02:31 am
Thanks for the reply!

Quote
What about calling Bind() in your case ?

You're totally right, that's the best solution :), it slipped my mind (somehow I thought it was protected/Sprite-friend or something)

I'll post the BigImage/BigSprite as soon as I finish them.

Regards
-Martín
Title: "Huge Sprites"
Post by: nitram_cero on December 18, 2008, 02:07:01 am
OK, the BigImage/BigSprite are somewhat functional.

Test demo download link: (http://nitram.cero.googlepages.com/BigImageBigSpriteTest.rar)

(VC++2008 Express project included)

EDIT:
Update: Code tide up and better doxygen like in-code documentation.
Update: Bug-fix

Notes:

It must be built static (for now) because it needs references from internal ImageLoader.cpp and GraphicsContext.cpp
and in DLL mode they are not exported.
Sorry if the project can't be compiled "out of the box", some dependencies in Linker could need to be changed.

Sub Width and Sub Height

BigImage::LoadFromFile differs from the sf::Image version because it has two extra parameters that define the image sub-width and sub-height.


Let me expand a little on that:

If you pass BigImage::DefaultDimension, it uses the maximum allowed by the Graphics card.

In some cases you might want to use lower values for vertex culling and such.

If the one selected is no valid, it's made valid (power of 2 and not higher than the card's max).
i.e.: If you choose 500x500 but the card supports max of 256x256 (as really old Voodoo3's did), it will use 256.
With a Radeon 9800 that supports 2048x2048, but only power-of-two textures, then the it would use 512x512.


Pixel caching in system memory:

Caching of pixels (because of the nature of BIG images) is disabled by default.
This means you can't use SetPixel.

but...
If you use SetCachePixels(true) BEFORE calling LoadFromFile(), then the buffers are not freed, so you can SetPixel all you want because it will be re-applied to the texture.

Use it with caution as big images are usually heavy. The example image in the demo is 4000x2200 pixels (not that big) and it uses 34MB of memory! :shock:


The Demo:

In the demo it takes a while to load because it slices the 34MB (uncompressed) picture into smaller buffers.
And then loads it to textures.

Move: Arrows
Rotate: Numpad "+","-"
Scale: "," and "." (Comma and Period next to the shift key)

For the curious: The picture was taken in Capilla del Monte, Córdoba, Argentina.


Conclusion:


The classes need more work and functionality, but it's quite what I need right now for my game, so it's the best I can offer.

I tried my best to make it at transparent as possible to the interface of sf::Sprite and sf::Image

Maybe I'll work on it some more, adding functions to them to better approximate to the sf::Image/sf::Sprite classes.
But that'll have to wait 'til March.

I hope this is useful to someone :D
Thanks for reading and tolerating my endless feature-request nagging  :lol:
-Martín
Title: "Huge Sprites"
Post by: nitram_cero on February 20, 2009, 09:10:09 pm
I'm sorry to resurrect this thread, but I finished a game for a competition that uses this "BigImage/BigSprite" that I posted some time ago in this thread to easily work with huge images (as if it would be a regular sf::Sprite/sf::Image).
Notice the background while you play, it's a big ass image. And it works like a charm.

I'm still thinking it would be a great addition to the library.

Thanks for the great library! Laurent, I love you man... you and Erin Catto (Box2D)  :lol: .

http://nitram.cero.googlepages.com/avor
It's not finished (will it ever be? who knows) but it's playable.
Speech voice translation is not done yet, sorry.

Good luck!
-Martín
Title: "Huge Sprites"
Post by: Laurent on February 20, 2009, 09:49:08 pm
Yeah, I didn't forget about this, and I'd like to have something working for SFML 2.0.

Your class is very good actually, but I want to see first if I can integrate it in a better way into SFML. My ultimate goal is to avoid creating a new class, but it won't be easy.

By the way, very good and funny game :lol: