-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed text rendering artifacts on Radeon cards. #1568
Conversation
Padding set to 1.0 was causing adjacent glyphs leaking on Radeon cards. The initial padding value for glyphs was too high anyway, the proper value should be 0.5 on each side.
Can you please post before-after screenshots, so we can compare two padding values directly? |
I see, so there's some tearing. Does the distance between glyphs change? Or is it still the same? |
I tested different padding sizes and compared screenshots before my initial commit with padding 1.0. Since I apply the padding to the size and uv coords the glyph size and positioning stay the same, so no worries with that. Adding an additional parameter would not serve any useful purpose. |
Am I correct that setting padding of 0.5 makes vertices have non-integer coordinates? This makes me a bit anxious, because from experience I've seen non-integer coordinates cause a lot of tearing. Otherwise we need to test:
Right now it looks like text rendering is really simplistic in SFML and it doesn't take kerning into account, as far as I see. If you render text with 1.0 padding to sf::RenderTexture, does tearing go away? |
Don't be. Btw it's the same as half texel offset used for textures by @LaurentGomila several years ago. |
I've tested it myself on Win10 with AMD, nVidia and integrated Intel and also on the Raspberry PI |
There is an alternative route, but requires modifying glyph texture generation. You leave the padding as 1.0 and add 2 pixel padding between glyphs on the glyph sheet instead of 1. |
I think this looks pretty solid then, thanks for testing and explanations. |
@binary1248 - any thoughts as well? |
I wouldn't call this phenomenon "leaking" as it can occur even when there is a lot of space between "set" texels, and it should be consistent every time a glyph is rendered, which doesn't look to be the case. I'm guessing this is one of those cases where the OpenGL specification didn't 100% define how texture filtering should be performed along the edges of the source rectangle where a part of the sampling area basically consists of "undefined" data. I would have to guess how big the sampling area is, but it would be at least 3x3 texels around the centre, maybe even larger. Sampling is always done at texel centres, so adding 0.5 would be more correct in that sense and leave less room for interpretation/interpolation by the implementation. I would still also bump the padding between glyphs up to 2 just to be extra safe. I don't think requiring that extra space is going to make any difference in real world scenarios. It is amortized when dealing with larger glyphs anyway which is the only scenario where you would have to worry about texture size. |
So, does it mean that you approve my commit? |
Implementations might change. Remembering my work from years ago when broken Intel IGPs were still prevalent, I remember that a padding of 1 was not enough in some rare scenarios. That's why I always used a value of at least 2 in my own code. This might no longer be necessary now with less broken implementations around, so we have the choice of increasing the padding now as a precautionary measure or waiting until a real bug report comes in and increasing it then. |
I'd vote for merging it as is and wait & see. @oomek Do you still have the example code? I want to give this a run on my system. |
@ eXpl0it3r sure, but tomorrow if you don't mind, as my daughter is occupying my PC atm. |
I'm sorry, I forgot to post a sample code, but because I couldn't find it I wrote a new one:
Use up and down arrows. Update: displaying glyph texture |
Thanks for the code! 🙂 It's interesting. If I use SFML-master on the integrated Intel GPU, I notice the artifacts. If I use SFML-master on the discrete Nvidia GPU, I don't see any artifacts. In all cases the letters wobble quite a bit when slowly enlarging the font, but I guess that's to be expected. Will try my AMD GPU at home this evening. |
What's very weird, when you don't change the size of the characters there aren't any artifacts, and it does not matter what setCharacterSize() you have set in the beginning. It only manifests when you change the font size at runtime, so it made me thinking, maybe reused textures for glyphs have old uncleared glyph data still in the padding area? what do you think? |
Glyph textures are never reused. A sf::Font instance does not know what glyphs and sizes will be requested by sf::Text objects, so once it has generated something, it keeps it until its destruction. Internally, sf::Font keeps one separate set of textures per requested character size. |
Sorry wrong wording, not glyph data, the textures reused by the driver. Please try my revised code. It's clearly showing the old glyphs from previous sizes that were generated before. When you analyze the screenshot you will see that the distance between new glyphs is 2 pixels, but between an old data is just 1 pixel. I believe this is where the artifacts are coming from. Changing the padding made filtering not catching the old glyph data, but now I think the root of the problem should be addressed. A simple clearing the texture after it's generated should fix the problem. |
Would be easy to try, the clearing should be made here: https://github.com/SFML/SFML/blob/master/src/SFML/Graphics/Font.cpp#L729 |
Is it ok to change the newTexture to RenderTexture? Clearing with update() from an image or a buffer may add some unneccessary overhead. Update: I'm answering to myself. With RenderTexture I can't use update() or swap() without an intermediary sprite. |
Would this be acceptable? It works, but requires an Image.
It would be handy to have an additional texture.create() parameter that takes a color to fill it after initialization, or a dedicated clear() function. |
Before talking about the best way to do it... does it solve the problem? Did you make sure to revert your previous changes (padding) before testing this solution? |
Yes, it works with padding 1.0 |
Ok great. The best way to clear the texture would be to use a dedicated OpenGL feature (attaching to a FBO + glClear, or the GL_ARB_texture_clear extension, ... found on stackoverflow). No idea how hard it would be to integrate to SFML, or if we can even do it, according to our requirements on OpenGL version. The other solution is to allocate a vector of width*height pixels and call texture.update(...) with that. With the possibility to reuse a single vector to avoid big allocations. |
Would you rather have a dedicated function for it, or mixed code in font.cpp? glClearTexImage() seems like the most elegant solution to me. This extension is OpenGL 1.3 complaint. Is this enough of backward compatibility? |
Ok, how about this:
It's actually working. |
Seems like we need to summon @binary1248 😉 |
There are a few points to discuss here. Is clearing the whole texture necessary when resizing it to accommodate for new glyphs? If e.g. we resized the texture to 4x the size as is done in the current implementation, and only wanted to insert a single glyph, wouldn't it be wasteful to initialize the area that we never intended on using anyway? If uninitialized texture data is the root cause of the problem (and it seems likely), I would focus my effort on initializing only the regions we intend on using, i.e. the area around glyphs that will actually be considered for filtering. When resizing an already generated texture object, OpenGL essentially does a "free" and then a "malloc". Because of the proximity of the operations, it would almost always lead to the old block being reused, maybe slightly offset from before in some way. As such, trying to access this data, even through sampling, would be considered as "undefined behaviour" in C++ land. Quoting the OpenGL specification:
As I said in my previous comment, if OpenGL is factoring in a bigger texture area than we initialized, we will get these artifacts. The simplest solution would be to increase the padding and see what happens. Clearing out the whole texture is overkill if you ask me. When considering performance, which as I understand is what is currently being discussed, we have to look at the actual access patterns before ending up micro-optimizing or even pessimizing the implementation in ways that don't correspond to real-world usage. How often will an Bear in mind that for quite a while, GPUs and iGPUs have been attached to the system via PCIe or some other form of PCIe-like interface. These interfaces don't like small transfers. Any decent driver will batch a big chunk of "data" together before being sent to the GPU. This increases average latency but also significantly increases throughput as well. As such, if I had to guess, expanding the rectangle around a glyph to initialize a bigger padding area to proper values wouldn't even have a performance penalty, unless of course the glyphs were really really huge which doesn't happen every day. This would be the variant I favour. Because I too find that in some situations clearing a texture to a predefined colour can be useful when the user knows that they would otherwise have to do it "manually", I propose adding a Edit: I tested the test code on my desktop with an AMD RX Vega 64 and I can see constant periodic "flickering"/"blinking" of the |
Oh god, I somehow knew I'm not gonna get a simple yes or no answer, but I agree with everything you said. I was also thinking about increasing the padding to 2.0 for font generation and using a smaller padding 1.0 for drawing. The only thing that was stopping me was a hope that we will get a clear() function for textures which I was hoping to get as a bonus, but since you've mentioned it I'm all up for it. I was having the same artifacts on the bottom and right edges while moving textures after updating them dynamically with png's with various sizes. I'm glad we can agree on that. I'm updating the PR then. |
Reverted the padding used for drawing to 1.0 and increased the padding during glyph generation instead.
My final analysis would be: PR #1452 introduced a 1 pixel border of "overdraw" around each glyph in order to reduce motion artifacts. This border started to place texel centres within the padding region of the glyph textures. Since this padding was only 1 pixel wide, when OpenGL is forced to interpolate the texture data due to vertices having non-integer coordinates, linear filtering is applied (SFML always set font textures to smooth). Linear filtering samples a 2x2 texel area whose centre lies closest to the texture coordinate in question. Because this 2x2 area would include texels beyond the 1 pixel padding area, regions of undefined texel data would be sampled as well leading to the artifacts along the edges of the glyphs. The solution was to adjust the padding in accordance with the overdraw introduced in PR #1452 to make sure that any potentially sampled texels within the padding area are initialized to transparent. The padding in Font.cpp should therefore always be 1 more than the padding in Text.cpp in order to prevent artifacts from appearing. As an example: setting padding to 2 in both files will cause artifacts as well. |
Agreed 100% 👍 |
This fixed Nintendo Switch port too. |
Padding set to 1.0 was causing adjacent glyphs leaking on Radeon cards. The initial padding value for glyphs was too high anyway, the proper value should be 0.5 on each side.
Example video: https://youtu.be/sN44kuPA1Jk