[Solved] ScissorRectangle Redux: Scaling Errors

Yay, talking about scissor rectangles again. I think this issue is more straight forward. I know I can hack my way around it but I’m hoping for a more enlightening answer.

To put it as simply as I can: because scissor rectangle dimensions are relative to your actual backbuffer/screen, I’m running into issue getting precision clipping. Here’s why…

I have a virtual resolution of 1280x720. I drop a 450x650 square into the top-center of the screen. This puts the X position at 415 (415 * 2 = 830 + 450 = 1280).

My real screen size is 1600x900. This gives a scale factor of 1.25. I make a scale matrix with that value and drop it into the SpriteBatch.Begin().

After applying the scale, I should expect my square to become: x: 518.75, y: 0, w: 562.5, h: 812.5

But whoops, we ended up with floats.

Now if I wanted my scissor rectangle to occupy the exact same position and size as my square, I presumed I could scale each of its values by 1.25 and be fine. After all, that’s the same thing SpriteBatch is doing with the matrix, right?

And yet, I’m off by one lousy pixel.

If you look carefully, the yellow bar is 1px outside the bounds of the blue square on the left. The yellow bar is being drawn by my scissored SpriteBatch, and the (scaled) scissorRect is the same dimensions and position as the blue square, so how is the yellow bar 1px outside the bounds of the blue square if the scissor is in the same spot as the blue square?

Now I presumed this was a rounding error… but my confusion rises as I’ve tried floor and ceiling on my scissorRect values and still get this error. I also checked all the values in the actual Draw() functions of my GUI’s classes to be absolutely sure the boxes were in the right position and the right dimensions, and they were. Everything gets reported being in just the place it should be… except the scissorRectangle.

I assumed that when SpriteBatch renders to the screen, it positions and scales by the scale factor of my matrix, which isn’t any different(I assume), than me manually scaling the Rectangle for my scissorRectangle by that same value… and yet the scissorRect doesn’t end up in the same place as the blue square despite them being precisely the same Rectangle getting scaled by precisely the same scaleFactor.

Now I know I could just add/subtract a few pixels here and there for the scissorRect to ensure it’s always inside the blue square, but have its size always be a few pixels smaller in each dimension. It wouldn’t be the end of the world or anything. I don’t absolutely NEED pixel-perfect precision for scissoring my GUI… but it sure would be nice just to KNOW why it’s happening.

Ideas?

Could this be related to the HalfPixelOffset?

Not sure, but I think I found a less hacky but more complicated workaround anyway.

My current rendering flow is:

Start my batches, with scaling matrix being applied to the spritebatch right away > draw ‘background’ elements(the blue square) > start new batch with scissor raster state enabled and scaling matrix applied > draw the contents meant to be clipped > end the first batch, end the scissoring batch.

The trouble with this is that since the scissorRect doesn’t get automagically scaled by the spritebatch(its all handled by the GPU), there’s no guarantee it will sync up with how the spritebatch renders the scaled sprites.

My solution was to start using RenderTarget2D, which I gather most people seem to use anyway so maybe I should have too. The new flow is now this:

Set graphics device to a rendertarget2d with width/height of my virtual resolution(1280x720) > Start a normal batch without applying a scaling matrix > draw the blue square and end batch > start a new batch with scissorRect and raster state and no scaling matrix > draw clipped yellow square to the rendertarget and end batch > set graphics device rendertarget back to null > start a new batch, draw the rendertarget2D WITH a scaling matrix

Doing this has given me the pixel perfect precision. It works, I suppose, because the scissor rectangle no longer has to be scaled to match the dimensions of the blue square. The blue square is unscaled at that point in the work flow, so the scissor doesn’t need to be scaled. The scaling happens all at once to the rendertarget at the end.

Not sure if any of that makes sense, but either way this does work, albeit in a more roundabout way.

I am curious about that bool, though. I’m guessing it’s used to add 0.5F somewhere in the maths.

1 Like

That’s the preferred way of doing things, especially if you’re targeting a specific resolution like many 2D games do. RenderTargets make scaling so much easier. Glad you figured it out! UseHalfPixelOffset is for DX9 compatibility to maintain the same look if you end up building both DesktopGL and WindowsDX projects; I’d recommend keeping it off otherwise.

An additional benefit of using a RenderTarget is now you can perform all sorts of fancy post-processing effects.

Yeah, I think this will work out for the best. I’ll go ahead and mark this solved even though technically I’m still unsure why it was actually happening. But since there’s an obvious and preferred method of rendering which avoids the issue in the first place, that’s fine.

Now, time to go refactor my SpriteDispatcher class for the 4th time in the last 2 days.

Thanks again for your insights!