sdl-gpu
sdl-gpu copied to clipboard
Texel coordinates causing bleeding of texture atlas when scaled
When blitting, the texel coordinates are not properly attained https://github.com/grimfang4/sdl-gpu/blob/e3d350b325a0e0d0b3007f69ede62313df46c6ef/src/renderer_GL_common.inl#L4535-L4539
The above code assumes texels are at the following u, v coordinates
However, the correct u, v coordinate are as shown below
function get_texel_coords(x, y, tex_width, tex_height)
u = (x + 0.5) / tex_width
v = (y + 0.5) / tex_height
return u, v
end
This is called the "half pixel correction". In this case, we are always sampling the center of each texel, which prevents bleeding of adjacent textures within a texture atlas. You can read more about it here https://docs.microsoft.com/en-us/windows/desktop/direct3d9/directly-mapping-texels-to-pixels
I noticed this issue when attempting to scale a sprite that was using a texture atlas with no padding between textures. My sprite was having the adjacent textures bleed on to it. In order to correct the issue currently, I add 0.5 to the x and y of the source rect for blitting, and subtract 1 from the width and height of the source rect. It'd be nicer if it was just fixed internally instead of using this hack.
Can confirm. I noticed a tiny 1 pixel offset from what I was expecting. Effect gets huge if you blow up a small bitmap to a much larger size (like in pixel-art games). Seems exactly half the original pixel off.
Does anyone have a working fix for this?
The original post contains a work around that I'm still using to this day. It works great.
I add 0.5 to the x and y of the source rect for blitting, and subtract 1 from the width and height of the source rect.
If done correctly, there should be no bleeding of textures.
Note that this type of thing would be really simple to fix internally too
@Wizzard033 Yes, fixing this in sdl-gpu was my intention.
Replacing the lines I referenced in the first post with the following ought to do the trick
// Scale src_rect tex coords according to actual texture dims
x1 = (src_rect->x + 0.5)/(float)tex_w;
y1 = (src_rect->y + 0.5)/(float)tex_h;
x2 = (src_rect->x + src_rect->w - 0.5)/(float)tex_w;
y2 = (src_rect->y + src_rect->h - 0.5)/(float)tex_h;
I think there's some improvement to be made for when scaling up, but I wasn't able to get these changes suggested here to work well in the pixel-perfect test program which doesn't use scaling.
Have you adjusted the image snapping settings for your program? SDL_gpu tries to make the default behavior align images to the pixel grid, but this may not be what you need for your texture atlas (or, of course, there could be a bug here). If you haven't yet, try GPU_SetSnapMode(image, GPU_SNAP_NONE);
to see if you get the results you're expecting.
If you're still having an issue, can you provide a simple program to demonstrate the texel bleeding? I'd love to get that fixed.
I don't have a setup to compile C code using SDL_gpu quickly, and so I just wrote a Lua script for my game engine so that I could show this off faster. The below code should be easy to replicate in C.
I expect to have no texture bleeding in the following code
local TILESET_TILE_SIZE = 8
local _, image
local scale = C.floatv(1, 1)
game.onStart:Add("TEST", function()
_, image = resources.LoadImage("default_tileset.png")
gpu.SetSnapMode(image, gpu.SNAP_NONE)
end)
game.onGUI:Add("TEST", function()
im.SliderFloat("Scale", scale, 0.1, 10, nil, 1)
end)
game.onRender:Add("TEST", function(screen)
local w, h = scale[0] * TILESET_TILE_SIZE, scale[0] * TILESET_TILE_SIZE
local tw, th = TILESET_TILE_SIZE, TILESET_TILE_SIZE
for i = 0, 15 do
if i % 2 == 0 then
for j = 0, 15 do
if j % 2 == 0 then
local x, y = j * scale[0] * TILESET_TILE_SIZE, i * scale[0] * TILESET_TILE_SIZE
local tx, ty = j * TILESET_TILE_SIZE, i * TILESET_TILE_SIZE
gpu.BlitRect(image, gpu.MakeRect(tx, ty, tw, th), screen, gpu.MakeRect(x, y, w, h))
end
end
end
end
end)
Result of above code (note the grass tiles bleeding into rock tiles)
After testing my "fix" a bit, it seems that it doesn't work quite right. It removes the bleeding, but the tiles then become zoomed in, where as they should be pixel-perfect.
Here's the default_tileset.png I was testing with
In my experience, when one pixel bleeds in into a sprite from a sprite next to it in the atlas, on the opposite side of the sprite one pixel is cut out. Here's a visual of the problem:

Should be:
Note that not only a white line is bleeding in from below, but also a pixel is missing from above (the hand is missing the topmost line of pixels).
Replacing the lines I referenced in the first post with the following ought to do the trick
// Scale src_rect tex coords according to actual texture dims x1 = (src_rect->x + 0.5)/(float)tex_w; y1 = (src_rect->y + 0.5)/(float)tex_h; x2 = (src_rect->x + src_rect->w - 0.5)/(float)tex_w; y2 = (src_rect->y + src_rect->h - 0.5)/(float)tex_h;
This solution fixes the bleeding, but it accentuates cutting pixels out and causes lines to be drawn twice within the sprite. Note this is all using a NEAREST filter. See the mustache in this image as an example (while the hand is still cut):

Edit: This seems to happen even without scaling when I render to a texture (?). If you think my issue is different from the OP, let me know and I'll move this to a separate issue.
Edit2: I'm pretty sure at this point this is a different issue. I've opened a new issue (204) explaining my problem.