SpriteBatch draw methods which take a Vector2 position and/or a Vector2 scale are actually snapping to pixel coordinates prior to rendering (rounding to integers).
My recollection of XNA behavior, which probably needs to be verified, is that this was not the case. As I recall having to add 'round' calls to all positions passed in to DrawString or the text would be very blurry otherwise.
Nevertheless, not having the 'ability' to render with subpixel precision is a serious problem for smoothly scaling / positioning of a sprite. If you want to round to integers, use the rectangle api or round it yourself prior to calling draw. If you pass in a position or size with floating point precision spritebatch should assume it was intentional and obey it.
Based on the stuff I was looking at when rewriting the SpriteBatch, you're right. This is what I've got at the moment:
Everything I read pointed towards storing anything and everything as Vectors, since it's easier to throw those into XMVECTOR math anyway.
Pixel precision is still probably going to be wrong for stuff like OpenGL due to the various h4x that attempt to accommodate the half-texel offset (getting rid of these is a big priority for FNA), but for backends without this issue it should be more accurate.
Side note: my understanding is that OpenGL hasn't needed manual half-pixel offsets for some time now. There is a flag somewhere that controls this (top left vs center) and its default is, for current versions of OpenGL, the center which matches directx behavior.
OpenGL hasn't needed manual half-pixel offsets
Might be true of newer versions of OpenGL, but MonoGame has to deal with older versions as well.
unless your targeting OpenGL 1.2, "center" is the default. Exceptions to this rule include SoC's which rely on mesa or other rasterizers.
@tomspilman I dont understand what this issue means. The target of Vector2 overload is for smoothing movement of sprite and it executes this purpose very well.
I dont understand what this issue means.
It means that if you render a sprite using SpriteBatch.Draw which takes a Vector2 for position. And that position is Vector2(10.5f, 10.5f). It renders the sprite at 10,10.
This means somewhere within the draw that uses Vector2 for positions we're rounding to integer boundaries. This is fine for the SpriteBatch.Draw that takes an integer Rectange ... but not for one that allows for floating point positioning.
Hmm, than thread creator wants that 10.5 pixel to be blurred between 10 and 11 through pixel shader. It could be useful for 3D sprites or something ?
The only thing that is important here is that this is different from how XNA does it.
Should be simple to setup a unit test for this and fix the implementation.
Oops, missclick.
Has this ever been resolved? I've just ran into this issue now, over three years later. I'm trying to use the overload of SpriteBatch that accepts a Vector2 for positioning and for scale, so that I can properly draw my RectangleF class with subpixel precision. The draw calls seem to be truncating to integers, which results in very noticeable stuttering with movements less than one pixel a frame.
This is with DirectX, and MonoGame 3.6.
I just tried it with MonoGame 3.7, and the issue is still there. Here is a minimal project to use to reproduce the issue. Just create a new DirectX MonoGame project, replace the Game1.cs template with this code and change the namespace, and then run. By moving the square around using the arrow keys, the truncating of floating point values can clearly be seen.
```c#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace SubpixelTest
{
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch batch;
Vector2 position;
Texture2D blankTexture;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
blankTexture = new Texture2D(GraphicsDevice, 32, 32);
Color[] colorArray = new Color[blankTexture.Width * blankTexture.Height];
for (int i = 0; i < colorArray.Length; i++)
colorArray[i] = new Color(255, 255, 255);
blankTexture.SetData(colorArray);
base.Initialize();
}
protected override void LoadContent()
{
batch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
const float speed = .05f;
if (Keyboard.GetState().IsKeyDown(Keys.Escape) ||
GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
Exit();
if (Keyboard.GetState().IsKeyDown(Keys.Up))
position.Y -= speed;
if (Keyboard.GetState().IsKeyDown(Keys.Down))
position.Y += speed;
if (Keyboard.GetState().IsKeyDown(Keys.Left))
position.X -= speed;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
position.X += speed;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);
batch.Begin();
batch.Draw(blankTexture, position, Color.White);
batch.End();
base.Draw(gameTime);
}
}
}
```
What's weird is, I remember this working before.
Futher investigation has led to the discovery that if the texture is rotated at all, so that its rotation value is not zero, the subpixel movement behaves as expected.
Replace the Draw call with this line:
c#
batch.Draw(blankTexture, position, null, Color.White, .1f, Vector2.Zero, 1f, SpriteEffects.None, 1f);
And it suddenly works.
Looking at the source, it appears that if the rotation is 0f, then one overload of SpriteBatchItem.Set is called, but if it's non-zero, a different overload is called. The problem must be in the former overload of SpriteBatchItem.Set.
Unfortunately, the "Set" methods of SpriteBatchItem.cs are completely inscrutable to me, so I'm not in a position that I could provide a fix.
Edit: Actually, I think I'm wrong about the overload of Set being responsible. I tried setting the rotation to some miniscule value, like .0001f, and the "issue" came up again. It seems like the issue is related to whether or not the square _appears_ to be rotated, not whether or not it's actually rotated. In other words, if the vertices of the texture are lined up exactly vertically and horizontally, regardless of what the rotation value is, then no subpixel movement happens.
... with subpixel precision.
There are two ways to fix this, either fade the borders of your sprite or enable multisampling.
Multisampling is expensive, for 2D games the first method is preferred considering you usually draw from back to front.
To enable Multisampling change game1() to this:
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferMultiSampling = true;
}
... with subpixel precision.
There are two ways to fix this, either fade the borders of your sprite or enable multisampling.
Multisampling is expensive, for 2D games the first method is preferred considering you usually draw from back to front.To enable Multisampling change game1() to this:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferMultiSampling = true; }
Thank you! Both those suggestions did the trick, though for multisampling to work, I had to set the GraphicsProfile to HiDef.
I guess this isn't an issue and can be closed now. Thanks!
I looked through the source code. We do not truncate the position in the Vector2 path. We possibly delegated to a Rectangle overload before.