Breaking change to 1.12: rendering sub-images
This artcile includes the information in June 2020. There's a possibility the plan will change.
Ebiten is adding a breaking change to 1.12 (the current master branch). Ebiten will no longer check the source-image region when rendering an image, and unexpected pixels might be rendered in some cases. This rendering result's difference might be seen when all the following conditions are satisfied:
- A sub-image (an image created by
SubImagefunction) is used
- The image is rendered with scaling or rotating
- The adjacent areas's graphics are not continuous
For example, rendering tiles on texture atlas with scaling or rotating might cause a different result from 1.11. The rendering result might include pixels in its adjacent area. On the other hand, for example, rendering 9-patches should not be problematic since the graphics are continuous.
The next image is an example of a sub-image and the graphics that are not continuous on the right side.
If necessary, you'd need to create a texture atlas with paddings for each area.
Ebiten has a feature called automatic texture atlases. Even if you create many
ebiten.Image objects, Ebiten tries to allocate them onto one texture atlas when possible. This is for an efficient rendering by reducing graphics commands sent to the GPU. Additionally, Ebiten users don't have to care about this. That's great!
When Ebiten rendered a part of a texture atlas, Ebiten did a hack not to expose the pixels in the adjacent areas. Without the hack, such adjacent pixels can be exposed, especially when the image is rendered with rotating or scaling. This is a general issue of graphics programming. As the hack, Ebiten did two things:
- Shift the texels a little bit.
- Check the source region in the shader.
Both were problematic in terms of code maintainability, and the second hack was especially problematic. One reason is performance. This hack requires 'if' branches in the shader programs and degrades the performance. Another thing is that this hack made it hard to define custom shaders, we are now introducing at 1.12. In the custom shaders, we wanted the users to take pixels from an image in an easy way, but we would need a special function for this purpose to perform this hack.
Removing the hacks
We decided to remove these hacks. Instead, all the images are allocated onto the automatic texture atlas with 1px paddings. Even without the check of the source region, the outside pixels are transparent color and then this never violates the adjacent areas.
However, Ebiten can treat sub-images, which are created by
(*ebiten.Image).SubImage function. They are parts of existing images. They cannot be allocated with paddings independently. There was an idea to allocate all the sub-images as independent images with paddings, but this was not good for performance and memory. If a user called
SubImage onto an image many times with 1px-shifted regions, each sub-image would be allocated.
Then, we gave up to support such paddings around sub-images. As a result, rendering a sub-image can cause different results compared to 1.11. The adjacent area's pixels can be rendered, especially when the image is rotated or scaled.
This is an example that causes bleeding edges by a scaled and rotated sub-image. You can see an unexpected green pixel on the right side. This rendering is tested on the latest master branch (aee5d6d7). The result depends on the machine's GPU.
What you should do
If you have your own texture atlas, and the parts are rendered with scaling or rotating, and the parts' graphics are not continuous, you need to add paddings for each part.