Can anyone tell me how to handle this?
You're running into the infamous fenceposting problem. Simply spoken, texture coordinates 0 and 1 are not pixel centers, but pixel borders. Have a look at this simple sketch
| 0 | 1 | 2 | 3 |
^ ^
0.0 1.0
0 1 2 3 4
--- --- --- --- ---
4 4 4 4 4
So to address pixel i (say 0) of a texture N (say 4) pixels wide you must use the texture coordinate
(i/N + (i+1)/N)/2 = i/2N + (i+1)/2N = (2i + 1) / 2N = (i + 0.5)/N
So adding that 0.5 is not a hack. However in the shader you can just use texelFetch
to address the texture in absolute pixels, instead of a sampling coordinate. However you must implement filtering yourself then.